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 +30/−0
- Setup.hs +2/−0
- chart-unit.cabal +154/−0
- readme.lhs +226/−0
- src/Chart/Types.hs +137/−0
- src/Chart/Unit.hs +275/−0
+ 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) [](https://travis-ci.org/tonyday567/chart-unit)+===++scratchpad+---++My newest chart `padsvg $ linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]`++++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++++Histogram++++Line++++Lines++++Labelled Bar Chart++++rasterific png renders+---++++Scatter++++Histogram++++Line++++Lines++++Labelled Bar Chart++++> {-# 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.++++Axes break this scale invariance. Ticks and tick labels can hide this to some extent and look almost the same across scales.++++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)+