packages feed

chart-unit (empty) → 0.1.0.0

raw patch · 6 files changed

+824/−0 lines, 6 filesdep +basedep +chart-unitdep +coloursetup-changed

Dependencies added: base, chart-unit, colour, containers, data-default, diagrams, diagrams-core, diagrams-lib, diagrams-rasterific, diagrams-svg, foldl, lens, primitive, protolude, random-fu, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Tony Day (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Tony Day nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ chart-unit.cabal view
@@ -0,0 +1,154 @@+name:+  chart-unit+version:+  0.1.0.0+synopsis:+  A set of native haskell charts.+description:+  readme.lhs+category:+  charts+homepage:+  https://github.com/tonyday567/chart-unit+license:+  BSD3+license-file:+  LICENSE+author:+  Tony Day+maintainer:+  tonyday567@gmail.com+copyright:+  2016 Tony Day+build-type:+  Simple+cabal-version:+  >=1.10++library+  hs-source-dirs:+    src+  exposed-modules:+    Chart.Unit,+    Chart.Types+  ghc-options:+    -funbox-strict-fields+    -fforce-recomp          +  build-depends:+    base >= 4.7 && < 5,+    colour,+    containers,+    data-default,+    diagrams,+    diagrams-core,+    diagrams-lib,+    diagrams-svg,+    diagrams-rasterific,+    foldl,+    lens,+    primitive,+    protolude,+    text+  default-language:+    Haskell2010+  default-extensions:+    NoImplicitPrelude,+    UnicodeSyntax,+    BangPatterns,+    BinaryLiterals,+    DeriveFoldable,+    DeriveFunctor,+    DeriveGeneric,+    DeriveTraversable,+    DisambiguateRecordFields,+    EmptyCase,+    FlexibleContexts,+    FlexibleInstances,+    FunctionalDependencies,+    GADTSyntax,+    InstanceSigs,+    KindSignatures,+    LambdaCase,+    MonadComprehensions,+    MultiParamTypeClasses,+    MultiWayIf,+    NegativeLiterals,+    OverloadedStrings,+    ParallelListComp,+    PartialTypeSignatures,+    PatternSynonyms,+    RankNTypes,+    RecordWildCards,+    RecursiveDo,+    ScopedTypeVariables,+    TupleSections,+    TypeFamilies,+    TypeOperators++executable readme+  default-language:+    Haskell2010+  ghc-options:+    -funbox-strict-fields+    -fforce-recomp+    -threaded+    -rtsopts+    -with-rtsopts=-N+  hs-source-dirs:+    ./+  main-is:+    readme.lhs+  build-depends:+    base >= 4.7 && < 5,+    chart-unit,+    containers,+    diagrams,+    diagrams-core,+    diagrams-lib,+    diagrams-rasterific,+    diagrams-svg,+    foldl,+    lens,+    primitive,+    protolude,+    random-fu,+    text+  default-extensions:+    NoImplicitPrelude,+    UnicodeSyntax,+    BangPatterns,+    BinaryLiterals,+    DeriveFoldable,+    DeriveFunctor,+    DeriveGeneric,+    DeriveTraversable,+    DisambiguateRecordFields,+    EmptyCase,+    FlexibleContexts,+    FlexibleInstances,+    FunctionalDependencies,+    GADTSyntax,+    InstanceSigs,+    KindSignatures,+    LambdaCase,+    MonadComprehensions,+    MultiParamTypeClasses,+    MultiWayIf,+    NegativeLiterals,+    OverloadedStrings,+    ParallelListComp,+    PartialTypeSignatures,+    PatternSynonyms,+    RankNTypes,+    RecordWildCards,+    RecursiveDo,+    ScopedTypeVariables,+    TupleSections,+    TypeFamilies,+    TypeOperators++source-repository head+  type:+    git+  location:+    https://github.com/tonyday567/chart-unit
+ readme.lhs view
@@ -0,0 +1,226 @@+```include+other/header.md+```++[chart-unit](https://tonyday567.github.io/chart-unit.html) [![Build Status](https://travis-ci.org/tonyday567/chart-unit.png)](https://travis-ci.org/tonyday567/chart-unit)+===++scratchpad+---++My newest chart `padsvg $ linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]`++![](other/scratchpad.svg)++This slowly growing collection of charts:++- renders nicely over a wide chart size range, svg and png formats.+- render similarly at different scale+- are opinionated minimalism+- are unit shapes in the spirit of the [diagrams](http://projects.haskell.org/diagrams/doc/quickstart.html) design space.+- can be quickly integrated into ad-hoc haskell data analytics, providing a visual feedback loop.++charts+---++Scatter++![](other/scatter.svg)++Histogram++![](other/hist.svg)++Line++![](other/line.svg)++Lines++![](other/lines.svg)++Labelled Bar Chart++![](other/bar.svg)++rasterific png renders+---++![](other/scratchpad.png)++Scatter++![](other/scatter.png)++Histogram++![](other/hist.png)++Line++![](other/line.png)++Lines++![](other/lines.png)++Labelled Bar Chart++![](other/bar.png)++> {-# OPTIONS_GHC -Wall #-}+> {-# OPTIONS_GHC -fno-warn-type-defaults #-}+> {-# OPTIONS_GHC -fno-warn-missing-signatures #-}+> import Protolude+> import Control.Monad.Primitive (unsafeInlineIO)+> import Diagrams.Prelude hiding ((<>))+> import qualified Control.Foldl as L+> import qualified Data.Random as R+> import qualified Data.Map.Strict as Map+> import qualified Data.Text as Text+>+> import Chart.Unit++some test data+---++Standard normal random variates.  Called ys to distinguish from the horizontal axis of the chart (xs) which are often implicitly [0..]++> ys :: Int -> IO [Double]+> ys n =+>   replicateM n $ R.runRVar R.stdNormal R.StdRandom+>++A bunch of ys, accumulated.++> yss :: (Int, Int) -> [[Double]]+> yss (n,m) = unsafeInlineIO $ do+>   yss' <- replicateM m $ ys n+>   pure $ (drop 1 . L.scan L.sum) <$> yss'+>++xys is a list of X,Y pairs, correlated normal random variates to add some shape to chart examples.++> rXYs :: Int -> Double -> [(Double,Double)]+> rXYs n c = unsafeInlineIO $ do+>   s0 <- replicateM n $ R.runRVar R.stdNormal R.StdRandom+>   s1 <- replicateM n $ R.runRVar R.stdNormal R.StdRandom+>   let s1' = zipWith (\x y -> c * x + sqrt (1 - c * c) * y) s0 s1+>   pure $ zip s0 s1'+>+> xys = rXYs 1000 0.8+>++XY random walk++> rwxy = L.scan (L.Fold (\(x,y) (x',y') -> (x+x',y+y')) (0.0,0.0) identity) (take 100 xys)+>++xysHist is a histogram of 10000 one-dim random normals.++The data out is a (X,Y) pair list, with mid-point of the bucket as X, and bucket count as Y.++> xysHist :: [(Double,Double)]+> xysHist = unsafeInlineIO $ do+>   ys' <- replicateM 10000 $ R.runRVar R.stdNormal R.StdRandom :: IO [Double]+>   let (f,s,n) = mkTicks' (range1D ys') 100+>   let cuts = (\x -> f+s*fromIntegral x) <$> [0..n]+>   let mids = (+(s/2)) <$> cuts+>   let count = L.Fold (\x a -> Map.insertWith (+) a 1 x) Map.empty identity+>   let countBool = L.Fold (\x a -> x + if a then 1 else 0) 0 identity+>   let histMap = L.fold count $ (\x -> L.fold countBool (fmap (x >) cuts)) <$> ys'+>   let histList = (\x -> Map.findWithDefault 0 x histMap) <$> [0..n]+>   return (zip mids (fromIntegral <$> histList))+>++Scale Robustness+---++xys rendered on the XY plane as dots - a scatter chart with no axes - is invariant to scale.  The data could be multiplied by any scalar, and look exactly the same.++![](other/dots.svg)++Axes break this scale invariance. Ticks and tick labels can hide this to some extent and look almost the same across scales.++![](other/scatter.svg)++This chart will look the same on a data scale change, except for tick magnitudes.++main+---++>+> main :: IO ()+> main = do+ +See develop section below for my workflow.++>   padsvg $+>       linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]+>   fileSvg "other/line.svg" (200,200) $+>     (lineXY def rwxy)+>   filePng "other/line.png" (200,200) $+>     (lineXY def rwxy)+>   fileSvg "other/lines.svg" (200,200) $+>     (linesXY def $ zip [0..] <$> yss (1000, 10))+>   filePng "other/lines.png" (200,200) $+>     (linesXY def $ zip [0..] <$> yss (1000, 10))+>   fileSvg "other/dots.svg" (200,200) $+>     (scatter def xys)+>   filePng "other/dots.png" (200,200) $+>     (scatter def xys)+>   fileSvg "other/scatter.svg" (200,200) $+>     (scatterXY def xys)+>   filePng "other/scatter.png" (200,200) $+>     (scatterXY def xys)+>   fileSvg "other/bar.svg" (200,200) $+>     barLabelled def (unsafeInlineIO $ ys 10) (fmap Text.pack <$> take 10 $ (:[]) <$> ['a'..])+>   filePng "other/bar.png" (200,200) $+>     barLabelled def (unsafeInlineIO $ ys 10) (fmap Text.pack <$> take 10 $ (:[]) <$> ['a'..])+>   fileSvg "other/hist.svg" (200,200) $+>     barRange def xysHist+>   filePng "other/hist.png" (200,200) $+>     barRange def xysHist++diagrams development recipe+---++In constructing new `units`:++- diagrams go from abstract to concrete+- start with the unitSquare: 4 points, 1x1, origin in the center+- work out where the origin should be, given the scaling needed.+- turn the pointful shape into a Trail+- close the Trail into a SVG-like loop+- turn the Trail into a QDiagram++You can slide up and down the various diagrams abstraction levels creating transformations at each level.  For example, here's something I use to work at the point level:++> unitp f = unitSquare # f # fromVertices # closeTrail # strokeTrail++workflow+---++> padsvg :: ChartSvg -> IO ()+> padsvg t =+>   fileSvg "other/scratchpad.svg" (400,400) t+>+> padpng :: ChartPng -> IO ()+> padpng t =+>   filePng "other/scratchpad.png" (400,400) t+>++Create a markdown version of readme.lhs:++~~~+pandoc -f markdown+lhs -t html -i readme.lhs -o index.html+~~~++Then fire up an intero session, and use padq to display coding results on-the-fly, mashing the refresh button on a browser pointed to readme.html.++or go for a compilation loop like:++~~~+stack install && readme && pandoc -f markdown+lhs -t html -i readme.lhs -o index.html --mathjax --filter pandoc-include && pandoc -f markdown+lhs -t markdown -i readme.lhs -o readme.md --mathjax --filter pandoc-include+~~~+
+ src/Chart/Types.hs view
@@ -0,0 +1,137 @@+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}+{-# LANGUAGE TemplateHaskell #-}++module Chart.Types where++import Protolude+import Diagrams.Prelude+-- import Data.Default+import Diagrams.Backend.SVG (SVG, renderSVG)+import Diagrams.Backend.Rasterific (Rasterific, renderRasterific)++type ChartSvg = QDiagram SVG V2 Double Any+type ChartPng = QDiagram Rasterific V2 Double Any++fileSvg ∷ FilePath → (Double, Double) → ChartSvg → IO ()+fileSvg f s = renderSVG f (mkSizeSpec (Just <$> r2 s))++filePng ∷ FilePath → (Double,Double) → ChartPng → IO ()+filePng f s = renderRasterific f (mkSizeSpec (Just <$> r2 s))++rgba :: (Floating a, Ord a) => (a, a, a, a) -> AlphaColour a+rgba (r,g,b,a) = withOpacity (sRGB (r/255) (g/255) (b/255)) a++data Orientation = X | Y++data Placement = AxisLeft | AxisRight | AxisTop | AxisBottom++data TickStyle = TickNone | TickLabels [Text] | TickNumber Int++data AxisConfig = AxisConfig+  { _axisOrientation :: Orientation+  , _axisPlacement :: Placement+  , _axisHeight :: Double+  , _axisColor :: AlphaColour Double+  , _axisMarkSize :: Double -- mark length+  , _axisMarkColor :: AlphaColour Double+  , _axisStrutSize :: Double -- distance of label from mark+  , _axisTextSize :: Double+  , _axisTextColor :: AlphaColour Double+  , _axisTickStyle :: TickStyle+  , _axisAlignedTextRight :: Double+  , _axisAlignedTextBottom :: Double+  }++instance Default AxisConfig where+  def =+    AxisConfig +    X+    AxisBottom+    0.02+    (rgba(94, 19, 94, 0.5))+    0.02+    (rgba (0, 102, 200, 0.5))+    0.02+    0.04+    (rgba (30,30,30,0.7))+    (TickNumber 10)+    0.5+    1++makeLenses ''AxisConfig ++data ChartConfig = ChartConfig+  { _chartPad :: Double+  , _chartColor :: AlphaColour Double+  , _chartAxes :: [AxisConfig]+  }++instance Default ChartConfig where+  def =+    ChartConfig+    1.3+    (rgba(128, 128, 128, 0.6))+    [def,+     axisAlignedTextBottom .~ 0.65 $+     axisAlignedTextRight .~ 1 $+     axisOrientation .~ Y $+     axisPlacement .~ AxisLeft $+     def]++makeLenses ''ChartConfig++data ScatterConfig = ScatterConfig+  { _scatterChart :: ChartConfig+  , _scatterSize :: Double+  }++instance Default ScatterConfig where+  def = ScatterConfig (chartColor .~ rgba(128, 128, 128, 0.1) $ def) 0.03++makeLenses ''ScatterConfig++data BarConfig = BarConfig+  { _barChart :: ChartConfig+  , _barSep :: Double+  }++instance Default BarConfig where+  def = BarConfig (chartColor .~ rgba(59, 89, 152, 0.6) $ def) 0.01++makeLenses ''BarConfig++data LineConfig = LineConfig+  { _lineChart :: ChartConfig+  , _lineSize :: Double+  }++instance Default LineConfig where+  def = LineConfig (chartColor .~ rgba(59, 89, 152, 0.6) $ def) 0.001++makeLenses ''LineConfig++data OneLine = OneLine+    { _lColor :: AlphaColour Double+    , _lSize :: Double+    }++instance Default OneLine where+  def = OneLine (rgba(59, 89, 152, 0.6)) 0.001++makeLenses ''OneLine++data LinesConfig = LinesConfig+  { _linesChart :: ChartConfig+  , _linesLines :: [OneLine]+  }++instance Default LinesConfig where+  def = LinesConfig def [ OneLine (rgba(60,90,150,1)) 0.002+                        , OneLine (rgba(0,128,255,1)) 0.002+                        , OneLine (rgba(255,128,0,1)) 0.002+                        , OneLine (rgba(128,0,255,1)) 0.002+                        , OneLine (rgba(0,255,255,1)) 0.002+                        ]++makeLenses ''LinesConfig
+ src/Chart/Unit.hs view
@@ -0,0 +1,275 @@+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++module Chart.Unit (+    range1D,+    range1Ds,+    unit,+    units,+    unitXY,+    unitsXY,+    chartXY,+    scatter,+    scatterXY,+    barRange,+    barLabelled,+    bars,+    line,+    lines,+    lineXY,+    linesXY,+    axisXY,+    -- toFile,+    mkTicks,+    mkTicks',+    module Chart.Types,+    module Control.Lens,+    module Data.Default+) where++import Chart.Types+import qualified Control.Foldl as L+import Control.Lens hiding (beside, none, (#))+import Data.Default (def)+import qualified Data.Text as Text+import Diagrams.Prelude hiding (unit) +import Protolude hiding (min,max)+import Text.Printf+import qualified Diagrams.TwoD.Text++range1D :: (Fractional t, Ord t, Foldable f) => f t -> (t, t)+range1D = L.fold (L.Fold step initial extract)+  where+    step Nothing x = Just (x,x)+    step (Just (min,max)) x =+      Just (min' x min, max' x max)+    max' x1 x2 = if x1 > x2 then x1 else x2+    min' x1 x2 = if x1 < x2 then x1 else x2+    initial = Nothing+    extract = fromMaybe (-0.5,0.5)++range1Ds :: (Fractional t, Ord t, Foldable f, Foldable f') => f' (f t) -> (t, t)+range1Ds xss = L.fold (L.Fold step initial extract) xss+  where+    step Nothing x = Just (range1D x)+    step (Just (min, max)) x =+      Just (min', max')+      where+        (min'', max'') = range1D x+        min' = if min'' < min then min'' else min+        max' = if max'' > max then max'' else max+    initial = Nothing+    extract = fromMaybe (-0.5,0.5)++-- unit scales and translates to a [-0.5,0.5] range+unit :: (Fractional b, Functor f, Ord b, Foldable f) => f b -> f b+unit xs =+  let (minX,maxX) = range1D xs in+  (\x -> (x-minX)/(maxX-minX) - 0.5) <$> xs++-- units scales multiple xs to a common range+units :: (Fractional b, Functor f, Ord b, Foldable f, Functor f', Foldable f') =>+    f' (f b) -> f' (f b)+units xss =+  let (minX,maxX) = range1Ds xss in+  (fmap (\x -> (x-minX)/(maxX-minX) - 0.5)) <$> xss++-- scale 2d points (XY) to ((-0.5,-0.5), (0.5,0.5))+unitXY :: (Fractional b, Fractional a, Ord b, Ord a) => [(a, b)] -> [(a, b)]+unitXY xys = zip (unit $ fst <$> xys) (unit $ snd <$> xys)++-- scale multiple 2d series+unitsXY :: (Fractional a, Ord a, Fractional b, Ord b) => [[(a, b)]] -> [[(a, b)]]+unitsXY xyss = zipWith (\x y -> zip x y) xs' ys'+  where+    xs = fmap fst <$> xyss+    ys = fmap snd <$> xyss+    xs' = units xs+    ys' = units ys++chartXY :: (Renderable (Path V2 Double) a, (Renderable (Diagrams.TwoD.Text.Text Double) a)) => ChartConfig+  -> ([(Double, Double)] -> QDiagram a V2 Double Any)+  -> [(Double, Double)]+  -> QDiagram a V2 Double Any+chartXY (ChartConfig p _ axes) chart xys =+  L.fold (L.Fold step (chart xys) (pad p)) axes+  where+    step x a =+      beside (v (a ^. axisPlacement))+      x+      (axisXY a (d (a ^. axisOrientation)))++    v AxisBottom = r2 (0,-1)+    v AxisTop = r2 (0,1)+    v AxisLeft = r2 (-1,0)+    v AxisRight = r2 (1,0)++    d X = range1D $ fst <$> xys+    d Y = range1D $ snd <$> xys++-- axis rendering+axisXY :: ((Renderable (Diagrams.TwoD.Text.Text Double) a), Renderable (Path V2 Double) a) => AxisConfig -> (Double,Double) -> QDiagram a V2 Double Any+axisXY cfg range = centerXY $+  atPoints+    (p2 . t <$> tickLocations)+    ((\x -> mkLabel x cfg) <$> tickLabels)+  `atop`+  (axisRect (cfg ^. axisHeight) (-0.5,0.5)+   # unitRect (cfg ^. axisColor))+  where+    t = case cfg ^. axisOrientation of+      X -> \x -> (x,0)+      Y -> \y -> (-(cfg ^. axisMarkSize), y)+    tickLocations = case cfg ^. axisTickStyle of+      TickNone -> []+      TickNumber n -> unit $ mkTicks range n+      TickLabels ls -> unit $ fromIntegral <$> [1..length ls]+    tickLabels = case cfg ^. axisTickStyle of+      TickNone -> []+      TickNumber n -> Text.pack . printf "%7.1g" <$> mkTicks range n+      TickLabels ls -> ls+    axisRect h (min, max) = case cfg ^. axisOrientation of+      X -> moveTo (p2 (max,0)) .+          strokeTrail .+          closeTrail .+          fromVertices .+          scaleX (max-min) .+          scaleY h $+          unitSquare+      Y -> moveTo (p2 (0,min)) .+          strokeTrail .+          closeTrail .+          fromVertices .+          scaleY (max-min) .+          scaleX h $+          unitSquare++-- somewhat a base class of a wide variety of chart marks+unitRect ∷ (Floating (N a), Ord (N a), Typeable (N a), HasStyle a, V a ~ V2) ⇒+    AlphaColour Double → a → a+unitRect c = fcA c # lcA (withOpacity black 0) # lw none++-- polymorphic dots+scatter :: (Typeable (N r), Monoid r, Semigroup r, Transformable r,HasStyle r, HasOrigin r, TrailLike r, V r ~ V2, N r ~ Double) =>+    ScatterConfig -> [(N r, N r)] -> r+scatter cfg xys =+  atPoints (p2 <$> unitXY xys)+    (repeat $ circle (cfg ^. scatterSize) #+     unitRect (cfg ^. scatterChart ^. chartColor)+    )++scatterXY :: (Renderable (Path V2 Double) a, (Renderable (Diagrams.TwoD.Text.Text Double) a)) => ScatterConfig -> [(Double,Double)] -> QDiagram a V2 Double Any+scatterXY cfg xys =+  chartXY (cfg ^. scatterChart) (scatter cfg) xys++-- bar+bars :: (Renderable (Path V2 Double) a) => BarConfig -> [Double] -> QDiagram a V2 Double Any+bars cfg ys =+  cat' (r2 (1,0)) (with Diagrams.Prelude.& sep .~ cfg ^. barSep)+  ((\y ->+    unitSquare+    # moveOriginTo (p2 (-0.5,-0.5))+    # if y==0 then scaleY epsilon else scaleY y) <$> ys)+    # unitRect (cfg ^. barChart ^. chartColor)+    # centerXY+    # scaleX (1/fromIntegral (length ys)) # scaleY (1/(max-min))+  where+    (min,max) = range1D ys+    epsilon = 1e-8++barRange :: (Renderable (Path V2 Double) a, (Renderable (Diagrams.TwoD.Text.Text Double) a)) => BarConfig -> [(Double, Double)] -> QDiagram a V2 Double Any+barRange cfg xys = chartXY (cfg ^. barChart) (\x -> bars cfg (snd <$> x)) xys++barLabelled :: (Renderable (Path V2 Double) a, (Renderable (Diagrams.TwoD.Text.Text Double) a)) => BarConfig -> [Double] -> [Text] -> QDiagram a V2 Double Any+barLabelled cfg ys labels = barRange+     ( barChart . chartAxes .~+       [ axisTickStyle .~+         TickLabels labels $ def+       ]+       $ cfg+     ) (zip [0..] ys)++-- a line is just a scatter chart rendered with a line (and with a usually stable x-value series)+line :: (Renderable (Path V2 Double) a) => [(Double,Double)] -> QDiagram a V2 Double Any+line xys = strokeT $ trailFromVertices $ p2 <$> unitXY xys++lineXY :: (Renderable (Path V2 Double) a, (Renderable (Diagrams.TwoD.Text.Text Double) a)) => LineConfig -> [(Double,Double)] -> QDiagram a V2 Double Any+lineXY cfg xys =+    chartXY (cfg ^. lineChart)+    (\x -> line x # centerXY # lcA (cfg ^. lineChart ^. chartColor) # lwN (cfg ^. lineSize))+    xys++-- multiple lines with a common range for both x and y values+lines :: (Renderable (Path V2 Double) a) => LinesConfig -> [[(Double,Double)]] -> QDiagram a V2 Double Any+lines cfg xyss = centerXY $ mconcat $+    zipWith (\d c -> d # lcA (c ^. lColor) # lwN (c ^. lSize))+    (l xyss)+    (cycle $ cfg ^.linesLines)+  where+    l xyss' = strokeT . trailFromVertices . fmap p2 <$> unitsXY xyss'++linesXY :: ((Renderable (Diagrams.TwoD.Text.Text Double) a), Renderable (Path V2 Double) a) =>+  LinesConfig+  -> [[(Double, Double)]]+  -> QDiagram a V2 Double Any+linesXY cfg@(LinesConfig (ChartConfig p _ axes) _) xyss =+  L.fold (L.Fold step (lines cfg xyss) (pad p)) axes+  where+    step x a =+      beside (v (a ^. axisPlacement))+      x+      (axisXY a (d (a ^. axisOrientation)))++    v AxisBottom = r2 (0,-1)+    v AxisTop = r2 (0,1)+    v AxisLeft = r2 (-1,0)+    v AxisRight = r2 (1,0)++    d X = range1Ds $ fmap fst <$> xyss+    d Y = range1Ds $ fmap snd <$> xyss+++mkTicks :: (Double,Double) -> Int -> [Double]+mkTicks r n = (f +) . (s *) . fromIntegral <$> [0..n']+  where+    (f,s,n') = mkTicks' r n++mkTicks' :: (Double,Double) -> Int -> (Double, Double, Int)+mkTicks' (min, max) n = (f, step', n')+  where+    span' = max - min+    step' = 10 ^^ floor (logBase 10 (span'/fromIntegral n))+    err = fromIntegral n / span' * step'+    step+      | err <= 0.15 = 10 * step'+      | err <= 0.35 = 5 * step'+      | err <= 0.75 = 2 * step'+      | otherwise = step'+    f = step * fromIntegral (floor (min/step))+    l = step * fromIntegral (floor (max/step))+    n' = round ((l - f)/step)++mkLabel :: ((Renderable (Diagrams.TwoD.Text.Text Double) a), Renderable (Path V2 Double) a) => Text -> AxisConfig -> QDiagram a V2 Double Any+mkLabel label cfg =+  beside dir+  (beside dir+   (rule (cfg ^. axisMarkSize) #+   lcA (cfg ^. axisMarkColor))+    s)+  (Diagrams.Prelude.alignedText+    (cfg ^. axisAlignedTextRight)+    (cfg ^. axisAlignedTextBottom)+    (Text.unpack label) #+  scale (cfg ^. axisTextSize) #+  fcA (cfg ^.axisTextColor))+  where+    dir = case cfg ^. axisOrientation of+      X -> r2 (0,-1)+      Y -> r2 (-1,0)+    rule = case cfg ^. axisOrientation of+      X -> vrule+      Y -> hrule+    s = case cfg ^. axisOrientation of+      X -> strutY (cfg ^. axisStrutSize)+      Y -> strutX (cfg ^. axisStrutSize)+