packages feed

amby 0.2.1 → 0.3.1

raw patch · 12 files changed

+1120/−188 lines, 12 filesdep +doctestdep +lensdep +mwc-randomdep −microlensdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: doctest, lens, mwc-random, tasty, tasty-hunit, vector-algorithms

Dependencies removed: microlens

Dependency ranges changed: base

API changes (from Hackage documentation)

- Amby: getBgColor :: Theme -> AlphaColour Double
- Amby: getColorCycle :: Theme -> [AlphaColour Double]
- Amby: getFontFamily :: Theme -> String
- Amby: getFontSize :: Theme -> Double
- Amby: getGridLineColor :: Theme -> AlphaColour Double
- Amby: getPlotBgColor :: Theme -> AlphaColour Double
+ Amby: (.=) :: MonadState s m => ASetter s s a b -> b -> m ()
+ Amby: B :: AmbyColor
+ Amby: BwScalar :: Double -> Bandwidth
+ Amby: C :: AmbyColor
+ Amby: CustomColor :: (AlphaColour Double) -> AmbyColor
+ Amby: DefaultColor :: AmbyColor
+ Amby: G :: AmbyColor
+ Amby: K :: AmbyColor
+ Amby: M :: AmbyColor
+ Amby: R :: AmbyColor
+ Amby: Scott :: Bandwidth
+ Amby: Theme :: AlphaColour Double -> AlphaColour Double -> AlphaColour Double -> String -> Double -> [AlphaColour Double] -> Theme
+ Amby: W :: AmbyColor
+ Amby: XAxis :: Axis
+ Amby: Y :: AmbyColor
+ Amby: YAxis :: Axis
+ Amby: [_themeBgColor] :: Theme -> AlphaColour Double
+ Amby: [_themeColorCycle] :: Theme -> [AlphaColour Double]
+ Amby: [_themeFontFamily] :: Theme -> String
+ Amby: [_themeFontSize] :: Theme -> Double
+ Amby: [_themeGridLineColor] :: Theme -> AlphaColour Double
+ Amby: [_themePlotBgColor] :: Theme -> AlphaColour Double
+ Amby: axis :: HasAxis s a => Lens' s a
+ Amby: bgColor :: HasBgColor s a => Lens' s a
+ Amby: bins :: HasBins s a => Lens' s a
+ Amby: bw :: HasBw s a => Lens' s a
+ Amby: color :: HasColor s a => Lens' s a
+ Amby: colorCycle :: HasColorCycle s a => Lens' s a
+ Amby: cut :: HasCut s a => Lens' s a
+ Amby: data AmbyColor
+ Amby: data Axis
+ Amby: data Bandwidth
+ Amby: data DistPlotOpts
+ Amby: data KdePlotOpts
+ Amby: data PlotEqOpts
+ Amby: data PlotOpts
+ Amby: data RugPlotOpts
+ Amby: distPlot :: AmbyContainer c => c -> State DistPlotOpts () -> AmbyChart ()
+ Amby: distPlot' :: AmbyContainer c => c -> AmbyChart ()
+ Amby: fontFamily :: HasFontFamily s a => Lens' s a
+ Amby: fontSize :: HasFontSize s a => Lens' s a
+ Amby: freedmanDiaconisBins :: (Vector v Double) => v Double -> Int
+ Amby: gridLineColor :: HasGridLineColor s a => Lens' s a
+ Amby: gridsize :: HasGridsize s a => Lens' s a
+ Amby: height :: HasHeight s a => Lens' s a
+ Amby: hexToRgb :: String -> AmbyColor
+ Amby: hexToRgba :: String -> Double -> AmbyColor
+ Amby: hist :: HasHist s a => Lens' s a
+ Amby: histLinewidth :: HasHistLinewidth s a => Lens' s a
+ Amby: infix 4 .=
+ Amby: interquartileRange :: (Vector v Double) => v Double -> Double
+ Amby: kde :: HasKde s a => Lens' s a
+ Amby: kdeLinewidth :: HasKdeLinewidth s a => Lens' s a
+ Amby: kdePlot :: AmbyContainer c => c -> State KdePlotOpts () -> AmbyChart ()
+ Amby: kdePlot' :: AmbyContainer c => c -> AmbyChart ()
+ Amby: linewidth :: HasLinewidth s a => Lens' s a
+ Amby: plot' :: AmbyContainer c => c -> c -> AmbyChart ()
+ Amby: plotBgColor :: HasPlotBgColor s a => Lens' s a
+ Amby: plotEq' :: AmbyContainer c => c -> (Value c -> Value c) -> AmbyChart ()
+ Amby: random :: (ContGen d) => d -> Int -> IO (Vector Double)
+ Amby: rug :: HasRug s a => Lens' s a
+ Amby: rugHeight :: HasRugHeight s a => Lens' s a
+ Amby: rugLinewidth :: HasRugLinewidth s a => Lens' s a
+ Amby: rugPlot :: AmbyContainer c => c -> State RugPlotOpts () -> AmbyChart ()
+ Amby: rugPlot' :: AmbyContainer c => c -> AmbyChart ()
+ Amby: scoreAtPercentile :: (Vector v Double) => v Double -> Double -> Double
+ Amby: shade :: HasShade s a => Lens' s a
+ Amby: toColour :: AmbyColor -> AlphaColour Double -> AlphaColour Double
+ Amby: type family Value c :: *;
+ Amby: }
- Amby: class AmbyContainer c a | c -> a
+ Amby: class AmbyContainer c where type Value c :: * where {
- Amby: plot :: AmbyContainer c a => c -> c -> AmbyChart ()
+ Amby: plot :: AmbyContainer c => c -> c -> State PlotOpts () -> AmbyChart ()
- Amby: plotEq :: AmbyContainer c a => c -> (a -> a) -> AmbyChart ()
+ Amby: plotEq :: AmbyContainer c => c -> (Value c -> Value c) -> State PlotEqOpts () -> AmbyChart ()

Files

amby.cabal view
@@ -1,10 +1,24 @@ name:                amby-version:             0.2.1+version:             0.3.1 synopsis:            Statistical data visualization-description:         Statistical data visualization. Provides a high-level-                     interface built on top of-                     <https://github.com/timbod7/haskell-chart/wiki Chart>-                     to quickly display attractive visualizations within GHCi.+description:         <<https://travis-ci.org/jsermeno/amby.svg?branch=master>>+                     <<https://img.shields.io/badge/language-Haskell-blue.svg>>+                     <<http://img.shields.io/badge/license-BSD3-brightgreen.svg>>+                     .+                     <<https://cloud.githubusercontent.com/assets/197051/20435959/262da202-ad7c-11e6-99e4-b6348cab0898.png>>+                     <<https://cloud.githubusercontent.com/assets/197051/20435962/2796c380-ad7c-11e6-9cc8-0fbc74ba259a.png>>+                     <<https://cloud.githubusercontent.com/assets/197051/20435968/2a059e52-ad7c-11e6-8f8d-1fd648dfcf4b.png>>+                     .+                     A statistics visualization library built on top of+                     <https://github.com/timbod7/haskell-chart Chart> inspired by+                     <https://github.com/mwaskom/seaborn Seaborn>. Amby provides+                     a high-level interface to quickly display attractive+                     visualizations. Amby also provides tools to display Charts+                     from both Amby and the Chart package within GHCi.+                     .+                     For examples visit the+                     <https://github.com/jsermeno/amby#readme README>+ homepage:            https://github.com/jsermeno/amby#readme license:             BSD3 license-file:        LICENSE@@ -25,14 +39,16 @@                      , Amby.Numeric                      , Amby.Plot                      , Amby.Display+                     , Amby.Compatibility.HistogramPlot+                     , Amby.Compatibility.HistogramNumeric   build-depends:       base >= 4.7 && < 5                      , data-default-class                      , Chart-cairo                      , Chart-diagrams                      , Chart                      , vector+                     , vector-algorithms                      , statistics-                     , microlens                      , colour                      , scientific                      , mtl@@ -42,14 +58,10 @@                      , process                      , exceptions                      , data-default-  default-language:    Haskell2010--executable amby-exe-  hs-source-dirs:      app-  main-is:             Main.hs-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N-  build-depends:       base-                     , amby+                     -- 'Lens' package used as dependency of 'Chart'+                     , lens+                     , mwc-random+  ghc-options:         -Wall -fno-warn-orphans   default-language:    Haskell2010  test-suite amby-test@@ -58,6 +70,10 @@   main-is:             Spec.hs   build-depends:       base                      , amby+                     , doctest+                     , tasty+                     , tasty-hunit+                     , vector   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010 
− app/Main.hs
@@ -1,6 +0,0 @@-module Main where--import Amby--main :: IO ()-main = return ()
src/Amby.hs view
@@ -11,11 +11,15 @@   , module Amby.Numeric   , module Amby.Types   , module Amby.Plot++  -- * Lens operators+  , (.=)   ) where +import Graphics.Rendering.Chart.Easy ((.=))+ import Amby.Types import Amby.Numeric import Amby.Theme-import Amby.Style import Amby.Plot import Amby.Display ()
+ src/Amby/Compatibility/HistogramNumeric.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE PatternGuards #-}++module Amby.Compatibility.HistogramNumeric ( Range+                                           , binBounds+                                           , histValues+                                           , histWeightedValues+                                           , histWithBins+                                           ) where++import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import Control.Monad.ST++type Range a = (a,a)++-- | 'binBounds a b n' generates bounds for 'n' bins spaced linearly between+-- 'a' and 'b'+--+-- Examples:+--+-- >>> binBounds 0 3 4+-- [(0.0,0.75),(0.75,1.5),(1.5,2.25),(2.25,3.0)]+binBounds :: RealFrac a => a -> a -> Int -> [Range a]+binBounds a b n = map (\i->(lbound i, lbound (i+1))) [0..n-1]+        where lbound i = a + (b-a) * realToFrac i / realToFrac n++-- | 'histValues a b n vs' returns the bins for the histogram of+-- 'vs' on the range from 'a' to 'b' with 'n' bins+histValues :: RealFrac a => a -> a -> Int -> [a] -> V.Vector (Range a, Int)+histValues a b n = histWithBins (V.fromList $ binBounds a b n) . zip (repeat 1)++-- | 'histValues a b n vs' returns the bins for the weighted histogram of+-- 'vs' on the range from 'a' to 'b' with 'n' bins+histWeightedValues :: RealFrac a => a -> a -> Int -> [(Double,a)] -> V.Vector (Range a, Double)+histWeightedValues a b n = histWithBins (V.fromList $ binBounds a b n)++-- | 'histWithBins bins xs' is the histogram of weighted values 'xs' with 'bins'+--+-- Examples:+--+-- >>> :{+-- histWithBins+--     (V.fromList [(0.0, 0.75), (0.75, 1.5), (1.5, 2.25), (2.25, 3.0)])+--     [(1, 0), (1, 0), (1, 1), (1, 2), (1, 2), (1, 2), (1, 3)]+-- :}+-- [((0.0,0.75),2),((0.75,1.5),1),((1.5,2.25),3),((2.25,3.0),1)]+histWithBins :: (Num w, RealFrac a) => V.Vector (Range a) -> [(w, a)] -> V.Vector (Range a, w)+histWithBins bins xs =+    let n = V.length bins+        testBin :: RealFrac a => a -> (Int, Range a) -> Bool+        testBin x (i, (a,b)) =+            if i == n - 1+                then x >= a && x <= b+                else x >= a && x < b++        f :: (RealFrac a, Num w)+          => V.Vector (Range a) -> MV.STVector s w -> (w, a)+          -> ST s ()+        f bins1 bs (w,x) =+            case V.dropWhile (not . testBin x) $ V.indexed bins1 of+                v | V.null v  -> return ()+                v | (idx,_) <- V.head v  -> do+                    m <- MV.read bs idx+                    MV.write bs idx $! m+w++        counts = runST $ do b <- MV.replicate n 0+                            mapM_ (f bins b) xs+                            V.freeze b+    in V.zip bins counts
+ src/Amby/Compatibility/HistogramPlot.hs view
@@ -0,0 +1,216 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE CPP #-}+module Amby.Compatibility.HistogramPlot+  ( -- * Histograms+    PlotHist (..)+  , histToPlot+  , defaultPlotHist+  , defaultFloatPlotHist+  , defaultNormedPlotHist+    -- * Accessors+  , plot_hist_title+  , plot_hist_bins+  , plot_hist_values+  , plot_hist_no_zeros+  , plot_hist_range+  , plot_hist_drop_lines+  , plot_hist_line_style+  , plot_hist_fill_style+  , plot_hist_norm_func+  , plot_hist_vertical+  ) where++import Control.Monad (when)+import Data.Monoid+import Data.Maybe (fromMaybe)+import qualified Data.Foldable as F+import qualified Data.Vector as V++import Graphics.Rendering.Chart.Easy (makeLenses)+import Graphics.Rendering.Chart.Plot.Types+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Data.Default.Class++import Data.Colour (opaque)+import Data.Colour.Names (blue)+import Data.Colour.SRGB (sRGB)++import Amby.Compatibility.HistogramNumeric++#if MIN_VERSION_Chart(1,7,0)+#else+type BackendProgram a = ChartBackend a+#endif++data PlotHist x y = PlotHist+    { -- | Plot title+      _plot_hist_title                :: String++      -- | Number of bins+    , _plot_hist_bins                 :: Int++      -- | Values to histogram+    , _plot_hist_values               :: [x]++      -- | Don't attempt to plot bins with zero counts. Useful when+      -- the y-axis is logarithmically scaled.+    , _plot_hist_no_zeros             :: Bool++      -- | Override the range of the histogram. If @Nothing@ the+      -- range of @_plot_hist_values@ is used.+      --+      -- Note that any normalization is always computed over the full+      -- data set, including samples not falling in the histogram range.+    , _plot_hist_range                :: Maybe (x,x)++      -- | Plot vertical lines between bins+    , _plot_hist_drop_lines           :: Bool++      -- | Fill style of the bins+    , _plot_hist_fill_style           :: FillStyle++      -- | Line style of the bin outlines+    , _plot_hist_line_style           :: LineStyle++      -- | Normalization function+    , _plot_hist_norm_func            :: Double -> Int -> y++      -- | If true observed values are one y-axis+    , _plot_hist_vertical             :: Bool+    }++instance Default (PlotHist x Int) where+    def = defaultPlotHist++-- | The default style is an unnormalized histogram of 20 bins.+defaultPlotHist :: PlotHist x Int+defaultPlotHist = PlotHist { _plot_hist_bins        = 20+                           , _plot_hist_title       = ""+                           , _plot_hist_values      = []+                           , _plot_hist_no_zeros    = False+                           , _plot_hist_range       = Nothing+                           , _plot_hist_drop_lines  = False+                           , _plot_hist_line_style  = defaultLineStyle+                           , _plot_hist_fill_style  = defaultFillStyle+                           , _plot_hist_norm_func   = const id+                           , _plot_hist_vertical    = False+                           }++-- | @defaultPlotHist@ but with real counts+defaultFloatPlotHist :: PlotHist x Double+defaultFloatPlotHist = defaultPlotHist { _plot_hist_norm_func = const realToFrac }++-- | @defaultPlotHist@ but normalized such that the integral of the+-- histogram is one.+defaultNormedPlotHist :: PlotHist x Double+defaultNormedPlotHist = defaultPlotHist { _plot_hist_norm_func = \n y->realToFrac y / n }++defaultFillStyle :: FillStyle+defaultFillStyle = solidFillStyle (opaque $ sRGB 0.5 0.5 1.0)++defaultLineStyle :: LineStyle+defaultLineStyle = (solidLine 1 $ opaque blue)+     { _line_cap  = LineCapButt+     , _line_join = LineJoinMiter+     }++-- | Convert a @PlotHist@ to a @Plot@+--+-- N.B. In principle this should be Chart's @ToPlot@ class but unfortunately+-- this does not allow us to set bounds on the x and y axis types, hence+-- the need for this function.+histToPlot :: (RealFrac x) => PlotHist x x -> Plot x x+histToPlot p = Plot {+        _plot_render      = renderPlotHist p,+        _plot_legend      = [(_plot_hist_title p, renderPlotLegendHist p)],+        _plot_all_points  = unzip+                            $ concatMap binToPoints+                            $ histToBins p+    }+    where binToPoints ((x1,x2), y) = if _plot_hist_vertical p+              then [(y,x1), (y,x2), (0,x1), (0,x2)]+              else [(x1,y), (x2,y), (x1,0), (x2,0)]++buildHistPath :: (RealFrac x)+              => PointMapFn x x -> [((x,x), x)] -> Path+buildHistPath _ [] = End+buildHistPath pmap bins = MoveTo (pt xb 0) (go bins)+    where go [((x1,x2),y)]      = LineTo (pt x1 y)+                                $ LineTo (pt x2 y)+                                $ LineTo (pt x2 0)+                                $ End+          go (((x1,x2),y):rest) = LineTo (pt x1 y)+                                $ LineTo (pt x2 y)+                                $ go rest+          go []                 = End+          ((xb,_),_) = head bins+          pt x y = pmap (LValue x, LValue y)++buildHistPathVertical :: (RealFrac x)+                      => PointMapFn x x -> [((x,x), x)] -> Path+buildHistPathVertical _ [] = End+buildHistPathVertical pmap bins = MoveTo (pt 0 xb) (go bins)+    where go [((x1,x2),y)]      = LineTo (pt y x1)+                                $ LineTo (pt y x2)+                                $ LineTo (pt 0 x2)+                                $ End+          go (((x1,x2),y):rest) = LineTo (pt y x1)+                                $ LineTo (pt y x2)+                                $ go rest+          go []                 = End+          ((xb,_),_) = head bins+          pt x y = pmap (LValue x, LValue y)++renderPlotHist :: (RealFrac x)+               => PlotHist x x -> PointMapFn x x -> BackendProgram ()+renderPlotHist p pmap+    | null bins = return ()+    | otherwise = do+        withFillStyle (_plot_hist_fill_style p) $+            alignFillPath (buildHistPathFn pmap bins) >>= fillPath+        withLineStyle (_plot_hist_line_style p) $ do+            when (_plot_hist_drop_lines p) $+                alignStrokePath dropLinesPath >>= strokePath+            alignStrokePath (buildHistPathFn pmap bins) >>= strokePath+    where buildHistPathFn = if _plot_hist_vertical p+              then buildHistPathVertical+              else buildHistPath+          bins = histToBins p+          pt x y = pmap (LValue x, LValue y)+          dropLinesPath = F.foldMap dropLinesFoldFn $ tail bins+          dropLinesFoldFn ((x1,_), y) = if _plot_hist_vertical p+              then moveTo (pt 0 x1) <> lineTo (pt y x1)+              else moveTo (pt x1 0) <> lineTo (pt x1 y)++renderPlotLegendHist :: PlotHist x y -> Rect -> BackendProgram ()+renderPlotLegendHist p (Rect p1 p2) =+    withLineStyle (_plot_hist_line_style p) $+        let y = (p_y p1 + p_y p2) / 2+        in strokePath $ moveTo' (p_x p1) y <> lineTo' (p_x p2) y++histToBins :: (RealFrac x, Num y, Ord y) => PlotHist x y -> [((x,x), y)]+histToBins hist =+    filter_zeros $ zip bounds $ counts+    where n = _plot_hist_bins hist+          (a,b) = realHistRange hist+          dx = realToFrac (b-a) / realToFrac n+          bounds = binBounds a b n+          values = V.fromList (_plot_hist_values hist)+          filter_zeros | _plot_hist_no_zeros hist  = filter (\(_,c)->c > 0)+                       | otherwise                 = id+          norm = dx * realToFrac (V.length values)+          normalize = _plot_hist_norm_func hist norm+          counts = V.toList $ V.map (normalize . snd)+                   $ histWithBins (V.fromList bounds)+                   $ zip (repeat 1) (V.toList values)++realHistRange :: (RealFrac x) => PlotHist x y -> (x,x)+realHistRange hist = fromMaybe range $ _plot_hist_range hist+    where values = V.fromList (_plot_hist_values hist)+          range = if V.null values+                    then (0,0)+                    else (V.minimum values, V.maximum values)++$( makeLenses ''PlotHist )
src/Amby/Display.hs view
@@ -39,10 +39,12 @@   catch readImg cHandler   where     readImg = do-      (ec, stdout, stderr) <- readProcessWithExitCode "imgcat" [Plot.cairoDefSave] ""-      if | (ExitFailure code) <- ec -> return (mkDtStr "Unable to display chart.")+      (ec, stdout, _) <- readProcessWithExitCode "imgcat" [Plot.cairoDefSave] ""+      if | (ExitFailure _) <- ec -> return (mkDtStr "Unable to display chart.")          | otherwise -> return (mkDtStr stdout)     cHandler e-      | Just (e :: SomeException) <- fromException e =+      | Just (_ :: SomeException) <- fromException e =         return $ mkDt "Could not find imgcat executable."+      | otherwise =+        return $ mkDt "Unknown display error." 
src/Amby/Numeric.hs view
@@ -1,24 +1,55 @@ {-# LANGUAGE FlexibleContexts #-}+---------------------------------------------------------------------------------+-- |+-- Module       : Amby.Numeric+-- Copyright    : (C) 2016 Justin Sermeno+-- License      : BSD3+-- Maintainer   : Justin Sermeno+--+-- Functions for working with numerical ranges and statistics.+--------------------------------------------------------------------------------- module Amby.Numeric-  ( contDistrDomain+  ( -- * Ranges+    contDistrDomain   , contDistrRange   , linspace   , arange+  , random++  -- * Frequencies+  , scoreAtPercentile+  , interquartileRange+  , freedmanDiaconisBins+   ) where -import Data.Either.Combinators-import Data.Scientific+import Data.Vector.Generic ((!)) import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U import qualified Data.Vector as V+import qualified Data.Vector.Algorithms.Intro as V+ import Statistics.Distribution+import System.Random.MWC (withSystemRandom, asGenST) +-- $setup+-- >>> let demoData = V.fromList $ concat $ zipWith replicate [5, 4, 3, 7] [0..3]++---------------------+-- Ranges+---------------------++-- | @contDistrDomain d n@ generates a domain of 'n' evenly spaced points+-- for the continuous distribution 'd'. contDistrDomain :: (ContDistr d) => d -> Int -> U.Vector Double contDistrDomain d num = linspace (quantile d 0.0001) (quantile d 0.9999) num +-- | @contDistrRange d xs@ generates the pdf value of the continious distribution+-- 'd' for each value in 'xs'. contDistrRange :: (ContDistr d) => d -> U.Vector Double -> U.Vector Double contDistrRange d x = U.map (density d) x +-- | @linspace s e n@ generates 'n' evenly spaced values between ['s', 'e']. linspace :: Double -> Double -> Int -> U.Vector Double linspace start stop num   | num < 0 = error ("Number of samples, " ++ show num ++ ", must be non-negative.")@@ -29,5 +60,84 @@     step = delta / fromIntegral (num - 1)     addStart = U.map (realToFrac . (+ start)) +-- | @arange s e i@ generates numbers between ['s', 'e'] spaced by amount 'i'.+-- 'arange' is the equivalent of haskell's range notation except that it generates+-- a 'Vector'. As a result, the last element may be greater than less than, or+-- greater than the stop point. arange :: Double -> Double -> Double -> U.Vector Double arange start stop step = U.fromList [start,(start + step)..stop]++-- | Generates an unboxed vectors of random numbers from a distribution+-- that is an instance of 'ContGen'. This function is meant for ease of use+-- and is expensive.+random :: (ContGen d) => d -> Int -> IO (U.Vector Double)+random d n = withSystemRandom . asGenST $ \gen ->+  U.replicateM n $ genContVar d gen++---------------------+-- Frequencies+---------------------++-- | @scoreAtPercentile xs p@ calculates the score at percentile 'p'.+--+-- Examples:+--+-- >>> let a = arange 0 99 1+--+-- >>> scoreAtPercentile a 50+-- 49.5+scoreAtPercentile :: (G.Vector v Double) => v Double -> Double -> Double+scoreAtPercentile xs p+  | n == 0 = modErr "scoreAtPercentile" "Percentile sample size is 0"+  | p < 0 || p > 100 = modErr "scoreAtPercentile" "Percentile must be in [0, 100]"+  | otherwise = (getScore . G.modify V.sort) xs+  where+    n = G.length xs+    idx = (p / 100) * fromIntegral (n - 1)+    i = floor idx+    j = ceiling idx++    isInt :: Double -> Bool+    isInt a = fromIntegral (round a :: Int) == a++    interop :: (G.Vector v Double) => v Double -> Double+    interop vec =+      (vec ! i) * (fromIntegral j - idx) ++      (vec ! j) * (idx - fromIntegral i)++    getScore vec = if isInt idx+      then vec ! i+      else interop vec+{-# SPECIALIZE scoreAtPercentile :: U.Vector Double -> Double -> Double #-}+{-# SPECIALIZE scoreAtPercentile :: V.Vector Double -> Double -> Double #-}++-- | Calculate the interquartile range.+--+-- Examples:+--+-- >>> interquartileRange demoData+-- 2.5+interquartileRange :: (G.Vector v Double) => v Double -> Double+interquartileRange xs = scoreAtPercentile xs 75 - scoreAtPercentile xs 25+{-# SPECIALIZE interquartileRange :: U.Vector Double -> Double #-}+{-# SPECIALIZE interquartileRange :: V.Vector Double -> Double #-}++-- | Estimate a good default bin size.+--+-- Examples:+--+-- >>> freedmanDiaconisBins demoData+-- 2+freedmanDiaconisBins :: (G.Vector v Double) => v Double -> Int+freedmanDiaconisBins xs =+    if h == 0+      then floor $ sqrt $ (fromIntegral n :: Double)+      else ceiling $ (G.maximum xs - G.minimum xs) / h+  where+    n = G.length xs+    h = 2 * interquartileRange xs / fromIntegral n ** (1 / 3)++modErr :: String -> String -> a+modErr f err = error+  $ showString "Amby.Numeric."+  $ showString f err
src/Amby/Plot.hs view
@@ -1,13 +1,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE IncoherentInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-deprecations #-} module Amby.Plot-  ( plot-  , plotEq-  , getEC+  ( getEC   , save   , saveSvg @@ -19,21 +19,303 @@ import Control.Monad import Control.Monad.State import qualified Data.List as L+import qualified Data.Maybe as Maybe+import Data.Tuple (swap) import qualified Data.Vector.Generic as G+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as U +import Control.Lens+import Data.Colour as Colour import Data.Default.Class-import Graphics.Rendering.Chart.Easy hiding (plot)+import Graphics.Rendering.Chart.Easy (Layout, EC) import qualified Graphics.Rendering.Chart.Easy as Chart import qualified Graphics.Rendering.Chart.Backend.Cairo as Cairo import qualified Graphics.Rendering.Chart.Backend.Diagrams as Diagrams-import Lens.Micro-import Statistics.Distribution+import Safe (maximumMay)+import qualified Statistics.Sample.KernelDensity.Simple as Stats+import qualified Statistics.Sample as Stats -import Amby.Types+import Amby.Compatibility.HistogramPlot import Amby.Numeric-import Amby.Theme-import Amby.Style+import Amby.Theme (toColour)+import Amby.Types +--------------------------+-- Instance declarations+--------------------------++instance AmbyContainer (V.Vector Double) where+  type Value (V.Vector Double) = Double+  plot = plotVec+  plot' = plotVec'+  plotEq = plotEqVec+  plotEq' = plotEqVec'+  distPlot = distPlotVec+  distPlot' = distPlotVec'+  kdePlot = kdePlotVec+  kdePlot' = kdePlotVec'+  rugPlot = rugPlotVec+  rugPlot' = rugPlotVec'++instance AmbyContainer (U.Vector Double) where+  type Value (U.Vector Double) = Double+  plot = plotVec+  plot' = plotVec'+  plotEq = plotEqVec+  plotEq' = plotEqVec'+  distPlot = distPlotVec+  distPlot' = distPlotVec'+  kdePlot = kdePlotVec+  kdePlot' = kdePlotVec'+  rugPlot = rugPlotVec+  rugPlot' = rugPlotVec'++instance (Real a) => AmbyContainer [a] where+  type Value [a] = a++  plot :: [a] -> [a] -> State PlotOpts () -> AmbyChart ()+  plot x y optsState = plotList vals opts+    where+      opts = execState optsState def+      vals = L.zipWith (\a b -> (realToFrac a, realToFrac b)) x y++  plot' :: [a] -> [a] -> AmbyChart ()+  plot' x y = plot x y $ return ()++  plotEq :: [a] -> (a -> a) -> State PlotEqOpts () -> AmbyChart ()+  plotEq x fn optsState = plotList vals $ def+      & color .~ (opts ^. color)+    where+      opts = execState optsState def+      vals = L.zipWith (\a b -> (realToFrac a, realToFrac b)) x $ map fn x++  plotEq' :: [a] -> (a -> a) -> AmbyChart ()+  plotEq' x fn = plotEq x fn $ return ()++  distPlot :: [a] -> State DistPlotOpts () -> AmbyChart ()+  distPlot xs optsState = distPlotVec valsVec optsState+    where+      valsVec = (V.map realToFrac . V.fromList) xs++  distPlot' :: [a] -> AmbyChart ()+  distPlot' xs = distPlot xs $ return ()++  kdePlot :: [a] -> State KdePlotOpts () -> AmbyChart ()+  kdePlot xs optsState = kdePlotVec valsVec optsState+    where+      valsVec = (V.map realToFrac . V.fromList) xs++  kdePlot' :: [a] -> AmbyChart ()+  kdePlot' xs = kdePlot xs $ return ()++  rugPlot :: [a] -> State RugPlotOpts () -> AmbyChart ()+  rugPlot xs optsState = rugPlotVec valsVec optsState+    where+      valsVec = (V.map realToFrac . V.fromList) xs++  rugPlot' :: [a] -> AmbyChart ()+  rugPlot' xs = rugPlot xs $ return ()++--------------------------+-- Generic vec plotters+--------------------------++plotVec :: (G.Vector v Double, G.Vector v (Double, Double))+        => v Double -> v Double -> State PlotOpts () -> AmbyChart ()+plotVec x y optsState = plotList vals opts+  where+    opts = execState optsState def+    vals = G.toList $ G.zip x y++plotVec' :: (G.Vector v Double, G.Vector v (Double, Double))+         => v Double -> v Double -> AmbyChart ()+plotVec' x y = plotVec x y $ return ()++plotEqVec :: (G.Vector v Double, G.Vector v (Double, Double))+          => v Double -> (Double -> Double) -> State PlotEqOpts ()+          -> AmbyChart ()+plotEqVec x fn optsState = plotList vals $ def+    & color .~ (opts ^. color)+  where+    opts = execState optsState def+    vals = G.toList $ G.zip x (G.map fn x)++plotEqVec' :: (G.Vector v Double, G.Vector v (Double, Double))+           => v Double -> (Double -> Double) -> AmbyChart ()+plotEqVec' x fn = plotEqVec x fn $ return ()++distPlotVec :: (G.Vector v Double) => v Double -> State DistPlotOpts ()+            -> AmbyChart ()+distPlotVec xs optsState = plotDistribution xs $ execState optsState def++distPlotVec' :: (G.Vector v Double) => v Double -> AmbyChart ()+distPlotVec' xs = distPlotVec xs $ return ()++kdePlotVec :: (G.Vector v Double) => v Double -> State KdePlotOpts ()+           -> AmbyChart ()+kdePlotVec xs optsState = do+    layout <- takeLayout+    putLayout $ do+      layout+      nextColor <- toColour (opts ^. color) <$> Chart.takeColor+      plotKde nextColor xs opts+  where+    opts = execState optsState def++kdePlotVec' :: (G.Vector v Double) => v Double -> AmbyChart ()+kdePlotVec' xs = kdePlotVec xs $ return ()++rugPlotVec :: (G.Vector v Double)+           => v Double -> State RugPlotOpts ()+           -> AmbyChart ()+rugPlotVec xs optsState = do+    layout <- takeLayout+    putLayout $ do+      layout+      nextColor <- toColour (opts ^. color) <$> Chart.takeColor+      plotRug nextColor xs opts+  where+    opts = execState optsState def++rugPlotVec' :: (G.Vector v Double)+            => v Double -> AmbyChart ()+rugPlotVec' xs = rugPlotVec xs $ return ()++--------------------------+-- Generic plotters+--------------------------++plotDistribution :: (G.Vector v Double) => v Double -> DistPlotOpts -> AmbyChart ()+plotDistribution xs opts = do+    layout <- takeLayout+    putLayout $ do+      layout+      nextColor <- toColour (opts ^. color) <$> Chart.takeColor+      when (opts ^. hist) $ plotHist opts nextColor numBins xs+      when (opts ^. kde) $ plotKde nextColor xs $ def+        & bw .~ (opts ^. bw)+        & shade .~ (opts ^. shade)+        & gridsize .~ (opts ^. gridsize)+        & axis .~ (opts ^. axis)+        & linewidth .~ (opts ^. kdeLinewidth)+        & cut .~ (opts ^. cut)+      when (opts ^. rug) $ plotRug nextColor xs $ def+        & height .~ (opts ^. rugHeight)+        & axis .~ (opts ^. axis)+        & linewidth .~ (opts ^. rugLinewidth)+  where+    numBins = if opts ^. bins /= 0+      then opts ^. bins+      else min 50 $ freedmanDiaconisBins xs++plotList :: [(Double, Double)] -> PlotOpts -> AmbyChart ()+plotList xs opts = do+    layout <- takeLayout+    putLayout $ do+      layout+      nextColor <- toColour (opts ^. color) <$> Chart.takeColor+      plotLine nextColor (opts ^. linewidth) xs++--------------------------+-- Composable plotters+--------------------------++plotHist :: (G.Vector v Double) => DistPlotOpts -> AlphaColour Double -> Int -> v Double+         -> EC (Layout Double Double) ()+plotHist opts c numBins xs =+  Chart.plot $ fmap histToPlot $ Chart.liftEC $ do+    plot_hist_bins .= numBins+    plot_hist_drop_lines .= True+    plot_hist_values .= G.toList xs+    plot_hist_line_style . Chart.line_width .= (opts ^. histLinewidth)+    plot_hist_line_style . Chart.line_color .= Colour.dissolve 0.5 c+    plot_hist_fill_style . Chart.fill_color .= Colour.dissolve 0.4 c+    plot_hist_vertical .= (opts ^. axis == YAxis)++plotRug :: (G.Vector v Double)+        => AlphaColour Double -> v Double -> RugPlotOpts+        -> EC (Layout Double Double) ()+plotRug c xs opts = do+      maxMay <- getPlotMaxHeightMay+      h <- calcRugHeight maxMay+      G.foldM (plotSingleLine h) () xs+      updatePlotHeight maxMay+    where+      getPlotMaxHeightMay :: EC (Layout Double Double) (Maybe Double)+      getPlotMaxHeightMay = do+        layout <- get+        layout ^.. Chart.layout_plots . each . Chart.plot_all_points+          & concatMap (if opts ^. axis == XAxis then snd else fst)+          & maximumMay+          & return++      calcRugHeight :: (Maybe Double) -> EC (Layout Double Double) Double+      calcRugHeight maxMay =+        return $ (Maybe.fromMaybe 1.0 maxMay) * (opts ^. height)++      plotSingleLine :: Double -> () -> Double -> EC (Layout Double Double) ()+      plotSingleLine h _ x = if opts ^. axis == XAxis+        then plotLine c (opts ^. linewidth) [(x, 0), (x, h)]+        else plotLine c (opts ^. linewidth)  [(0, x), (h, x)]++      updatePlotHeight :: (Maybe Double)+                       -> EC (Layout Double Double) ()+      updatePlotHeight maxMay = do+        layout2 <- get+        layout2 & Chart.layout_plots . mapped . Chart.plot_all_points+          %~ updatePlotPoints maxMay+          & put++      updatePlotPoints :: (Maybe Double) -> ([Double], [Double])+                       -> ([Double], [Double])+      updatePlotPoints maxMay (as, bs) =+        if | maxMay == Nothing && opts ^. axis == XAxis -> (0.0:as, 1.0:bs)+           | maxMay == Nothing && opts ^. axis == YAxis -> (1.0:as, 0.0:bs)+           | otherwise -> (as, bs)++plotKde :: (G.Vector v Double) => AlphaColour Double -> v Double -> KdePlotOpts+        -> EC (Layout Double Double) ()+plotKde c xs opts = do+    plotLine c (opts ^. linewidth) kdeVals+    when (opts ^. shade) $ plotShade c kdeVals+  where+    minX = G.minimum xs+    maxX = G.maximum xs+    iqr = interquartileRange xs+    std = Stats.stdDev xs+    n = fromIntegral $ G.length xs+    a = min std (iqr / 1.34)+    bandwidth = if | opts ^. bw == Scott -> 1.059 * a * n ** ((-1) / 5)+                   | (BwScalar b) <- opts ^. bw -> b+    clipLeft = minX - (opts ^. cut) * bandwidth+    clipRight = maxX + (opts ^. cut) * bandwidth+    sizeEstimate = fromIntegral $ opts ^. gridsize :: Double+    numEstimates = 2 ^ (ceiling (log sizeEstimate / log 2) :: Int)+    xVals = Stats.Points $ linspace clipLeft clipRight numEstimates+    kdeResult = Stats.estimatePDF Stats.gaussianKernel bandwidth xs xVals+    kdeVals' = U.zip (Stats.fromPoints xVals) kdeResult+    kdeVals = if opts ^. axis == YAxis+      then U.toList $ U.map swap kdeVals'+      else U.toList kdeVals'++plotLine :: AlphaColour Double -> Double -> [(Double, Double)]+         -> EC (Layout Double Double) ()+plotLine c lineWidth xs = Chart.plot $ Chart.liftEC $ do+  Chart.plot_lines_values .= [xs]+  Chart.plot_lines_style . Chart.line_width .= lineWidth+  Chart.plot_lines_style . Chart.line_color .= c++plotShade :: AlphaColour Double -> [(Double, Double)]+          -> EC (Layout Double Double) ()+plotShade c xs = Chart.plot $ Chart.liftEC $ do+  Chart.plot_fillbetween_values .= (xs & mapped . _2 %~ \y -> (0, y))+  Chart.plot_fillbetween_style .= Chart.solidFillStyle (Colour.dissolve 0.25 c)++--------------------------+-- Rendering+--------------------------+ cairoDefSave :: FilePath cairoDefSave = ".__amby.png" @@ -50,52 +332,19 @@ -- Short-hand to render to png file using Cairo backend. save :: AmbyChart () -> IO () save chart = Cairo.toFile-    def { Cairo._fo_size = size }+    def { Cairo._fo_size = newSize }     cairoDefSave     (getLayout st)   where     st = getState chart-    size = getSize st+    newSize = getSize st  -- | Short-hand to render to svg using Cairo backend saveSvg :: AmbyChart () -> IO () saveSvg chart = Diagrams.toFile-    def { Diagrams._fo_size = join (***) fromIntegral size }+    def { Diagrams._fo_size = join (***) fromIntegral newSize }     diagramsDefSave     (getLayout st)   where     st = getState chart-    size = getSize st---- | Basic x y line plot.-instance (G.Vector v Double, G.Vector v (Double, Double))-  => AmbyContainer (v Double) Double where-  plot :: v Double -> v Double -> AmbyChart ()-  plot x y = plotList $ G.toList (G.zip x y)--  plotEq :: v Double -> (Double -> Double) -> AmbyChart ()-  plotEq x fn = plotList $ G.toList (G.zip x (G.map fn x))--instance (Real a) => AmbyContainer [a] a where-  plot :: [a] -> [a] -> AmbyChart ()-  plot x y = plotList $ L.zipWith (\a b -> (realToFrac a, realToFrac b)) x y--  plotEq x fn = plotList $ L.zipWith (\a b -> (realToFrac a, realToFrac b)) x (map fn x)--plotList :: [(Double, Double)] -> AmbyChart ()-plotList plotVals = do-    theme <- takeTheme-    layout <- takeLayout-    putLayout $ layout >> mkLayout theme-  where--    linePlot :: EC l (PlotLines Double Double)-    linePlot = liftEC $ do-      nextColor <- takeColor-      plot_lines_values .= [plotVals]-      plot_lines_style . line_width .= 2.5-      plot_lines_style . line_color .= nextColor--    mkLayout :: Theme -> EC (Layout Double Double) ()-    mkLayout theme = do-      Chart.plot linePlot+    newSize = getSize st
src/Amby/Style.hs view
@@ -3,72 +3,123 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE CPP #-} module Amby.Style-  ( roundAxisData-  , noEdgeAxisData-  , scaledPaddedAxis-  , setThemeStyles+  ( setThemeStyles+  , scaledAxisCustom   )   where -import Safe import qualified Data.List as L+import Data.Ord (comparing) import Numeric -import Graphics.Rendering.Chart.Easy hiding (plot)+import Control.Lens+import Data.Default+import Safe +import Graphics.Rendering.Chart.Easy (EC, Layout, AxisData(..))+import qualified Graphics.Rendering.Chart.Easy as Chart+import Graphics.Rendering.Chart.Utils (isValidNumber)+import Graphics.Rendering.Chart.Axis.Floating (LinearAxisParams(..))+ import Amby.Theme --- | Equalizes precision for all labels.--- For example the labels [0, 0.2] become [0.0, 0.2].-roundAxisData :: AxisData x -> AxisData x-roundAxisData axisData = axisData & axis_labels %~ go+countAfterDecimal :: String -> Int+countAfterDecimal xs = case L.findIndex (== '.') xs of+  Nothing -> 0+  Just idx -> (length xs - 1) - idx++setThemeStyles :: Theme -> EC (Layout x y) ()+setThemeStyles theme = do+  Chart.layout_background .= (Chart.FillStyleSolid $ theme ^. bgColor)+  Chart.layout_plot_background .= Just (Chart.FillStyleSolid $ theme ^. plotBgColor)+  Chart.layout_all_font_styles . Chart.font_name .= (theme ^. fontFamily)+  Chart.layout_all_font_styles . Chart.font_size .= (theme ^. fontSize)+  Chart.layout_axes_styles . Chart.axis_line_style . Chart.line_width .= 0.0+  Chart.layout_axes_styles . Chart.axis_grid_style . Chart.line_color+    .= (theme ^. gridLineColor)+  Chart.layout_axes_styles . Chart.axis_grid_style . Chart.line_width .= 1.5+  Chart.layout_axes_styles . Chart.axis_grid_style . Chart.line_dashes .= []+  Chart.layout_axes_styles . Chart.axis_label_gap .= 8+  Chart.layout_x_axis . Chart.laxis_override .= roundAxisData+  Chart.layout_y_axis . Chart.laxis_override .= roundAxisData+  Chart.layout_margin .= 10++--- | Equalizes precision for all labels.+--- For example the labels [0, 0.2] become [0.0, 0.2].+roundAxisData :: Chart.AxisData x -> Chart.AxisData x+roundAxisData axisData = axisData & Chart.axis_labels %~ go   where     go xs = map go' xs     go' xs = xs         & mapped . _2-        %~ \x -> case readMay x of+        %~ \x -> case readMay x :: Maybe Double of           Just a -> showFFloat (Just precision) a ""           Nothing -> x       where         precision = maximum $ map (countAfterDecimal . snd) xs -noEdgeAxisData :: AxisData x -> AxisData x-noEdgeAxisData axisData = axisData & axis_grid %~ removeEdges+------------------------+-- Scaling+------------------------++-- | Take from the 'Chart' package with minor modifications.+scaledAxisCustom :: RealFloat a => Chart.LinearAxisParams a -> (a, a)+                 -> Chart.AxisFn a+scaledAxisCustom lap rs@(minV,maxV) ps0 = makeAxisCustom'+    realToFrac+    realToFrac+    labelFn+    (labelvs, tickvs, gridvs)+    rs   where-    removeEdges xs-      | length xs < 2 = []-      | otherwise = (init . tail) xs+#if MIN_VERSION_Chart(1,7,0)+    labelFn   = _la_labelf lap+#else+    labelFn   = map (_la_labelf lap)+#endif -scaledPaddedAxis :: forall x. (RealFloat x, PlotValue x) => [x] -> AxisData x-scaledPaddedAxis axisPoints = defaultAxis-    & axis_grid %~ (\xs -> gridMin : xs ++ [gridMax])+    ps        = filter isValidNumber ps0+    range []  = (0,1)+    range _   | minV == maxV = if minV==0 then (-1,1) else+                               let d = abs (minV * 0.01) in (minV-d,maxV+d)+              | otherwise    = rs+    labelvs   = map fromRational $ steps (fromIntegral (_la_nLabels lap)) r+    tickvs    = map fromRational $ steps (fromIntegral (_la_nTicks lap))+                                         (minimum labelvs,maximum labelvs)+    gridvs    = labelvs+    r         = range ps++steps :: RealFloat a => a -> (a,a) -> [Rational]+steps nSteps rs@(minV,maxV) = map ((s*) . fromIntegral) [min' .. max']   where-    getGridLimits xs-      | length xs < 1 = error "Not enough grid points"-      | otherwise = (head xs - spacing, last xs + spacing)-      where-        getSpacing (x:y:xs) = y - x-        spacing = getSpacing xs-    defaultAxis = autoAxis axisPoints-    (gridMin, gridMax) = getGridLimits (defaultAxis ^. axis_grid)+    s    = chooseStep nSteps rs+    min' :: Integer+    min' = floor   $ realToFrac minV / s+    max' = ceiling $ realToFrac maxV / s -countAfterDecimal :: String -> Int-countAfterDecimal xs = case L.findIndex (== '.') xs of-  Nothing -> 0-  Just idx -> (length xs - 1) - idx+chooseStep :: RealFloat a => a -> (a,a) -> Rational+chooseStep nsteps (x1,x2) = L.minimumBy (comparing proximity) stepVals+  where+    delta = x2 - x1+    mult  | delta == 0 = 1  -- Otherwise the case below will use all of memory+          | otherwise  = 10 ^^ ((floor $ log $ delta / nsteps)::Integer)+    stepVals = map (mult*) [0.1,0.2,0.25,0.5,1.0,2.0,2.5,5.0,10,20,25,50]+    proximity x = abs $ delta / realToFrac x - nsteps -setThemeStyles :: Theme -> EC (Layout Double Double) ()-setThemeStyles theme = do-  layout_background .= (FillStyleSolid $ getBgColor theme)-  layout_plot_background .= Just (FillStyleSolid $ getPlotBgColor theme)-  layout_all_font_styles . font_name .= (getFontFamily theme)-  layout_all_font_styles . font_size .= (getFontSize theme)-  layout_axes_styles . axis_line_style . line_width .= 0.0-  layout_axes_styles . axis_grid_style . line_color .= (getGridLineColor theme)-  layout_axes_styles . axis_grid_style . line_width .= 1.5-  layout_axes_styles . axis_grid_style . line_dashes .= []-  layout_axes_styles . axis_label_gap .= 8-  layout_x_axis . laxis_override .= roundAxisData-  layout_y_axis . laxis_override .= roundAxisData-  layout_margin .= 10+makeAxisCustom' :: Ord x => (x -> Double) -> (Double -> x) -> ([x] -> [String])+                   -> ([x],[x],[x]) -> (x, x) -> AxisData x+makeAxisCustom' t f labelf (labelvs, tickvs, gridvs) (minX, minY) = AxisData {+    _axis_visibility = def,+    _axis_viewport = Chart.linMap t (minX, minY),+    _axis_tropweiv = Chart.invLinMap f t (minimum labelvs, maximum labelvs),+    _axis_ticks    = zip tickvs (repeat 2)  ++  zip labelvs (repeat 5),+    _axis_grid     = gridvs,+    _axis_labels   =+      let zipWithLengthCheck (x:xs) (y:ys) = (x,y) : zipWithLengthCheck xs ys+          zipWithLengthCheck [] [] = []+          zipWithLengthCheck _ _ =+            error "makeAxis': label function returned the wrong number of labels"+      in [zipWithLengthCheck labelvs (labelf labelvs)]+    }
src/Amby/Theme.hs view
@@ -1,45 +1,65 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleInstances #-} module Amby.Theme-  ( Theme+  ( Theme(..)+  , AmbyColor(..)    -- * Themes   , mutedTheme   , deepTheme   , cleanTheme -  -- * Accessors-  , getBgColor-  , getPlotBgColor-  , getGridLineColor-  , getColorCycle-  , getFontFamily-  , getFontSize+  -- * Lenses+  , bgColor+  , plotBgColor+  , gridLineColor+  , colorCycle+  , fontFamily+  , fontSize++  -- * Color helpers+  , hexToRgb+  , hexToRgba+  , toColour   )   where +import Control.Lens import Data.Colour import Data.Colour.SRGB-import Data.Default.Class+import Data.Default +-- | Used to style a chart. data Theme = Theme-  { bgColor :: AlphaColour Double-  , plotBgColor :: AlphaColour Double-  , gridLineColor :: AlphaColour Double-  , fontFamily :: String-  , fontSize :: Double-  , colorCycle :: [AlphaColour Double]+  { _themeBgColor :: AlphaColour Double+  , _themePlotBgColor :: AlphaColour Double+  , _themeGridLineColor :: AlphaColour Double+  , _themeFontFamily :: String+  , _themeFontSize :: Double+  , _themeColorCycle :: [AlphaColour Double]   }+makeFields ''Theme  instance Default Theme where   def = deepTheme +-- | Api facing color selection.+data AmbyColor =+    DefaultColor+  | R | G | B | C | M | Y | K | W+  | CustomColor (AlphaColour Double)+  deriving (Show, Eq)+ mutedTheme :: Theme mutedTheme = Theme-  { bgColor = opaque (sRGB24read "#FFFFFF")-  , plotBgColor = opaque (sRGB24read "#EAEAF2")-  , gridLineColor = opaque (sRGB24read "#FFFFFF")-  , fontFamily = "Verdana"-  , fontSize = 14-  , colorCycle =+  { _themeBgColor = opaque (sRGB24read "#FFFFFF")+  , _themePlotBgColor = opaque (sRGB24read "#EAEAF2")+  , _themeGridLineColor = opaque (sRGB24read "#FFFFFF")+  , _themeFontFamily = "Verdana"+  , _themeFontSize = 14+  , _themeColorCycle =     [ opaque (sRGB24read "#4878CF")     , opaque (sRGB24read "#6ACC65")     , opaque (sRGB24read "#D65F5F")@@ -51,12 +71,12 @@  deepTheme :: Theme deepTheme = Theme-  { bgColor = opaque (sRGB24read "#FFFFFF")-  , plotBgColor = opaque (sRGB24read "#EAEAF2")-  , gridLineColor = opaque (sRGB24read "#FFFFFF")-  , fontFamily = "Verdana"-  , fontSize = 14-  , colorCycle =+  { _themeBgColor = opaque (sRGB24read "#FFFFFF")+  , _themePlotBgColor = opaque (sRGB24read "#EAEAF2")+  , _themeGridLineColor = opaque (sRGB24read "#FFFFFF")+  , _themeFontFamily = "Verdana"+  , _themeFontSize = 14+  , _themeColorCycle =     [ opaque (sRGB24read "#4C72B0")     , opaque (sRGB24read "#55A868")     , opaque (sRGB24read "#C44E52")@@ -68,10 +88,10 @@  cleanTheme :: Theme cleanTheme = def-  { bgColor = opaque (sRGB24read "#FFFFFF")-  , plotBgColor = opaque (sRGB24read "#FFFFFF")-  , gridLineColor = opaque (sRGB24read "#EEEEEE")-  , colorCycle =+  { _themeBgColor = opaque (sRGB24read "#FFFFFF")+  , _themePlotBgColor = opaque (sRGB24read "#FFFFFF")+  , _themeGridLineColor = opaque (sRGB24read "#EEEEEE")+  , _themeColorCycle =     [ opaque (sRGB24read "#1776B6")     , opaque (sRGB24read "#FF962A")     , opaque (sRGB24read "#24A122")@@ -80,20 +100,25 @@     ]   } -getBgColor :: Theme -> AlphaColour Double-getBgColor = bgColor--getPlotBgColor :: Theme -> AlphaColour Double-getPlotBgColor = plotBgColor--getGridLineColor :: Theme -> AlphaColour Double-getGridLineColor = gridLineColor+------------------------+-- Color helpers+------------------------ -getColorCycle :: Theme -> [AlphaColour Double]-getColorCycle = colorCycle+hexToRgb :: String -> AmbyColor+hexToRgb s = CustomColor $ opaque (sRGB24read s) -getFontFamily :: Theme -> String-getFontFamily = fontFamily+hexToRgba :: String -> Double -> AmbyColor+hexToRgba s a = CustomColor $ withOpacity (sRGB24read s) a -getFontSize :: Theme -> Double-getFontSize = fontSize+-- | Conversion from Amby Api 'Color' to  underlying 'Colour' type.+toColour :: AmbyColor -> AlphaColour Double -> AlphaColour Double+toColour DefaultColor d = d+toColour (CustomColor c) _ = c+toColour B _ = opaque (sRGB24read "#4878CF")+toColour G _ = opaque (sRGB24read "#6ACC65")+toColour R _ = opaque (sRGB24read "#D65F5F")+toColour M _ = opaque (sRGB24read "#B47CC7")+toColour Y _ = opaque (sRGB24read "#C4AD66")+toColour C _ = opaque (sRGB24read "#77BEDB")+toColour K _ = opaque (sRGB24read "#000000")+toColour W _ = opaque (sRGB24read "#FFFFFF")
src/Amby/Types.hs view
@@ -1,12 +1,15 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-} module Amby.Types   ( AmbyContainer(..)   , AmbyState   , AmbyChart -  -- * Accessors+  -- * General accessors   , takeTheme   , theme   , xlim@@ -16,40 +19,138 @@   , getLayout   , getSize   , putLayout+  , linewidth+  , histLinewidth+  , kdeLinewidth+  , rugLinewidth++  -- * Plot options+  , PlotOpts+  , PlotEqOpts+  , DistPlotOpts+  , KdePlotOpts+  , RugPlotOpts+  , Bandwidth(..)+  , Axis(..)+  , bins+  , hist+  , rug+  , rugHeight+  , cut+  , shade+  , kde+  , axis+  , height+  , gridsize+  , bw+  , color   )   where  import Control.Monad.State++import Control.Lens import Data.Default.Class-import Graphics.Rendering.Chart.Easy hiding (plot)+import Graphics.Rendering.Chart.Easy (EC, Layout)+import qualified Graphics.Rendering.Chart.Easy as Chart import Graphics.Rendering.Chart.Backend.Cairo (FileOptions(..)) +import Amby.Compatibility.HistogramPlot import Amby.Theme import Amby.Style +-----------------------------------+-- Parameter option types+-----------------------------------++data Axis = XAxis | YAxis deriving (Show, Eq)+data Bandwidth = Scott | BwScalar Double deriving (Show, Eq)++data PlotOpts = PlotOpts+  { _plotOptsColor :: AmbyColor+  , _plotOptsLinewidth :: Double+  } deriving (Show)+makeFields ''PlotOpts++data PlotEqOpts = PlotEqOpts+  { _plotEqOptsColor :: AmbyColor+  , _plotEqOptsLinewidth :: Double+  } deriving (Show)+makeFields ''PlotEqOpts++data DistPlotOpts = DistPlotOpts+  { _distPlotOptsRug :: Bool+  , _distPlotOptsKde :: Bool+  , _distPlotOptsHist :: Bool+  , _distPlotOptsColor :: AmbyColor++  -- hist opts+  , _distPlotOptsHistLinewidth :: Double+  , _distPlotOptsBins :: Int++  -- kde opts+  , _distPlotOptsShade :: Bool+  , _distPlotOptsBw :: Bandwidth+  , _distPlotOptsCut :: Double+  , _distPlotOptsAxis :: Axis+  , _distPlotOptsGridsize :: Int+  , _distPlotOptsKdeLinewidth :: Double++  -- rug opts+  , _distPlotOptsRugHeight :: Double+  , _distPlotOptsRugLinewidth :: Double+  } deriving (Show)+makeFields ''DistPlotOpts++data KdePlotOpts = KdePlotOpts+  { _kdePlotOptsShade :: Bool+  , _kdePlotOptsBw :: Bandwidth+  , _kdePlotOptsAxis :: Axis+  , _kdePlotOptsGridsize :: Int+  , _kdePlotOptsColor :: AmbyColor+  , _kdePlotOptsLinewidth :: Double+  , _kdePlotOptsCut :: Double+  } deriving (Show)+makeFields ''KdePlotOpts++data RugPlotOpts = RugPlotOpts+  { _rugPlotOptsHeight :: Double+  , _rugPlotOptsAxis :: Axis+  , _rugPlotOptsColor :: AmbyColor+  , _rugPlotOptsLinewidth :: Double+  }+makeFields ''RugPlotOpts++-----------------------------------+-- Main types+-----------------------------------+ data AmbyState = AmbyState   { _asThemeState :: Theme   , _asLayoutState :: EC (Layout Double Double) ()   , _asSize :: (Int, Int)   }--instance Default AmbyState where-  def = AmbyState-    { _asThemeState = def-    , _asLayoutState = do-      setColors (getColorCycle def)-      setThemeStyles def-    , _asSize = _fo_size def-    }--$( makeLenses ''AmbyState)+makeLenses ''AmbyState  type AmbyChart a = State AmbyState a -class AmbyContainer c a | c -> a where-  plot :: c -> c -> AmbyChart ()-  plotEq :: c -> (a -> a) -> AmbyChart ()+class AmbyContainer c where+  type Value c :: *+  plot :: c -> c -> State PlotOpts () -> AmbyChart ()+  plot' :: c -> c -> AmbyChart ()+  plotEq :: c -> (Value c -> Value c) -> State PlotEqOpts () -> AmbyChart ()+  plotEq' :: c -> (Value c -> Value c) -> AmbyChart ()+  distPlot :: c -> State DistPlotOpts () -> AmbyChart ()+  distPlot' :: c -> AmbyChart ()+  kdePlot :: c -> State KdePlotOpts () -> AmbyChart ()+  kdePlot' :: c -> AmbyChart ()+  rugPlot :: c -> State RugPlotOpts () -> AmbyChart()+  rugPlot' :: c -> AmbyChart () +-----------------------------------+-- General options+-----------------------------------+ getLayout :: AmbyState -> EC (Layout Double Double) () getLayout s = s ^. asLayoutState @@ -75,7 +176,7 @@   l <- use asLayoutState   asLayoutState .= do     l-    setColors (getColorCycle t)+    Chart.setColors $ t ^. colorCycle     setThemeStyles t   asThemeState .= t @@ -84,17 +185,92 @@   l <- use asLayoutState   asLayoutState .= do     l-    layout_x_axis . laxis_generate .= scaledAxis def rs+    Chart.layout_x_axis . Chart.laxis_generate .= scaledAxisCustom def rs  ylim :: (Double, Double) -> AmbyChart () ylim rs = do   l <- use asLayoutState   asLayoutState .= do     l-    layout_y_axis . laxis_generate .= scaledAxis def rs+    Chart.layout_y_axis . Chart.laxis_generate .= scaledAxisCustom def rs  size :: (Int, Int) -> AmbyChart ()-size rs = do-  l <- use asSize-  asSize .= rs+size rs = asSize .= rs +--------------------+-- Default instances+--------------------++instance Default AmbyState where+  def = AmbyState+    { _asThemeState = def+    , _asLayoutState = do+      Chart.setColors $ (def :: Theme) ^. colorCycle+      setThemeStyles def+    , _asSize = _fo_size def+    }++instance Default (PlotHist x Double) where+  def = PlotHist+    { _plot_hist_bins = 20+    , _plot_hist_title       = ""+    , _plot_hist_values      = []+    , _plot_hist_no_zeros    = False+    , _plot_hist_range       = Nothing+    , _plot_hist_drop_lines  = False+    , _plot_hist_line_style  = def+    , _plot_hist_fill_style  = def+    , _plot_hist_norm_func   = (\a b -> fromIntegral b / a)+    , _plot_hist_vertical    = False+    }++instance Default PlotOpts where+  def = PlotOpts+    { _plotOptsColor = DefaultColor+    , _plotOptsLinewidth = 2.5+    }++instance Default PlotEqOpts where+  def = PlotEqOpts+    { _plotEqOptsColor = DefaultColor+    , _plotEqOptsLinewidth = 2.5+    }++instance Default DistPlotOpts where+  def = DistPlotOpts+    { _distPlotOptsRug = False+    , _distPlotOptsHist = True+    , _distPlotOptsKde = True+    , _distPlotOptsBins = 0+    , _distPlotOptsColor = DefaultColor+    , _distPlotOptsHistLinewidth = 2.5+    , _distPlotOptsAxis = XAxis++    , _distPlotOptsShade = False+    , _distPlotOptsBw = Scott+    , _distPlotOptsGridsize = 100+    , _distPlotOptsKdeLinewidth = 2.5+    , _distPlotOptsCut = 3++    , _distPlotOptsRugHeight = 0.05+    , _distPlotOptsRugLinewidth = 1.2+    }++instance Default KdePlotOpts where+  def = KdePlotOpts+    { _kdePlotOptsShade = False+    , _kdePlotOptsBw = Scott+    , _kdePlotOptsGridsize = 100+    , _kdePlotOptsAxis = XAxis+    , _kdePlotOptsColor = DefaultColor+    , _kdePlotOptsLinewidth = 2.5+    , _kdePlotOptsCut = 3+    }++instance Default RugPlotOpts where+  def = RugPlotOpts+    { _rugPlotOptsHeight = 0.05+    , _rugPlotOptsAxis = XAxis+    , _rugPlotOptsColor = DefaultColor+    , _rugPlotOptsLinewidth = 1.2+    }
test/Spec.hs view
@@ -1,2 +1,22 @@+import qualified Data.Vector.Unboxed as U++import Test.DocTest+import Test.Tasty+import Test.Tasty.HUnit++import qualified Amby as Am+ main :: IO ()-main = putStrLn "Test suite not yet implemented"+main = do+  doctest+    [ "src/Amby/Numeric.hs"+    ]+  defaultMain tests++tests :: TestTree+tests = testGroup "Unit tests"+  [ testCase "linspace vector generation" $+      Am.linspace 0 5 6 @?= U.fromList [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]+  , testCase "arange vector generation" $+      Am.arange 0 5 1 @?= U.fromList [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]+  ]