module Chart.Unit (
range1D,
range1Ds,
unit,
units,
unitXY,
unitsXY,
chartXY,
scatter,
scatterXY,
barRange,
barLabelled,
bars,
line,
lines,
lineXY,
linesXY,
axisXY,
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 :: (Fractional b, Functor f, Ord b, Foldable f) => f b -> f b
unit xs =
let (minX,maxX) = range1D xs in
(\x -> (xminX)/(maxXminX) 0.5) <$> xs
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 -> (xminX)/(maxXminX) 0.5)) <$> xss
unitXY :: (Fractional b, Fractional a, Ord b, Ord a) => [(a, b)] -> [(a, b)]
unitXY xys = zip (unit $ fst <$> xys) (unit $ snd <$> xys)
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
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 (maxmin) .
scaleY h $
unitSquare
Y -> moveTo (p2 (0,min)) .
strokeTrail .
closeTrail .
fromVertices .
scaleY (maxmin) .
scaleX h $
unitSquare
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
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
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/(maxmin))
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)
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
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)