Chart 0.11 → 1.9.5
raw patch · 44 files changed
Files
- Chart.cabal +59/−20
- Graphics/Rendering/Chart.hs +30/−20
- Graphics/Rendering/Chart/Axis.hs +12/−606
- Graphics/Rendering/Chart/Axis/Floating.hs +340/−0
- Graphics/Rendering/Chart/Axis/Indexed.hs +57/−0
- Graphics/Rendering/Chart/Axis/Int.hs +133/−0
- Graphics/Rendering/Chart/Axis/LocalTime.hs +286/−0
- Graphics/Rendering/Chart/Axis/Time.hs +316/−0
- Graphics/Rendering/Chart/Axis/Types.hs +454/−0
- Graphics/Rendering/Chart/Axis/Unit.hs +34/−0
- Graphics/Rendering/Chart/Backend.hs +67/−0
- Graphics/Rendering/Chart/Backend/Impl.hs +139/−0
- Graphics/Rendering/Chart/Backend/Types.hs +189/−0
- Graphics/Rendering/Chart/Drawing.hs +504/−0
- Graphics/Rendering/Chart/Easy.hs +108/−0
- Graphics/Rendering/Chart/Geometry.hs +396/−0
- Graphics/Rendering/Chart/Grid.hs +200/−124
- Graphics/Rendering/Chart/Gtk.hs +0/−50
- Graphics/Rendering/Chart/Layout.hs +852/−341
- Graphics/Rendering/Chart/Legend.hs +83/−38
- Graphics/Rendering/Chart/Pie.hs +0/−219
- Graphics/Rendering/Chart/Plot.hs +28/−518
- Graphics/Rendering/Chart/Plot/Annotation.hs +101/−0
- Graphics/Rendering/Chart/Plot/AreaSpots.hs +192/−0
- Graphics/Rendering/Chart/Plot/Bars.hs +411/−0
- Graphics/Rendering/Chart/Plot/Candle.hs +151/−0
- Graphics/Rendering/Chart/Plot/ErrBars.hs +127/−0
- Graphics/Rendering/Chart/Plot/FillBetween.hs +89/−0
- Graphics/Rendering/Chart/Plot/Hidden.hs +36/−0
- Graphics/Rendering/Chart/Plot/Histogram.hs +188/−0
- Graphics/Rendering/Chart/Plot/Lines.hs +102/−0
- Graphics/Rendering/Chart/Plot/Pie.hs +228/−0
- Graphics/Rendering/Chart/Plot/Points.hs +69/−0
- Graphics/Rendering/Chart/Plot/Types.hs +79/−0
- Graphics/Rendering/Chart/Plot/Vectors.hs +145/−0
- Graphics/Rendering/Chart/Renderable.hs +154/−209
- Graphics/Rendering/Chart/Simple.hs +0/−304
- Graphics/Rendering/Chart/SparkLine.hs +169/−0
- Graphics/Rendering/Chart/State.hs +108/−0
- Graphics/Rendering/Chart/Types.hs +0/−507
- Graphics/Rendering/Chart/Utils.hs +24/−0
- Numeric/Histogram.hs +69/−0
- tests/Prices.hs +0/−296
- tests/test.hs +0/−456
Chart.cabal view
@@ -1,39 +1,78 @@ Name: Chart-Version: 0.11+Version: 1.9.5 License: BSD3 License-file: LICENSE-Copyright: Tim Docker, 2006-2009+Copyright: Tim Docker, 2006-2014 Author: Tim Docker <tim@dockerz.net> Maintainer: Tim Docker <tim@dockerz.net>-Homepage: http://www.dockerz.net/software/chart.html+Homepage: https://github.com/timbod7/haskell-chart/wiki Synopsis: A library for generating 2D Charts and Plots-Description: A library for generating 2D Charts and Plots, based upon the cairo graphics library.+Description: A library for generating 2D Charts and Plots, with backends provided by+ Cairo (<http://hackage.haskell.org/package/Chart-cairo>)+ and+ Diagrams (<http://hackage.haskell.org/package/Chart-diagrams>).++ Documentation: https://github.com/timbod7/haskell-chart/wiki. Category: Graphics-Cabal-Version: >= 1.6+Cabal-Version: 1.18 Build-Type: Simple -Extra-Source-Files: tests/test.hs, tests/Prices.hs+library+ default-language: Haskell98+ Build-depends: base >= 3 && < 5+ , old-locale+ , time, array+ , lens >= 3.9 && < 5.3+ , colour >= 2.2.1 && < 2.4+ , data-default-class < 0.2+ , mtl >= 2.0 && < 2.4+ , operational >= 0.2.2 && < 0.3+ , vector >=0.9 && <0.14 -flag splitbase- description: Choose the new smaller, split-up base package.+ if !impl(ghc >= 8.0)+ build-depends: semigroups >= 0.18.4 && <0.19 -library- if flag(splitbase)- Build-depends: base >= 3, old-locale, time, mtl, array- else- Build-depends: base < 3- Build-depends: gtk >= 0.9.11, cairo >= 0.9.11, time, mtl, array, data-accessor == 0.2.*, data-accessor-template >= 0.2.1.1 && < 0.3, colour >= 2.2.1 + Ghc-options: -Wall -fno-warn-orphans+ Exposed-modules: Graphics.Rendering.Chart,- Graphics.Rendering.Chart.Gtk,- Graphics.Rendering.Chart.Types,+ Graphics.Rendering.Chart.Drawing,+ Graphics.Rendering.Chart.Geometry,+ Graphics.Rendering.Chart.Utils, Graphics.Rendering.Chart.Renderable, Graphics.Rendering.Chart.Axis,+ Graphics.Rendering.Chart.Axis.Floating,+ Graphics.Rendering.Chart.Axis.Indexed,+ Graphics.Rendering.Chart.Axis.Int,+ Graphics.Rendering.Chart.Axis.Time,+ Graphics.Rendering.Chart.Axis.LocalTime,+ Graphics.Rendering.Chart.Axis.Types,+ Graphics.Rendering.Chart.Axis.Unit, Graphics.Rendering.Chart.Layout, Graphics.Rendering.Chart.Legend,+ Graphics.Rendering.Chart.Grid, Graphics.Rendering.Chart.Plot,- Graphics.Rendering.Chart.Pie,- Graphics.Rendering.Chart.Simple,- Graphics.Rendering.Chart.Grid-+ Graphics.Rendering.Chart.Plot.Types,+ Graphics.Rendering.Chart.Plot.Annotation,+ Graphics.Rendering.Chart.Plot.AreaSpots,+ Graphics.Rendering.Chart.Plot.Bars,+ Graphics.Rendering.Chart.Plot.Candle,+ Graphics.Rendering.Chart.Plot.ErrBars,+ Graphics.Rendering.Chart.Plot.FillBetween,+ Graphics.Rendering.Chart.Plot.Hidden,+ Graphics.Rendering.Chart.Plot.Lines,+ Graphics.Rendering.Chart.Plot.Vectors,+ Graphics.Rendering.Chart.Plot.Pie,+ Graphics.Rendering.Chart.Plot.Points,+ Graphics.Rendering.Chart.Plot.Histogram+ Graphics.Rendering.Chart.SparkLine+ Graphics.Rendering.Chart.Backend+ Graphics.Rendering.Chart.Backend.Impl+ Graphics.Rendering.Chart.Backend.Types+ Graphics.Rendering.Chart.Easy+ Graphics.Rendering.Chart.State+ Numeric.Histogram+source-repository head+ type: git+ location: https://github.com/timbod7/haskell-chart
Graphics/Rendering/Chart.hs view
@@ -1,51 +1,61 @@ ---------------------------------------------------------------------------- -- | -- Module : Graphics.Rendering.Chart--- Copyright : (c) Tim Docker 2006+-- Copyright : (c) Tim Docker 2006-2013 -- License : BSD-style (see chart/COPYRIGHT) -- -- A framework for creating 2D charts in Haskell. ----- The basic model is that you define a value of type 'Renderable',--- typically by applying 'toRenderable' to some other value. This--- 'Renderable' is then actually displayed or output by calling either--- 'renderableToPNGFile', or 'Graphics.Rendering.Chart.Gtk.renderableToWindow'.+-- For the simplest API, see the "Graphics.Rendering.Chart.Easy"+-- module. ----- Currently, there are two kinds of 'Renderable' for displaying charts:+-- When more control is required, understanding the various data types+-- is necessary. The basic model is that you define a value+-- representing a chart to be displayed (eg. a `Layout`), and then+-- convert it to a 'Renderable' by applying 'toRenderable'. This+-- 'Renderable' is then actually output by calling a function in an+-- appropriate graphics backend, eg 'renderableToFile'. ----- * a standard two axes chart can be is created by applying--- 'toRenderable' to a value of type 'Graphics.Rendering.Chart.Layout.Layout1'+-- Currently, there are three types of charts: ----- * a pie chart can be is created by applying--- 'toRenderable' to a value of type 'Graphics.Rendering.Chart.Pie.PieLayout'+-- * 'Layout' is a standard XY chart+-- +-- * 'LayoutLR' is an XY chart with independent left+-- and right axes ----- Multiple Renderables can be composed using the "Graphics.Rendering.Chart.Grid" module.+-- * 'PieLayout' is a pie chart ----- Many of the record structure involved in the API have a large--- number of fields For each record type X, there is generally a--- default value called defaultX with sensibly initialised fields - eg--- 'Layout1' has 'defaultLayout1' etc.+-- 'Layout' and 'LayoutLR' charts can be stacked vertically using+-- the 'StackedLayouts' type.+-- +-- 'Renderable's can be composed in arbitrary ways using the+-- "Graphics.Rendering.Chart.Grid" module. ----- For a simpler though less flexible API, see "Graphics.Rendering.Chart.Simple".+-- Many of the record structure involved in the API have a large+-- number of fields. 'Lens'es are provided to access each field. Also,+-- for each record type, there is generally a default value, which can+-- be accessed through the 'def' value of the 'Default' typeclass. -- ----------------------------------------------------------------------------- module Graphics.Rendering.Chart( - module Graphics.Rendering.Chart.Types,+ module Graphics.Rendering.Chart.Geometry,+ module Graphics.Rendering.Chart.Drawing, module Graphics.Rendering.Chart.Renderable, module Graphics.Rendering.Chart.Layout, module Graphics.Rendering.Chart.Axis, module Graphics.Rendering.Chart.Plot, module Graphics.Rendering.Chart.Legend,- module Graphics.Rendering.Chart.Pie,+ module Graphics.Rendering.Chart.Backend.Types ) where -import Graphics.Rendering.Chart.Types+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing import Graphics.Rendering.Chart.Renderable import Graphics.Rendering.Chart.Layout import Graphics.Rendering.Chart.Axis import Graphics.Rendering.Chart.Plot import Graphics.Rendering.Chart.Legend-import Graphics.Rendering.Chart.Pie+import Graphics.Rendering.Chart.Backend.Types
Graphics/Rendering/Chart/Axis.hs view
@@ -6,613 +6,19 @@ -- -- Code to calculate and render axes. ----- Note that template haskell is used to derive accessor functions--- (see 'Data.Accessor') for each field of the following data types:------ * 'AxisData'------ * 'Axis'------ * 'AxisStyle'------ * 'LinearAxisParams'------ * 'LogAxisParams'------ These accessors are not shown in this API documentation. They have--- the same name as the field, but with the trailing underscore--- dropped. Hence for data field f_::F in type D, they have type------ @--- f :: Data.Accessor.Accessor D F--- @--- -{-# OPTIONS_GHC -XTemplateHaskell #-}- module Graphics.Rendering.Chart.Axis(- AxisData(..),- AxisT(..),- LinearAxisParams(..),- LogAxisParams(..),- AxisStyle(..),- PlotValue(..),- LogValue(..),- PlotIndex(..),- AxisFn,-- defaultAxisLineStyle, - defaultLinearAxis,- defaultLogAxis,- defaultAxisStyle,- autoScaledAxis,- autoScaledLogAxis,- timeAxis,- autoTimeAxis,- days, months, years,- autoIndexAxis,- addIndexes,-- axisToRenderable,- renderAxisGrid,- axisOverhang,- vmap,-- axisGridAtTicks,- axisGridAtLabels,- axisGridHide,- axisTicksHide,- axisLabelsHide,-- axis_viewport,- axis_ticks,- axis_labels,- axis_grid,-- axis_line_style,- axis_label_style,- axis_grid_style,- axis_label_gap,-- la_labelf,- la_nLabels,- la_nTicks,-- loga_labelf,-+ module Graphics.Rendering.Chart.Axis.Types,+ module Graphics.Rendering.Chart.Axis.Floating,+ module Graphics.Rendering.Chart.Axis.Int,+ module Graphics.Rendering.Chart.Axis.Time,+ module Graphics.Rendering.Chart.Axis.Unit,+ module Graphics.Rendering.Chart.Axis.Indexed, ) where -import qualified Graphics.Rendering.Cairo as C-import Data.Time-import System.Locale (defaultTimeLocale)-import Control.Monad-import Data.List-import Data.Accessor.Template-import Data.Colour (opaque)-import Data.Colour.Names (black, lightgrey)--import Graphics.Rendering.Chart.Types-import Graphics.Rendering.Chart.Renderable---- | The basic data associated with an axis showing values of type x-data AxisData x = AxisData {-- -- | The axis_viewport_ function maps values into device- -- cordinates.- axis_viewport_ :: Range -> x -> Double,-- -- | The tick marks on the axis as pairs.- -- The first element is the position on the axis- -- (in viewport units) and the second element is the- -- length of the tick in output coordinates.- -- The tick starts on the axis, and positive number are drawn- -- towards the plot area.- axis_ticks_ :: [(x,Double)],-- -- | The labels on an axis as pairs. The first element - -- is the position on the axis (in viewport units) and- -- the second is the label text string.- axis_labels_ :: [ (x, String) ],-- -- | The positions on the axis (in viewport units) where- -- we want to show grid lines.- axis_grid_ :: [ x ]-}---- | Control values for how an axis gets displayed-data AxisStyle = AxisStyle {- axis_line_style_ :: CairoLineStyle,- axis_label_style_ :: CairoFontStyle,- axis_grid_style_ :: CairoLineStyle,-- -- | How far the labels are to be drawn from the axis.- axis_label_gap_ :: Double-}----- | A function to generate the axis data given the data values--- to be plotted against it.-type AxisFn x = [x] -> AxisData x---- | Collect the information we need to render an axis. The--- bool is true if the axis direction is reversed-data AxisT x = AxisT RectEdge AxisStyle Bool (AxisData x)--instance ToRenderable (AxisT x) where- toRenderable = setPickFn nullPickFn.axisToRenderable--axisToRenderable :: AxisT x -> Renderable x-axisToRenderable at = Renderable {- minsize=minsizeAxis at,- render=renderAxis at- }--axisGridHide :: AxisData x -> AxisData x-axisGridHide ad = ad{axis_grid_=[]}--axisGridAtTicks :: AxisData x -> AxisData x-axisGridAtTicks ad = ad{axis_grid_=map fst (axis_ticks_ ad)}--axisGridAtLabels :: AxisData x -> AxisData x-axisGridAtLabels ad = ad{axis_grid_=map fst (axis_labels_ ad)}--axisTicksHide :: AxisData x -> AxisData x-axisTicksHide ad = ad{axis_ticks_=[]}--axisLabelsHide :: AxisData x -> AxisData x-axisLabelsHide ad = ad{axis_labels_=[]}--minsizeAxis :: AxisT x -> CRender RectSize-minsizeAxis (AxisT at as rev ad) = do- let labels = map snd (axis_labels_ ad)- labelSizes <- preserveCState $ do- setFontStyle (axis_label_style_ as)- mapM textSize labels- let (lw,lh) = foldl maxsz (0,0) labelSizes- let ag = axis_label_gap_ as- let tsize = maximum ([0] ++ [ max 0 (-l) | (v,l) <- axis_ticks_ ad ])- let sz = case at of- E_Top -> (lw,max (addIfNZ lh ag) tsize)- E_Bottom -> (lw,max (addIfNZ lh ag) tsize)- E_Left -> (max (addIfNZ lw ag) tsize, lh)- E_Right -> (max (addIfNZ lw ag) tsize, lh)- return sz-- where- maxsz (w1,h1) (w2,h2) = (max w1 w2, max h1 h2)- addIfNZ a b | a == 0 = 0- | otherwise = a+b----- | Calculate the amount by which the labels extend beyond--- the ends of the axis-axisOverhang :: Ord x => AxisT x -> CRender (Double,Double)-axisOverhang (AxisT at as rev ad) = do- let labels = map snd (sort (axis_labels_ ad))- labelSizes <- preserveCState $ do- setFontStyle (axis_label_style_ as)- mapM textSize labels- case labelSizes of- [] -> return (0,0)- ls -> let l1 = head ls- l2 = last ls- ohangv = return (snd l1 / 2, snd l2 / 2)- ohangh = return (fst l1 / 2, fst l2 / 2)- in- case at of- E_Top -> ohangh- E_Bottom -> ohangh- E_Left -> ohangv- E_Right -> ohangh--renderAxis :: AxisT x -> RectSize -> CRender (PickFn x)-renderAxis at@(AxisT et as rev ad) sz = do- let ls = axis_line_style_ as- preserveCState $ do- setLineStyle ls{line_cap_=C.LineCapSquare}- strokeLines [Point sx sy,Point ex ey]- preserveCState $ do- setLineStyle ls{line_cap_=C.LineCapButt}- mapM_ drawTick (axis_ticks_ ad)- preserveCState $ do- setFontStyle (axis_label_style_ as)- mapM_ drawLabel (axis_labels_ ad)- return nullPickFn- where- (sx,sy,ex,ey,tp,axisPoint) = axisMapping at sz-- drawTick (value,length) = - let t1 = axisPoint value- t2 = t1 `pvadd` (vscale length tp)- in strokeLines [t1,t2]-- (hta,vta,lp) = - let g = axis_label_gap_ as- in case et of- E_Top -> (HTA_Centre,VTA_Bottom,(Vector 0 (-g)))- E_Bottom -> (HTA_Centre,VTA_Top,(Vector 0 g))- E_Left -> (HTA_Right,VTA_Centre,(Vector (-g) 0))- E_Right -> (HTA_Left,VTA_Centre,(Vector g 0))-- drawLabel (value,s) = do- drawText hta vta (axisPoint value `pvadd` lp) s--axisMapping :: AxisT z -> RectSize -> (Double,Double,Double,Double,Vector,z->Point)-axisMapping (AxisT et as rev ad) (x2,y2) = case et of- E_Top -> (x1,y2,x2,y2, (Vector 0 1), mapx (x1,x2) y2) - E_Bottom -> (x1,y1,x2,y1, (Vector 0 (-1)), mapx (x1,x2) y1)- E_Left -> (x2,y2,x2,y1, (Vector (1) 0), mapy (y1,y2) x2) - E_Right -> (x1,y2,x1,y1, (Vector (-1) 0), mapy (y1,y2) x1)- where- (x1,y1) = (0,0)-- mapx xr y x = Point (axis_viewport_ ad (reverse xr) x) y- mapy (yr0,yr1) x y = Point x (axis_viewport_ ad (reverse (yr1,yr0)) y)- reverse r@(r0,r1) = if rev then (r1,r0) else r--renderAxisGrid :: RectSize -> AxisT z -> CRender ()-renderAxisGrid sz@(w,h) at@(AxisT re as rev ad) = do- preserveCState $ do- setLineStyle (axis_grid_style_ as)- mapM_ (drawGridLine re) (axis_grid_ ad)- where- (sx,sy,ex,ey,tp,axisPoint) = axisMapping at sz-- drawGridLine E_Top = vline- drawGridLine E_Bottom = vline- drawGridLine E_Left = hline- drawGridLine E_Right = hline-- vline v = let v' = p_x (axisPoint v)- in strokeLines [Point v' 0,Point v' h]-- hline v = let v' = p_y (axisPoint v)- in strokeLines [Point 0 v',Point w v']----steps:: Double -> Range -> [Rational]-steps nSteps (min,max) = [ (fromIntegral (min' + i)) * s | i <- [0..n] ]- where- min' = floor (min / fromRational s)- max' = ceiling (max / fromRational s)- n = (max' - min')- s = chooseStep nSteps (min,max)--chooseStep :: Double -> Range -> Rational-chooseStep nsteps (min,max) = s- where- mult = 10 ^^ (floor ((log (max-min) - log nsteps) / log 10))- steps = map (mult*) [0.1, 0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10, 20, 25, 50]- steps' = sort [ (abs((max-min)/(fromRational s) - nsteps), s) | s <- steps ]- s = snd (head steps')--makeAxis :: PlotValue x => (x -> String) -> ([x],[x],[x]) -> AxisData x-makeAxis labelf (labelvs, tickvs, gridvs) = AxisData {- axis_viewport_=newViewport,- axis_ticks_=newTicks,- axis_grid_=gridvs,- axis_labels_=newLabels- }- where- newViewport = vmap (min',max')- newTicks = [ (v,2) | v <- tickvs ] ++ [ (v,5) | v <- labelvs ] - newLabels = [(v,labelf v) | v <- labelvs]- min' = minimum labelvs- max' = maximum labelvs--data GridMode = GridNone | GridAtMajor | GridAtMinor--data LinearAxisParams = LinearAxisParams {- -- | The function used to show the axes labels- la_labelf_ :: Double -> String,-- -- | The target number of labels to be shown- la_nLabels_ :: Int,-- -- | The target number of ticks to be shown- la_nTicks_ :: Int-}---defaultLinearAxis = LinearAxisParams {- la_labelf_ = showD,- la_nLabels_ = 5,- la_nTicks_ = 50-}---- | Generate a linear axis automatically.--- The supplied axis is used as a template, with the viewport, ticks, labels--- and grid set appropriately for the data displayed against that axies.--- The resulting axis will only show a grid if the template has some grid--- values.-autoScaledAxis :: LinearAxisParams -> AxisFn Double-autoScaledAxis lap ps0 = makeAxis (la_labelf_ lap) (labelvs,tickvs,gridvs)- where- ps = filter isValidNumber ps0- (min,max) = (minimum ps,maximum ps)- range [] = (0,1)- range _ | min == max = (min-0.5,min+0.5)- | otherwise = (min,max)- 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--showD x = case reverse $ show x of- '0':'.':r -> reverse r- _ -> show x- --log10 :: (Floating a) => a -> a-log10 = logBase 10--frac x | 0 <= b = (a,b)- | otherwise = (a-1,b+1)- where- (a,b) = properFraction x--{- - Rules: Do no subdivide between powers of 10 until all powers of 10- get a major ticks.- Do not subdivide between powers of ten as [1,2,4,6,8,10] when- 5 gets a major ticks - (ie the major ticks need to be a subset of the minor tick)--}-logTicks :: Range -> ([Rational],[Rational],[Rational])-logTicks (low,high) = (major,minor,major)- where- ratio = high/low- lower a l = let (i,r) = frac (log10 a) in- (maximum (1:(filter (\x -> log10 (fromRational x) <= r) l)))*10^^i- upper a l = let (i,r) = properFraction (log10 a) in- (minimum (10:(filter (\x -> r <= log10 (fromRational x)) l)))*10^^i- inRange (a,b) l x = (lower a l <= x) && (x <= upper b l)- powers :: (Double,Double) -> [Rational] -> [Rational]- powers (x,y) l = [a*10^^p | p<-[(floor (log10 x))..(ceiling (log10 y))], a<-l]- midselection r l = filter (inRange r l) (powers r l)- major | 17.5 < log10 ratio = map (\x -> 10^^(round x)) $- steps (min 5 (log10 ratio)) (log10 low, log10 high)- | 12 < log10 ratio = map (\x -> 10^^(round x)) $- steps ((log10 ratio)/5) (log10 low, log10 high)- | 6 < log10 ratio = map (\x -> 10^^(round x)) $- steps ((log10 ratio)/2) (log10 low, log10 high)- | 3 < log10 ratio = midselection (low,high) [1,10]- | 20 < ratio = midselection (low,high) [1,5,10]- | 6 < ratio = midselection (low,high) [1,2,4,6,8,10]- | 3 < ratio = midselection (low,high) [1..10]- | otherwise = steps 5 (low,high)- (l',h') = (minimum major, maximum major)- (dl',dh') = (fromRational l', fromRational h')- ratio' = fromRational (h'/l')- minor | 50 < log10 ratio' = map (\x -> 10^^(round x)) $- steps 50 (log10 $ dl', log10 $ dh')- | 6 < log10 ratio' = filter (\x -> l'<=x && x <=h') $- powers (dl', dh') [1,10]- | 3 < log10 ratio' = filter (\x -> l'<=x && x <=h') $- powers (dl',dh') [1,5,10]- | 6 < ratio' = filter (\x -> l'<=x && x <=h') $ - powers (dl',dh') [1..10]- | 3 < ratio' = filter (\x -> l'<=x && x <=h') $ - powers (dl',dh') [1,1.2..10]- | otherwise = steps 50 (dl', dh')---- | Generate a log axis automatically.--- The supplied axis is used as a template, with the viewport, ticks, labels--- and grid set appropriately for the data displayed against that axies.--- The resulting axis will only show a grid if the template has some grid--- values.-autoScaledLogAxis :: LogAxisParams -> AxisFn LogValue-autoScaledLogAxis lap ps0 = makeAxis labelf (wrap rlabelvs, wrap rtickvs, wrap rgridvs)- where- ps = filter (\(LogValue x) -> isValidNumber x && 0 < x) ps0- (min, max) = (minimum ps,maximum ps)- range [] = (3,30)- range _ | min == max = (unLogValue min/3,unLogValue max*3)- | otherwise = (unLogValue min,unLogValue max)- (rlabelvs, rtickvs, rgridvs) = logTicks (range ps)- wrap = map (LogValue . fromRational)- unLogValue (LogValue x) = x- labelf = loga_labelf_ lap---data LogAxisParams = LogAxisParams {- -- | The function used to show the axes labels- loga_labelf_ :: LogValue -> String-}--defaultLogAxis = LogAxisParams {- loga_labelf_ = \(LogValue x) -> showD x-}--------------------------------------------------------------------------defaultAxisLineStyle = solidLine 1 $ opaque black-defaultGridLineStyle = dashedLine 1 [5,5] $ opaque lightgrey--defaultAxisStyle = AxisStyle {- axis_line_style_ = defaultAxisLineStyle,- axis_label_style_ = defaultFontStyle,- axis_grid_style_ = defaultGridLineStyle,- axis_label_gap_ = 10-}---------------------------------------------------------------------------- | Map a LocalTime value to a plot cordinate-doubleFromLocalTime :: LocalTime -> Double-doubleFromLocalTime lt = fromIntegral (toModifiedJulianDay (localDay lt)) - + fromRational (timeOfDayToDayFraction (localTimeOfDay lt))---- | Map a plot cordinate to a LocalTime-localTimeFromDouble :: Double -> LocalTime-localTimeFromDouble v = - LocalTime (ModifiedJulianDay i) (dayFractionToTimeOfDay (toRational d))- where- (i,d) = properFraction v---- | TimeSeq is a (potentially infinite) set of times. When passes--- a reference time, the function returns a a pair of lists. The first--- contains all times in the set less than the reference time in--- decreasing order. The second contains all times in the set greater--- than or equal to the reference time, in increasing order.-type TimeSeq = LocalTime-> ([LocalTime],[LocalTime])--coverTS tseq min max = min' ++ enumerateTS tseq min max ++ max'- where- min' = if elemTS min tseq then [] else take 1 (fst (tseq min))- max' = if elemTS max tseq then [] else take 1 (snd (tseq max))--enumerateTS tseq min max = reverse (takeWhile (>=min) ts1) ++ takeWhile (<=max) ts2- where- (ts1,ts2) = tseq min--elemTS t tseq = case tseq t of- (_,(t0:_)) | t == t0 -> True- _ -> False---- | How to display a time-type TimeLabelFn = LocalTime -> String---- | Create an 'AxisFn' to for a time axis. The first 'TimeSeq' sets the minor ticks,--- and the ultimate range will aligned to it's elements. The second 'TimeSeq' sets--- the labels and grid. The 'TimeLabelFn' is used to format LocalTimes for labels.--- The values to be plotted against this axis can be created with 'doubleFromLocalTime'-timeAxis :: TimeSeq -> TimeSeq -> TimeLabelFn -> AxisFn LocalTime-timeAxis tseq lseq labelf pts = AxisData {- axis_viewport_=vmap(min', max'),- axis_ticks_=[ (t,2) | t <- times] ++ [ (t,5) | t <- ltimes, visible t],- axis_labels_=[ (t,l) | (t,l) <- labels, visible t],- axis_grid_=[ t | t <- ltimes, visible t]- }- where- (min,max) = case pts of- [] -> (refLocalTime,refLocalTime)- ps -> (minimum ps, maximum ps)- refLocalTime = LocalTime (ModifiedJulianDay 0) midnight- times = coverTS tseq min max- ltimes = coverTS lseq min max- min' = minimum times- max' = maximum times- visible t = min' <= t && t <= max'- labels = [ (avg m1 m2, labelf m1) | (m1,m2) <- zip ltimes (tail ltimes) ]- avg m1 m2 = localTimeFromDouble $ m1' + (m2' - m1')/2- where- m1' = doubleFromLocalTime m1- m2' = doubleFromLocalTime m2---- | A 'TimeSeq' for calendar days-days :: TimeSeq-days t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))- where t0 = (localDay t)- t1 = if (toTime t0) < t then t0 else (rev t0)- rev = pred- fwd = succ- toTime d = LocalTime d midnight---- | A 'TimeSeq' for calendar months-months :: TimeSeq-months t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))- where t0 = let (y,m,d) = toGregorian $ localDay t in fromGregorian y m 1- t1 = if toTime t0 < t then t0 else (rev t0)- rev = addGregorianMonthsClip (-1)- fwd = addGregorianMonthsClip 1- toTime d = LocalTime d midnight---- | A 'TimeSeq' for calendar years-years :: TimeSeq-years t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))- where t0 = let (y,m,d) = toGregorian $ localDay t in y- t1 = if toTime t0 < t then t0 else (rev t0)- rev = pred- fwd = succ- toTime y = LocalTime (fromGregorian y 1 1) midnight---- | Automatically choose a suitable time axis, based upon the time range of data.--- The values to be plotted against this axis can be created with 'doubleFromLocalTime'-autoTimeAxis :: AxisFn LocalTime-autoTimeAxis [] = timeAxis days days (formatTime defaultTimeLocale "%d-%b") []-autoTimeAxis pts = - if tdiff < 15- then timeAxis days days (ft "%d-%b") pts- else if tdiff < 90- then timeAxis days months (ft "%b-%y") pts- else if tdiff < 450- then timeAxis months months (ft "%b-%y") pts- else if tdiff < 1800- then timeAxis months years (ft "%Y") pts- else timeAxis years years (ft "%Y") pts- where- tdiff = diffDays (localDay t1) (localDay t0)- t1 = maximum pts- t0 = minimum pts- ft = formatTime defaultTimeLocale---------------------------------------------------------------------------------class Ord a => PlotValue a where- toValue :: a -> Double- autoAxis ::AxisFn a--instance PlotValue Double where- toValue = id- autoAxis = autoScaledAxis defaultLinearAxis--newtype LogValue = LogValue Double- deriving (Eq, Ord)--instance Show LogValue where- show (LogValue x) = show x--instance PlotValue LogValue where- toValue (LogValue x) = log x- autoAxis = autoScaledLogAxis defaultLogAxis--instance PlotValue LocalTime where- toValue = doubleFromLocalTime- autoAxis = autoTimeAxis---------------------------------------------------------------------------- | Type for capturing values plotted by index number--- (ie position in a list) rather than a numerical value-newtype PlotIndex = PlotIndex { plotindex_i :: Int }- deriving (Eq,Ord)--instance PlotValue PlotIndex where- toValue (PlotIndex i)= fromIntegral i- autoAxis = autoIndexAxis []---- | Create an axis for values indexed by position. The --- list of strings are the labels to be used.-autoIndexAxis :: [String] -> [PlotIndex] -> AxisData PlotIndex-autoIndexAxis labels vs = AxisData {- axis_viewport_= vport,- axis_ticks_ = [],- axis_labels_ = filter (\(i,l) -> i >= imin && i <= imax) (addIndexes labels),- axis_grid_ = []- }- where- vport r (PlotIndex i) = vmap (fi (plotindex_i imin) - 0.5,- fi (plotindex_i imax) + 0.5) r (fi i)- imin = minimum vs- imax = maximum vs- fi = fromIntegral :: Int -> Double----- | Augment a list of values with index numbers for plotting-addIndexes :: [a] -> [(PlotIndex,a)]-addIndexes as = map (\(i,a) -> (PlotIndex i,a)) (zip [0..] as)----- | A linear mapping of points in one range to another-vmap :: PlotValue x => (x,x) -> Range -> x -> Double-vmap (v1,v2) (v3,v4) v = v3 + (toValue v - toValue v1) * (v4-v3) / (toValue v2 - toValue v1)--------------------------------------------------------------------------- Template haskell to derive an instance of Data.Accessor.Accessor for each field-$( deriveAccessors ''AxisData )-$( deriveAccessors ''AxisStyle )-$( deriveAccessors ''LinearAxisParams )-$( deriveAccessors ''LogAxisParams )-+import Graphics.Rendering.Chart.Axis.Types+import Graphics.Rendering.Chart.Axis.Floating+import Graphics.Rendering.Chart.Axis.Int+import Graphics.Rendering.Chart.Axis.Time+import Graphics.Rendering.Chart.Axis.Unit+import Graphics.Rendering.Chart.Axis.Indexed
+ Graphics/Rendering/Chart/Axis/Floating.hs view
@@ -0,0 +1,340 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Floating+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render floating value axes+-- including doubles with linear, log, and percentage scaling.+--++{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Graphics.Rendering.Chart.Axis.Floating(+ Percent(..),+ LinearAxisParams(..),+ LogValue(..),+ LogAxisParams(..),+ scaledAxis,+ autoScaledAxis,+ autoScaledLogAxis,+ autoSteps,++ la_labelf,+ la_nLabels,+ la_nTicks,++ loga_labelf+) where++import Data.List(minimumBy, nub)+import Data.Ord (comparing)+import Data.Default.Class+import Numeric (showEFloat, showFFloat)++import Control.Lens+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Utils+import Graphics.Rendering.Chart.Axis.Types++-- Note: the following code uses explicit Integer types+-- to avoid -Wall 'defaulting to Integer' messages.++instance PlotValue Double where+ toValue = id+ fromValue= id+ autoAxis = autoScaledAxis def++instance PlotValue Float where+ toValue = realToFrac+ fromValue= realToFrac+ autoAxis = autoScaledAxis def++-- | A wrapper class for doubles used to indicate they are to+-- be plotted against a percentage axis.+newtype Percent = Percent {unPercent :: Double}+ deriving (Eq,Ord,Num,Real,Fractional,RealFrac,Floating,RealFloat)++instance Show Percent where+ show (Percent d) = showD (d*100) ++ "%"++instance PlotValue Percent where+ toValue = unPercent+ fromValue= Percent+ autoAxis = autoScaledAxis def {-_la_labelf=-}++-- | A wrapper class for doubles used to indicate they are to+-- be plotted against a log axis.+newtype LogValue = LogValue {unLogValue :: Double}+ deriving (Eq, Ord, Num, Real, Fractional, RealFrac, Floating, RealFloat)++instance Show LogValue where+ show (LogValue x) = show x++instance PlotValue LogValue where+ toValue (LogValue x) = log x+ fromValue d = LogValue (exp d)+ autoAxis = autoScaledLogAxis def++-- | Show a list of axis labels.+-- If some are too big or all are too small, switch to scientific notation for all.+-- If the range is much smaller than the mean, use an offset.+-- TODO: show this offset only once, not on every label.+-- When thinking about improving this function,+-- https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/ticker.py+-- is a good read.+--+-- >>> showDs [0, 1, 2 :: Double]+-- ["0","1","2"]+--+-- >>> showDs [0, 1000000, 2000000 :: Double]+-- ["0.0e0","1.0e6","2.0e6"]+--+-- >>> showDs [0, 0.001, 0.002 :: Double]+-- ["0","0.001","0.002"]+--+-- >>> showDs [-10000000, -1000000, 9000000 :: Double]+-- ["-1.0e7","-1.0e6","9.0e6"]+--+-- >>> showDs [10, 11, 12 :: Double]+-- ["10","11","12"]+--+-- >>> showDs [100, 101, 102 :: Double]+-- ["100","101","102"]+--+-- >>> showDs [100000, 100001, 100002 :: Double]+-- ["100000","100001","100002"]+--+-- >>> showDs [1000000, 1000001, 1000002 :: Double]+-- ["1.0e6 + 0","1.0e6 + 1","1.0e6 + 2"]+--+-- >>> showDs [10000000, 10000001, 10000002 :: Double]+-- ["1.0e7 + 0","1.0e7 + 1","1.0e7 + 2"]+--+-- >>> showDs [-10000000, -10000001, -10000002 :: Double]+-- ["-1.0e7 + 2","-1.0e7 + 1","-1.0e7 + 0"]+--+-- prop> let [s0, s1] = showDs [x, x + 1.0 :: Double] in s0 /= s1+showDs :: forall d . (RealFloat d) => [d] -> [String]+showDs xs = case showWithoutOffset xs of+ (s0:others)+ | anyEqualNeighbor s0 others -> map addShownOffset $ showWithoutOffset (map (\x -> x - offset) xs)+ s -> s+ where+ anyEqualNeighbor z0 (z1:others)+ | z0 == z1 = True+ | otherwise = anyEqualNeighbor z1 others+ anyEqualNeighbor _ [] = False++ -- Use the min for offset. Another good choice could be the mean.+ offset :: d+ offset = minimum xs+ shownOffset = case showWithoutOffset [offset] of+ [r] -> r+ rs -> error $ "showDs: shownOffset expected 1 element, got " ++ show (length rs)++ addShownOffset :: String -> String+ addShownOffset ('-':x) = shownOffset ++ " - " ++ x+ addShownOffset x = shownOffset ++ " + " ++ x++showWithoutOffset :: RealFloat d => [d] -> [String]+showWithoutOffset xs+ | useScientificNotation = map (showEFloat' (Just 1)) xs+ | otherwise = map showD xs+ where+ -- use scientific notation if max value is too big or too small+ useScientificNotation = maxAbs >= 1e6 || maxAbs <= 1e-6+ maxAbs = maximum (map abs xs)+++-- | Changes the behavior of showEFloat to drop more than one trailings 0.+-- Instead of 1.000e4 you get 1.0e4+showEFloat' :: forall d . RealFloat d => Maybe Int -> d -> String+showEFloat' mdigits x = reverse $ cleanup0 (reverse shown0)+ where+ shown0 = showEFloat mdigits x ""++ -- wait until we get the "e"+ cleanup0 :: String -> String+ cleanup0 (e@'e':ys) = e:cleanup1 ys+ cleanup0 (y:ys) = y : cleanup0 ys+ cleanup0 [] = reverse shown0 -- something went wrong, just return the original++ -- get rid of redundant 0s before the '.'+ cleanup1 :: String -> String+ cleanup1 ('0':ys@('0':_)) = cleanup1 ys+ cleanup1 y = y+++showD :: (RealFloat d) => d -> String+showD x = case reverse $ showFFloat Nothing x "" of+ '0':'.':r -> reverse r+ r -> reverse r++data LinearAxisParams a = LinearAxisParams {+ -- | The function used to show the axes labels.+ _la_labelf :: [a] -> [String],++ -- | The target number of labels to be shown.+ _la_nLabels :: Int,++ -- | The target number of ticks to be shown.+ _la_nTicks :: Int+}++instance (Show a, RealFloat a) => Default (LinearAxisParams a) where+ def = LinearAxisParams+ { _la_labelf = showDs+ , _la_nLabels = 5+ , _la_nTicks = 50+ }++-- | Generate a linear axis with the specified bounds+scaledAxis :: RealFloat a => LinearAxisParams a -> (a,a) -> AxisFn a+scaledAxis lap rs@(minV,maxV) ps0 = makeAxis' realToFrac realToFrac+ (_la_labelf lap) (labelvs,tickvs,gridvs)+ where+ 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++-- | Generate a linear axis automatically, scaled appropriately for the+-- input data.+autoScaledAxis :: RealFloat a => LinearAxisParams a -> AxisFn a+autoScaledAxis lap ps0 = scaledAxis lap rs ps+ where+ ps = filter isValidNumber ps0+ rs = (minimum ps,maximum ps)++steps :: RealFloat a => a -> (a,a) -> [Rational]+steps nSteps rs@(minV,maxV) = map ((s*) . fromIntegral) [min' .. max']+ where+ s = chooseStep nSteps rs+ min' :: Integer+ min' = floor $ realToFrac minV / s+ max' = ceiling $ realToFrac maxV / s++chooseStep :: RealFloat a => a -> (a,a) -> Rational+chooseStep nsteps (x1,x2) = minimumBy (comparing proximity) stepVals+ where+ delta = x2 - x1+ mult | delta == 0 = 1 -- Otherwise the case below will use all of memory+ | otherwise = 10 ^^ ((floor $ log10 $ 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++-- | Given a target number of values, and a list of input points,+-- find evenly spaced values from the set {1*X, 2*X, 2.5*X, 5*X} (where+-- X is some power of ten) that evenly cover the input points.+autoSteps :: Int -> [Double] -> [Double]+autoSteps nSteps vs = map fromRational $ steps (fromIntegral nSteps) r+ where+ range [] = (0,1)+ range _ | minV == maxV = (minV-0.5,minV+0.5)+ | otherwise = rs+ rs@(minV,maxV) = (minimum ps,maximum ps)+ ps = filter isValidNumber vs+ r = range ps++----------------------------------------------------------------------++instance (Show a, RealFloat a) => Default (LogAxisParams a) where+ def = LogAxisParams+ { _loga_labelf = showDs+ }++-- | Generate a log axis automatically, scaled appropriately for the+-- input data.+autoScaledLogAxis :: RealFloat a => LogAxisParams a -> AxisFn a+autoScaledLogAxis lap ps0 =+ makeAxis' (realToFrac . log) (realToFrac . exp)+ (_loga_labelf lap) (wrap rlabelvs, wrap rtickvs, wrap rlabelvs)+ where+ ps = filter (\x -> isValidNumber x && 0 < x) ps0+ (minV,maxV) = (minimum ps,maximum ps)+ wrap = map fromRational+ range [] = (3,30)+ range _ | minV == maxV = (realToFrac $ minV/3, realToFrac $ maxV*3)+ | otherwise = (realToFrac $ minV, realToFrac $ maxV)+ (rlabelvs, rtickvs) = logTicks (range ps)+++data LogAxisParams a = LogAxisParams {+ -- | The function used to show the axes labels.+ _loga_labelf :: [a] -> [String]+}++{-+ Rules: Do not subdivide between powers of 10 until all powers of 10+ get major ticks.+ Do not subdivide between powers of ten as [1,2,4,6,8,10] when+ 5 gets a major tick+ (i.e. the major ticks need to be a subset of the minor ticks)+-}+logTicks :: Range -> ([Rational],[Rational])+logTicks (low,high) = (nub major,nub minor)+ where+ pf :: RealFrac a => a -> (Integer, a)+ pf = properFraction++ -- frac :: (RealFrac a, Integral b) => a -> (b, a)+ frac :: (RealFrac a) => a -> (Integer, a)+ frac x | 0 <= b = (a,b)+ | otherwise = (a-1,b+1)+ where+ (a,b) = properFraction x++ ratio = high/low+ lower a l = let (i,r) = frac (log10 a) in+ maximum (1:filter (\x -> log10 (fromRational x) <= r) l)*10^^i+ upper a l = let (i,r) = pf (log10 a) in+ minimum (10:filter (\x -> r <= log10 (fromRational x)) l)*10^^i++ powers :: (Double,Double) -> [Rational] -> [Rational]+ powers (x,y) l = [ a*10^^p | p <- [(floor (log10 x))..(ceiling (log10 y))] :: [Integer]+ , a <- l ]+ midselection r l = filter (inRange r l) (powers r l)+ inRange (a,b) l x = (lower a l <= x) && (x <= upper b l)++ logRange = (log10 low, log10 high)++ roundPow x = 10^^(round x :: Integer)++ major | 17.5 < log10 ratio = map roundPow $+ steps (min 5 (log10 ratio)) logRange+ | 12 < log10 ratio = map roundPow $+ steps (log10 ratio / 5) logRange+ | 6 < log10 ratio = map roundPow $+ steps (log10 ratio / 2) logRange+ | 3 < log10 ratio = midselection (low,high) [1,10]+ | 20 < ratio = midselection (low,high) [1,5,10]+ | 6 < ratio = midselection (low,high) [1,2,4,6,8,10]+ | 3 < ratio = midselection (low,high) [1..10]+ | otherwise = steps 5 (low,high)++ (l',h') = (minimum major, maximum major)+ (dl',dh') = (fromRational l', fromRational h')+ ratio' :: Double+ ratio' = fromRational (h'/l')+ filterX = filter (\x -> l'<=x && x <=h') . powers (dl',dh')++ minor | 50 < log10 ratio' = map roundPow $+ steps 50 (log10 dl', log10 dh')+ | 6 < log10 ratio' = filterX [1,10]+ | 4 < log10 ratio' = filterX [1,5,10]+ | 6 < ratio' = filterX [1..10]+ | 3 < ratio' = filterX [1,1.2..10]+ | otherwise = steps 50 (dl', dh')++$( makeLenses ''LinearAxisParams )+$( makeLenses ''LogAxisParams )
+ Graphics/Rendering/Chart/Axis/Indexed.hs view
@@ -0,0 +1,57 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Unit+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render indexed axes++{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TupleSections #-}++module Graphics.Rendering.Chart.Axis.Indexed(+ PlotIndex(..),+ autoIndexAxis',+ autoIndexAxis,+ addIndexes,+) where++import Data.Default.Class++import Graphics.Rendering.Chart.Axis.Types++-- | Type for capturing values plotted by index number+-- (ie position in a list) rather than a numerical value.+newtype PlotIndex = PlotIndex { plotindex_i :: Int }+ deriving (Eq,Ord,Enum,Num,Real,Integral,Show)++instance PlotValue PlotIndex where+ toValue (PlotIndex i) = fromIntegral i+ fromValue = PlotIndex . round+ autoAxis = autoIndexAxis []++-- | Augment a list of values with index numbers for plotting.+addIndexes :: [a] -> [(PlotIndex,a)]+addIndexes = zipWith (\n x -> (PlotIndex n, x)) [0..]++-- | Create an axis for values indexed by position. The+-- list of strings are the labels to be used.+autoIndexAxis' :: Integral i => Bool -> [String] -> AxisFn i+autoIndexAxis' tks labels vs = AxisData {+ _axis_visibility = def { _axis_show_ticks = False },+ _axis_viewport = vport,+ _axis_tropweiv = invport,+ _axis_ticks = if tks then map (, 5) $ take (length labels) [0..] else [],+ _axis_labels = [filter (\(i,_) -> i >= imin && i <= imax)+ (zip [0..] labels)],+ _axis_grid = []+ }+ where+ vport r i = linMap id ( fromIntegral imin - 0.5+ , fromIntegral imax + 0.5) r (fromIntegral i)+ invport = invLinMap round fromIntegral (imin, imax)+ imin = minimum vs+ imax = maximum vs++autoIndexAxis :: Integral i => [String] -> AxisFn i+autoIndexAxis = autoIndexAxis' False
+ Graphics/Rendering/Chart/Axis/Int.hs view
@@ -0,0 +1,133 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Int+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render integer indexed axes+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Graphics.Rendering.Chart.Axis.Int(+ defaultIntAxis,+ scaledIntAxis,+ autoScaledIntAxis+) where++import Data.List(genericLength)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Word (Word8, Word16, Word32, Word64)+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Axis.Types+import Graphics.Rendering.Chart.Axis.Floating++instance PlotValue Int where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Int8 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Int16 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Int32 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Int64 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Word where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Word8 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Word16 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Word32 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Word64 where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++instance PlotValue Integer where+ toValue = fromIntegral+ fromValue = round+ autoAxis = autoScaledIntAxis defaultIntAxis++defaultIntAxis :: (Show a) => LinearAxisParams a+defaultIntAxis = LinearAxisParams {+ _la_labelf = map show,+ _la_nLabels = 5,+ _la_nTicks = 10+}++autoScaledIntAxis :: (Integral i, PlotValue i) =>+ LinearAxisParams i -> AxisFn i+autoScaledIntAxis lap ps = scaledIntAxis lap rs ps+ where+ rs = (minimum ps,maximum ps)++scaledIntAxis :: (Integral i, PlotValue i) =>+ LinearAxisParams i -> (i,i) -> AxisFn i+scaledIntAxis lap (minI,maxI) ps =+ makeAxis (_la_labelf lap) (labelvs,tickvs,gridvs)+ where+ range [] = (0,1)+ range _ | minI == maxI = (fromIntegral $ minI-1, fromIntegral $ minI+1)+ | otherwise = (fromIntegral minI, fromIntegral maxI)+-- labelvs :: [i]+ labelvs = stepsInt (fromIntegral $ _la_nLabels lap) r+ tickvs = stepsInt (fromIntegral $ _la_nTicks lap)+ ( fromIntegral $ minimum labelvs+ , fromIntegral $ maximum labelvs )+ gridvs = labelvs+ r = range ps++stepsInt :: Integral a => a -> Range -> [a]+stepsInt nSteps range = bestSize (goodness alt0) alt0 alts+ where+ bestSize n a (a':as) = let n' = goodness a' in+ if n' < n then bestSize n' a' as else a+ bestSize _ _ [] = []++ goodness vs = abs (genericLength vs - nSteps)++ (alt0:alts) = map (\n -> steps n range) sampleSteps'++ -- throw away sampleSteps that are definitely too small as+ -- they takes a long time to process + sampleSteps' = let rangeMag = ceiling (snd range - fst range)+ + (s1,s2) = span (< (rangeMag `div` nSteps)) sampleSteps+ in ((reverse . take 5 . reverse) s1) ++ s2++ -- generate all possible step sizes+ sampleSteps = [1,2,5] ++ sampleSteps1+ sampleSteps1 = [10,20,25,50] ++ map (*10) sampleSteps1++ steps size (minV,maxV) = takeWhile (<b) [a,a+size..] ++ [b]+ where+ a = (floor (minV / fromIntegral size)) * size+ b = (ceiling (maxV / fromIntegral size)) * size+
+ Graphics/Rendering/Chart/Axis/LocalTime.hs view
@@ -0,0 +1,286 @@+{-# LANGUAGE CPP #-}++-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.LocalTime+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render time axes++module Graphics.Rendering.Chart.Axis.LocalTime+ {-# DEPRECATED "Use Graphics.Rendering.Chart.Axis.Time module" #-}+ (+ TimeSeq,+ TimeLabelFn,+ TimeLabelAlignment(..),++ timeAxis,+ autoTimeAxis,++ days, months, years,++ -- * Utilities+ doubleFromLocalTime++ )+ where++import Data.Default.Class+#if MIN_VERSION_time(1,5,0)+import Data.Time hiding (months)+#else+import Data.Time+import System.Locale (defaultTimeLocale)+#endif+import Data.Fixed+import Control.Lens++import Graphics.Rendering.Chart.Axis.Types+import Graphics.Rendering.Chart.Axis.Time ()++----------------------------------------------------------------------++-- | Map a LocalTime value to a plot coordinate.+doubleFromLocalTime :: LocalTime -> Double+doubleFromLocalTime lt = fromIntegral (toModifiedJulianDay (localDay lt))+ + fromRational (timeOfDayToDayFraction (localTimeOfDay lt))++-- | Map a plot coordinate to a LocalTime.+localTimeFromDouble :: Double -> LocalTime+localTimeFromDouble v =+ LocalTime (ModifiedJulianDay i) (dayFractionToTimeOfDay (toRational d))+ where+ (i,d) = properFraction v++-- | TimeSeq is a (potentially infinite) set of times. When passed+-- a reference time, the function returns a a pair of lists. The first+-- contains all times in the set less than the reference time in+-- decreasing order. The second contains all times in the set greater+-- than or equal to the reference time, in increasing order.+type TimeSeq = LocalTime-> ([LocalTime],[LocalTime])++coverTS :: TimeSeq -> LocalTime -> LocalTime -> [LocalTime]+coverTS tseq minT maxT = min' ++ enumerateTS tseq minT maxT ++ max'+ where+ min' = if elemTS minT tseq then [] else take 1 (fst (tseq minT))+ max' = if elemTS maxT tseq then [] else take 1 (snd (tseq maxT))++enumerateTS :: TimeSeq -> LocalTime -> LocalTime -> [LocalTime]+enumerateTS tseq minT maxT =+ reverse (takeWhile (>=minT) ts1) ++ takeWhile (<=maxT) ts2+ where+ (ts1,ts2) = tseq minT++elemTS :: LocalTime -> TimeSeq -> Bool+elemTS t tseq = case tseq t of+ (_,t0:_) | t == t0 -> True+ _ -> False++-- | How to display a time+type TimeLabelFn = LocalTime -> String++data TimeLabelAlignment = UnderTicks+ | BetweenTicks+ deriving (Show)++-- | Create an 'AxisFn' to for a time axis.+--+-- The values to be plotted against this axis can be created with+-- 'doubleFromLocalTime'.+timeAxis ::+ TimeSeq+ -- ^ Set the minor ticks, and the final range will be aligned to its+ -- elements.+ -> TimeSeq+ -- ^ Set the labels and grid.+ -> TimeLabelFn+ -> TimeLabelAlignment+ -> TimeSeq+ -- ^ Set the second line of labels.+ -> TimeLabelFn+ -- ^ Format `LocalTime` for labels.+ -> TimeLabelAlignment+ -> AxisFn LocalTime+timeAxis tseq lseq labelf lal cseq contextf clal pts = AxisData {+ _axis_visibility = def,+ _axis_viewport = vmap(min', max'),+ _axis_tropweiv = invmap(min', max'),+ _axis_ticks = [ (t,2) | t <- times] ++ [ (t,5) | t <- ltimes, visible t],+ _axis_labels = [ [ (t,l) | (t,l) <- labels labelf ltimes lal, visible t]+ , [ (t,l) | (t,l) <- labels contextf ctimes clal, visible t]+ ],+ _axis_grid = [ t | t <- ltimes, visible t]+ }+ where+ (minT,maxT) = case pts of+ [] -> (refLocalTime,refLocalTime)+ ps -> (minimum ps, maximum ps)+ refLocalTime = LocalTime (ModifiedJulianDay 0) midnight+ times = coverTS tseq minT maxT+ ltimes = coverTS lseq minT maxT+ ctimes = coverTS cseq minT maxT+ min' = minimum times+ max' = maximum times+ visible t = min' <= t && t <= max'+ labels f ts lal' =+ [ (align lal' m1' m2', f m1)+ | (m1,m2) <- zip ts (tail ts)+ , let m1' = if m1<min' then min' else m1+ , let m2' = if m2>max' then max' else m2 ]++ align BetweenTicks m1 m2 = avg m1 m2+ align UnderTicks m1 _ = m1++ avg m1 m2 = localTimeFromDouble $ m1' + (m2' - m1')/2+ where+ m1' = doubleFromLocalTime m1+ m2' = doubleFromLocalTime m2++normalizeTimeOfDay :: LocalTime -> LocalTime+normalizeTimeOfDay t@(LocalTime day (TimeOfDay h m s))+ | s < 0 = normalizeTimeOfDay (LocalTime day (TimeOfDay h (m-1) (s+60)))+ | m < 0 = normalizeTimeOfDay (LocalTime day (TimeOfDay (h-1) (m+60) s))+ | h < 0 = normalizeTimeOfDay (LocalTime (addDays (-1) day) (TimeOfDay (h+24) m s))+ | s >= 60 = normalizeTimeOfDay (LocalTime day (TimeOfDay h (m+s`div'`60)+ (s`mod'`60)))+ | m >= 60 = normalizeTimeOfDay (LocalTime day (TimeOfDay (h+m`div`60)+ (m`mod`60) s))+ | h >= 24 = LocalTime (addDays (fromIntegral (h`div`24)) day)+ (TimeOfDay (h`mod`24) m s)+ | otherwise = t++addTod :: Int -> Int -> Pico -> LocalTime -> LocalTime+addTod dh dm ds (LocalTime day (TimeOfDay h m s)) = normalizeTimeOfDay t'+ where t' = LocalTime day (TimeOfDay (h+dh) (m+dm) (s+ds))++truncateTo :: (HasResolution a) => Fixed a -> Fixed a -> Fixed a+truncateTo t step = t - t `mod'` step++secondSeq :: Pico -> TimeSeq+secondSeq step t = (iterate rev t1, tail (iterate fwd t1))+ where h0 = todHour (localTimeOfDay t)+ m0 = todMin (localTimeOfDay t)+ s0 = todSec (localTimeOfDay t) `truncateTo` (1 / 1000)+ t0 = LocalTime (localDay t) (TimeOfDay h0 m0 s0)+ t1 = if t0 < t then t0 else rev t0+ rev = addTod 0 0 (negate step)+ fwd = addTod 0 0 step++millis1, millis10, millis100, seconds, fiveSeconds :: TimeSeq+millis1 = secondSeq (1 / 1000)+millis10 = secondSeq (1 / 100)+millis100 = secondSeq (1 / 10)+seconds = secondSeq 1+fiveSeconds = secondSeq 5++minuteSeq :: Int -> TimeSeq+minuteSeq step t = (iterate rev t1, tail (iterate fwd t1))+ where h0 = todHour (localTimeOfDay t)+ m0 = todMin (localTimeOfDay t)+ t0 = LocalTime (localDay t) (TimeOfDay h0 m0 0)+ t1 = if t0 < t then t0 else rev t0+ rev = addTod 0 (negate step) 0+ fwd = addTod 0 step 0++minutes, fiveMinutes :: TimeSeq+minutes = minuteSeq 1+fiveMinutes = minuteSeq 5++-- | A 'TimeSeq' for hours.+hours :: TimeSeq+hours t = (iterate rev t1, tail (iterate fwd t1))+ where h0 = todHour (localTimeOfDay t)+ t0 = LocalTime (localDay t) (TimeOfDay h0 0 0)+ t1 = if t0 < t then t0 else rev t0+ rev = addTod (-1) 0 0+ fwd = addTod 1 0 0++-- | A 'TimeSeq' for calendar days.+days :: TimeSeq+days t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = localDay t+ t1 = if toTime t0 < t then t0 else rev t0+ rev = pred+ fwd = succ+ toTime d = LocalTime d midnight++-- | A 'TimeSeq' for calendar months.+months :: TimeSeq+months t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = let (y,m,_) = toGregorian $ localDay t in fromGregorian y m 1+ t1 = if toTime t0 < t then t0 else rev t0+ rev = addGregorianMonthsClip (-1)+ fwd = addGregorianMonthsClip 1+ toTime d = LocalTime d midnight++-- | A 'TimeSeq' for calendar years.+years :: TimeSeq+years t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = toGregorian (localDay t) ^. _1+ t1 = if toTime t0 < t then t0 else rev t0+ rev = pred+ fwd = succ+ toTime y = LocalTime (fromGregorian y 1 1) midnight++-- | A 'TimeSeq' for no sequence at all.+noTime :: TimeSeq+noTime _ = ([],[])++-- | Automatically choose a suitable time axis, based upon the time range+-- of data. The values to be plotted against this axis can be created+-- with 'doubleFromLocalTime'.+autoTimeAxis :: AxisFn LocalTime+autoTimeAxis pts+ | null pts = timeAxis days days (ft "%d-%b-%y") UnderTicks+ noTime (ft "") UnderTicks []+ | tdiff==0 && 100*dsec<1= timeAxis millis1 millis1 (ft "%S%Q") UnderTicks+ noTime (ft "%S%Q") UnderTicks pts+ | tdiff==0 && 10*dsec<1 = timeAxis millis10 millis10 (ft "%S%Q") UnderTicks+ noTime (ft "%S%Q") UnderTicks pts+ | tdiff==0 && dsec<1 = timeAxis millis10 millis100 (ft "%S%Q") UnderTicks+ seconds (ft "%M:%S") BetweenTicks pts+ | tdiff==0 && dsec<5 = timeAxis millis100 seconds (ft "%M:%S%Q") UnderTicks+ seconds (ft "%M:%S") BetweenTicks pts+ | tdiff==0 && dsec<32 = timeAxis seconds seconds (ft "%Ss") UnderTicks+ minutes (ft "%d-%b-%y %H:%M") BetweenTicks pts+ | tdiff==0 && dsec<120 = timeAxis seconds fiveSeconds (ft "%Ss") UnderTicks+ minutes (ft "%d-%b-%y %H:%M") BetweenTicks pts+ | tdiff==0 && dmin<7 = timeAxis fiveSeconds minutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | tdiff==0 && dmin<32 = timeAxis minutes minutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | tdiff==0 && dmin<90 = timeAxis minutes fiveMinutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | tdiff < 2 && dhour<4 = timeAxis fiveMinutes hours (ft "%H:%M") UnderTicks+ days (ft "%d-%b-%y") BetweenTicks pts+ | tdiff < 2 && dhour<32 = timeAxis hours hours (ft "%H:%M") UnderTicks+ days (ft "%d-%b-%y") BetweenTicks pts+ | tdiff < 4 = timeAxis hours days (ft "%d-%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | tdiff < 12 = timeAxis days days (ft "%d-%b") BetweenTicks+ years (ft "%Y") BetweenTicks pts+ | tdiff < 45 = timeAxis days days (ft "%d") BetweenTicks+ months (ft "%b-%y") BetweenTicks pts+ | tdiff < 95 = timeAxis days months (ft "%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | tdiff < 450 = timeAxis months months (ft "%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | tdiff < 735 = timeAxis months months (ft "%b") BetweenTicks+ years (ft "%Y") BetweenTicks pts+ | tdiff < 1800 = timeAxis months years (ft "%Y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | otherwise = timeAxis years years (ft "%Y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ where+ tdiff = diffDays (localDay t1) (localDay t0)+ dhour = if tdiff==0 then h1-h0 else 24*fromIntegral tdiff +h1-h0+ dmin = 60*dhour+(m1-m0)+ dsec = fromIntegral (60*dmin) + (s1-s0)+ (TimeOfDay h0 m0 s0) = localTimeOfDay t0+ (TimeOfDay h1 m1 s1) = localTimeOfDay t1+ t1 = maximum pts+ t0 = minimum pts+ ft = formatTime defaultTimeLocale++
+ Graphics/Rendering/Chart/Axis/Time.hs view
@@ -0,0 +1,316 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Time+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render time axes++module Graphics.Rendering.Chart.Axis.Time(+ TimeSeq,+ TimeLabelFn,+ TimeLabelAlignment(..),++ TimeValue (..),++ timeValueAxis,+ autoTimeValueAxis,++ days, months, years,++ ) where++import Data.Default.Class+#if MIN_VERSION_time(1,5,0)+import Data.Time hiding (months)+#else+import Data.Time+import System.Locale (defaultTimeLocale)+#endif+import Data.Fixed+import Control.Lens++import Graphics.Rendering.Chart.Axis.Types+import Graphics.Rendering.Chart.Geometry (Range)++-- | A typeclass abstracting the functions we need+-- to be able to plot against an axis of time type @d@.+class TimeValue t where+ utctimeFromTV :: t -> UTCTime+ tvFromUTCTime :: UTCTime -> t++ {-# MINIMAL utctimeFromTV, tvFromUTCTime #-}++ doubleFromTimeValue :: t -> Double+ doubleFromTimeValue = doubleFromTimeValue . utctimeFromTV++ timeValueFromDouble :: Double -> t+ timeValueFromDouble = tvFromUTCTime . timeValueFromDouble++instance TimeValue UTCTime where+ utctimeFromTV = id+ tvFromUTCTime = id+ doubleFromTimeValue = doubleFromUTCTime+ timeValueFromDouble = utcTimeFromDouble++instance TimeValue Day where+ utctimeFromTV d = UTCTime d 0+ tvFromUTCTime = utctDay+ doubleFromTimeValue = doubleFromDay+ timeValueFromDouble = dayFromDouble++instance TimeValue LocalTime where+ utctimeFromTV (LocalTime d tod) = UTCTime d (timeOfDayToTime tod)+ tvFromUTCTime (UTCTime d dt) = LocalTime d (timeToTimeOfDay dt)++----------------------------------------------------------------------++instance PlotValue LocalTime where+ toValue = doubleFromTimeValue+ fromValue = timeValueFromDouble+ autoAxis = autoTimeValueAxis++instance PlotValue UTCTime where+ toValue = doubleFromTimeValue+ fromValue = timeValueFromDouble+ autoAxis = autoTimeValueAxis++instance PlotValue Day where+ toValue = doubleFromTimeValue+ fromValue = timeValueFromDouble+ autoAxis = autoTimeValueAxis++----------------------------------------------------------------------++-- | Map a UTCTime value to a plot coordinate.+doubleFromUTCTime :: UTCTime -> Double+doubleFromUTCTime ut = fromIntegral (toModifiedJulianDay (utctDay ut))+ + fromRational (timeOfDayToDayFraction (timeToTimeOfDay (utctDayTime ut)))++-- | Map a plot coordinate to a UTCTime.+utcTimeFromDouble :: Double -> UTCTime+utcTimeFromDouble v =+ UTCTime (ModifiedJulianDay i) (timeOfDayToTime (dayFractionToTimeOfDay (toRational d)))+ where+ (i,d) = properFraction v++-- | Map a Day value to a plot coordinate.+doubleFromDay :: Day -> Double+doubleFromDay d = fromIntegral (toModifiedJulianDay d)++-- | Map a plot coordinate to a Day.+dayFromDouble :: Double -> Day+dayFromDouble v = ModifiedJulianDay (truncate v)++----------------------------------------------------------------------++-- | TimeSeq is a (potentially infinite) set of times. When passed+-- a reference time, the function returns a a pair of lists. The first+-- contains all times in the set less than the reference time in+-- decreasing order. The second contains all times in the set greater+-- than or equal to the reference time, in increasing order.+type TimeSeq = UTCTime -> ([UTCTime],[UTCTime])++coverTS :: TimeSeq -> UTCTime -> UTCTime -> [UTCTime]+coverTS tseq minT maxT = min' ++ enumerateTS tseq minT maxT ++ max'+ where+ min' = if elemTS minT tseq then [] else take 1 (fst (tseq minT))+ max' = if elemTS maxT tseq then [] else take 1 (snd (tseq maxT))++enumerateTS :: TimeSeq -> UTCTime -> UTCTime -> [UTCTime]+enumerateTS tseq minT maxT =+ reverse (takeWhile (>=minT) ts1) ++ takeWhile (<=maxT) ts2+ where+ (ts1,ts2) = tseq minT++elemTS :: UTCTime -> TimeSeq -> Bool+elemTS t tseq = case tseq t of+ (_,t0:_) | t == t0 -> True+ _ -> False++-- | How to display a time+type TimeLabelFn = UTCTime -> String++data TimeLabelAlignment = UnderTicks+ | BetweenTicks+ deriving (Show)++-- | Create an 'AxisFn' to for a time axis.+--+-- The values to be plotted against this axis can be created with+-- 'doubleFromLocalTime'.+--+-- Implementation detail: 'PlotValue' constraint is needed to use `vmap`.+timeValueAxis ::+ TimeValue t+ => TimeSeq+ -- ^ Set the minor ticks, and the final range will be aligned to its+ -- elements.+ -> TimeSeq+ -- ^ Set the labels and grid.+ -> TimeLabelFn+ -> TimeLabelAlignment+ -> TimeSeq+ -- ^ Set the second line of labels.+ -> TimeLabelFn+ -- ^ Format @t@ for labels.+ -> TimeLabelAlignment+ -> AxisFn t+timeValueAxis tseq lseq labelf lal cseq contextf clal pts = AxisData {+ _axis_visibility = def,+ _axis_viewport = vmap' (tvFromUTCTime min', tvFromUTCTime max'),+ _axis_tropweiv = invmap' (tvFromUTCTime min', tvFromUTCTime max'),+ _axis_ticks = [ (tvFromUTCTime t,2) | t <- times] ++ [ (tvFromUTCTime t,5) | t <- ltimes, visible t],+ _axis_labels = [ [ (tvFromUTCTime t,l) | (t,l) <- labels labelf ltimes lal, visible t]+ , [ (tvFromUTCTime t,l) | (t,l) <- labels contextf ctimes clal, visible t]+ ],+ _axis_grid = [ tvFromUTCTime t | t <- ltimes, visible t]+ }+ where+ (minT,maxT) = case pts of+ [] -> (refTimeValue,refTimeValue)+ ps -> (minimum (map utctimeFromTV ps), maximum (map utctimeFromTV ps))+ refTimeValue = timeValueFromDouble 0++ times, ltimes, ctimes :: [UTCTime]+ times = coverTS tseq minT maxT+ ltimes = coverTS lseq minT maxT+ ctimes = coverTS cseq minT maxT+ min' = minimum times+ max' = maximum times+ visible t = min' <= t && t <= max'+ labels f ts lal' =+ [ (align lal' m1' m2', f m1)+ | (m1,m2) <- zip ts (tail ts)+ , let m1' = if m1<min' then min' else m1+ , let m2' = if m2>max' then max' else m2 ]++ align BetweenTicks m1 m2 = avg m1 m2+ align UnderTicks m1 _ = m1++ avg m1 m2 = timeValueFromDouble $ m1' + (m2' - m1')/2+ where+ m1' = doubleFromTimeValue m1+ m2' = doubleFromTimeValue m2++vmap' :: TimeValue x => (x,x) -> Range -> x -> Double+vmap' (v1,v2) (v3,v4) v = v3 + (doubleFromTimeValue v - doubleFromTimeValue v1) * (v4-v3)+ / (doubleFromTimeValue v2 - doubleFromTimeValue v1)++invmap' :: TimeValue x => (x,x) -> Range -> Double -> x+invmap' (v3,v4) (d1,d2) d = timeValueFromDouble (doubleFromTimeValue v3 + ( (d-d1) * doubleRange+ / (d2-d1) ))+ where doubleRange = doubleFromTimeValue v4 - doubleFromTimeValue v3++truncateTo :: Real a => a -> a -> a+truncateTo t step = t - t `mod'` step++secondSeq :: NominalDiffTime -> TimeSeq+secondSeq step t@(UTCTime day dt) = (iterate rev t1, tail (iterate fwd t1))+ where t0 = UTCTime day (truncateTo dt step')+ t1 = if t0 < t then t0 else rev t0+ rev = addUTCTime (negate step)+ fwd = addUTCTime step+ step' = realToFrac step++millis1, millis10, millis100, seconds, fiveSeconds :: TimeSeq+millis1 = secondSeq (1 / 1000)+millis10 = secondSeq (1 / 100)+millis100 = secondSeq (1 / 10)+seconds = secondSeq 1+fiveSeconds = secondSeq 5++minutes, fiveMinutes :: TimeSeq+minutes = secondSeq 60+fiveMinutes = secondSeq (5 * 60)++-- | A 'TimeSeq' for hours.+hours :: TimeSeq+hours = secondSeq (60 * 60)++-- | A 'TimeSeq' for calendar days.+days :: TimeSeq+days t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = utctDay t+ t1 = if toTime t0 < t then t0 else rev t0+ rev = pred+ fwd = succ+ toTime d = UTCTime d 0++-- | A 'TimeSeq' for calendar months.+months :: TimeSeq+months t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = let (y,m,_) = toGregorian $ utctDay t in fromGregorian y m 1+ t1 = if toTime t0 < t then t0 else rev t0+ rev = addGregorianMonthsClip (-1)+ fwd = addGregorianMonthsClip 1+ toTime d = UTCTime d 0++-- | A 'TimeSeq' for calendar years.+years :: TimeSeq+years t = (map toTime $ iterate rev t1, map toTime $ tail (iterate fwd t1))+ where t0 = toGregorian (utctDay t) ^. _1+ t1 = if toTime t0 < t then t0 else rev t0+ rev = pred+ fwd = succ+ toTime y = UTCTime (fromGregorian y 1 1) 0++-- | A 'TimeSeq' for no sequence at all.+noTime :: TimeSeq+noTime _ = ([],[])++-- | Automatically choose a suitable time axis, based upon the time range+-- of data. The values to be plotted against this axis can be created+-- with 'doubleFromTimeValue'.+autoTimeValueAxis :: TimeValue t => AxisFn t+autoTimeValueAxis pts+ | null pts = timeValueAxis days days (ft "%d-%b-%y") UnderTicks+ noTime (ft "") UnderTicks []+ | 100*dsec<1 = timeValueAxis millis1 millis1 (ft "%S%Q") UnderTicks+ noTime (ft "%S%Q") UnderTicks pts+ | 10*dsec<1 = timeValueAxis millis10 millis10 (ft "%S%Q") UnderTicks+ noTime (ft "%S%Q") UnderTicks pts+ | dsec<1 = timeValueAxis millis10 millis100 (ft "%S%Q") UnderTicks+ seconds (ft "%M:%S") BetweenTicks pts+ | dsec<5 = timeValueAxis millis100 seconds (ft "%M:%S%Q") UnderTicks+ seconds (ft "%M:%S") BetweenTicks pts+ | dsec<32 = timeValueAxis seconds seconds (ft "%Ss") UnderTicks+ minutes (ft "%d-%b-%y %H:%M") BetweenTicks pts+ | dsec<120 = timeValueAxis seconds fiveSeconds (ft "%Ss") UnderTicks+ minutes (ft "%d-%b-%y %H:%M") BetweenTicks pts+ | dsec<7*60 = timeValueAxis fiveSeconds minutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | dsec<32*60 = timeValueAxis minutes minutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | dsec<90*60 = timeValueAxis minutes fiveMinutes (ft "%Mm") UnderTicks+ hours (ft "%d-%b-%y %H:00") BetweenTicks pts+ | dsec<4*3600 = timeValueAxis fiveMinutes hours (ft "%H:%M") UnderTicks+ days (ft "%d-%b-%y") BetweenTicks pts+ | dsec<32*3600 = timeValueAxis hours hours (ft "%H:%M") UnderTicks+ days (ft "%d-%b-%y") BetweenTicks pts+ | dday<4 = timeValueAxis hours days (ft "%d-%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | dday<12 = timeValueAxis days days (ft "%d-%b") BetweenTicks+ years (ft "%Y") BetweenTicks pts+ | dday<45 = timeValueAxis days days (ft "%d") BetweenTicks+ months (ft "%b-%y") BetweenTicks pts+ | dday<95 = timeValueAxis days months (ft "%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | dday<450 = timeValueAxis months months (ft "%b-%y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | dday<735 = timeValueAxis months months (ft "%b") BetweenTicks+ years (ft "%Y") BetweenTicks pts+ | dday<1800 = timeValueAxis months years (ft "%Y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ | otherwise = timeValueAxis years years (ft "%Y") BetweenTicks+ noTime (ft "") BetweenTicks pts+ where+ upts = map utctimeFromTV pts+ dsec = diffUTCTime t1 t0 -- seconds+ dday = dsec / 86400 -- days+ t1 = maximum upts+ t0 = minimum upts+ ft = formatTime defaultTimeLocale
+ Graphics/Rendering/Chart/Axis/Types.hs view
@@ -0,0 +1,454 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Types+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Type definitions for Axes+--++{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Axis.Types(+ AxisData(..),+ AxisVisibility(..),+ AxisT(..),+ AxisStyle(..),+ PlotValue(..),+ AxisFn,++ defaultAxisLineStyle,+ defaultGridLineStyle,++ makeAxis,+ makeAxis',++ axisToRenderable,+ renderAxisGrid,+ axisOverhang,+ vmap,+ invmap,++ linMap,+ invLinMap,++ axisGridAtTicks,+ axisGridAtBigTicks,+ axisGridAtLabels,+ axisGridHide,+ axisLabelsOverride,+ + axis_show_line,+ axis_show_ticks,+ axis_show_labels,++ axis_visibility,+ axis_viewport,+ axis_tropweiv,+ axis_ticks,+ axis_labels,+ axis_grid,++ axis_line_style,+ axis_label_style,+ axis_grid_style,+ axis_label_gap,++) where++import Control.Monad+import Data.List(sort,intersperse)+import Control.Lens hiding (at, re)+import Data.Colour (opaque)+import Data.Colour.Names (black, lightgrey)+import Data.Default.Class++import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Renderable++-- | A typeclass abstracting the functions we need+-- to be able to plot against an axis of type a+class Ord a => PlotValue a where+ toValue :: a -> Double+ fromValue:: Double -> a+ autoAxis :: AxisFn a++-- | Configures whick visual elements of a axis are shown at the+-- appropriate edge of a plot area.+data AxisVisibility = AxisVisibility+ { -- | Whether to display a line along the axis.+ _axis_show_line :: Bool+ + -- | Whether to display the tick marks.+ , _axis_show_ticks :: Bool++ -- | Whether to display the labels.+ , _axis_show_labels :: Bool+ }++-- | The basic data associated with an axis showing values of type x.+data AxisData x = AxisData {+ + -- | Which parts of the axis shall be displayed.+ _axis_visibility :: AxisVisibility,+ + -- | The _axis_viewport function maps values into device coordinates.+ _axis_viewport :: Range -> x -> Double,++ -- | The _axis_tropweiv function maps device coordinates back to values.+ _axis_tropweiv :: Range -> Double -> x,++ -- | The tick marks on the axis as pairs.+ -- The first element is the position on the axis+ -- (in viewport units) and the second element is the+ -- length of the tick in output coordinates.+ -- The tick starts on the axis, and positive numbers are drawn+ -- towards the plot area.+ _axis_ticks :: [(x,Double)],++ -- | The labels on an axis as pairs. The first element of the pair+ -- is the position on the axis (in viewport units) and the+ -- second is the label text string. Note that multiple sets of+ -- labels can be specified, and are shown successively further+ -- away from the axis line.+ _axis_labels :: [[(x, String)]],++ -- | The positions on the axis (in viewport units) where+ -- we want to show grid lines.+ _axis_grid :: [ x ]+}++-- | Control values for how an axis gets displayed.+data AxisStyle = AxisStyle {+ -- | 'LineStyle' to use for axis line and ticks.+ _axis_line_style :: LineStyle,+ -- | 'FontStyle' to use for axis labels.+ _axis_label_style :: FontStyle,+ -- | 'LineStyle' to use for axis grid.+ _axis_grid_style :: LineStyle,++ -- | How far the labels are to be drawn from the axis.+ _axis_label_gap :: Double+}++-- | A function to generate the axis data, given the data values+-- to be plotted against it.+type AxisFn x = [x] -> AxisData x++-- | Collect the information we need to render an axis. The+-- bool is true if the axis direction is reversed.+data AxisT x = AxisT RectEdge AxisStyle Bool (AxisData x)++-- | Construct a renderable from an axis, in order that+-- it can be composed with other renderables and drawn. This+-- does not include the drawing of the grid, which must be done+-- separately by the `renderAxisGrid` function.+axisToRenderable :: AxisT x -> Renderable x+axisToRenderable at = Renderable {+ minsize = minsizeAxis at,+ render = renderAxis at+ }++-- | Modifier to remove grid lines from an axis+axisGridHide :: AxisData x -> AxisData x+axisGridHide ad = ad{ _axis_grid = [] }++-- | Modifier to position grid lines to line up with the ticks+axisGridAtTicks :: AxisData x -> AxisData x+axisGridAtTicks ad = ad{ _axis_grid = map fst (_axis_ticks ad) }++-- | Modifier to position grid lines to line up with only the major ticks+axisGridAtBigTicks :: AxisData x -> AxisData x+axisGridAtBigTicks ad = ad{ _axis_grid =+ map fst $+ filter ((> minimum (map (abs.snd) (_axis_ticks ad))).snd) $+ _axis_ticks ad }++-- | Modifier to position grid lines to line up with the labels+axisGridAtLabels :: AxisData x -> AxisData x+axisGridAtLabels ad = ad{ _axis_grid = map fst vs }+ where+ vs = case _axis_labels ad of+ [] -> []+ ls -> head ls++-- | Modifier to change labels on an axis+axisLabelsOverride :: [(x,String)] -> AxisData x -> AxisData x+axisLabelsOverride o ad = ad{ _axis_labels = [o] }++minsizeAxis :: AxisT x -> BackendProgram RectSize+minsizeAxis (AxisT at as _ ad) = do+ let labelVis = _axis_show_labels $ _axis_visibility ad+ tickVis = _axis_show_ticks $ _axis_visibility ad+ labels = if labelVis then labelTexts ad else []+ ticks = if tickVis then _axis_ticks ad else []+ labelSizes <- withFontStyle (_axis_label_style as) $+ mapM (mapM textDimension) labels++ let ag = _axis_label_gap as+ let tsize = maximum (0 : [ max 0 (-l) | (_,l) <- ticks ])++ let hw = maximum0 (map (maximum0.map fst) labelSizes)+ let hh = ag + tsize + (sum . intersperse ag . map (maximum0.map snd) $ labelSizes)++ let vw = ag + tsize + (sum . intersperse ag . map (maximum0.map fst) $ labelSizes)+ let vh = maximum0 (map (maximum0.map snd) labelSizes)++ let sz = case at of+ E_Top -> (hw,hh)+ E_Bottom -> (hw,hh)+ E_Left -> (vw,vh)+ E_Right -> (vw,vh)+ return sz++labelTexts :: AxisData a -> [[String]]+labelTexts ad = map (map snd) (_axis_labels ad)++maximum0 :: (Num a, Ord a) => [a] -> a+maximum0 [] = 0+maximum0 vs = maximum vs++-- | Calculate the amount by which the labels extend beyond+-- the ends of the axis.+axisOverhang :: (Ord x) => AxisT x -> BackendProgram (Double,Double)+axisOverhang (AxisT at as _ ad) = do+ let labels = map snd . sort . concat . _axis_labels $ ad+ labelSizes <- withFontStyle (_axis_label_style as) $+ mapM textDimension labels+ case labelSizes of+ [] -> return (0,0)+ ls -> let l1 = head ls+ l2 = last ls+ ohangv = return (snd l1 / 2, snd l2 / 2)+ ohangh = return (fst l1 / 2, fst l2 / 2)+ in case at of+ E_Top -> ohangh+ E_Bottom -> ohangh+ E_Left -> ohangv+ E_Right -> ohangh++renderAxis :: AxisT x -> RectSize -> BackendProgram (PickFn x)+renderAxis at@(AxisT et as _ ad) sz = do+ let ls = _axis_line_style as+ vis = _axis_visibility ad+ when (_axis_show_line vis) $ + withLineStyle (ls {_line_cap = LineCapSquare}) $ do+ p <- alignStrokePoints [Point sx sy,Point ex ey]+ strokePointPath p+ when (_axis_show_ticks vis) $ + withLineStyle (ls {_line_cap = LineCapButt}) $ + mapM_ drawTick (_axis_ticks ad)+ when (_axis_show_labels vis) $ + withFontStyle (_axis_label_style as) $ do+ labelSizes <- mapM (mapM textDimension) (labelTexts ad)+ let sizes = map ((+ag).maximum0.map coord) labelSizes+ let offsets = scanl (+) ag sizes+ mapM_ drawLabels (zip offsets (_axis_labels ad))+ return pickfn+ where+ (sx,sy,ex,ey,tp,axisPoint,invAxisPoint) = axisMapping at sz++ drawTick (value,len) =+ let t1 = axisPoint value+ t2 = t1 `pvadd` vscale len tp+ in alignStrokePoints [t1,t2] >>= strokePointPath++ (hta,vta,coord,awayFromAxis) = case et of+ E_Top -> (HTA_Centre, VTA_Bottom, snd, \v -> Vector 0 (-v))+ E_Bottom -> (HTA_Centre, VTA_Top, snd, \v -> Vector 0 v)+ E_Left -> (HTA_Right, VTA_Centre, fst, \v -> Vector (-v) 0)+ E_Right -> (HTA_Left, VTA_Centre, fst, \v -> Vector v 0)++ avoidOverlaps labels = do+ rects <- mapM labelDrawRect labels+ return $ map snd . head . filter (noOverlaps . map fst)+ $ map (`eachNth` rects) [0 .. length rects]++ labelDrawRect (value,s) = do+ let pt = axisPoint value `pvadd` awayFromAxis ag+ r <- textDrawRect hta vta pt s+ return (hBufferRect r,(value,s))++ drawLabels (offset,labels) = do+ labels' <- avoidOverlaps labels+ mapM_ drawLabel labels'+ where+ drawLabel (value,s) = do+ drawTextA hta vta (axisPoint value `pvadd` awayFromAxis offset) s+ textDimension s++ ag = _axis_label_gap as+ pickfn = Just . invAxisPoint++hBufferRect :: Rect -> Rect+hBufferRect (Rect p (Point x y)) = Rect p $ Point x' y+ where x' = x + w/2+ w = x - p_x p++noOverlaps :: [Rect] -> Bool+noOverlaps [] = True+noOverlaps [_] = True+noOverlaps (x:y:l) | rectsOverlap x y = False+ | otherwise = noOverlaps (y:l)++rectsOverlap :: Rect -> Rect -> Bool+rectsOverlap (Rect p1 p2) r = any (withinRect r) ps+ where (Point x1 y1) = p1+ (Point x2 y2) = p2+ p3 = Point x1 y2+ p4 = Point x2 y1+ ps = [p1,p2,p3,p4]++eachNth :: Int -> [a] -> [a]+eachNth n = skipN+ where+ n' = n - 1+ skipN [] = []+ skipN (x:xs) = x : skipN (drop n' xs)++withinRect :: Rect -> Point -> Bool+withinRect (Rect (Point x1 y1) (Point x2 y2)) (Point x y)+ = and [x >= x1 && x <= x2,+ y >= y1 && y <= y2]++axisMapping :: AxisT z -> RectSize+ -> (Double,Double,Double,Double,Vector,z->Point,Point->z)+axisMapping (AxisT et _ rev ad) (x2,y2) = case et of+ E_Top -> (x1,y2,x2,y2, Vector 0 1, mapx y2, imapx)+ E_Bottom -> (x1,y1,x2,y1, Vector 0 (-1), mapx y1, imapx)+ E_Left -> (x2,y2,x2,y1, Vector 1 0, mapy x2, imapy) + E_Right -> (x1,y2,x1,y1, Vector (-1) 0, mapy x1, imapy)+ where+ (x1,y1) = (0,0)+ xr = reverseR (x1,x2)+ yr = reverseR (y2,y1)++ mapx y x = Point (_axis_viewport ad xr x) y+ mapy x y = Point x (_axis_viewport ad yr y)++ imapx (Point x _) = _axis_tropweiv ad xr x+ imapy (Point _ y) = _axis_tropweiv ad yr y++ reverseR r@(r0,r1) = if rev then (r1,r0) else r++-- +renderAxisGrid :: RectSize -> AxisT z -> BackendProgram ()+renderAxisGrid sz@(w,h) at@(AxisT re as _ ad) = + withLineStyle (_axis_grid_style as) $ + mapM_ (drawGridLine re) (_axis_grid ad)+ where+ (_,_,_,_,_,axisPoint,_) = axisMapping at sz++ drawGridLine E_Top = vline+ drawGridLine E_Bottom = vline+ drawGridLine E_Left = hline+ drawGridLine E_Right = hline++ vline v = let v' = p_x (axisPoint v)+ in alignStrokePoints [Point v' 0,Point v' h] >>= strokePointPath++ hline v = let v' = p_y (axisPoint v)+ in alignStrokePoints [Point 0 v',Point w v'] >>= strokePointPath+++-- | Construct an axis given the positions for ticks, grid lines, and +-- labels, and the labelling function+makeAxis :: PlotValue x => ([x] -> [String]) -> ([x],[x],[x]) -> AxisData x+makeAxis labelf (labelvs, tickvs, gridvs) = AxisData {+ _axis_visibility = def,+ _axis_viewport = newViewport,+ _axis_tropweiv = newTropweiv,+ _axis_ticks = newTicks,+ _axis_grid = gridvs,+ _axis_labels = [newLabels]+ }+ where+ newViewport = vmap (min',max')+ newTropweiv = invmap (min',max')+ newTicks = [ (v,2) | v <- tickvs ] ++ [ (v,5) | v <- labelvs ]+ newLabels = zipWithLengthCheck labelvs (labelf labelvs)+ where+ zipWithLengthCheck (x:xs) (y:ys) = (x,y) : zipWithLengthCheck xs ys+ zipWithLengthCheck [] [] = []+ zipWithLengthCheck _ _ =+ error "makeAxis: label function returned the wrong number of labels"++ min' = minimum labelvs+ max' = maximum labelvs++-- | Construct an axis given the positions for ticks, grid lines, and +-- labels, and the positioning and labelling functions+makeAxis' :: Ord x => (x -> Double) -> (Double -> x) -> ([x] -> [String])+ -> ([x],[x],[x]) -> AxisData x+makeAxis' t f labelf (labelvs, tickvs, gridvs) = AxisData {+ _axis_visibility = def,+ _axis_viewport = linMap t (minimum labelvs, maximum labelvs),+ _axis_tropweiv = 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)]+ }+++----------------------------------------------------------------------++-- | The default 'LineStyle' of an axis.+defaultAxisLineStyle :: LineStyle+defaultAxisLineStyle = solidLine 1 $ opaque black++-- | The default 'LineStyle' of a plot area grid.+defaultGridLineStyle :: LineStyle+defaultGridLineStyle = dashedLine 1 [5,5] $ opaque lightgrey++instance Default AxisStyle where+ def = AxisStyle + { _axis_line_style = defaultAxisLineStyle+ , _axis_label_style = def+ , _axis_grid_style = defaultGridLineStyle+ , _axis_label_gap = 10+ }++-- | By default all parts of a axis are visible.+instance Default AxisVisibility where+ def = AxisVisibility+ { _axis_show_line = True+ , _axis_show_ticks = True+ , _axis_show_labels = True+ }++----------------------------------------------------------------------++-- | A linear mapping of points in one range to another.+vmap :: PlotValue x => (x,x) -> Range -> x -> Double+vmap (v1,v2) (v3,v4) v = v3 + (toValue v - toValue v1) * (v4-v3)+ / (toValue v2 - toValue v1)++-- | The inverse mapping from device co-ordinate range back to+-- interesting values.+invmap :: PlotValue x => (x,x) -> Range -> Double -> x+invmap (v3,v4) (d1,d2) d = fromValue (toValue v3 + ( (d-d1) * doubleRange+ / (d2-d1) ))+ where doubleRange = toValue v4 - toValue v3++-- | A linear mapping of points in one range to another.+linMap :: (a -> Double) -> (a,a) -> Range -> a -> Double+linMap f (x1,x2) (d1,d2) x =+ d1 + (d2 - d1) * (f x - f x1) / (f x2 - f x1)++-- | An inverse linear mapping of points from one range to another.+invLinMap :: (Double -> a) -> (a -> Double) -> (a,a) -> Range -> Double -> a+invLinMap f t (v3,v4) (d1,d2) d =+ f (t v3 + ( (d-d1) * doubleRange / (d2-d1) ))+ where+ doubleRange = t v4 - t v3++$( makeLenses ''AxisVisibility )+$( makeLenses ''AxisData )+$( makeLenses ''AxisStyle )+
+ Graphics/Rendering/Chart/Axis/Unit.hs view
@@ -0,0 +1,34 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Axis.Unit+-- Copyright : (c) Tim Docker 2010+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Calculate and render unit indexed axes+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Graphics.Rendering.Chart.Axis.Unit(+ unitAxis,+) where++import Data.Default.Class++import Graphics.Rendering.Chart.Axis.Types++instance PlotValue () where+ toValue () = 0+ fromValue = const ()+ autoAxis = const unitAxis++unitAxis :: AxisData ()+unitAxis = AxisData {+ _axis_visibility = def + { _axis_show_ticks = False+ , _axis_show_labels = False + },+ _axis_viewport = \(x0,x1) _ -> (x0+x1)/2,+ _axis_tropweiv = \_ _ -> (),+ _axis_ticks = [((), 0)],+ _axis_labels = [[((), "")]],+ _axis_grid = []+}
+ Graphics/Rendering/Chart/Backend.hs view
@@ -0,0 +1,67 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Backend+-- Copyright : (c) Tim Docker 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- This module provides the API for drawing operations abstracted+-- to drive arbitrary Backend.++module Graphics.Rendering.Chart.Backend+ (+ -- * The backend Monad+ BackendProgram+ + -- * Backend Operations+ , fillPath+ , strokePath+ , drawText, textSize+ , withTransform+ , withClipRegion+ , withFontStyle, withFillStyle, withLineStyle+ + -- * Backend Helpers+-- , getTransform+-- , getFillStyle, getFontStyle+-- , getLineStyle, getClipRegion+ , getPointAlignFn, getCoordAlignFn++ -- * Text Metrics+ , TextSize(..) + + -- * Line Types+ , LineCap(..)+ , LineJoin(..)+ , LineStyle(..)+ + , line_width+ , line_color+ , line_dashes+ , line_cap+ , line_join+ + -- * Fill Types+ , FillStyle(..)++ -- * Font and Text Types+ , FontWeight(..)+ , FontSlant(..)+ , FontStyle(..)++ , HTextAnchor(..)+ , VTextAnchor(..)++ , font_name+ , font_size+ , font_slant+ , font_weight+ , font_color+ + , AlignmentFn+ , AlignmentFns+ , vectorAlignmentFns+ , bitmapAlignmentFns+ ) where++import Graphics.Rendering.Chart.Backend.Types+import Graphics.Rendering.Chart.Backend.Impl
+ Graphics/Rendering/Chart/Backend/Impl.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE GADTs #-}++-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Backend.Impl+-- Copyright : (c) Tim Docker 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- This module provides the implementation details common to all 'ChartBackend's.++module Graphics.Rendering.Chart.Backend.Impl where++import Control.Monad+import Control.Monad.Operational++import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Backend.Types++-- -----------------------------------------------------------------------+-- Rendering Backend Class+-- -----------------------------------------------------------------------++-- | The abstract drawing operation generated when using the+-- the chart drawing API.+--+-- See the documentation of the different function for the correct semantics+-- of each instruction:+--+-- * 'strokePath', 'fillPath'+--+-- * 'drawText', 'textSize'+--+-- * 'getPointAlignFn', 'getCoordAlignFn', 'AlignmentFns'+--+-- * 'withTransform', 'withClipRegion'+--+-- * 'withLineStyle', 'withFillStyle', 'withFontStyle'+--+data ChartBackendInstr a where+ StrokePath :: Path -> ChartBackendInstr ()+ FillPath :: Path -> ChartBackendInstr ()+ GetTextSize :: String -> ChartBackendInstr TextSize+ DrawText :: Point -> String -> ChartBackendInstr ()+ GetAlignments :: ChartBackendInstr AlignmentFns+ WithTransform :: Matrix -> Program ChartBackendInstr a -> ChartBackendInstr a+ WithFontStyle :: FontStyle -> Program ChartBackendInstr a -> ChartBackendInstr a+ WithFillStyle :: FillStyle -> Program ChartBackendInstr a -> ChartBackendInstr a+ WithLineStyle :: LineStyle -> Program ChartBackendInstr a -> ChartBackendInstr a+ WithClipRegion :: Rect -> Program ChartBackendInstr a -> ChartBackendInstr a++-- | A 'BackendProgram' provides the capability to render a chart somewhere.+--+-- The coordinate system of the backend has its initial origin (0,0)+-- in the top left corner of the drawing plane. The x-axis points+-- towards the top right corner and the y-axis points towards+-- the bottom left corner. The unit used by coordinates, the font size,+-- and lengths is the always the same, but depends on the backend.+-- All angles are measured in radians.+--+-- The line, fill and font style are set to their default values+-- initially.+--+-- Information about the semantics of the instructions can be+-- found in the documentation of 'ChartBackendInstr'.+type BackendProgram a = Program ChartBackendInstr a++-- | Stroke the outline of the given path using the+-- current 'LineStyle'. This function does /not/ perform+-- alignment operations on the path. See 'Path' for the exact semantic+-- of paths.+strokePath :: Path -> BackendProgram ()+strokePath p = singleton (StrokePath p)++-- | Fill the given path using the current 'FillStyle'.+-- The given path will be closed prior to filling.+-- This function does /not/ perform+-- alignment operations on the path.+-- See 'Path' for the exact semantic of paths.+fillPath :: Path -> BackendProgram ()+fillPath p = singleton (FillPath p)++-- | Calculate a 'TextSize' object with rendering information+-- about the given string without actually rendering it.+textSize :: String -> BackendProgram TextSize+textSize text = singleton (GetTextSize text)++-- | Draw a single-line textual label anchored by the baseline (vertical)+-- left (horizontal) point. Uses the current 'FontStyle' for drawing.+drawText :: Point -> String -> BackendProgram ()+drawText p text = singleton (DrawText p text)++-- | Apply the given transformation in this local+-- environment when drawing. The given transformation+-- is applied after the current transformation. This+-- means both are combined.+withTransform :: Matrix -> BackendProgram a -> BackendProgram a+withTransform t p = singleton (WithTransform t p)++-- | Use the given font style in this local+-- environment when drawing text.+--+-- An implementing backend is expected to guarantee+-- to support the following font families: @serif@, @sans-serif@ and @monospace@;+--+-- If the backend is not able to find or load a given font+-- it is required to fall back to a custom fail-safe font+-- and use it instead.+withFontStyle :: FontStyle -> BackendProgram a -> BackendProgram a+withFontStyle fs p = singleton (WithFontStyle fs p)++-- | Use the given fill style in this local+-- environment when filling paths.+withFillStyle :: FillStyle -> BackendProgram a -> BackendProgram a+withFillStyle fs p = singleton (WithFillStyle fs p)++-- | Use the given line style in this local+-- environment when stroking paths.+withLineStyle :: LineStyle -> BackendProgram a -> BackendProgram a+withLineStyle ls p = singleton (WithLineStyle ls p)++-- | Use the given clipping rectangle when drawing+-- in this local environment. The new clipping region+-- is intersected with the given clip region. You cannot+-- escape the clip!+withClipRegion :: Rect -> BackendProgram a -> BackendProgram a+withClipRegion c p = singleton (WithClipRegion c p)++-- -----------------------------------------------------------------------+-- Rendering Utility Functions+-- -----------------------------------------------------------------------++-- | Get the point alignment function+getPointAlignFn :: BackendProgram (Point->Point)+getPointAlignFn = liftM afPointAlignFn (singleton GetAlignments)++-- | Get the coordinate alignment function+getCoordAlignFn :: BackendProgram (Point->Point)+getCoordAlignFn = liftM afCoordAlignFn (singleton GetAlignments)+
+ Graphics/Rendering/Chart/Backend/Types.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Backend.Types+-- Copyright : (c) Tim Docker 2014+-- License : BSD-style (see chart/COPYRIGHT)+--++module Graphics.Rendering.Chart.Backend.Types where++import Data.Default.Class+import Data.Colour+import Data.Colour.Names+import Control.Lens++import Graphics.Rendering.Chart.Geometry++-- -----------------------------------------------------------------------+-- Line Types+-- -----------------------------------------------------------------------++-- | The different supported line ends.+data LineCap = LineCapButt -- ^ Just cut the line straight.+ | LineCapRound -- ^ Make a rounded line end.+ | LineCapSquare -- ^ Make a square that ends the line.+ deriving (Show, Eq, Ord)++-- | The different supported ways to join line ends.+data LineJoin = LineJoinMiter -- ^ Extends the outline until they meet each other.+ | LineJoinRound -- ^ Draw a circle fragment to connet line end.+ | LineJoinBevel -- ^ Like miter, but cuts it off if a certain + -- threshold is exceeded.+ deriving (Show, Eq, Ord)++-- | Data type for the style of a line.+data LineStyle = LineStyle + { _line_width :: Double+ -- ^ The thickness of a line in device units.+ , _line_color :: AlphaColour Double+ -- ^ The color of a line.+ , _line_dashes :: [Double]+ -- ^ The dash pattern. Every value at a even index gives a dash width and + -- every value at a odd index gives a gap width in device units.+ , _line_cap :: LineCap+ -- ^ How to end a line.+ , _line_join :: LineJoin+ -- ^ How to connect two lines.+ } deriving (Show, Eq)++-- | The default line style.+instance Default LineStyle where+ def = LineStyle + { _line_width = 1+ , _line_color = opaque black+ , _line_dashes = []+ , _line_cap = LineCapButt+ , _line_join = LineJoinBevel+ }++-- -----------------------------------------------------------------------+-- Font & Text Types+-- -----------------------------------------------------------------------++-- | The possible slants of a font.+data FontSlant = FontSlantNormal -- ^ Normal font style without slant.+ | FontSlantItalic -- ^ With a slight slant.+ | FontSlantOblique -- ^ With a greater slant.+ deriving (Show, Eq, Ord)++-- | The default font slant.+instance Default FontSlant where+ def = FontSlantNormal++-- | The possible weights of a font.+data FontWeight = FontWeightNormal -- ^ Normal font style without weight.+ | FontWeightBold -- ^ Bold font.+ deriving (Show, Eq, Ord)++-- | The default font weight.+instance Default FontWeight where+ def = FontWeightNormal++-- | Data type for a font.+data FontStyle = FontStyle {+ _font_name :: String,+ -- ^ The font family or font face to use.+ _font_size :: Double,+ -- ^ The height of the rendered font in device coordinates.+ _font_slant :: FontSlant,+ -- ^ The slant to render with.+ _font_weight :: FontWeight,+ -- ^ The weight to render with.+ _font_color :: AlphaColour Double+ -- ^ The color to render text with.+} deriving (Show, Eq)++-- | The default font style.+instance Default FontStyle where+ def = FontStyle + { _font_name = "sans-serif"+ , _font_size = 10+ , _font_slant = def+ , _font_weight = def+ , _font_color = opaque black+ }++-- | Possible horizontal anchor points for text.+data HTextAnchor = HTA_Left + | HTA_Centre + | HTA_Right + deriving (Show, Eq, Ord)++-- | Possible vertical anchor points for text.+data VTextAnchor = VTA_Top + | VTA_Centre + | VTA_Bottom + | VTA_BaseLine + deriving (Show, Eq, Ord)++-- | Text metrics returned by 'textSize'.+data TextSize = TextSize + { textSizeWidth :: Double -- ^ The total width of the text.+ , textSizeAscent :: Double -- ^ The ascent or space above the baseline.+ , textSizeDescent :: Double -- ^ The decent or space below the baseline.+ , textSizeYBearing :: Double -- ^ The Y bearing.+ , textSizeHeight :: Double -- ^ The total height of the text.+ } deriving (Show, Eq)++-- -----------------------------------------------------------------------+-- Fill Types+-- -----------------------------------------------------------------------++-- | Abstract data type for a fill style.+--+-- The contained action sets the required fill+-- style in the rendering state.+newtype FillStyle = FillStyleSolid + { _fill_color :: AlphaColour Double + } deriving (Show, Eq)++-- | The default fill style.+instance Default FillStyle where+ def = FillStyleSolid { _fill_color = opaque white }++-------------------------------------------------------------------------++-- | A function to align points for a certain rendering device.+type AlignmentFn = Point -> Point++-- | Holds the point and coordinate alignment function.+data AlignmentFns = AlignmentFns {+ afPointAlignFn :: AlignmentFn,+ -- ^ An adjustment applied immediately prior to points+ -- being displayed in device coordinates.+ --+ -- When device coordinates correspond to pixels, a cleaner+ -- image is created if this transform rounds to the nearest+ -- pixel. With higher-resolution output, this transform can+ -- just be the identity function.+ -- + -- This is usually used to align prior to stroking.++ -- | The adjustment applied immediately prior to coordinates+ -- being transformed.+ -- + -- This is usually used to align prior to filling.+ afCoordAlignFn :: AlignmentFn+ }++-- | Alignment to render on raster based graphics.+bitmapAlignmentFns :: AlignmentFns+bitmapAlignmentFns = AlignmentFns (adjfn 0.5) (adjfn 0.0) + where+ adjfn offset (Point x y) = Point (adj x) (adj y)+ where+ -- avoid messages about Integer default+ rnd :: Double -> Integer+ rnd = round+ adj v = (fromIntegral.rnd) v +offset++-- | Alignment to render on vector based graphics.+vectorAlignmentFns :: AlignmentFns+vectorAlignmentFns = AlignmentFns id id++$( makeLenses ''LineStyle )+$( makeLenses ''FontStyle )+$( makeLenses ''FillStyle )+
+ Graphics/Rendering/Chart/Drawing.hs view
@@ -0,0 +1,504 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Drawing+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- This module contains basic types and functions used for drawing.+--+-- Note that Template Haskell is used to derive accessor functions+-- (see 'Control.Lens') for each field of the following data types:+--+-- * 'PointStyle'+--+-- These accessors are not shown in this API documentation. They have+-- the same name as the field, but with the trailing underscore+-- dropped. Hence for data field @f_::F@ in type @D@, they have type+--+-- @+-- f :: Control.Lens.Lens' D F+-- @+--++{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Drawing+ ( -- * Point Types and Drawing+ PointShape(..)+ , PointStyle(..)+ , drawPoint++ -- * Alignments and Paths+ , alignPath+ , alignFillPath+ , alignStrokePath+ , alignFillPoints+ , alignStrokePoints++ , alignFillPoint+ , alignStrokePoint++ , strokePointPath+ , fillPointPath++ -- * Transformation and Style Helpers+ , withRotation+ , withTranslation+ , withScale+ , withScaleX, withScaleY+ , withPointStyle+ , withDefaultStyle++ -- * Text Drawing+ , drawTextA+ , drawTextR+ , drawTextsR+ , textDrawRect+ , textDimension++ -- * Style Helpers+ , defaultColorSeq++ , solidLine+ , dashedLine++ , filledCircles+ , hollowCircles+ , filledPolygon+ , hollowPolygon+ , plusses+ , exes+ , stars+ , arrows++ , solidFillStyle++ -- * Backend and general Types+ , module Graphics.Rendering.Chart.Backend++ -- * Accessors+ , point_color+ , point_border_color+ , point_border_width+ , point_radius+ , point_shape+) where++import Data.Default.Class+-- lens < 4 includes Control.Lens.Zipper.moveTo which clashes+-- with Graphics.Rendering.Chart.Geometry.moveTo (so you get+-- -Wall notices). This would suggest a 'hiding (moveTo)' in+-- the import, but it's been removed in lens-4.0 and I don't+-- feel it's worth the use of conditional compilation. This does+-- lead to the qualified Geometry import below.+import Control.Lens+import Data.Colour+import Data.Colour.Names+import Data.List (unfoldr)++import Graphics.Rendering.Chart.Backend+import Graphics.Rendering.Chart.Geometry hiding (moveTo)+import qualified Graphics.Rendering.Chart.Geometry as G++-- -----------------------------------------------------------------------+-- Transformation helpers+-- -----------------------------------------------------------------------++-- | Apply a local rotation. The angle is given in radians.+withRotation :: Double -> BackendProgram a -> BackendProgram a+withRotation angle = withTransform (rotate angle 1)++-- | Apply a local translation.+withTranslation :: Point -> BackendProgram a -> BackendProgram a+withTranslation p = withTransform (translate (pointToVec p) 1)++-- | Apply a local scale.+withScale :: Vector -> BackendProgram a -> BackendProgram a+withScale v = withTransform (scale v 1)++-- | Apply a local scale on the x-axis.+withScaleX :: Double -> BackendProgram a -> BackendProgram a+withScaleX x = withScale (Vector x 1)++-- | Apply a local scale on the y-axis.+withScaleY :: Double -> BackendProgram a -> BackendProgram a+withScaleY y = withScale (Vector 1 y)++-- | Changes the 'LineStyle' and 'FillStyle' to comply with+-- the given 'PointStyle'.+withPointStyle :: PointStyle -> BackendProgram a -> BackendProgram a+withPointStyle (PointStyle cl bcl bw _ _) m =+ withLineStyle (def { _line_color = bcl, _line_width = bw, _line_join = LineJoinMiter }) $+ withFillStyle (solidFillStyle cl) m++withDefaultStyle :: BackendProgram a -> BackendProgram a+withDefaultStyle = withLineStyle def . withFillStyle def . withFontStyle def++-- -----------------------------------------------------------------------+-- Alignment Helpers+-- -----------------------------------------------------------------------++-- | Align the path by applying the given function on all points.+alignPath :: (Point -> Point) -> Path -> Path+alignPath f = foldPath (G.moveTo . f)+ (lineTo . f)+ (arc . f)+ (arcNeg . f)+ close++-- | Align the path using the environment's alignment function for points.+-- This is generally useful when stroking.+-- See 'alignPath' and 'getPointAlignFn'.+alignStrokePath :: Path -> BackendProgram Path+alignStrokePath p = do+ f <- getPointAlignFn+ return $ alignPath f p++-- | Align the path using the environment's alignment function for coordinates.+-- This is generally useful when filling.+-- See 'alignPath' and 'getCoordAlignFn'.+alignFillPath :: Path -> BackendProgram Path+alignFillPath p = do+ f <- getCoordAlignFn+ return $ alignPath f p++-- | The points will be aligned by the 'getPointAlignFn', so that+-- when drawing bitmaps, 1 pixel wide lines will be centred on the+-- pixels.+alignStrokePoints :: [Point] -> BackendProgram [Point]+alignStrokePoints p = do+ f <- getPointAlignFn+ return $ fmap f p++-- | The points will be aligned by the 'getCoordAlignFn', so that+-- when drawing bitmaps, the edges of the region will fall between+-- pixels.+alignFillPoints :: [Point] -> BackendProgram [Point]+alignFillPoints p = do+ f <- getCoordAlignFn+ return $ fmap f p++-- | Align the point using the environment's alignment function for points.+-- See 'getPointAlignFn'.+alignStrokePoint :: Point -> BackendProgram Point+alignStrokePoint p = do+ alignfn <- getPointAlignFn+ return (alignfn p)++-- | Align the point using the environment's alignment function for coordinates.+-- See 'getCoordAlignFn'.+alignFillPoint :: Point -> BackendProgram Point+alignFillPoint p = do+ alignfn <- getCoordAlignFn+ return (alignfn p)++-- | Create a path by connecting all points with a line.+-- The path is not closed.+stepPath :: [Point] -> Path+stepPath (p:ps) = G.moveTo p+ <> mconcat (map lineTo ps)+stepPath [] = mempty++-- | Draw lines between the specified points.+strokePointPath :: [Point] -> BackendProgram ()+strokePointPath pts = strokePath $ stepPath pts++-- | Fill the region with the given corners.+fillPointPath :: [Point] -> BackendProgram ()+fillPointPath pts = fillPath $ stepPath pts++-- -----------------------------------------------------------------------+-- Text Drawing+-- -----------------------------------------------------------------------++-- | Draw a line of text that is aligned at a different anchor point.+-- See 'drawText'.+drawTextA :: HTextAnchor -> VTextAnchor -> Point -> String -> BackendProgram ()+drawTextA hta vta = drawTextR hta vta 0++{-+ The following is useful for checking out the bounding-box+ calculation. At present it looks okay for PNG/Cairo but+ is a bit off for SVG/Diagrams; this may well be down to+ differences in how fonts are rendered in the two backends++drawTextA hta vta p txt =+ drawTextR hta vta 0 p txt+ >> withLineStyle (solidLine 1 (opaque red))+ (textDrawRect hta vta p txt+ >>= \rect -> alignStrokePath (rectPath rect) >>= strokePath)+-}++-- | Draw a textual label anchored by one of its corners+-- or edges, with rotation. Rotation angle is given in degrees,+-- rotation is performed around anchor point.+-- See 'drawText'.+drawTextR :: HTextAnchor -> VTextAnchor -> Double -> Point -> String -> BackendProgram ()+drawTextR hta vta angle p s =+ withTranslation p $+ withRotation theta $ do+ ts <- textSize s+ drawText (adjustText hta vta ts) s+ where+ theta = angle*pi/180.0++-- | Draw a multi-line textual label anchored by one of its corners+-- or edges, with rotation. Rotation angle is given in degrees,+-- rotation is performed around anchor point.+-- See 'drawText'.+drawTextsR :: HTextAnchor -> VTextAnchor -> Double -> Point -> String -> BackendProgram ()+drawTextsR hta vta angle p s = case num of+ 0 -> return ()+ 1 -> drawTextR hta vta angle p s+ _ ->+ withTranslation p $+ withRotation theta $ do+ tss <- mapM textSize ss+ let ts = head tss+ let -- widths = map textSizeWidth tss+ -- maxw = maximum widths+ maxh = maximum (map textSizeYBearing tss)+ gap = maxh / 2 -- half-line spacing+ totalHeight = fromIntegral num*maxh ++ (fromIntegral num-1)*gap+ ys = take num (unfoldr (\y-> Just (y, y-gap-maxh))+ (yinit vta ts totalHeight))+ xs = map (adjustTextX hta) tss+ sequence_ (zipWith3 drawT xs ys ss)+ where+ ss = lines s+ num = length ss++ drawT x y = drawText (Point x y)+ theta = angle*pi/180.0++ yinit VTA_Top ts _ = textSizeAscent ts+ yinit VTA_BaseLine _ _ = 0+ yinit VTA_Centre ts height = height / 2 + textSizeAscent ts+ yinit VTA_Bottom ts height = height + textSizeAscent ts++-- | Calculate the correct offset to align the text anchor.+adjustText :: HTextAnchor -> VTextAnchor -> TextSize -> Point+adjustText hta vta ts = Point (adjustTextX hta ts) (adjustTextY vta ts)++-- | Calculate the correct offset to align the horizontal anchor.+adjustTextX :: HTextAnchor -> TextSize -> Double+adjustTextX HTA_Left _ = 0+adjustTextX HTA_Centre ts = - (textSizeWidth ts / 2)+adjustTextX HTA_Right ts = - textSizeWidth ts++-- | Calculate the correct offset to align the vertical anchor.+adjustTextY :: VTextAnchor -> TextSize -> Double+adjustTextY VTA_Top ts = textSizeAscent ts+adjustTextY VTA_Centre ts = - textSizeYBearing ts / 2+adjustTextY VTA_BaseLine _ = 0+adjustTextY VTA_Bottom ts = - textSizeDescent ts++-- | Return the bounding rectangle for a text string positioned+-- where it would be drawn by 'drawText'.+-- See 'textSize'.+textDrawRect :: HTextAnchor -> VTextAnchor -> Point -> String -> BackendProgram Rect+textDrawRect hta vta (Point x y) s = do+ ts <- textSize s+ -- This does not account for the pixel width of the label; e.g.+ -- with a label "bread" and a large-enough foint size (e.g. 36)+ -- I have seen the right-hand edge of the bounding box go through+ -- the vertical part of the 'd' character (see chart-tests/tests/Test8.hs+ -- and bump up the label size).+ let (w,h,dh) = (textSizeWidth ts, textSizeHeight ts, textSizeDescent ts)+ lx = adjustTextX hta ts+ ly = adjustTextY vta ts+ (x',y') = (x + lx, y + ly + dh)+ p1 = Point x' (y' - h)+ p2 = Point (x' + w) y'+ return $ Rect p1 p2++-- | Get the width and height of the string when rendered.+-- See 'textSize'.+textDimension :: String -> BackendProgram RectSize+textDimension s = do+ ts <- textSize s+ return (textSizeWidth ts, textSizeHeight ts)++-- -----------------------------------------------------------------------+-- Point Types and Drawing+-- -----------------------------------------------------------------------++-- | The different shapes a point can have.+data PointShape = PointShapeCircle -- ^ A circle.+ | PointShapePolygon Int Bool -- ^ Number of vertices and is right-side-up?+ | PointShapePlus -- ^ A plus sign.+ | PointShapeCross -- ^ A cross.+ | PointShapeStar -- ^ Combination of a cross and a plus.+ | PointShapeArrowHead Double+ | PointShapeEllipse Double Double -- ^ Ratio of minor to major axis and rotation++-- | Abstract data type for the style of a plotted point.+data PointStyle = PointStyle+ { _point_color :: AlphaColour Double+ -- ^ The color to fill the point with.+ , _point_border_color :: AlphaColour Double+ -- ^ The color to stroke the outline with.+ , _point_border_width :: Double+ -- ^ The width of the outline.+ , _point_radius :: Double+ -- ^ The radius of the tightest surrounding circle of the point.+ , _point_shape :: PointShape+ -- ^ The shape.+ }++-- | Default style to use for points.+instance Default PointStyle where+ def = PointStyle+ { _point_color = opaque black+ , _point_border_color = transparent+ , _point_border_width = 0+ , _point_radius = 1+ , _point_shape = PointShapeCircle+ }++-- | Draw a single point at the given location.+drawPoint :: PointStyle -- ^ Style to use when rendering the point.+ -> Point -- ^ Position of the point to render.+ -> BackendProgram ()+drawPoint ps@(PointStyle cl _ _ r shape) p = withPointStyle ps $ do+ p'@(Point x y) <- alignStrokePoint p+ case shape of+ PointShapeCircle -> do+ let path = arc p' r 0 (2*pi)+ fillPath path+ strokePath path+ PointShapePolygon sides isrot -> do+ let intToAngle n =+ if isrot+ then fromIntegral n * 2*pi/fromIntegral sides+ else (0.5 + fromIntegral n)*2*pi/fromIntegral sides+ angles = map intToAngle [0 .. sides-1]+ (p1:p1':p1s) = map (\a -> Point (x + r * sin a)+ (y + r * cos a)) angles+ let path = G.moveTo p1 <> mconcat (map lineTo $ p1':p1s) <> lineTo p1 <> lineTo p1'+ fillPath path+ strokePath path+ PointShapeArrowHead theta ->+ withTranslation p $ withRotation (theta - pi/2) $+ drawPoint (filledPolygon r 3 True cl) (Point 0 0)+ PointShapePlus ->+ strokePath $ moveTo' (x+r) y+ <> lineTo' (x-r) y+ <> moveTo' x (y-r)+ <> lineTo' x (y+r)+ PointShapeCross -> do+ let rad = r / sqrt 2+ strokePath $ moveTo' (x+rad) (y+rad)+ <> lineTo' (x-rad) (y-rad)+ <> moveTo' (x+rad) (y-rad)+ <> lineTo' (x-rad) (y+rad)+ PointShapeStar -> do+ let rad = r / sqrt 2+ strokePath $ moveTo' (x+r) y+ <> lineTo' (x-r) y+ <> moveTo' x (y-r)+ <> lineTo' x (y+r)+ <> moveTo' (x+rad) (y+rad)+ <> lineTo' (x-rad) (y-rad)+ <> moveTo' (x+rad) (y-rad)+ <> lineTo' (x-rad) (y+rad)+ PointShapeEllipse b theta ->+ withTranslation p $ withRotation theta $ withScaleX b $ do+ let path = arc (Point 0 0) r 0 (2*pi)+ fillPath path+ strokePath path++-- -----------------------------------------------------------------------+-- Style Helpers+-- -----------------------------------------------------------------------++-- | The default sequence of colours to use when plotings different data sets+-- in a graph.+defaultColorSeq :: [AlphaColour Double]+defaultColorSeq = cycle $ map opaque [blue, red, green, yellow, cyan, magenta]++-- | Create a solid line style (not dashed).+solidLine :: Double -- ^ Width of line.+ -> AlphaColour Double -- ^ Colour of line.+ -> LineStyle+solidLine w cl = LineStyle w cl [] LineCapButt LineJoinMiter++-- | Create a dashed line style.+dashedLine :: Double -- ^ Width of line.+ -> [Double] -- ^ The dash pattern in device coordinates.+ -> AlphaColour Double -- ^ Colour of line.+ -> LineStyle+dashedLine w ds cl = LineStyle w cl ds LineCapButt LineJoinMiter++-- | Style for filled circle points.+filledCircles :: Double -- ^ Radius of circle.+ -> AlphaColour Double -- ^ Fill colour.+ -> PointStyle+filledCircles radius cl =+ PointStyle cl transparent 0 radius PointShapeCircle++-- | Style for stroked circle points.+hollowCircles :: Double -- ^ Radius of circle.+ -> Double -- ^ Thickness of line.+ -> AlphaColour Double -- Colour of line.+ -> PointStyle+hollowCircles radius w cl =+ PointStyle transparent cl w radius PointShapeCircle++-- | Style for stroked polygon points.+hollowPolygon :: Double -- ^ Radius of circle.+ -> Double -- ^ Thickness of line.+ -> Int -- ^ Number of vertices.+ -> Bool -- ^ Is right-side-up?+ -> AlphaColour Double -- ^ Colour of line.+ -> PointStyle+hollowPolygon radius w sides isrot cl =+ PointStyle transparent cl w radius (PointShapePolygon sides isrot)++-- | Style for filled polygon points.+filledPolygon :: Double -- ^ Radius of circle.+ -> Int -- ^ Number of vertices.+ -> Bool -- ^ Is right-side-up?+ -> AlphaColour Double -- ^ Fill color.+ -> PointStyle+filledPolygon radius sides isrot cl =+ PointStyle cl transparent 0 radius (PointShapePolygon sides isrot)++-- | Plus sign point style.+plusses :: Double -- ^ Radius of tightest surrounding circle.+ -> Double -- ^ Thickness of line.+ -> AlphaColour Double -- ^ Color of line.+ -> PointStyle+plusses radius w cl =+ PointStyle transparent cl w radius PointShapePlus++-- | Cross point style.+exes :: Double -- ^ Radius of circle.+ -> Double -- ^ Thickness of line.+ -> AlphaColour Double -- ^ Color of line.+ -> PointStyle+exes radius w cl =+ PointStyle transparent cl w radius PointShapeCross++-- | Combination of plus and cross point style.+stars :: Double -- ^ Radius of circle.+ -> Double -- ^ Thickness of line.+ -> AlphaColour Double -- ^ Color of line.+ -> PointStyle+stars radius w cl =+ PointStyle transparent cl w radius PointShapeStar++arrows :: Double -- ^ Radius of circle.+ -> Double -- ^ Rotation (Tau)+ -> Double -- ^ Thickness of line.+ -> AlphaColour Double -- ^ Color of line.+ -> PointStyle+arrows radius angle w cl =+ PointStyle transparent cl w radius (PointShapeArrowHead angle)++-- | Fill style that fill everything this the given colour.+solidFillStyle :: AlphaColour Double -> FillStyle+solidFillStyle = FillStyleSolid++$( makeLenses ''PointStyle )
+ Graphics/Rendering/Chart/Easy.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances #-}+----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Easy+-- Copyright : (c) Tim Docker 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- A high level API for generating a plot quickly.+--+-- Importing the Easy module brings into scope all core functions and types required+-- for working with the chart library. This includes key external dependencies such as+-- Control.Lens and Data.Colour. The module also provides several helper functions for+-- quickly generating common plots. Note that chart backends must still be explicitly+-- imported, as some backends cannot be built on all platforms.+--+-- Example usage:+--+-- > import Graphics.Rendering.Chart.Easy+-- > import Graphics.Rendering.Chart.Backend.Cairo+-- >+-- > signal :: [Double] -> [(Double,Double)]+-- > signal xs = [ (x,(sin (x*3.14159/45) + 1) / 2 * (sin (x*3.14159/5))) | x <- xs ]+-- >+-- > main = toFile def "example.png" $ do+-- > layout_title .= "Amplitude Modulation"+-- > plot (line "am" [signal [0,(0.5)..400]])+-- > plot (points "am points" (signal [0,7..400]))+--+-- More examples can be found on the <https://github.com/timbod7/haskell-chart/wiki library's wiki>++module Graphics.Rendering.Chart.Easy(++ module Control.Lens,+ module Data.Default.Class,+ module Data.Colour,+ module Data.Colour.Names,++ module Graphics.Rendering.Chart,+ module Graphics.Rendering.Chart.State,++ line,+ points,+ bars,+ setColors,+ setShapes+ ) where++import Control.Lens+import Control.Monad(unless)+import Data.Default.Class+import Data.Colour hiding (over) -- overlaps with lens over function+import Data.Colour.Names+import Graphics.Rendering.Chart+import Graphics.Rendering.Chart.State++-- | Set the contents of the colour source, for+-- subsequent plots+setColors :: [AlphaColour Double] -> EC l ()+setColors cs = liftCState $ colors .= cycle cs++-- | Set the contents of the shape source, for+-- subsequent plots+setShapes :: [PointShape] -> EC l ()+setShapes ps = liftCState $ shapes .= cycle ps++-- | Constuct a line plot with the given title and+-- data, using the next available color.+line :: String -> [[(x,y)]] -> EC l (PlotLines x y)+line title values = liftEC $ do+ color <- takeColor+ plot_lines_title .= title+ plot_lines_values .= values+ plot_lines_style . line_color .= color++-- | Construct a scatter plot with the given title and data, using the+-- next available color and point shape.+points :: String -> [(x,y)] -> EC l (PlotPoints x y)+points title values = liftEC $ do+ color <- takeColor+ shape <- takeShape+ plot_points_values .= values+ plot_points_title .= title+ plot_points_style . point_color .= color+ plot_points_style . point_shape .= shape+ plot_points_style . point_radius .= 2++ -- Show borders for unfilled shapes+ unless (isFilled shape) $ do+ plot_points_style . point_border_color .= color+ plot_points_style . point_border_width .= 1++isFilled :: PointShape -> Bool+isFilled PointShapeCircle = True+isFilled PointShapePolygon{} = True+isFilled _ = False++-- | Construct a bar chart with the given titles and data, using the+-- next available colors+bars :: (PlotValue x, BarsPlotValue y) => [String] -> [(x,[y])] -> EC l (PlotBars x y)+bars titles vals = liftEC $ do+ styles <- sequence [fmap mkStyle takeColor | _ <- titles]+ plot_bars_titles .= titles+ plot_bars_values .= vals+ plot_bars_style .= BarsClustered+ plot_bars_spacing .= BarsFixGap 30 5+ plot_bars_item_styles .= styles+ where+ mkStyle c = (solidFillStyle c, Just (solidLine 1.0 $ opaque black))
+ Graphics/Rendering/Chart/Geometry.hs view
@@ -0,0 +1,396 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Geometry+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+module Graphics.Rendering.Chart.Geometry+ ( -- * Points and Vectors+ Rect(..)+ , Point(..)+ , Vector(..)++ , RectSize+ , Range++ , pointToVec++ , mkrect+ , rectPath+ , pvadd+ , pvsub+ , psub+ , vangle+ , vlen+ , vscale+ , within+ , intersectRect++ , RectEdge(..)+ , Limit(..)+ , PointMapFn++ -- * Paths+ , Path(..)+ , lineTo, moveTo+ , lineTo', moveTo'+ , arc, arc'+ , arcNeg, arcNeg'+ , close++ , foldPath+ , makeLinesExplicit++ -- * Matrices+ , transformP, scaleP, rotateP, translateP+ , Matrix(..)+ , identity+ , rotate, scale, translate+ , scalarMultiply+ , adjoint+ , invert+ ) where++import qualified Prelude+import Prelude hiding ((^))++-- The homomorphic version to avoid casts inside the code.+(^) :: Num a => a -> Integer -> a+(^) = (Prelude.^)++-- | A point in two dimensions.+data Point = Point {+ p_x :: Double,+ p_y :: Double+} deriving Show++-- | A vector in two dimensions.+data Vector = Vector {+ v_x :: Double,+ v_y :: Double+} deriving Show++-- | Convert a 'Point' to a 'Vector'.+pointToVec :: Point -> Vector+pointToVec (Point x y) = Vector x y++-- | Angle of a vector (counterclockwise from positive x-axis)+vangle :: Vector -> Double+vangle (Vector x y)+ | x > 0 = atan (y/x)+ | x < 0 = atan (y/x) + pi+ | otherwise = if y > 0 then pi/2 else -pi/2++-- | Length/magnitude of a vector+vlen :: Vector -> Double+vlen (Vector x y) = sqrt $ x^2 + y^2++-- | Scale a vector by a constant.+vscale :: Double -> Vector -> Vector+vscale c (Vector x y) = Vector (x*c) (y*c)++-- | Add a point and a vector.+pvadd :: Point -> Vector -> Point+pvadd (Point x1 y1) (Vector x2 y2) = Point (x1+x2) (y1+y2)++-- | Subtract a vector from a point.+pvsub :: Point -> Vector -> Point+pvsub (Point x1 y1) (Vector x2 y2) = Point (x1-x2) (y1-y2)++-- | Subtract two points.+psub :: Point -> Point -> Vector+psub (Point x1 y1) (Point x2 y2) = Vector (x1-x2) (y1-y2)++data Limit a = LMin | LValue a | LMax+ deriving Show++-- | A function mapping between points.+type PointMapFn x y = (Limit x, Limit y) -> Point++-- | A rectangle is defined by two points.+data Rect = Rect Point Point+ deriving Show++-- | Edge of a rectangle.+data RectEdge = E_Top | E_Bottom | E_Left | E_Right++-- | Create a rectangle based upon the coordinates of 4 points.+mkrect :: Point -> Point -> Point -> Point -> Rect+mkrect (Point x1 _) (Point _ y2) (Point x3 _) (Point _ y4) =+ Rect (Point x1 y2) (Point x3 y4)++-- | Test if a point is within a rectangle.+within :: Point -> Rect -> Bool+within (Point x y) (Rect (Point x1 y1) (Point x2 y2)) =+ x >= x1 && x <= x2 && y >= y1 && y <= y2++-- | Intersects the rectangles. If they intersect the+-- intersection rectangle is returned.+-- 'LMin' is the empty rectangle / intersection and+-- 'LMax' is the infinite plane.+intersectRect :: Limit Rect -> Limit Rect -> Limit Rect+intersectRect LMax r = r+intersectRect r LMax = r+intersectRect LMin _ = LMin+intersectRect _ LMin = LMin+intersectRect (LValue (Rect (Point x11 y11) (Point x12 y12)))+ (LValue (Rect (Point x21 y21) (Point x22 y22))) =+ let p1@(Point x1 y1) = Point (max x11 x21) (max y11 y21)+ p2@(Point x2 y2) = Point (min x12 x22) (min y12 y22)+ in if x2 < x1 || y2 < y1+ then LMin+ else LValue $ Rect p1 p2++type Range = (Double,Double)+type RectSize = (Double,Double)++{-+-- | Make a path from a rectangle.+rectPointPath :: Rect -> [Point]+rectPointPath (Rect p1@(Point x1 y1) p3@(Point x2 y2)) = [p1,p2,p3,p4,p1]+ where+ p2 = (Point x1 y2)+ p4 = (Point x2 y1)+-}++-- | Make a path from a rectangle.+rectPath :: Rect -> Path+rectPath (Rect p1@(Point x1 y1) p3@(Point x2 y2)) =+ let p2 = Point x1 y2+ p4 = Point x2 y1+ in moveTo p1 <> lineTo p2 <> lineTo p3 <> lineTo p4 <> close++-- -----------------------------------------------------------------------+-- Path Types+-- -----------------------------------------------------------------------++-- | The path type used by Charts.+--+-- A path can consist of several subpaths. Each+-- is started by a 'MoveTo' operation. All subpaths+-- are open, except the last one, which may be closed+-- using the 'Close' operation. When filling a path+-- all subpaths are closed implicitly.+--+-- Closing a subpath means that a line is drawn from+-- the end point to the start point of the subpath.+--+-- If a 'Arc' (or 'ArcNeg') is drawn a implicit line+-- from the last end point of the subpath is drawn+-- to the beginning of the arc. Another implicit line+-- is drawn from the end of an arc to the beginning of+-- the next path segment.+--+-- The beginning of a subpath is either (0,0) or set+-- by a 'MoveTo' instruction. If the first subpath is started+-- with an arc the beginning of that subpath is the beginning+-- of the arc.+data Path = MoveTo Point Path+ | LineTo Point Path+ | Arc Point Double Double Double Path+ | ArcNeg Point Double Double Double Path+ | End+ | Close++-- | Paths are monoids. After a path is closed you can not append+-- anything to it anymore. The empty path is open.+-- Use 'close' to close a path.+instance Semigroup Path where+ p1 <> p2 = case p1 of+ MoveTo p path -> MoveTo p $ path <> p2+ LineTo p path -> LineTo p $ path <> p2+ Arc p r a1 a2 path -> Arc p r a1 a2 $ path <> p2+ ArcNeg p r a1 a2 path -> ArcNeg p r a1 a2 $ path <> p2+ End -> p2+ Close -> Close++instance Monoid Path where+ mappend = (<>)+ mempty = End++-- | Move the paths pointer to the given location.+moveTo :: Point -> Path+moveTo p = MoveTo p mempty++-- | Short-cut for 'moveTo', if you don't want to create a 'Point'.+moveTo' :: Double -> Double -> Path+moveTo' x y = moveTo $ Point x y++-- | Move the paths pointer to the given location and draw a straight+-- line while doing so.+lineTo :: Point -> Path+lineTo p = LineTo p mempty++-- | Short-cut for 'lineTo', if you don't want to create a 'Point'.+lineTo' :: Double -> Double -> Path+lineTo' x y = lineTo $ Point x y++-- | Draw the arc of a circle. A straight line connects+-- the end of the previous path with the beginning of the arc.+-- The zero angle points in direction of the positive x-axis.+-- Angles increase in clock-wise direction. If the stop angle+-- is smaller then the start angle it is increased by multiples of+-- @2 * pi@ until is is greater or equal.+arc :: Point -- ^ Center point of the circle arc.+ -> Double -- ^ Radius of the circle.+ -> Double -- ^ Angle to start drawing at, in radians.+ -> Double -- ^ Angle to stop drawing at, in radians.+ -> Path+arc p r a1 a2 = Arc p r a1 a2 mempty++-- | Short-cut for 'arc', if you don't want to create a 'Point'.+arc' :: Double -> Double -> Double -> Double -> Double -> Path+arc' x y r a1 a2 = Arc (Point x y) r a1 a2 mempty++-- | Like 'arc', but draws from the stop angle to the start angle+-- instead of between them.+arcNeg :: Point -> Double -> Double -> Double -> Path+arcNeg p r a1 a2 = ArcNeg p r a1 a2 mempty++-- | Short-cut for 'arcNeg', if you don't want to create a 'Point'.+arcNeg' :: Double -> Double -> Double -> Double -> Double -> Path+arcNeg' x y r a1 a2 = ArcNeg (Point x y) r a1 a2 mempty++-- | A closed empty path. Closes a path when appended.+close :: Path+close = Close++-- | Fold the given path to a monoid structure.+foldPath :: (Monoid m)+ => (Point -> m) -- ^ MoveTo+ -> (Point -> m) -- ^ LineTo+ -> (Point -> Double -> Double -> Double -> m) -- ^ Arc+ -> (Point -> Double -> Double -> Double -> m) -- ^ ArcNeg+ -> m -- ^ Close+ -> Path -- ^ Path to fold+ -> m+foldPath moveTo_ lineTo_ arc_ arcNeg_ close_ path =+ let restF = foldPath moveTo_ lineTo_ arc_ arcNeg_ close_+ in case path of+ MoveTo p rest -> moveTo_ p `mappend` restF rest+ LineTo p rest -> lineTo_ p `mappend` restF rest+ Arc p r a1 a2 rest -> arc_ p r a1 a2 `mappend` restF rest+ ArcNeg p r a1 a2 rest -> arcNeg_ p r a1 a2 `mappend` restF rest+ End -> mempty+ Close -> close_++-- | Enriches the path with explicit instructions to draw lines,+-- that otherwise would be implicit. See 'Path' for details+-- about what lines in paths are implicit.+makeLinesExplicit :: Path -> Path+makeLinesExplicit (Arc c r s e rest) =+ Arc c r s e $ makeLinesExplicit' rest+makeLinesExplicit (ArcNeg c r s e rest) =+ ArcNeg c r s e $ makeLinesExplicit' rest+makeLinesExplicit path = makeLinesExplicit' path++-- | Utility for 'makeLinesExplicit'.+makeLinesExplicit' :: Path -> Path+makeLinesExplicit' End = End+makeLinesExplicit' Close = Close+makeLinesExplicit' (Arc c r s e rest) =+ let p = translateP (pointToVec c) $ rotateP s $ Point r 0+ in lineTo p <> arc c r s e <> makeLinesExplicit' rest+makeLinesExplicit' (ArcNeg c r s e rest) =+ let p = translateP (pointToVec c) $ rotateP s $ Point r 0+ in lineTo p <> arcNeg c r s e <> makeLinesExplicit' rest+makeLinesExplicit' (MoveTo p0 rest) =+ MoveTo p0 $ makeLinesExplicit' rest+makeLinesExplicit' (LineTo p0 rest) =+ LineTo p0 $ makeLinesExplicit' rest++-- -----------------------------------------------------------------------+-- Matrix Type+-- -----------------------------------------------------------------------++-- | Transform a point using the given matrix.+transformP :: Matrix -> Point -> Point+transformP t (Point x y) = Point+ (xx t * x + xy t * y + x0 t)+ (yx t * x + yy t * y + y0 t)++-- | Rotate a point around the origin.+-- The angle is given in radians.+rotateP :: Double -> Point -> Point+rotateP a = transformP (rotate a 1)++-- | Scale a point.+scaleP :: Vector -> Point -> Point+scaleP s = transformP (scale s 1)++-- | Translate a point.+translateP :: Vector -> Point -> Point+translateP = flip pvadd++-- | Copied from Graphics.Rendering.Cairo.Matrix+data Matrix = Matrix { xx :: !Double, yx :: !Double,+ xy :: !Double, yy :: !Double,+ x0 :: !Double, y0 :: !Double }+ deriving Show++-- | Copied from Graphics.Rendering.Cairo.Matrix+instance Num Matrix where+ -- use underscore to avoid ghc complaints about shadowing the Matrix+ -- field names+ (*) (Matrix xx_ yx_ xy_ yy_ x0_ y0_)+ (Matrix xx'_ yx'_ xy'_ yy'_ x0'_ y0'_) =+ Matrix (xx_ * xx'_ + yx_ * xy'_)+ (xx_ * yx'_ + yx_ * yy'_)+ (xy_ * xx'_ + yy_ * xy'_)+ (xy_ * yx'_ + yy_ * yy'_)+ (x0_ * xx'_ + y0_ * xy'_ + x0'_)+ (x0_ * yx'_ + y0_ * yy'_ + y0'_)++ (+) = pointwise2 (+)+ (-) = pointwise2 (-)++ negate = pointwise negate+ abs = pointwise abs+ signum = pointwise signum++ fromInteger n = Matrix (fromInteger n) 0 0 (fromInteger n) 0 0++-- | Copied from Graphics.Rendering.Cairo.Matrix+{-# INLINE pointwise #-}+pointwise :: (Double -> Double) -> Matrix -> Matrix+pointwise f (Matrix xx_ yx_ xy_ yy_ x0_ y0_) =+ Matrix (f xx_) (f yx_) (f xy_) (f yy_) (f x0_) (f y0_)++-- | Copied from Graphics.Rendering.Cairo.Matrix+{-# INLINE pointwise2 #-}+pointwise2 :: (Double -> Double -> Double) -> Matrix -> Matrix -> Matrix+pointwise2 f (Matrix xx_ yx_ xy_ yy_ x0_ y0_) (Matrix xx'_ yx'_ xy'_ yy'_ x0'_ y0'_) =+ Matrix (f xx_ xx'_) (f yx_ yx'_) (f xy_ xy'_) (f yy_ yy'_) (f x0_ x0'_) (f y0_ y0'_)++-- | Copied from Graphics.Rendering.Cairo.Matrix+identity :: Matrix+identity = Matrix 1 0 0 1 0 0++-- | Copied and adopted from Graphics.Rendering.Cairo.Matrix+translate :: Vector -> Matrix -> Matrix+translate tv m = m * Matrix 1 0 0 1 (v_x tv) (v_y tv)++-- | Copied and adopted from Graphics.Rendering.Cairo.Matrix+scale :: Vector -> Matrix -> Matrix+scale sv m = m * Matrix (v_x sv) 0 0 (v_y sv) 0 0++-- | Copied from Graphics.Rendering.Cairo.Matrix+-- Rotations angle is given in radians.+rotate :: Double -> Matrix -> Matrix+rotate r m = m * Matrix c s (-s) c 0 0+ where s = sin r+ c = cos r++-- | Copied from Graphics.Rendering.Cairo.Matrix+scalarMultiply :: Double -> Matrix -> Matrix+scalarMultiply scalar = pointwise (* scalar)++-- | Copied from Graphics.Rendering.Cairo.Matrix+adjoint :: Matrix -> Matrix+adjoint (Matrix a b c d tx ty) =+ Matrix d (-b) (-c) a (c*ty - d*tx) (b*tx - a*ty)++-- | Copied from Graphics.Rendering.Cairo.Matrix+invert :: Matrix -> Matrix+invert m@(Matrix xx_ yx_ xy_ yy_ _ _) = scalarMultiply (recip det) $ adjoint m+ where det = xx_*yy_ - yx_*xy_
Graphics/Rendering/Chart/Grid.hs view
@@ -1,5 +1,14 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Grid+-- Copyright : (c) Tim Docker 2010, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- A container type for values that can be composed by horizonal+-- and vertical layout.+ module Graphics.Rendering.Chart.Grid (- Grid,+ Grid, Span, SpaceWeight, tval, tspan, empty, nullt, (.|.), (./.),@@ -8,119 +17,169 @@ overlay, width, height, gridToRenderable,- weights+ weights,+ aboveWide,+ wideAbove,+ tallBeside,+ besideTall,+ fullOverlayUnder,+ fullOverlayOver ) where -import Data.List import Data.Array--- import qualified Data.Map as Map import Control.Monad-import Numeric import Graphics.Rendering.Chart.Renderable-import Graphics.Rendering.Chart.Types---import Graphics.Rendering.Chart.Gtk-import qualified Graphics.Rendering.Cairo as C+import Graphics.Rendering.Chart.Geometry hiding (x0, y0)+import Graphics.Rendering.Chart.Drawing -type Span = (Int,Int)-type Size = (Int,Int)+type Span = (Int,Int)+type Size = (Int,Int)++-- | When more space is available for an item than the total width of items,+-- extra added space is proportional to 'space weight'. type SpaceWeight = (Double,Double) -type Cell a = (a,Span,SpaceWeight)+type Cell a = (a,Span,SpaceWeight) -data Grid a = Value (a,Span,SpaceWeight)- | Above (Grid a) (Grid a) Size - | Beside (Grid a) (Grid a) Size- | Overlay (Grid a) (Grid a) Size- | Empty- | Null+-- | Abstract datatype representing a grid.+data Grid a+ = Value (a,Span,SpaceWeight) -- ^ A singleton grid item "a" spanning+ -- a given rectangle (measured in grid+ -- cells), with given space weight.+ | Above (Grid a) (Grid a) Size -- ^ One grid above the other. "Size" is+ -- their cached total size (so it is+ -- NOT specified manually).+ | Beside (Grid a) (Grid a) Size -- ^ One grid horizontally beside+ -- the other.+ | Overlay (Grid a) (Grid a) Size -- ^ Two grids positioned one over+ -- the other.+ | Empty -- ^ An empty 1x1 grid cell.+ | Null -- ^ An empty 0x0 grid cell. deriving (Show) width :: Grid a -> Int-width Null = 0-width Empty = 1-width (Value _) = 1-width (Beside _ _ (w,h)) = w-width (Above _ _ (w,h)) = w-width (Overlay _ _ (w,h)) = w+width Null = 0+width Empty = 1+width (Value _) = 1+width (Beside _ _ (w,_)) = w+width (Above _ _ (w,_)) = w+width (Overlay _ _ (w,_)) = w height :: Grid a -> Int-height Null = 0-height Empty = 1-height (Value _) = 1-height (Beside _ _ (w,h)) = h-height (Above _ _ (w,h)) = h-height (Overlay _ _ (w,h)) = h+height Null = 0+height Empty = 1+height (Value _) = 1+height (Beside _ _ (_,h)) = h+height (Above _ _ (_,h)) = h+height (Overlay _ _ (_,h)) = h +-- | A 1x1 grid from a given value, with no extra space. tval :: a -> Grid a tval a = Value (a,(1,1),(0,0)) +-- | A WxH (measured in cells) grid from a given value, with space weight (1,1). tspan :: a -> Span -> Grid a-tspan a span = Value (a,span,(1,1))+tspan a spn = Value (a,spn,(1,1)) -empty, nullt :: Grid a+-- | A 1x1 empty grid.+empty :: Grid a empty = Empty++-- | A 0x0 empty grid.+nullt :: Grid a nullt = Null above, beside :: Grid a -> Grid a -> Grid a above Null t = t above t Null = t-above t1 t2 = Above t1 t2 size+above t1 t2 = Above t1 t2 size where size = (max (width t1) (width t2), height t1 + height t2)- ++-- | A value occupying 1 row with the same horizontal span as the grid.+wideAbove :: a -> Grid a -> Grid a+wideAbove a g = weights (0,0) (tspan a (width g,1)) `above` g++-- | A value placed below the grid, occupying 1 row with the same+-- horizontal span as the grid.+aboveWide :: Grid a -> a -> Grid a+aboveWide g a = g `above` weights (0,0) (tspan a (width g,1))++-- | A value placed to the left of the grid, occupying 1 column with+-- the same vertical span as the grid.+tallBeside :: a -> Grid a -> Grid a+tallBeside a g = weights (0,0) (tspan a (1,height g)) `beside` g++-- | A value placed to the right of the grid, occupying 1 column with+-- the same vertical span as the grid.+besideTall :: Grid a -> a -> Grid a+besideTall g a = g `beside` weights (0,0) (tspan a (1,height g))++-- | A value placed under a grid, with the same span as the grid.+fullOverlayUnder :: a -> Grid a -> Grid a+fullOverlayUnder a g = g `overlay` tspan a (width g,height g)++-- | A value placed over a grid, with the same span as the grid.+fullOverlayOver :: a -> Grid a -> Grid a+fullOverlayOver a g = tspan a (width g,height g) `overlay` g+ beside Null t = t beside t Null = t-beside t1 t2 = Beside t1 t2 size- where size = (width t1 + width t2, max (height t1) (height t2))+beside t1 t2 = Beside t1 t2 size+ where size = (width t1 + width t2, max (height t1) (height t2)) aboveN, besideN :: [Grid a] -> Grid a-aboveN = foldl above nullt+aboveN = foldl above nullt besideN = foldl beside nullt +-- | One grid over the other. The first argument is shallow, the second is deep.+overlay :: Grid a -> Grid a -> Grid a overlay Null t = t overlay t Null = t-overlay t1 t2 = Overlay t1 t2 size- where size = (max (width t1) (width t2), max (height t1) (height t2))+overlay t1 t2 = Overlay t1 t2 size+ where size = (max (width t1) (width t2), max (height t1) (height t2)) +-- | A synonym for 'beside'.+(.|.) :: Grid a -> Grid a -> Grid a (.|.) = beside++-- | A synonym for 'above'.+(./.) :: Grid a -> Grid a -> Grid a (./.) = above +-- | Sets the space weight of *every* cell of the grid to given value. weights :: SpaceWeight -> Grid a -> Grid a-weights sw Null = Null-weights sw Empty = Empty-weights sw (Value (v,sp,_)) = Value (v,sp,sw)-weights sw (Above t1 t2 sz) = Above (weights sw t1) (weights sw t2) sz-weights sw (Beside t1 t2 sz) = Beside (weights sw t1) (weights sw t2) sz+weights _ Null = Null+weights _ Empty = Empty+weights sw (Value (v,sp,_)) = Value (v,sp,sw)+weights sw (Above t1 t2 sz) = Above (weights sw t1) (weights sw t2) sz+weights sw (Beside t1 t2 sz) = Beside (weights sw t1) (weights sw t2) sz weights sw (Overlay t1 t2 sz) = Overlay (weights sw t1) (weights sw t2) sz -- fix me, need to make .|. and .||. higher precedence -- than ./. and .//. instance Functor Grid where- fmap f (Value (a,span,ew)) = Value (f a,span,ew)- fmap f (Above t1 t2 s) = Above (fmap f t1) (fmap f t2) s- fmap f (Beside t1 t2 s) = Beside (fmap f t1) (fmap f t2) s- fmap f (Overlay t1 t2 s) = Overlay (fmap f t1) (fmap f t2) s- fmap f Empty = Empty- fmap f Null = Null+ fmap f (Value (a,spn,ew)) = Value (f a,spn,ew)+ fmap f (Above t1 t2 s) = Above (fmap f t1) (fmap f t2) s+ fmap f (Beside t1 t2 s) = Beside (fmap f t1) (fmap f t2) s+ fmap f (Overlay t1 t2 s) = Overlay (fmap f t1) (fmap f t2) s+ fmap _ Empty = Empty+ fmap _ Null = Null mapGridM :: Monad m => (a -> m b) -> Grid a -> m (Grid b)-mapGridM f (Value (a,span,ew)) = do - b <- f a- return (Value (b,span,ew))-mapGridM f (Above t1 t2 s) = do- t1' <- mapGridM f t1- t2' <- mapGridM f t2- return (Above t1' t2' s)-mapGridM f (Beside t1 t2 s) = do- t1' <- mapGridM f t1- t2' <- mapGridM f t2- return (Beside t1' t2' s)-mapGridM f (Overlay t1 t2 s) = do- t1' <- mapGridM f t1- t2' <- mapGridM f t2- return (Overlay t1' t2' s)-mapGridM _ Empty = return Empty-mapGridM _ Null = return Null+mapGridM f (Value (a,spn,ew)) = do b <- f a+ return (Value (b,spn,ew))+mapGridM f (Above t1 t2 s) = do t1' <- mapGridM f t1+ t2' <- mapGridM f t2+ return (Above t1' t2' s)+mapGridM f (Beside t1 t2 s) = do t1' <- mapGridM f t1+ t2' <- mapGridM f t2+ return (Beside t1' t2' s)+mapGridM f (Overlay t1 t2 s) = do t1' <- mapGridM f t1+ t2' <- mapGridM f t2+ return (Overlay t1' t2' s)+mapGridM _ Empty = return Empty+mapGridM _ Null = return Null ---------------------------------------------------------------------- type FlatGrid a = Array (Int,Int) [(a,Span,SpaceWeight)]@@ -132,21 +191,21 @@ type FlatEl a = ((Int,Int),Cell a) flatten2 :: (Int,Int) -> Grid a -> [FlatEl a] -> [FlatEl a]-flatten2 i Empty els = els-flatten2 i Null els = els+flatten2 _ Empty els = els+flatten2 _ Null els = els flatten2 i (Value cell) els = (i,cell):els -flatten2 i@(x,y) (Above t1 t2 size) els = (f1.f2) els+flatten2 i@(x,y) (Above t1 t2 _) els = (f1.f2) els where f1 = flatten2 i t1 f2 = flatten2 (x,y + height t1) t2 -flatten2 i@(x,y) (Beside t1 t2 size) els = (f1.f2) els+flatten2 i@(x,y) (Beside t1 t2 _) els = (f1.f2) els where f1 = flatten2 i t1 f2 = flatten2 (x + width t1, y) t2 -flatten2 i@(x,y) (Overlay t1 t2 size) els = (f1.f2) els+flatten2 i (Overlay t1 t2 _) els = (f1.f2) els where f1 = flatten2 i t1 f2 = flatten2 i t2@@ -154,91 +213,108 @@ foldT :: ((Int,Int) -> Cell a -> r -> r) -> r -> FlatGrid a -> r foldT f iv ft = foldr f' iv (assocs ft) where- f' (i,vs) r = foldr (\cell -> f i cell) r vs + f' (i,vs) r = foldr (f i) r vs ---------------------------------------------------------------------- type DArray = Array Int Double -gridToRenderable :: Grid (Renderable a) -> Renderable a-gridToRenderable t = Renderable minsizef renderf+getSizes :: Grid (Renderable a) -> BackendProgram (DArray, DArray, DArray, DArray)+getSizes t = do+ szs <- mapGridM minsize t :: BackendProgram (Grid RectSize)+ let szs' = flatten szs+ let widths = accumArray max 0 (0, width t - 1)+ (foldT (ef wf fst) [] szs')+ let heights = accumArray max 0 (0, height t - 1)+ (foldT (ef hf snd) [] szs')+ let xweights = accumArray max 0 (0, width t - 1)+ (foldT (ef xwf fst) [] szs')+ let yweights = accumArray max 0 (0, height t - 1)+ (foldT (ef ywf snd) [] szs')+ return (widths,heights,xweights,yweights) where- getSizes :: CRender (DArray, DArray, DArray, DArray)- getSizes = do- szs <- mapGridM minsize t :: CRender (Grid RectSize)- let szs' = flatten szs- let widths = accumArray max 0 (0, width t - 1) (foldT (ef wf) [] szs')- let heights = accumArray max 0 (0, height t - 1) (foldT (ef hf) [] szs')- let xweights = accumArray max 0 (0, width t - 1) (foldT (ef xwf) [] szs')- let yweights = accumArray max 0 (0, height t - 1) (foldT (ef ywf) [] szs')- return (widths,heights,xweights,yweights)+ wf (x,_) (w,_) _ = (x,w)+ hf (_,y) (_,h) _ = (y,h)+ xwf (x,_) _ (xw,_) = (x,xw)+ ywf (_,y) _ (_,yw) = (y,yw) - wf (x,y) (w,h) (ww,wh) = (x,w)- hf (x,y) (w,h) (ww,wh) = (y,h)- xwf (x,y) (w,h) (xw,yw) = (x,xw)- ywf (x,y) (w,h) (xw,yw) = (y,yw)+ ef f ds loc (size,spn,ew) r | ds spn == 1 = f loc size ew:r+ | otherwise = r - ef f loc (size,span,ew) | span == (1,1) = (f loc size ew:)- | otherwise = id- +instance (ToRenderable a) => ToRenderable (Grid a) where+ toRenderable = gridToRenderable . fmap toRenderable++gridToRenderable :: Grid (Renderable a) -> Renderable a+gridToRenderable gt = Renderable minsizef renderf+ where+ minsizef :: BackendProgram RectSize minsizef = do- (widths, heights, xweights, yweights) <- getSizes+ (widths, heights, _, _) <- getSizes gt return (sum (elems widths), sum (elems heights)) renderf (w,h) = do- (widths, heights, xweights, yweights) <- getSizes- let widths' = addExtraSpace w widths xweights+ (widths, heights, xweights, yweights) <- getSizes gt+ let widths' = addExtraSpace w widths xweights let heights' = addExtraSpace h heights yweights- let csizes = (ctotal widths',ctotal heights')- rf1 csizes (0,0) t+ let borders = (ctotal widths',ctotal heights')+ rf1 borders (0,0) gt - rf1 csizes loc@(i,j) t = case t of- Null -> return nullPickFn+ -- (x borders, y borders) -> (x,y) -> grid -> drawing+ rf1 borders loc@(i,j) t = case t of+ Null -> return nullPickFn Empty -> return nullPickFn- (Value (r,span,_)) -> do- let (Rect p0 p1) = mkRect csizes loc span- p0'@(Point x0 y0) <- alignc p0- p1'@(Point x1 y1) <- alignc p1- preserveCState $ do- c $ C.translate x0 y0- render r (x1-x0,y1-y0)+ (Value (r,spn,_)) -> do+ let (Rect p0 p1) = mkRect borders loc spn+ (Point x0 y0) <- alignFillPoint p0+ (Point x1 y1) <- alignFillPoint p1+ withTranslation (Point x0 y0) $ do+ pf <- render r (x1-x0,y1-y0)+ return (newpf pf x0 y0) (Above t1 t2 _) -> do- pf1 <- rf1 csizes (i,j) t1- pf2 <- rf1 csizes (i,j+height t1) t2- let pf p@(Point x y) = if y < (snd csizes ! (j + height t1)) then pf1 p- else pf2 p+ pf1 <- rf1 borders (i,j) t1+ pf2 <- rf1 borders (i,j+height t1) t2+ let pf p@(Point _ y) = if y < (snd borders ! (j + height t1))+ then pf1 p else pf2 p return pf (Beside t1 t2 _) -> do- pf1 <- rf1 csizes (i,j) t1- pf2 <- rf1 csizes (i+width t1,j) t2- let pf p@(Point x y) = if x < (fst csizes ! (i + width t1)) then pf1 p- else pf2 p+ pf1 <- rf1 borders (i,j) t1+ pf2 <- rf1 borders (i+width t1,j) t2+ let pf p@(Point x _) = if x < (fst borders ! (i + width t1))+ then pf1 p else pf2 p return pf (Overlay t1 t2 _) -> do- pf2 <- rf1 csizes (i,j) t2- pf1 <- rf1 csizes (i,j) t1+ pf2 <- rf1 borders (i,j) t2+ pf1 <- rf1 borders (i,j) t1 let pf p = pf1 p `mplus` pf2 p return pf + newpf pf x0 y0 (Point x1 y1) = pf (Point (x1-x0) (y1-y0))++ -- (x borders, y borders) -> (x,y) -> (w,h)+ -- -> rectangle of grid[x..x+w, y..y+h] mkRect :: (DArray, DArray) -> (Int,Int) -> (Int,Int) -> Rect mkRect (cwidths,cheights) (x,y) (w,h) = Rect (Point x1 y1) (Point x2 y2) where- x1 = cwidths ! x+ x1 = cwidths ! x y1 = cheights ! y- x2 = cwidths ! min (x+w) (snd $ bounds cwidths) + x2 = cwidths ! min (x+w) (snd $ bounds cwidths) y2 = cheights ! min (y+h) (snd $ bounds cheights)- mx = fst (bounds cwidths)- my = fst (bounds cheights)+ -- mx = fst (bounds cwidths)+ -- my = fst (bounds cheights) + -- total size -> item sizes -> item weights -> new item sizes such that+ -- their sum == total size, and added size is proportional to weight addExtraSpace :: Double -> DArray -> DArray -> DArray- addExtraSpace size sizes weights = + addExtraSpace size sizes weights' = if totalws == 0 then sizes else listArray (bounds sizes) sizes' where- ws = elems weights+ ws = elems weights' totalws = sum ws- extra = size - sum (elems sizes)- extras = map (*(extra/totalws)) ws- sizes' = zipWith (+) extras (elems sizes)+ extra = size - sum (elems sizes)+ extras = map (*(extra/totalws)) ws+ sizes' = zipWith (+) extras (elems sizes) + -- [1,2,3] -> [0,1,3,6]. ctotal :: DArray -> DArray- ctotal a = listArray (let (i,j) = bounds a in (i,j+1)) (scanl (+) 0 (elems a))+ ctotal a = listArray (let (i,j) = bounds a in (i,j+1))+ (scanl (+) 0 (elems a))
− Graphics/Rendering/Chart/Gtk.hs
@@ -1,50 +0,0 @@--------------------------------------------------------------------------------- |--- Module : Graphics.Rendering.Chart.Gtk--- Copyright : (c) Tim Docker 2006--- License : BSD-style (see chart/COPYRIGHT)--module Graphics.Rendering.Chart.Gtk(- renderableToWindow- ) where--import qualified Graphics.UI.Gtk as G-import qualified Graphics.UI.Gtk.Gdk.Events as G-import qualified Graphics.Rendering.Cairo as C-import Graphics.Rendering.Chart-import Graphics.Rendering.Chart.Renderable-import Graphics.Rendering.Chart.Types-import Data.List (isPrefixOf)---- do action m for any keypress (except meta keys)-anyKey :: (Monad m) => m a -> G.Event -> m Bool-anyKey m (G.Key {G.eventKeyName=key})- | any (`isPrefixOf` key) ignores = return True- | otherwise = m >> return True- where ignores = ["Shift","Control","Alt",- "Super","Meta","Hyper"]--renderableToWindow :: Renderable a -> Int -> Int -> IO ()-renderableToWindow chart windowWidth windowHeight = do- G.unsafeInitGUIForThreadedRTS- -- G.initGUI- window <- G.windowNew- canvas <- G.drawingAreaNew- -- fix size- -- G.windowSetResizable window False- G.widgetSetSizeRequest window windowWidth windowHeight- -- press any key to quit- G.onKeyPress window $ anyKey (G.widgetDestroy window)- G.onDestroy window G.mainQuit- G.onExpose canvas $ const (updateCanvas chart canvas)- G.set window [G.containerChild G.:= canvas]- G.widgetShowAll window- G.mainGUI--updateCanvas :: Renderable a -> G.DrawingArea -> IO Bool-updateCanvas chart canvas = do- win <- G.widgetGetDrawWindow canvas- (width, height) <- G.widgetGetSize canvas- let sz = (fromIntegral width,fromIntegral height)- G.renderWithDrawable win $ runCRender (render chart sz) bitmapEnv- return True
Graphics/Rendering/Chart/Layout.hs view
@@ -1,344 +1,855 @@ ----------------------------------------------------------------------------- -- | -- Module : Graphics.Rendering.Chart.Layout--- Copyright : (c) Tim Docker 2006--- License : BSD-style (see chart/COPYRIGHT)------ This module glues together axes and plots to actually create a renderable--- for a chart.------ Note that template haskell is used to derive accessor functions--- (see 'Data.Accessor') for each field of the following data types:------ * 'Layout1'------ * 'LayoutAxis'------ These accessors are not shown in this API documentation. They have--- the same name as the field, but with the trailing underscore--- dropped. Hence for data field f_::F in type D, they have type------ @--- f :: Data.Accessor.Accessor D F--- @-----{-# OPTIONS_GHC -XTemplateHaskell #-}--module Graphics.Rendering.Chart.Layout(- Layout1(..),- LayoutAxis(..),- MAxisFn,-- defaultLayout1,- linkAxes,- independentAxes,-- updateAllAxesStyles,- setLayout1Foreground,-- defaultLayoutAxis,- laxis_title_style,- laxis_title,- laxis_style,- laxis_visible,- laxis_generate,- laxis_override,- laxis_reverse,-- layout1_background,- layout1_plot_background,- layout1_title,- layout1_title_style,- layout1_left_axis,- layout1_right_axis,- layout1_top_axis,- layout1_bottom_axis,- layout1_yaxes_control,- layout1_margin,- layout1_plots,- layout1_legend,- layout1_grid_last- ) where--import qualified Graphics.Rendering.Cairo as C--import Graphics.Rendering.Chart.Axis-import Graphics.Rendering.Chart.Types-import Graphics.Rendering.Chart.Plot-import Graphics.Rendering.Chart.Legend-import Graphics.Rendering.Chart.Renderable-import Graphics.Rendering.Chart.Grid-import Control.Monad-import Control.Monad.Reader (local)-import Data.Accessor.Template-import Data.Accessor-import Data.Colour-import Data.Colour.Names (white)---- | A @MAxisFn@ is a function that generates an (optional) axis--- given the points plotted against that axis.-type MAxisFn t = [t] -> Maybe (AxisData t)--data LayoutAxis x = LayoutAxis {- laxis_title_style_ :: CairoFontStyle,- laxis_title_ :: String,- laxis_style_ :: AxisStyle,-- -- | function that determines whether an axis should be visible,- -- based upon the points plotted on this axis. The default value- -- is 'not.null'- laxis_visible_ :: [x] -> Bool,-- -- | function that generates the axis data, based upon the- -- points plotted. The default value is 'autoAxis'.- laxis_generate_ :: AxisFn x,-- -- | function that can be used to override the generated axis data.- -- The default value is 'id'.- laxis_override_ :: AxisData x -> AxisData x,-- -- | True if left to right (bottom to top) is to show descending values- laxis_reverse_ :: Bool--}---- | A Layout1 value is a single plot area, with optional: axes on--- each of the 4 sides; title at the top; legend at the bottom. It's--- parameterised by the types of values to be plotted on the horizonal--- and vertical axes.-data Layout1 x y = Layout1 {-- layout1_background_ :: CairoFillStyle,- layout1_plot_background_ :: Maybe CairoFillStyle,-- layout1_title_ :: String,- layout1_title_style_ :: CairoFontStyle,-- layout1_bottom_axis_ :: LayoutAxis x,- layout1_top_axis_ :: LayoutAxis x,- layout1_left_axis_ :: LayoutAxis y,- layout1_right_axis_ :: LayoutAxis y,-- -- | function to map points from the left/right plot- -- to the left/right axes. The default value is 'id'- layout1_yaxes_control_ :: ([y],[y]) -> ([y],[y]),-- layout1_margin_ :: Double,- layout1_plots_ :: [Either (Plot x y) (Plot x y)],- layout1_legend_ :: Maybe(LegendStyle),-- -- | True if the grid is to be rendered on top of the Plots- layout1_grid_last_ :: Bool-}--data Layout1Pick x y = L1P_Legend String- | L1P_PlotArea x y y- | L1P_BottomAxis x- | L1P_TopAxis x- | L1P_LeftAxis y- | L1P_RightAxis y--instance (Ord x, Ord y) => ToRenderable (Layout1 x y) where- toRenderable = setPickFn nullPickFn.layout1ToRenderable--layout1ToRenderable :: (Ord x, Ord y) => Layout1 x y -> Renderable (Layout1Pick x y)-layout1ToRenderable l =- fillBackground (layout1_background_ l) (- gridToRenderable $ aboveN [- tval $ addMargins (lm/2,0,0,0) title,- weights (1,1) $ tval $ addMargins (lm,lm,lm,lm) plotArea,- tval $ legends- ] )- where- title = label (layout1_title_style_ l) HTA_Centre VTA_Centre (layout1_title_ l)-- plotArea = gridToRenderable (layer2 `overlay` layer1)-- layer1 = aboveN [- besideN [er, er, er ],- besideN [er, er, er ],- besideN [er, er, weights (1,1) plots ]- ]-- layer2 = aboveN [- besideN [er, er, ttitle, er, er ],- besideN [er, tl, taxis, tr, er ],- besideN [ltitle, laxis, er, raxis, rtitle ],- besideN [er, bl, baxis, br, er ],- besideN [er, er, btitle, er, er ]- ]-- ttitle = atitle HTA_Centre VTA_Bottom 0 layout1_top_axis_- btitle = atitle HTA_Centre VTA_Top 0 layout1_bottom_axis_- ltitle = atitle HTA_Right VTA_Centre 90 layout1_left_axis_- rtitle = atitle HTA_Left VTA_Centre 90 layout1_right_axis_-- er = tval $ emptyRenderable-- atitle ha va rot af = if ttext == "" then er else tval $ rlabel tstyle ha va rot ttext- where- tstyle = laxis_title_style_ (af l)- ttext = laxis_title_ (af l)-- plots = tval $ mfill (layout1_plot_background_ l) $ plotsToRenderable l- where- mfill Nothing = id- mfill (Just fs) = fillBackground fs-- (ba,la,ta,ra) = getAxes l- baxis = tval $ maybe emptyRenderable (mapPickFn L1P_BottomAxis . axisToRenderable) ba- taxis = tval $ maybe emptyRenderable (mapPickFn L1P_TopAxis . axisToRenderable) ta- laxis = tval $ maybe emptyRenderable (mapPickFn L1P_LeftAxis . axisToRenderable) la- raxis = tval $ maybe emptyRenderable (mapPickFn L1P_RightAxis . axisToRenderable) ra-- tl = tval $ axesSpacer fst ta fst la- bl = tval $ axesSpacer fst ba snd la- tr = tval $ axesSpacer snd ta fst ra- br = tval $ axesSpacer snd ba snd ra-- legends = gridToRenderable (besideN [ tval $ mkLegend lefts,- weights (1,1) $ tval $ emptyRenderable,- tval $ mkLegend rights ])- lefts = concat [ plot_legend_ p | (Left p) <- (layout1_plots_ l) ] - rights = concat [ plot_legend_ p | (Right p) <- (layout1_plots_ l) ] -- mkLegend vals = case (layout1_legend_ l) of- Nothing -> emptyRenderable- (Just ls) -> case filter ((/="").fst) vals of- [] -> emptyRenderable- lvs -> addMargins (0,lm,lm,lm)- (mapPickFn L1P_Legend $ legendToRenderable (Legend True ls lvs))-- lm = layout1_margin_ l--plotsToRenderable :: Layout1 x y -> Renderable (Layout1Pick x y)-plotsToRenderable l = Renderable {- minsize=return (0,0),- render= renderPlots l- }--renderPlots :: Layout1 x y -> RectSize -> CRender (PickFn (Layout1Pick x y))-renderPlots l sz@(w,h) = do- when (not (layout1_grid_last_ l)) renderGrids- preserveCState $ do- -- render the plots- setClipRegion (Point 0 0) (Point w h)- mapM_ rPlot (layout1_plots_ l)- when (layout1_grid_last_ l) renderGrids- return nullPickFn-- where- (bAxis,lAxis,tAxis,rAxis) = getAxes l-- rPlot (Left p) = rPlot1 bAxis lAxis p- rPlot (Right p) = rPlot1 bAxis rAxis p-- rPlot1 (Just (AxisT _ xs xrev xaxis)) (Just (AxisT _ ys yrev yaxis)) p = - let xrange = if xrev then (w, 0) else (0,w)- yrange = if yrev then (0, h) else (h, 0)- pmfn (x,y) = Point (mapv xrange (axis_viewport_ xaxis xrange) x)- (mapv yrange (axis_viewport_ yaxis yrange) y)- mapv (min,max) _ LMin = min- mapv (min,max) _ LMax = max- mapv _ f (LValue v) = f v- in plot_render_ p pmfn- rPlot1 _ _ _ = return ()-- renderGrids = do- maybeM () (renderAxisGrid sz) tAxis- maybeM () (renderAxisGrid sz) bAxis- maybeM () (renderAxisGrid sz) lAxis- maybeM () (renderAxisGrid sz) rAxis--axesSpacer f1 a1 f2 a2 = embedRenderable $ do- oh1 <- maybeM (0,0) axisOverhang a1- oh2 <- maybeM (0,0) axisOverhang a2- return (spacer (f1 oh1, f2 oh2))--getAxes :: Layout1 x y -> (Maybe (AxisT x), Maybe (AxisT y), Maybe (AxisT x), Maybe (AxisT y))-getAxes l = (bAxis,lAxis,tAxis,rAxis)- where - (xvals0,xvals1,yvals0,yvals1) = allPlottedValues (layout1_plots_ l)- xvals = xvals0 ++ xvals1- (yvals0',yvals1') = layout1_yaxes_control_ l (yvals0,yvals1)-- bAxis = mkAxis E_Bottom (layout1_bottom_axis_ l) xvals- tAxis = mkAxis E_Top (layout1_top_axis_ l) xvals- lAxis = mkAxis E_Left (layout1_left_axis_ l) yvals0'- rAxis = mkAxis E_Right (layout1_right_axis_ l) yvals1'-- mkAxis t laxis vals = case laxis_visible_ laxis vals of- False -> Nothing- True -> Just (AxisT t style rev adata) - where- style = laxis_style_ laxis- rev = laxis_reverse_ laxis- adata = (laxis_override_ laxis) (laxis_generate_ laxis vals)--allPlottedValues :: [(Either (Plot x y) (Plot x' y'))] -> ( [x], [x'], [y], [y'] )-allPlottedValues plots = (xvals0,xvals1,yvals0,yvals1)- where- xvals0 = [ x | (Left p) <- plots, x <- fst $ plot_all_points_ p]- yvals0 = [ y | (Left p) <- plots, y <- snd $ plot_all_points_ p]- xvals1 = [ x | (Right p) <- plots, x <- fst $ plot_all_points_ p]- yvals1 = [ y | (Right p) <- plots, y <- snd $ plot_all_points_ p]--defaultLayout1 :: (PlotValue x,PlotValue y) => Layout1 x y-defaultLayout1 = Layout1 {- layout1_background_ = solidFillStyle $ opaque white,- layout1_plot_background_ = Nothing,-- layout1_title_ = "",- layout1_title_style_ = defaultFontStyle{font_size_=15, font_weight_=C.FontWeightBold},-- layout1_top_axis_ = defaultLayoutAxis {- laxis_visible_ = const False - },- layout1_bottom_axis_ = defaultLayoutAxis,- layout1_left_axis_ = defaultLayoutAxis,- layout1_right_axis_ = defaultLayoutAxis,-- layout1_yaxes_control_ = id,-- layout1_margin_ = 10,- layout1_plots_ = [],- layout1_legend_ = Just defaultLegendStyle,- layout1_grid_last_ = False-}--defaultLayoutAxis :: PlotValue t => LayoutAxis t-defaultLayoutAxis = LayoutAxis {- laxis_title_style_ = defaultFontStyle{font_size_=10},- laxis_title_ = "",- laxis_style_ = defaultAxisStyle,- laxis_visible_ = not.null, - laxis_generate_ = autoAxis,- laxis_override_ = id,- laxis_reverse_ = False-}--------------------------------------------------------------------------- Template haskell to derive an instance of Data.Accessor.Accessor for each field-$( deriveAccessors ''Layout1 )-$( deriveAccessors ''LayoutAxis )---- | Helper to update all axis styles on a Layout1 simultaneously-updateAllAxesStyles :: (AxisStyle -> AxisStyle) -> Layout1 x y -> Layout1 x y-updateAllAxesStyles uf = (layout1_top_axis .> laxis_style ^: uf) .- (layout1_bottom_axis .> laxis_style ^: uf) .- (layout1_left_axis .> laxis_style ^: uf) .- (layout1_right_axis .> laxis_style ^: uf)---- | Helper to set the forground color uniformly on a Layout1-setLayout1Foreground :: AlphaColour Double -> Layout1 x y -> Layout1 x y-setLayout1Foreground fg = updateAllAxesStyles (- (axis_line_style .> line_color ^= fg).- (axis_label_style .> font_color ^= fg)- )- . (layout1_title_style .> font_color ^= fg)- . (layout1_legend ^: fmap (legend_label_style .> font_color ^= fg))---linkAxes (ys1,ys2) = (ys1++ys2,ys1++ys2)-independentAxes (ys1,ys2) = (ys1,ys2)+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- This module glues together axes and plots to actually create a renderable+-- for a chart.+--+-- Note that Template haskell is used to derive accessor functions+-- (see 'Control.Lens') for each field of the following data types:+--+-- * 'Layout'+--+-- * 'LayoutLR'+--+-- * 'StackedLayouts'+--+-- * 'LayoutAxis'+--+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ExistentialQuantification #-}++module Graphics.Rendering.Chart.Layout+ ( -- * Types+ Layout(..)+ , LayoutLR(..)+ , LayoutAxis(..)+ , LayoutPick(..)+ , StackedLayouts(..)+ , StackedLayout(..)+ -- , LegendItem haddock complains about this being missing, but from what?+ , MAxisFn++ -- * Rendering+ , layoutToRenderable+ , layoutToGrid+ , layoutLRToRenderable+ , layoutLRToGrid+ , renderStackedLayouts++ -- * LayoutAxis lenses+ , laxis_title_style+ , laxis_title+ , laxis_style+ , laxis_generate+ , laxis_override+ , laxis_reverse++ -- * Layout lenses+ , layout_background+ , layout_plot_background+ , layout_title+ , layout_title_style+ , layout_x_axis+ , layout_top_axis_visibility+ , layout_bottom_axis_visibility+ , layout_y_axis+ , layout_left_axis_visibility+ , layout_right_axis_visibility+ , layout_margin+ , layout_plots+ , layout_legend+ , layout_grid_last++ , layout_axes_styles+ , layout_axes_title_styles+ , layout_all_font_styles+ , layout_foreground++ -- * LayoutLR lenses+ , layoutlr_background+ , layoutlr_plot_background+ , layoutlr_title+ , layoutlr_title_style+ , layoutlr_x_axis+ , layoutlr_top_axis_visibility+ , layoutlr_bottom_axis_visibility+ , layoutlr_left_axis+ , layoutlr_right_axis+ , layoutlr_left_axis_visibility+ , layoutlr_right_axis_visibility+ , layoutlr_plots+ , layoutlr_legend+ , layoutlr_margin+ , layoutlr_grid_last++ , layoutlr_axes_styles+ , layoutlr_axes_title_styles+ , layoutlr_all_font_styles+ , layoutlr_foreground++ -- * StackedLayouts lenses+ , slayouts_layouts+ , slayouts_compress_legend+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif++import Graphics.Rendering.Chart.Axis+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Utils+import Graphics.Rendering.Chart.Plot+import Graphics.Rendering.Chart.Legend+import Graphics.Rendering.Chart.Renderable+import Graphics.Rendering.Chart.Grid+import Control.Monad+import Control.Lens hiding (at)+import Data.Colour+import Data.Colour.Names (white)+import Data.Default.Class++-- | A @MAxisFn@ is a function that generates an (optional) axis+-- given the points plotted against that axis.+type MAxisFn t = [t] -> Maybe (AxisData t)++-- | Type of axis that is used in 'Layout' and 'LayoutLR'.+--+-- To generate the actual axis type ('AxisData' and 'AxisT')+-- the '_laxis_generate' function is called and custom settings+-- are applied with '_laxis_override'. Note that the 'AxisVisibility'+-- values in 'Layout' and 'LayoutLR' override visibility related+-- settings of the axis.+data LayoutAxis x = LayoutAxis+ { _laxis_title_style :: FontStyle+ -- ^ Font style to use for the axis title.+ , _laxis_title :: String+ -- ^ Title displayed for the axis.+ , _laxis_style :: AxisStyle+ -- ^ Axis style applied.++ , _laxis_generate :: AxisFn x+ -- ^ Function that generates the axis data, based upon the+ -- points plotted. The default value is 'autoAxis'.++ , _laxis_override :: AxisData x -> AxisData x+ -- ^ Function that can be used to override the generated axis data.+ -- The default value is 'id'.++ , _laxis_reverse :: Bool+ -- ^ True if left to right (bottom to top) is to show descending values.++ }++-- | Information on what is at a specifc location of a 'Layout' or 'LayoutLR'.+-- This is delivered by the 'PickFn' of a 'Renderable'.+data LayoutPick x y1 y2 = LayoutPick_Legend String -- ^ A legend entry.+ | LayoutPick_Title String -- ^ The title.+ | LayoutPick_XTopAxisTitle String -- ^ The title of the top x axis.+ | LayoutPick_XBottomAxisTitle String -- ^ The title of the bottom x axis.+ | LayoutPick_YLeftAxisTitle String -- ^ The title of the left y axis.+ | LayoutPick_YRightAxisTitle String -- ^ The title of the right y axis.+ | LayoutPick_PlotArea x y1 y2 -- ^ The plot area at the given plot coordinates.+ | LayoutPick_XTopAxis x -- ^ The top x axis at the given plot coordinate.+ | LayoutPick_XBottomAxis x -- ^ The bottom x axis at the given plot coordinate.+ | LayoutPick_YLeftAxis y1 -- ^ The left y axis at the given plot coordinate.+ | LayoutPick_YRightAxis y2 -- ^ The right y axis at the given plot coordinate.+ deriving (Show)++type LegendItem = (String,Rect -> BackendProgram ())++-- | A Layout value is a single plot area, with single x and y+-- axis. The title is at the top and the legend at the bottom. It's+-- parametrized by the types of values to be plotted on the x+-- and y axes.+data Layout x y = Layout+ { _layout_background :: FillStyle+ -- ^ How to fill the background of everything.+ , _layout_plot_background :: Maybe FillStyle+ -- ^ How to fill the background of the plot,+ -- if different from the overall background.++ , _layout_title :: String+ -- ^ Title to display above the chart.+ , _layout_title_style :: FontStyle+ -- ^ Font style to use for the title.++ , _layout_x_axis :: LayoutAxis x+ -- ^ Rules to generate the x axis.+ , _layout_top_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the top axis.+ , _layout_bottom_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the bottom axis.++ , _layout_y_axis :: LayoutAxis y+ -- ^ Rules to generate the y axis.+ , _layout_left_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the left axis.+ , _layout_right_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the right axis.++ , _layout_plots :: [Plot x y]+ -- ^ The data sets to plot in the chart.+ -- They are plotted over each other.++ , _layout_legend :: Maybe LegendStyle+ -- ^ How to style the legend.+ , _layout_margin :: Double+ -- ^ The margin distance to use.+ , _layout_grid_last :: Bool+ -- ^ If the grid shall be rendered+ -- beneath (@False@) or over (@True@) all plots.+ }++instance (Ord x, Ord y) => ToRenderable (Layout x y) where+ toRenderable = setPickFn nullPickFn . layoutToRenderable++-- | Render the given 'Layout'.+layoutToRenderable :: forall x y . (Ord x, Ord y) => Layout x y -> Renderable (LayoutPick x y y)+layoutToRenderable l = fillBackground (_layout_background l) $ gridToRenderable (layoutToGrid l)++layoutToGrid :: forall x y . (Ord x, Ord y) => Layout x y -> Grid (Renderable (LayoutPick x y y))+layoutToGrid l = grid+ where+ lp :: Grid a -> a -> Grid a+ lp = case maybe LegendBelow _legend_position $ _layout_legend l of+ LegendAbove -> flip wideAbove+ LegendBelow -> aboveWide+ LegendRight -> besideTall+ LegendLeft -> flip tallBeside++ title = titleToRenderable lm (_layout_title_style l) (_layout_title l)+ plotArea = addMarginsToGrid (lm,lm,lm,lm) (layoutPlotAreaToGrid l)+ legend = renderLegend l (getLegendItems l)+ grid = title `wideAbove` (plotArea `lp` legend)+ lm = _layout_margin l++getLayoutXVals :: Layout x y -> [x]+getLayoutXVals l = concatMap (fst . _plot_all_points) (_layout_plots l)++-- | Extract all 'LegendItem's from the plots of a 'Layout'.+getLegendItems :: Layout x y -> [LegendItem]+getLegendItems l = concat [ _plot_legend p | p <- _layout_plots l ]++-- | Render the given 'LegendItem's for a 'Layout'.+renderLegend :: Layout x y -> [LegendItem] -> Renderable (LayoutPick x y y)+renderLegend l legItems = gridToRenderable g+ where+ g = besideN [ tval $ mkLegend (_layout_legend l) (_layout_margin l) legItems+ , weights (1,1) $ tval emptyRenderable ]++-- | Render the plot area of a 'Layout'. This consists of the+-- actual plot area with all plots, the axis and their titles.+layoutPlotAreaToGrid :: forall x y. (Ord x, Ord y) =>+ Layout x y -> Grid (Renderable (LayoutPick x y y))+layoutPlotAreaToGrid l = buildGrid LayoutGridElements{+ lge_plots = mfill (_layout_plot_background l) $ plotsToRenderable l,+ lge_taxis = (tAxis,_laxis_title $ _layout_x_axis l, _laxis_title_style $ _layout_x_axis l),+ lge_baxis = (bAxis,_laxis_title $ _layout_x_axis l, _laxis_title_style $ _layout_x_axis l),+ lge_laxis = (lAxis,_laxis_title $ _layout_y_axis l, _laxis_title_style $ _layout_y_axis l),+ lge_raxis = (rAxis,"", def),+ lge_margin = _layout_margin l+ }+ where+ xvals = [ x | p <- _layout_plots l, x <- fst $ _plot_all_points p]+ yvals = [ y | p <- _layout_plots l, y <- snd $ _plot_all_points p]++ bAxis = mkAxis E_Bottom (overrideAxisVisibility l _layout_x_axis _layout_bottom_axis_visibility) xvals+ tAxis = mkAxis E_Top (overrideAxisVisibility l _layout_x_axis _layout_top_axis_visibility ) xvals+ lAxis = mkAxis E_Left (overrideAxisVisibility l _layout_y_axis _layout_left_axis_visibility ) yvals+ rAxis = mkAxis E_Right (overrideAxisVisibility l _layout_y_axis _layout_right_axis_visibility ) yvals+ axes = (bAxis,lAxis,tAxis,rAxis)++ plotsToRenderable lxy = Renderable {+ minsize = return (0,0),+ render = renderPlots lxy+ }++ -- | Render the plots of a 'Layout' to a plot area of given size.+ renderPlots :: Layout x y -> RectSize -> BackendProgram (PickFn (LayoutPick x y y))+ renderPlots lxy sz@(w,h) = do+ unless (_layout_grid_last lxy) (renderGrids sz axes)+ withClipRegion (Rect (Point 0 0) (Point w h)) $+ mapM_ rPlot (_layout_plots lxy)+ when (_layout_grid_last lxy) (renderGrids sz axes)+ return pickfn+ where+ rPlot = renderSinglePlot sz bAxis lAxis++ xr = (0, w)+ yr = (h, 0)++ pickfn :: PickFn (LayoutPick x y y)+ pickfn (Point x y) = do -- Maybe monad+ xat <- mxat+ yat <- myat+ return (LayoutPick_PlotArea (mapx xat x) (mapy yat y) (mapy yat y))+ where+ mxat = case (bAxis,tAxis) of+ (Just at,_) -> Just at+ (_,Just at) -> Just at+ (Nothing,Nothing) -> Nothing+ myat = case (lAxis,rAxis) of+ (Just at,_) -> Just at+ (_,Just at) -> Just at+ (Nothing,Nothing) -> Nothing+ mapx (AxisT _ _ rev ad) = _axis_tropweiv ad (optPairReverse rev xr)+ mapy (AxisT _ _ rev ad) = _axis_tropweiv ad (optPairReverse rev yr)++-- | Empty 'Layout' without title and plots. The background is white and+-- the grid is drawn beneath all plots. There will be a legend. The top+-- and right axis will not be visible.+instance (PlotValue x, PlotValue y) => Default (Layout x y) where+ def = Layout+ { _layout_background = solidFillStyle $ opaque white+ , _layout_plot_background = Nothing++ , _layout_title = ""+ , _layout_title_style = def { _font_size = 15+ , _font_weight = FontWeightBold }++ , _layout_x_axis = def+ , _layout_top_axis_visibility = def { _axis_show_line = False+ , _axis_show_ticks = False+ , _axis_show_labels = False }+ , _layout_bottom_axis_visibility = def+ , _layout_y_axis = def+ , _layout_left_axis_visibility = def+ , _layout_right_axis_visibility = def { _axis_show_line = False+ , _axis_show_ticks = False+ , _axis_show_labels = False }++ , _layout_margin = 10+ , _layout_plots = []+ , _layout_legend = Just def+ , _layout_grid_last = False+ }++----------------------------------------------------------------------++-- | A LayoutLR value is a single plot area, with an x axis and+-- independent left and right y axes, with a title at the top;+-- legend at the bottom. It's parametrized by the types of values+-- to be plotted on the x and two y axes.+data LayoutLR x y1 y2 = LayoutLR+ { _layoutlr_background :: FillStyle+ -- ^ How to fill the background of everything.+ , _layoutlr_plot_background :: Maybe FillStyle+ -- ^ How to fill the background of the plot,+ -- if different from the overall background.++ , _layoutlr_title :: String+ -- ^ Title to display above the chart.+ , _layoutlr_title_style :: FontStyle+ -- ^ Font style to use for the title.++ , _layoutlr_x_axis :: LayoutAxis x+ -- ^ Rules to generate the x axis.+ , _layoutlr_top_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the top axis.+ , _layoutlr_bottom_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the bottom axis.++ , _layoutlr_left_axis :: LayoutAxis y1+ -- ^ Rules to generate the left y axis.+ , _layoutlr_left_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the left axis.+ , _layoutlr_right_axis :: LayoutAxis y2+ -- ^ Rules to generate the right y axis.+ , _layoutlr_right_axis_visibility :: AxisVisibility+ -- ^ Visibility options for the right axis.++ , _layoutlr_plots :: [Either (Plot x y1) (Plot x y2)]+ -- ^ The data sets to plot in the chart.+ -- They are plotted over each other.+ -- The either type associates the plot with the+ -- left or right y axis.++ , _layoutlr_legend :: Maybe LegendStyle+ -- ^ How to style the legend.+ , _layoutlr_margin :: Double+ -- ^ The margin distance to use.+ , _layoutlr_grid_last :: Bool+ -- ^ If the grid shall be rendered+ -- beneath (@False@) or over (@True@) all plots.+ }++instance (Ord x, Ord yl, Ord yr) => ToRenderable (LayoutLR x yl yr) where+ toRenderable = setPickFn nullPickFn . layoutLRToRenderable++-- | Render the given 'LayoutLR'.+layoutLRToRenderable :: forall x yl yr . (Ord x, Ord yl, Ord yr)+ => LayoutLR x yl yr -> Renderable (LayoutPick x yl yr)+layoutLRToRenderable l = fillBackground (_layoutlr_background l)+ $ gridToRenderable (layoutLRToGrid l)++layoutLRToGrid :: forall x yl yr . (Ord x, Ord yl, Ord yr)+ => LayoutLR x yl yr -> Grid (Renderable (LayoutPick x yl yr))+layoutLRToGrid l = grid+ where+ grid = titleToRenderable lm (_layoutlr_title_style l) (_layoutlr_title l)+ `wideAbove`+ addMarginsToGrid (lm,lm,lm,lm) (layoutLRPlotAreaToGrid l)+ `aboveWide`+ renderLegendLR l (getLegendItemsLR l)+ lm = _layoutlr_margin l++getLayoutLRXVals :: LayoutLR x yl yr -> [x]+getLayoutLRXVals l = concatMap deEither $ _layoutlr_plots l+ where+ deEither :: Either (Plot x yl) (Plot x yr) -> [x]+ deEither (Left x) = fst $ _plot_all_points x+ deEither (Right x) = fst $ _plot_all_points x++-- | Extract all 'LegendItem's from the plots of a 'LayoutLR'.+-- Left and right plot legend items are still separated.+getLegendItemsLR :: LayoutLR x yl yr -> ([LegendItem],[LegendItem])+getLegendItemsLR l = (+ concat [ _plot_legend p | (Left p ) <- _layoutlr_plots l ],+ concat [ _plot_legend p | (Right p) <- _layoutlr_plots l ]+ )++-- | Render the given 'LegendItem's for a 'LayoutLR'.+renderLegendLR :: LayoutLR x yl yr -> ([LegendItem],[LegendItem]) -> Renderable (LayoutPick x yl yr)+renderLegendLR l (lefts,rights) = gridToRenderable g+ where+ g = besideN [ tval $ mkLegend (_layoutlr_legend l) (_layoutlr_margin l) lefts+ , weights (1,1) $ tval emptyRenderable+ , tval $ mkLegend (_layoutlr_legend l) (_layoutlr_margin l) rights ]+ -- lm = _layoutlr_margin l++layoutLRPlotAreaToGrid :: forall x yl yr. (Ord x, Ord yl, Ord yr)+ => LayoutLR x yl yr+ -> Grid (Renderable (LayoutPick x yl yr))+layoutLRPlotAreaToGrid l = buildGrid LayoutGridElements{+ lge_plots = mfill (_layoutlr_plot_background l) $ plotsToRenderable l,+ lge_taxis = (tAxis,_laxis_title $ _layoutlr_x_axis l, _laxis_title_style $ _layoutlr_x_axis l),+ lge_baxis = (bAxis,_laxis_title $ _layoutlr_x_axis l, _laxis_title_style $ _layoutlr_x_axis l),+ lge_laxis = (lAxis,_laxis_title $ _layoutlr_left_axis l, _laxis_title_style $ _layoutlr_left_axis l),+ lge_raxis = (rAxis,_laxis_title $ _layoutlr_right_axis l, _laxis_title_style $ _layoutlr_right_axis l),+ lge_margin = _layoutlr_margin l+ }+ where+ xvals = [ x | (Left p) <- _layoutlr_plots l, x <- fst $ _plot_all_points p]+ ++ [ x | (Right p) <- _layoutlr_plots l, x <- fst $ _plot_all_points p]+ yvalsL = [ y | (Left p) <- _layoutlr_plots l, y <- snd $ _plot_all_points p]+ yvalsR = [ y | (Right p) <- _layoutlr_plots l, y <- snd $ _plot_all_points p]++ bAxis = mkAxis E_Bottom (overrideAxisVisibility l _layoutlr_x_axis _layoutlr_bottom_axis_visibility) xvals+ tAxis = mkAxis E_Top (overrideAxisVisibility l _layoutlr_x_axis _layoutlr_top_axis_visibility ) xvals+ lAxis = mkAxis E_Left (overrideAxisVisibility l _layoutlr_left_axis _layoutlr_left_axis_visibility ) yvalsL+ rAxis = mkAxis E_Right (overrideAxisVisibility l _layoutlr_right_axis _layoutlr_right_axis_visibility) yvalsR+ axes = (bAxis,lAxis,tAxis,rAxis)++ plotsToRenderable llr = Renderable {+ minsize = return (0,0),+ render = renderPlots llr+ }++ renderPlots :: LayoutLR x yl yr -> RectSize -> BackendProgram (PickFn (LayoutPick x yl yr))+ renderPlots llr sz@(w,h) = do+ unless (_layoutlr_grid_last llr) (renderGrids sz axes)+ withClipRegion (Rect (Point 0 0) (Point w h)) $+ mapM_ rPlot (_layoutlr_plots llr)+ when (_layoutlr_grid_last llr) (renderGrids sz axes)+ return pickfn+ where+ rPlot (Left p) = renderSinglePlot sz bAxis lAxis p+ rPlot (Right p) = renderSinglePlot sz bAxis rAxis p++ xr = (0, w)+ yr = (h, 0)++ pickfn (Point x y) = do -- Maybe monad+ xat <- mxat+ (yatL,yatR) <- myats+ return (LayoutPick_PlotArea (mapx xat x) (mapy yatL y) (mapy yatR y))+ where+ mxat = case (bAxis,tAxis) of+ (Just at,_) -> Just at+ (_,Just at) -> Just at+ (Nothing,Nothing) -> Nothing+ myats = case (lAxis,rAxis) of+ (Just at1,Just at2) -> Just (at1,at2)+ (_,_) -> Nothing+ mapx (AxisT _ _ rev ad) = _axis_tropweiv ad (optPairReverse rev xr)+ mapy (AxisT _ _ rev ad) = _axis_tropweiv ad (optPairReverse rev yr)++----------------------------------------------------------------------++-- | A layout with its y type hidden, so that it can be stacked+-- with other layouts with differing y axis, but the same x axis.+-- See 'StackedLayouts'.+data StackedLayout x = forall y . (Ord y) => StackedLayout (Layout x y)+ -- ^ A 'Layout' to stack.+ | forall yl yr . (Ord yl, Ord yr) => StackedLayoutLR (LayoutLR x yl yr)+ -- ^ A 'LayoutLR' to stack.++-- | A container for a set of vertically 'StackedLayout's.+-- The x axis of the different layouts will be aligned.+data StackedLayouts x = StackedLayouts+ { _slayouts_layouts :: [StackedLayout x]+ -- ^ The stacked layouts from top (first element) to bottom (last element).+ , _slayouts_compress_legend :: Bool+ -- ^ If the different legends shall be combined in one legend at the bottom.+ }++-- | A empty 'StackedLayout' with compressions applied.+instance Default (StackedLayouts x) where+ def = StackedLayouts [] True++++instance Ord x => ToRenderable (StackedLayouts x) where+ toRenderable = renderStackedLayouts+++-- | Render several layouts with the same x-axis type and range,+-- vertically stacked so that their origins and x-values are aligned.+--+-- The legends from all the charts may be optionally combined, and shown+-- once on the bottom chart. See 'StackedLayouts' for further information.+renderStackedLayouts :: forall x. (Ord x) => StackedLayouts x -> Renderable ()+renderStackedLayouts (StackedLayouts{_slayouts_layouts=[]}) = emptyRenderable+renderStackedLayouts slp@(StackedLayouts{_slayouts_layouts=sls@(sl1:_)}) = gridToRenderable g+ where+ g = fullOverlayUnder (fillBackground bg emptyRenderable)+ $ foldr (above.mkGrid) nullt (zip sls [0,1..])++ mkGrid :: (StackedLayout x, Int) -> Grid (Renderable ())+ mkGrid (sl, i)+ = titleR+ `wideAbove`+ addMarginsToGrid (lm,lm,lm,lm) (mkPlotArea usedAxis)+ `aboveWide`+ (if showLegend then legendR else emptyRenderable)+ where+ titleR = case sl of+ StackedLayout l -> noPickFn $ titleToRenderable (_layout_margin l) (_layout_title_style l) (_layout_title l)+ StackedLayoutLR l -> noPickFn $ titleToRenderable (_layoutlr_margin l) (_layoutlr_title_style l) (_layoutlr_title l)+ legendR = case sl of+ StackedLayout l -> noPickFn $ renderLegend l $ fst legenditems+ StackedLayoutLR l -> noPickFn $ renderLegendLR l legenditems++ legenditems = case (_slayouts_compress_legend slp,isBottomPlot) of+ (False,_) -> case sl of+ StackedLayout l -> (getLegendItems l, [])+ StackedLayoutLR l -> getLegendItemsLR l+ (True,True) -> allLegendItems+ (True,False) -> ([],[])++ mkPlotArea :: LayoutAxis x -> Grid (Renderable ())+ mkPlotArea axis = case sl of+ StackedLayout l -> fmap noPickFn+ $ layoutPlotAreaToGrid+ $ l { _layout_x_axis = axis }+ StackedLayoutLR l -> fmap noPickFn+ $ layoutLRPlotAreaToGrid+ $ l { _layoutlr_x_axis = axis }++ showLegend = not (null (fst legenditems)) || not (null (snd legenditems))++ isBottomPlot = i == length sls - 1++ lm = case sl of+ StackedLayout l -> _layout_margin l+ StackedLayoutLR l -> _layoutlr_margin l++ xAxis :: LayoutAxis x+ xAxis = case sl of+ StackedLayout l -> _layout_x_axis l+ StackedLayoutLR l -> _layoutlr_x_axis l++ usedAxis :: LayoutAxis x+ usedAxis = xAxis+ { _laxis_generate = const (_laxis_generate xAxis all_xvals) }++ bg = case sl1 of+ StackedLayout l -> _layout_background l+ StackedLayoutLR l -> _layoutlr_background l++ getXVals :: StackedLayout x -> [x]+ getXVals (StackedLayout l) = getLayoutXVals l+ getXVals (StackedLayoutLR l) = getLayoutLRXVals l++ all_xvals = concatMap getXVals sls++ allLegendItems = (concatMap (fst.legendItems) sls, concatMap (snd.legendItems) sls)++ legendItems :: StackedLayout x -> ([LegendItem], [LegendItem])+ legendItems (StackedLayout l) = (getLegendItems l, [])+ legendItems (StackedLayoutLR l) = getLegendItemsLR l++ noPickFn :: Renderable a -> Renderable ()+ noPickFn = mapPickFn (const ())++----------------------------------------------------------------------++addMarginsToGrid :: (Double,Double,Double,Double) -> Grid (Renderable a)+ -> Grid (Renderable a)+addMarginsToGrid (t,b,l,r) g = aboveN [+ besideN [er, ts, er],+ besideN [ls, g, rs],+ besideN [er, bs, er]+ ]+ where+ er = empty+ ts = tval $ spacer (0,t)+ ls = tval $ spacer (l,0)+ bs = tval $ spacer (0,b)+ rs = tval $ spacer (r,0)++titleToRenderable :: Double -> FontStyle -> String -> Renderable (LayoutPick x yl yr)+titleToRenderable _ _ "" = emptyRenderable+titleToRenderable lm fs s = addMargins (lm/2,0,0,0) (mapPickFn LayoutPick_Title title)+ where+ title = label fs HTA_Centre VTA_Centre s++mkLegend :: Maybe LegendStyle -> Double -> [LegendItem] -> Renderable (LayoutPick x yl yr)+mkLegend mls lm vals = case mls of+ Nothing -> emptyRenderable+ Just ls -> case filter ((/="").fst) vals of+ [] -> emptyRenderable ;+ lvs -> addMargins (0,lm,lm,lm) $+ mapPickFn LayoutPick_Legend $ legendToRenderable (Legend ls lvs)+++data LayoutGridElements x yl yr = LayoutGridElements {+ lge_plots :: Renderable (LayoutPick x yl yr),++ lge_taxis :: (Maybe (AxisT x),String,FontStyle),+ lge_baxis :: (Maybe (AxisT x),String,FontStyle),+ lge_laxis :: (Maybe (AxisT yl),String,FontStyle),+ lge_raxis :: (Maybe (AxisT yr),String,FontStyle),++ lge_margin :: Double+}++buildGrid :: (Ord x, Ord yl, Ord yr) => LayoutGridElements x yl yr -> Grid (Renderable (LayoutPick x yl yr))+buildGrid lge = layer2 `overlay` layer1+ where+ layer1 = aboveN+ [ besideN [er, er, er, er ]+ , besideN [er, er, er, weights (1,1) plots ]+ ]++ layer2 = aboveN+ [ besideN [er, er, tl, taxis, tr, er, er ]+ , besideN [ltitle, lam, laxis, er, raxis, ram, rtitle ]+ , besideN [er, er, bl, baxis, br, er, er ]+ , besideN [er, er, er, btitle, er, er, er ]+ ]++ er = tval emptyRenderable++ plots = tval $ lge_plots lge++ (tdata,_,_) = lge_taxis lge+ (bdata,blbl,bstyle) = lge_baxis lge+ (ldata,llbl,lstyle) = lge_laxis lge+ (rdata,rlbl,rstyle) = lge_raxis lge++ -- (ttitle,_) = mktitle HTA_Centre VTA_Bottom 0 tlbl tstyle LayoutPick_XTopAxisTitle+ (btitle,_) = mktitle HTA_Centre VTA_Top 0 blbl bstyle LayoutPick_XBottomAxisTitle+ (ltitle,lam) = mktitle HTA_Right VTA_Centre 270 llbl lstyle LayoutPick_YLeftAxisTitle+ (rtitle,ram) = mktitle HTA_Left VTA_Centre 270 rlbl rstyle LayoutPick_YRightAxisTitle++ baxis = tval $ maybe emptyRenderable+ (mapPickFn LayoutPick_XBottomAxis . axisToRenderable) bdata+ taxis = tval $ maybe emptyRenderable+ (mapPickFn LayoutPick_XTopAxis . axisToRenderable) tdata+ laxis = tval $ maybe emptyRenderable+ (mapPickFn LayoutPick_YLeftAxis . axisToRenderable) ldata+ raxis = tval $ maybe emptyRenderable+ (mapPickFn LayoutPick_YRightAxis . axisToRenderable) rdata++ tl = tval $ axesSpacer fst tdata fst ldata+ bl = tval $ axesSpacer fst bdata snd ldata+ tr = tval $ axesSpacer snd tdata fst rdata+ br = tval $ axesSpacer snd bdata snd rdata++ mktitle :: HTextAnchor -> VTextAnchor+ -> Double+ -> String -> FontStyle+ -> (String -> LayoutPick x yl yr)+ -> ( Grid (Renderable (LayoutPick x yl yr))+ , Grid (Renderable (LayoutPick x yl yr)) )+ mktitle ha va rot lbl style pf = if lbl == "" then (er,er) else (labelG,gapG)+ where+ labelG = tval $ mapPickFn pf $ rlabel style ha va rot lbl+ gapG = tval $ spacer (lge_margin lge,0)++-- | Render the grids of the given axis to a plot area of given size.+renderGrids :: RectSize -> (Maybe (AxisT x), Maybe (AxisT yl), Maybe (AxisT x), Maybe (AxisT yr)) -> BackendProgram ()+renderGrids sz (bAxis, lAxis, tAxis, rAxis) = do+ maybeM () (renderAxisGrid sz) tAxis+ maybeM () (renderAxisGrid sz) bAxis+ maybeM () (renderAxisGrid sz) lAxis+ maybeM () (renderAxisGrid sz) rAxis++-- | Swap the contents of the pair depending on the flag.+optPairReverse :: Bool -> (a,a) -> (a,a)+optPairReverse rev (a,b) = if rev then (b,a) else (a,b)++-- | Render a single set of plot data onto a plot area of given size using+-- the given x and y axis.+renderSinglePlot :: RectSize -> Maybe (AxisT x) -> Maybe (AxisT y) -> Plot x y -> BackendProgram ()+renderSinglePlot (w, h) (Just (AxisT _ _ xrev xaxis)) (Just (AxisT _ _ yrev yaxis)) p =+ let xr = optPairReverse xrev (0, w)+ yr = optPairReverse yrev (h, 0)+ -- yrange = if yrev then (0, h) else (h, 0)+ pmfn (x,y) = Point (mapv xr (_axis_viewport xaxis xr) x)+ (mapv yr (_axis_viewport yaxis yr) y)+ mapv lims _ LMin = fst lims+ mapv lims _ LMax = snd lims+ mapv _ f (LValue v) = f v+ in _plot_render p pmfn+renderSinglePlot _ _ _ _ = return ()++axesSpacer :: (Ord x, Ord y)+ => ((Double, Double) -> Double) -> Maybe (AxisT x)+ -> ((Double, Double) -> Double) -> Maybe (AxisT y)+ -> Renderable a+axesSpacer f1 a1 f2 a2 = embedRenderable $ do+ oh1 <- maybeM (0,0) axisOverhang a1+ oh2 <- maybeM (0,0) axisOverhang a2+ return (spacer (f1 oh1, f2 oh2))++-- | Construct a axis for the given edge using the attributes+-- from a 'LayoutAxis' the given values.+mkAxis :: RectEdge -> LayoutAxis z -> [z] -> Maybe (AxisT z)+mkAxis edge laxis vals = if axisVisible+ then Just $ AxisT edge style rev adata+ else Nothing+ where+ style = _laxis_style laxis+ rev = _laxis_reverse laxis+ adata = _laxis_override laxis (_laxis_generate laxis vals)+ vis = _axis_visibility adata+ axisVisible = _axis_show_labels vis || _axis_show_line vis || _axis_show_ticks vis++-- | Override the visibility of a selected axis with the selected 'AxisVisibility'.+overrideAxisVisibility :: layout+ -> (layout -> LayoutAxis z)+ -> (layout -> AxisVisibility)+ -> LayoutAxis z+overrideAxisVisibility ly selAxis selVis =+ let vis = selVis ly+ in (selAxis ly) { _laxis_override = (\ad -> ad { _axis_visibility = vis })+ . _laxis_override (selAxis ly)+ }++mfill :: Maybe FillStyle -> Renderable a -> Renderable a+mfill Nothing = id+mfill (Just fs) = fillBackground fs++-- | Empty 'LayoutLR' without title and plots. The background is white and+-- the grid is drawn beneath all plots. There will be a legend. The top+-- axis will not be visible.+instance (PlotValue x, PlotValue y1, PlotValue y2) => Default (LayoutLR x y1 y2) where+ def = LayoutLR+ { _layoutlr_background = solidFillStyle $ opaque white+ , _layoutlr_plot_background = Nothing++ , _layoutlr_title = ""+ , _layoutlr_title_style = def { _font_size = 15+ , _font_weight = FontWeightBold }++ , _layoutlr_x_axis = def+ , _layoutlr_top_axis_visibility = def { _axis_show_line = False+ , _axis_show_ticks = False+ , _axis_show_labels = False }+ , _layoutlr_bottom_axis_visibility = def++ , _layoutlr_left_axis = def+ , _layoutlr_left_axis_visibility = def+ , _layoutlr_right_axis = def+ , _layoutlr_right_axis_visibility = def++ , _layoutlr_plots = []++ , _layoutlr_legend = Just def+ , _layoutlr_margin = 10+ , _layoutlr_grid_last = False+ }++instance PlotValue t => Default (LayoutAxis t) where+ def = LayoutAxis+ { _laxis_title_style = def { _font_size=10 }+ , _laxis_title = ""+ , _laxis_style = def+ , _laxis_generate = autoAxis+ , _laxis_override = id+ , _laxis_reverse = False+ }++----------------------------------------------------------------------+-- Template haskell to derive an instance of Data.Accessor.Accessor+-- for each field.+$( makeLenses ''Layout )+$( makeLenses ''LayoutLR )+$( makeLenses ''LayoutAxis )+$( makeLenses ''StackedLayouts )++-- | Setter to update all axis styles on a `Layout`+layout_axes_styles :: Setter' (Layout x y) AxisStyle+layout_axes_styles = sets $ \af ->+ (layout_x_axis . laxis_style %~ af) .+ (layout_y_axis . laxis_style %~ af)++-- | Setter to update all the axes title styles on a `Layout`+layout_axes_title_styles :: Setter' (Layout x y) FontStyle+layout_axes_title_styles = sets $ \af ->+ (layout_x_axis . laxis_title_style %~ af) .+ (layout_y_axis . laxis_title_style %~ af)++-- | Setter to update all the font styles on a `Layout`+layout_all_font_styles :: Setter' (Layout x y) FontStyle+layout_all_font_styles = sets $ \af ->+ (layout_axes_title_styles %~ af) .+ (layout_x_axis . laxis_style . axis_label_style %~ af) .+ (layout_y_axis . laxis_style . axis_label_style %~ af) .+ (layout_legend . _Just . legend_label_style %~ af) .+ (layout_title_style %~ af)++-- | Setter to update the foreground color of core chart elements on a `Layout`+layout_foreground :: Setter' (Layout x y) (AlphaColour Double)+layout_foreground = sets $ \af ->+ (layout_all_font_styles . font_color %~ af) .+ (layout_axes_styles . axis_line_style . line_color %~ af)++-- | Setter to update all axis styles on a `LayoutLR`+layoutlr_axes_styles :: Setter' (LayoutLR x y1 y2) AxisStyle+layoutlr_axes_styles = sets $ \af ->+ (layoutlr_x_axis . laxis_style %~ af) .+ (layoutlr_left_axis . laxis_style %~ af) .+ (layoutlr_right_axis . laxis_style %~ af)++-- | Setter to update all the axes title styles on a `LayoutLR`+layoutlr_axes_title_styles :: Setter' (LayoutLR x y1 y2) FontStyle+layoutlr_axes_title_styles = sets $ \af ->+ (layoutlr_x_axis . laxis_title_style %~ af) .+ (layoutlr_left_axis . laxis_title_style %~ af) .+ (layoutlr_right_axis . laxis_title_style %~ af)++-- | Setter to update all the font styles on a `LayoutLR`+layoutlr_all_font_styles :: Setter' (LayoutLR x y1 y2) FontStyle+layoutlr_all_font_styles = sets $ \af ->+ (layoutlr_axes_title_styles %~ af) .+ (layoutlr_x_axis . laxis_style . axis_label_style %~ af) .+ (layoutlr_left_axis . laxis_style . axis_label_style %~ af) .+ (layoutlr_right_axis . laxis_style . axis_label_style %~ af) .+ (layoutlr_legend . _Just . legend_label_style %~ af) .+ (layoutlr_title_style %~ af)++-- | Setter to update the foreground color of core chart elements on a `LayoutLR`+layoutlr_foreground :: Setter' (LayoutLR x y1 y2) (AlphaColour Double)+layoutlr_foreground = sets $ \af ->+ (layoutlr_all_font_styles . font_color %~ af) .+ (layoutlr_axes_styles . axis_line_style . line_color %~ af)
Graphics/Rendering/Chart/Legend.hs view
@@ -1,28 +1,32 @@ ----------------------------------------------------------------------------- -- | -- Module : Graphics.Rendering.Chart.Legend--- Copyright : (c) Tim Docker 2006+-- Copyright : (c) Tim Docker 2006, 2014 -- License : BSD-style (see chart/COPYRIGHT)--{-# OPTIONS_GHC -XTemplateHaskell #-}+-- +-- Types and functions for handling the legend(s) on a chart. A legend+-- is an area on the chart used to label the plotted values.+{-# LANGUAGE TemplateHaskell #-} module Graphics.Rendering.Chart.Legend( Legend(..), LegendStyle(..),- defaultLegendStyle,+ LegendOrientation(..),+ LegendPosition(..), legendToRenderable, legend_label_style, legend_margin, legend_plot_size,+ legend_orientation,+ legend_position ) where -import qualified Graphics.Rendering.Cairo as C-import Control.Monad-import Data.List (nub, partition,intersperse)-import Data.Accessor.Template+import Data.List (partition,intersperse)+import Control.Lens+import Data.Default.Class -import Graphics.Rendering.Chart.Types-import Graphics.Rendering.Chart.Plot+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing import Graphics.Rendering.Chart.Renderable import Graphics.Rendering.Chart.Grid @@ -30,51 +34,92 @@ -- Legend data LegendStyle = LegendStyle {- legend_label_style_ :: CairoFontStyle,- legend_margin_ :: Double,- legend_plot_size_ :: Double+ _legend_label_style :: FontStyle,+ _legend_margin :: Double,+ _legend_plot_size :: Double,+ _legend_orientation :: LegendOrientation,+ _legend_position :: LegendPosition } -data Legend x y = Legend Bool LegendStyle [(String, Rect -> CRender ())]+-- | Legends can be constructed in two orientations: in rows+-- (where we specify the maximum number of columns), and in+-- columns (where we specify the maximum number of rows)+data LegendOrientation = LORows Int+ | LOCols Int +-- | Defines the position of the legend, relative to the plot.+data LegendPosition = LegendAbove+ | LegendBelow+ | LegendRight+ | LegendLeft++data Legend x y = Legend LegendStyle [(String, Rect -> BackendProgram ())]+ instance ToRenderable (Legend x y) where- toRenderable = setPickFn nullPickFn.legendToRenderable+ toRenderable = setPickFn nullPickFn . legendToRenderable legendToRenderable :: Legend x y -> Renderable String-legendToRenderable (Legend _ ls lvs) = gridToRenderable grid+legendToRenderable (Legend ls lvs) = gridToRenderable grid where- grid = besideN $ intersperse ggap1 (map (tval.rf) ps)+ grid :: Grid (Renderable String)+ grid = case _legend_orientation ls of+ LORows n -> mkGrid n aboveG besideG+ LOCols n -> mkGrid n besideG aboveG - ps :: [(String, [Rect -> CRender ()])]- ps = join_nub lvs+ aboveG, besideG :: [Grid (Renderable String)] -> Grid (Renderable String)+ aboveG = aboveN.intersperse ggap1+ besideG = besideN.intersperse ggap1 - rf (title,rfs) = gridToRenderable grid1+ mkGrid :: Int+ -> ([Grid (Renderable String)] -> Grid (Renderable String))+ -> ([Grid (Renderable String)] -> Grid (Renderable String))+ -> Grid (Renderable String)+ mkGrid n join1 join2 = join1 [ join2 (map rf ps1) | ps1 <- groups n ps ]++ ps :: [(String, [Rect -> BackendProgram ()])]+ ps = join_nub lvs++ rf :: (String, [Rect -> BackendProgram ()]) -> Grid (Renderable String)+ rf (title,rfs) = besideN [gpic,ggap2,gtitle] where- grid1 = besideN $ intersperse ggap2 (map rp rfs) ++ [ggap2,gtitle]+ gpic :: Grid (Renderable String)+ gpic = besideN $ intersperse ggap2 (map rp rfs)++ gtitle :: Grid (Renderable String) gtitle = tval $ lbl title- rp rfn = tval $ Renderable {- minsize = return (legend_plot_size_ ls, 0),- render = \(w,h) -> do - rfn (Rect (Point 0 0) (Point w h))- return nullPickFn- } - ggap1 = tval $ spacer (legend_margin_ ls,0)+ rp :: (Rect -> BackendProgram ()) -> Grid (Renderable String)+ rp rfn = tval Renderable {+ minsize = return (_legend_plot_size ls, 0),+ render = \(w,h) -> do+ _ <- rfn (Rect (Point 0 0) (Point w h))+ return (\_-> Just title)+ }++ ggap1, ggap2 :: Grid (Renderable String)+ ggap1 = tval $ spacer (_legend_margin ls,_legend_margin ls / 2) ggap2 = tval $ spacer1 (lbl "X")- lbl s = label (legend_label_style_ ls) HTA_Centre VTA_Centre s + lbl :: String -> Renderable String+ lbl = label (_legend_label_style ls) HTA_Left VTA_Centre++groups :: Int -> [a] -> [[a]]+groups _ [] = []+groups n vs = let (vs1,vs2) = splitAt n vs in vs1:groups n vs2+ join_nub :: [(String, a)] -> [(String, [a])] join_nub ((x,a1):ys) = case partition ((==x) . fst) ys of (xs, rest) -> (x, a1:map snd xs) : join_nub rest-join_nub [] = []+join_nub [] = [] -defaultLegendStyle = LegendStyle {- legend_label_style_=defaultFontStyle,- legend_margin_=20,- legend_plot_size_=20-}+instance Default LegendStyle where+ def = LegendStyle+ { _legend_label_style = def+ , _legend_margin = 20+ , _legend_plot_size = 20+ , _legend_orientation = LORows 4+ , _legend_position = LegendBelow+ } -------------------------------------------------------------------------- Template haskell to derive an instance of Data.Accessor.Accessor for each field-$( deriveAccessors ''LegendStyle )+$( makeLenses ''LegendStyle )
− Graphics/Rendering/Chart/Pie.hs
@@ -1,219 +0,0 @@-{-# OPTIONS_GHC -XTemplateHaskell #-} ------------------------------------------------------------------------------ --- | --- Module : Graphics.Rendering.Chart.Layout --- Copyright : (c) Tim Docker 2008 --- License : BSD-style (see chart/COPYRIGHT) --- --- A basic pie chart. --- --- Note that template haskell is used to derive accessor functions --- (see 'Data.Accessor') for each field of the following data types: --- --- * 'PieLayout' --- --- * 'PieChart' --- --- * 'PieItem' --- --- These accessors are not shown in this API documentation. They have --- the same name as the field, but with the trailing underscore --- dropped. Hence for data field f_::F in type D, they have type --- --- @ --- f :: Data.Accessor.Accessor D F --- @ --- - - -module Graphics.Rendering.Chart.Pie( - PieLayout(..), - PieChart(..), - PieItem(..), - defaultPieLayout, - defaultPieChart, - defaultPieItem, - - pie_title, - pie_title_style, - pie_plot, - pie_background, - pie_margin, - pie_data, - pie_colors, - pie_label_style, - pie_label_line_style, - pie_start_angle, - pitem_label, - pitem_offset, - pitem_value, - -) where --- original code thanks to Neal Alexander - -import qualified Graphics.Rendering.Cairo as C - -import Data.List -import Data.Bits -import Data.Accessor.Template -import Data.Colour -import Data.Colour.Names (black, white) -import Control.Monad - -import Graphics.Rendering.Chart.Types -import Graphics.Rendering.Chart.Legend -import Graphics.Rendering.Chart.Renderable -import Graphics.Rendering.Chart.Grid - -data PieLayout = PieLayout { - pie_title_ :: String, - pie_title_style_ :: CairoFontStyle, - pie_plot_ :: PieChart, - pie_background_ :: CairoFillStyle, - pie_margin_ :: Double -} - -data PieChart = PieChart { - pie_data_ :: [PieItem], - pie_colors_ :: [AlphaColour Double], - pie_label_style_ :: CairoFontStyle, - pie_label_line_style_ :: CairoLineStyle, - pie_start_angle_ :: Double - -} - -data PieItem = PieItem { - pitem_label_ :: String, - pitem_offset_ :: Double, - pitem_value_ :: Double -} - -defaultPieChart = PieChart { - pie_data_ = [], - pie_colors_ = defaultColorSeq, - pie_label_style_ = defaultFontStyle, - pie_label_line_style_ = solidLine 1 $ opaque black, - pie_start_angle_ = 0 -} - -defaultPieItem = PieItem "" 0 0 - -defaultPieLayout = PieLayout { - pie_background_ = solidFillStyle $ opaque white, - pie_title_ = "", - pie_title_style_ = defaultFontStyle{font_size_=15, font_weight_=C.FontWeightBold}, - pie_plot_ = defaultPieChart, - pie_margin_ = 10 -} - -instance ToRenderable PieLayout where - toRenderable p = fillBackground (pie_background_ p) ( - gridToRenderable $ aboveN [ - tval $ addMargins (lm/2,0,0,0) title, - weights (1,1) $ tval $ addMargins (lm,lm,lm,lm) (toRenderable $ pie_plot_ p) - ] ) - where - title = label (pie_title_style_ p) HTA_Centre VTA_Top (pie_title_ p) - lm = pie_margin_ p - -instance ToRenderable PieChart where - toRenderable p = Renderable { - minsize=minsizePie p, - render=renderPie p - } - -extraSpace p = do - textSizes <- mapM textSize (map pitem_label_ (pie_data_ p)) - let maxw = foldr (max.fst) 0 textSizes - let maxh = foldr (max.snd) 0 textSizes - let maxo = foldr (max.pitem_offset_) 0 (pie_data_ p) - let extra = label_rgap + label_rlength + maxo - return (extra + maxw, extra + maxh ) - -minsizePie p = do - (extraw,extrah) <- extraSpace p - return (extraw * 2, extrah * 2) - -renderPie p (w,h) = do - (extraw,extrah) <- extraSpace p - let (w,h) = (p_x p2 - p_x p1, p_y p2 - p_y p1) - let center = Point (p_x p1 + w/2) (p_y p1 + h/2) - let radius = (min (w - 2*extraw) (h - 2*extrah)) / 2 - - foldM_ (paint center radius) (pie_start_angle_ p) (zip (pie_colors_ p) content) - return nullPickFn - - where - p1 = Point 0 0 - p2 = Point w h - content = let total = sum (map pitem_value_ (pie_data_ p)) - in [ pi{pitem_value_=pitem_value_ pi/total} | pi <- pie_data_ p ] - - paint :: Point -> Double -> Double -> (AlphaColour Double, PieItem) -> CRender Double - paint center radius a1 (color,pitem) = do - let ax = 360.0 * (pitem_value_ pitem) - let a2 = a1 + (ax / 2) - let a3 = a1 + ax - let offset = pitem_offset_ pitem - - pieSlice (ray a2 offset) a1 a3 color - pieLabel (pitem_label_ pitem) a2 offset - - return a3 - - where - pieLabel :: String -> Double -> Double -> CRender () - pieLabel name angle offset = do - setFontStyle (pie_label_style_ p) - setLineStyle (pie_label_line_style_ p) - - moveTo (ray angle (radius + label_rgap+offset)) - let p1 = (ray angle (radius + label_rgap + label_rlength+offset)) - lineTo p1 - (tw,th) <- textSize name - let (offset,anchor) = if angle < 90 || angle > 270 - then ((0+),HTA_Left) - else ((0-),HTA_Right) - c $ C.relLineTo (offset (tw + label_rgap)) 0 - c $ C.stroke - - let p2 = p1 `pvadd` (Vector (offset label_rgap) 0) - drawText anchor VTA_Bottom p2 name - - pieSlice :: Point -> Double -> Double -> AlphaColour Double -> CRender () - pieSlice (Point x y) a1 a2 color = c $ do - C.newPath - C.arc x y radius (radian a1) (radian a2) - C.lineTo x y - C.lineTo x y - C.closePath - - setSourceColor color - C.fillPreserve - C.setSourceRGBA 1 1 1 0.1 - - C.stroke - - ray :: Double -> Double -> Point - ray angle r = Point x' y' - where - x' = x + (cos' * x'') - y' = y + (sin' * x'') - cos' = (cos . radian) angle - sin' = (sin . radian) angle - x'' = ((x + r) - x) - x = p_x center - y = p_y center - - radian = (*(pi / 180.0)) - - -label_rgap = 5 -label_rlength = 15 - ----------------------------------------------------------------------- --- Template haskell to derive an instance of Data.Accessor.Accessor for each field -$( deriveAccessors ''PieLayout ) -$( deriveAccessors ''PieChart ) -$( deriveAccessors ''PieItem ) -
Graphics/Rendering/Chart/Plot.hs view
@@ -4,525 +4,35 @@ -- Copyright : (c) Tim Docker 2006 -- License : BSD-style (see chart/COPYRIGHT) ----- Definitions of various types of Plots we can put on a 2D Chart.------ Note that template haskell is used to derive accessor functions--- (see 'Data.Accessor') for each field of the following data types:------ * 'Plot'------ * 'PlotLines'------ * 'PlotPoints'------ * 'PlotFillBetween'------ * 'PlotErrBars'--- --- * 'PlotBars'------ These accessors are not shown in this API documentation. They have--- the same name as the field, but with the trailing underscore--- dropped. Hence for data field f_::F in type D, they have type------ @--- f :: Data.Accessor.Accessor D F--- @+-- Code to calculate and render various types of plots. -- --{-# OPTIONS_GHC -XTemplateHaskell #-}- module Graphics.Rendering.Chart.Plot(- Plot(..),- ToPlot(..),- PlotPoints(..),- PlotErrBars(..),- PlotLines(..),- PlotFillBetween(..),- ErrPoint(..),- PlotBars(..),- PlotBarsStyle(..),- PlotBarsSpacing(..),-- symErrPoint,-- defaultPlotLineStyle,- defaultPlotPoints,- defaultPlotErrBars,- defaultPlotFillBetween,- defaultPlotLines,- defaultPlotBars,-- plot_lines_title,- plot_lines_style,- plot_lines_values,- plot_lines_limit_values,-- hlinePlot,- vlinePlot,-- plot_render,- plot_legend,- plot_all_points,-- plot_points_title,- plot_points_style,- plot_points_values,-- plot_fillbetween_title,- plot_fillbetween_style,- plot_fillbetween_values,-- plot_errbars_title,- plot_errbars_line_style,- plot_errbars_tick_length,- plot_errbars_overhang,- plot_errbars_values,-- plotBars,- plot_bars_style,- plot_bars_item_styles,- plot_bars_titles,- plot_bars_spacing,- plot_bars_reference,- plot_bars_values,-- ) where--import qualified Graphics.Rendering.Cairo as C-import Graphics.Rendering.Chart.Types-import Graphics.Rendering.Chart.Axis-import Control.Monad-import Data.List-import Data.Accessor.Template-import Data.Colour-import Data.Colour.SRGB (sRGB)-import Data.Colour.Names---- | Interface to control plotting on a 2D area.-data Plot x y = Plot {-- -- | Given the mapping between model space coordinates and device coordinates,- -- render this plot into a chart.- plot_render_ :: PointMapFn x y -> CRender (),-- -- | Details for how to show this plot in a legend. For each item- -- the string is the text to show, and the function renders a- -- graphical sample of the plot- plot_legend_ :: [ (String, Rect -> CRender ()) ],-- -- | All of the model space coordinates to be plotted. These are- -- used to autoscale the axes where necessary.- plot_all_points_ :: ([x],[y])-}---- | a type class abstracting the conversion of a value to a Plot.-class ToPlot a where- toPlot :: a x y -> Plot x y--------------------------------------------------------------------------mapXY :: PointMapFn x y -> ((x,y) -> Point)-mapXY f (x,y) = f (LValue x, LValue y)---------------------------------------------------------------------------- | Value defining a series of (possibly disjointed) lines,--- and a style in which to render them-data PlotLines x y = PlotLines {- plot_lines_title_ :: String,- plot_lines_style_ :: CairoLineStyle,- plot_lines_values_ :: [[(x,y)]],- plot_lines_limit_values_ :: [[(Limit x, Limit y)]]-}---instance ToPlot PlotLines where- toPlot p = Plot {- plot_render_ = renderPlotLines p,- plot_legend_ = [(plot_lines_title_ p, renderPlotLegendLines p)],- plot_all_points_ = (map fst pts ++ xs , map snd pts ++ ys)- }- where- pts = concat (plot_lines_values_ p)- xs = [ x | (LValue x,_) <- concat (plot_lines_limit_values_ p)]- ys = [ y | (_,LValue y) <- concat (plot_lines_limit_values_ p)]--renderPlotLines :: PlotLines x y -> PointMapFn x y -> CRender ()-renderPlotLines p pmap = preserveCState $ do- setLineStyle (plot_lines_style_ p)- mapM_ (drawLines (mapXY pmap)) (plot_lines_values_ p)- mapM_ (drawLines pmap) (plot_lines_limit_values_ p)- where- drawLines pmap (p:ps) = do- moveTo (pmap p)- mapM_ (\p -> lineTo (pmap p)) ps- c $ C.stroke--renderPlotLegendLines :: PlotLines x y -> Rect -> CRender ()-renderPlotLegendLines p r@(Rect p1 p2) = preserveCState $ do- setLineStyle (plot_lines_style_ p)- let y = (p_y p1 + p_y p2) / 2- moveTo (Point (p_x p1) y)- lineTo (Point (p_x p2) y)- c $ C.stroke--defaultPlotLineStyle = (solidLine 1 $ opaque blue){- line_cap_ = C.LineCapRound,- line_join_ = C.LineJoinRound- }--defaultPlotLines = PlotLines {- plot_lines_title_ = "",- plot_lines_style_ = defaultPlotLineStyle,- plot_lines_values_ = [],- plot_lines_limit_values_ = []-}---- | Helper function to plot a single horizontal line-hlinePlot :: String -> CairoLineStyle -> b -> Plot a b-hlinePlot t ls v = toPlot defaultPlotLines {- plot_lines_title_=t,- plot_lines_style_=ls,- plot_lines_limit_values_=[[(LMin, LValue v),(LMax, LValue v)]]- }---- | Helper function to plot a single vertical line-vlinePlot :: String -> CairoLineStyle -> a -> Plot a b-vlinePlot t ls v = toPlot defaultPlotLines {- plot_lines_title_=t,- plot_lines_style_=ls,- plot_lines_limit_values_=[[(LValue v,LMin),(LValue v,LMax)]]- }---------------------------------------------------------------------------- | Value defining a series of datapoints, and a style in--- which to render them-data PlotPoints x y = PlotPoints {- plot_points_title_ :: String,- plot_points_style_ :: CairoPointStyle,- plot_points_values_ :: [(x,y)]-}---instance ToPlot PlotPoints where- toPlot p = Plot {- plot_render_ = renderPlotPoints p,- plot_legend_ = [(plot_points_title_ p, renderPlotLegendPoints p)],- plot_all_points_ = (map fst pts, map snd pts)- }- where- pts = plot_points_values_ p--renderPlotPoints :: PlotPoints x y -> PointMapFn x y -> CRender ()-renderPlotPoints p pmap = preserveCState $ do- mapM_ (drawPoint.pmap') (plot_points_values_ p)- where- pmap' = mapXY pmap- (CairoPointStyle drawPoint) = (plot_points_style_ p)---renderPlotLegendPoints :: PlotPoints x y -> Rect -> CRender ()-renderPlotLegendPoints p r@(Rect p1 p2) = preserveCState $ do- drawPoint (Point (p_x p1) ((p_y p1 + p_y p2)/2))- drawPoint (Point ((p_x p1 + p_x p2)/2) ((p_y p1 + p_y p2)/2))- drawPoint (Point (p_x p2) ((p_y p1 + p_y p2)/2))-- where- (CairoPointStyle drawPoint) = (plot_points_style_ p)--defaultPlotPoints = PlotPoints {- plot_points_title_ = "",- plot_points_style_ =defaultPointStyle,- plot_points_values_ = []-}-------------------------------------------------------------------------- | Value specifying a plot filling the area between two sets of Y--- coordinates, given common X coordinates.--data PlotFillBetween x y = PlotFillBetween {- plot_fillbetween_title_ :: String,- plot_fillbetween_style_ :: CairoFillStyle,- plot_fillbetween_values_ :: [ (x, (y,y))]-}---instance ToPlot PlotFillBetween where- toPlot p = Plot {- plot_render_ = renderPlotFillBetween p,- plot_legend_ = [(plot_fillbetween_title_ p, renderPlotLegendFill p)],- plot_all_points_ = plotAllPointsFillBetween p- }--renderPlotFillBetween :: PlotFillBetween x y -> PointMapFn x y -> CRender ()-renderPlotFillBetween p pmap = renderPlotFillBetween' p (plot_fillbetween_values_ p) pmap--renderPlotFillBetween' p [] _ = return ()-renderPlotFillBetween' p vs pmap = preserveCState $ do- setFillStyle (plot_fillbetween_style_ p)- moveTo p0- mapM_ lineTo p1s- mapM_ lineTo (reverse p2s)- lineTo p0- c $ C.fill- where- pmap' = mapXY pmap- (p0:p1s) = map pmap' [ (x,y1) | (x,(y1,y2)) <- vs ]- p2s = map pmap' [ (x,y2) | (x,(y1,y2)) <- vs ]--renderPlotLegendFill :: PlotFillBetween x y -> Rect -> CRender ()-renderPlotLegendFill p r = preserveCState $ do- setFillStyle (plot_fillbetween_style_ p)- rectPath r- c $ C.fill--plotAllPointsFillBetween :: PlotFillBetween x y -> ([x],[y])-plotAllPointsFillBetween p = ([x|(x,(_,_)) <- pts], concat [[y1,y2]|(_,(y1,y2)) <- pts])- where- pts = plot_fillbetween_values_ p---defaultPlotFillBetween = PlotFillBetween {- plot_fillbetween_title_ = "",- plot_fillbetween_style_ = solidFillStyle (opaque $ sRGB 0.5 0.5 1.0),- plot_fillbetween_values_= []-}---------------------------------------------------------------------------- | Value for holding a point with associated error bounds for--- each axis.--data ErrValue x = ErrValue {- ev_low :: x,- ev_best :: x,- ev_high :: x-} deriving Show--data ErrPoint x y = ErrPoint {- ep_x :: ErrValue x,- ep_y :: ErrValue y-} deriving Show---- | When the error is symetric, we can simply pass in dx for the error-symErrPoint x y dx dy = ErrPoint (ErrValue (x-dx) x (x+dx))- (ErrValue (y-dy) y (y+dy))---- | Value defining a series of error intervals, and a style in--- which to render them-data PlotErrBars x y = PlotErrBars {- plot_errbars_title_ :: String,- plot_errbars_line_style_ :: CairoLineStyle,- plot_errbars_tick_length_ :: Double,- plot_errbars_overhang_ :: Double,- plot_errbars_values_ :: [ErrPoint x y]-}---instance ToPlot PlotErrBars where- toPlot p = Plot {- plot_render_ = renderPlotErrBars p,- plot_legend_ = [(plot_errbars_title_ p,renderPlotLegendErrBars p)],- plot_all_points_ = ( concat [[ev_low x,ev_high x] |ErrPoint x _ <-pts ],- concat [[ev_low y,ev_high y] |ErrPoint _ y <-pts ] )- }- where- pts = plot_errbars_values_ p--renderPlotErrBars :: PlotErrBars x y -> PointMapFn x y -> CRender ()-renderPlotErrBars p pmap = preserveCState $ do- mapM_ (drawErrBar.epmap) (plot_errbars_values_ p)- where- epmap (ErrPoint (ErrValue xl x xh) (ErrValue yl y yh)) =- ErrPoint (ErrValue xl' x' xh') (ErrValue yl' y' yh')- where (Point x' y') = pmap' (x,y)- (Point xl' yl') = pmap' (xl,yl)- (Point xh' yh') = pmap' (xh,yh)- drawErrBar = drawErrBar0 p- pmap' = mapXY pmap--drawErrBar0 ps (ErrPoint (ErrValue xl x xh) (ErrValue yl y yh)) = do- let tl = plot_errbars_tick_length_ ps- let oh = plot_errbars_overhang_ ps- setLineStyle (plot_errbars_line_style_ ps)- c $ C.newPath- c $ C.moveTo (xl-oh) y- c $ C.lineTo (xh+oh) y- c $ C.moveTo x (yl-oh)- c $ C.lineTo x (yh+oh)- c $ C.moveTo xl (y-tl)- c $ C.lineTo xl (y+tl)- c $ C.moveTo (x-tl) yl- c $ C.lineTo (x+tl) yl- c $ C.moveTo xh (y-tl)- c $ C.lineTo xh (y+tl)- c $ C.moveTo (x-tl) yh- c $ C.lineTo (x+tl) yh- c $ C.stroke--renderPlotLegendErrBars :: PlotErrBars x y -> Rect -> CRender ()-renderPlotLegendErrBars p r@(Rect p1 p2) = preserveCState $ do- drawErrBar (symErrPoint (p_x p1) ((p_y p1 + p_y p2)/2) dx dx )- drawErrBar (symErrPoint ((p_x p1 + p_x p2)/2) ((p_y p1 + p_y p2)/2) dx dx)- drawErrBar (symErrPoint (p_x p2) ((p_y p1 + p_y p2)/2) dx dx)-- where- drawErrBar = drawErrBar0 p- dx = min ((p_x p2 - p_x p1)/6) ((p_y p2 - p_y p1)/2)--defaultPlotErrBars = PlotErrBars {- plot_errbars_title_ = "",- plot_errbars_line_style_ = solidLine 1 $ opaque blue,- plot_errbars_tick_length_ = 3,- plot_errbars_overhang_ = 0,- plot_errbars_values_ = []-}--------------------------------------------------------------------------class PlotValue a => BarsPlotValue a where- barsReference :: a- barsAdd :: a -> a -> a--instance BarsPlotValue Double where- barsReference = 0- barsAdd = (+)--data PlotBarsStyle = BarsStacked | BarsClustered-data PlotBarsSpacing = BarsFixWidth Double- | BarsFixGap Double---- | Value describing how to plot a set of bars.--- Note that the input data is typed [(x,[y])], ie for each x value--- we plot several y values. Typically the size of each [y] list would--- be the same.-data PlotBars x y = PlotBars {- -- | This value specifies whether each value from [y] should be- -- shown beside or above the previous value.- plot_bars_style_ :: PlotBarsStyle,-- -- | The style in which to draw each element of [y]. A fill style- -- is required, and if a linestyle is given, each bar will be- -- outlined.- plot_bars_item_styles_ :: [ (CairoFillStyle,Maybe CairoLineStyle) ],-- -- | The title of each element of [y]. These will be shown in the- -- legend.- plot_bars_titles_ :: [String],-- -- | This value controls how the widths of the bars are- -- calculated. Either the widths of the bars, or the gaps between- -- them can be fixed.- plot_bars_spacing_ :: PlotBarsSpacing,-- -- | The starting level for the chart (normally 0).- plot_bars_reference_ :: y,-- -- | The actual points to be plotted- plot_bars_values_ :: [ (x,[y]) ]-}--defaultPlotBars :: BarsPlotValue y => PlotBars x y-defaultPlotBars = PlotBars {- plot_bars_style_ = BarsClustered,- plot_bars_item_styles_ = cycle istyles,- plot_bars_titles_ = [],- plot_bars_spacing_ = BarsFixGap 10,- plot_bars_values_ = [],- plot_bars_reference_ = barsReference- }- where- istyles = map mkstyle defaultColorSeq- mkstyle c = (solidFillStyle c,Just (solidLine 1.0 $ opaque black))--plotBars :: (BarsPlotValue y) => PlotBars x y -> Plot x y-plotBars p = Plot {- plot_render_ = renderPlotBars p,- plot_legend_ = zip (plot_bars_titles_ p) (map renderPlotLegendBars (plot_bars_item_styles_ p)),- plot_all_points_ = allBarPoints p- }--renderPlotBars :: BarsPlotValue y => PlotBars x y -> PointMapFn x y -> CRender ()-renderPlotBars p pmap = case (plot_bars_style_ p) of- BarsClustered -> do forM_ vals clusteredBars- BarsStacked -> forM_ vals stackedBars- where- clusteredBars (x,ys) = preserveCState $ do- forM_ (zip3 [0,1..] ys styles) $ \(i, y, (fstyle,_)) -> do- setFillStyle fstyle- barPath (offset i) x yref0 y- c $ C.fill- forM_ (zip3 [0,1..] ys styles) $ \(i, y, (_,mlstyle)) -> do- whenJust mlstyle $ \lstyle -> do- setLineStyle lstyle- barPath (offset i) x yref0 y- c $ C.stroke-- offset i = fromIntegral (2*i-nys) * width/2-- stackedBars (x,ys) = preserveCState $ do- let y2s = zip (yref0:stack ys) (stack ys)- forM_ (zip y2s styles) $ \((y0,y1), (fstyle,_)) -> do- setFillStyle fstyle- barPath (-width/2) x y0 y1- c $ C.fill- forM_ (zip y2s styles) $ \((y0,y1), (_,mlstyle)) -> do- whenJust mlstyle $ \lstyle -> do- setLineStyle lstyle- barPath (-width/2) x y0 y1- c $ C.stroke-- barPath xos x y0 y1 = do- let (Point x' y') = pmap' (x,y1)- let (Point _ y0') = pmap' (x,y0)- rectPath (Rect (Point (x'+xos) y0') (Point (x'+xos+width) y'))-- yref0 = plot_bars_reference_ p- vals = plot_bars_values_ p- width = case plot_bars_spacing_ p of- BarsFixGap gap -> case (plot_bars_style_ p) of- BarsClustered -> (minXInterval - gap) / fromIntegral nys- BarsStacked -> (minXInterval - gap)- BarsFixWidth width -> width- styles = plot_bars_item_styles_ p-- minXInterval = minimum $ zipWith (-) (tail xs) xs- where - xs = nub $ sort $ map (\x-> p_x (pmap' (x,barsReference))) (fst (allBarPoints p))-- nys = maximum [ length ys | (x,ys) <- vals ]- - pmap' = mapXY pmap--whenJust :: (Monad m) => Maybe a -> (a -> m ()) -> m ()-whenJust (Just a) f = f a-whenJust _ _ = return ()--allBarPoints :: (BarsPlotValue y) => PlotBars x y -> ([x],[y])-allBarPoints p = case (plot_bars_style_ p) of- BarsClustered -> ( [x| (x,_) <- pts], concat [ys| (_,ys) <- pts] )- BarsStacked -> ( [x| (x,_) <- pts], concat [stack ys | (_,ys) <- pts] )- where- pts = plot_bars_values_ p- y0 = plot_bars_reference_ p--stack :: (BarsPlotValue y) => [y] -> [y]-stack ys = scanl1 barsAdd ys- --renderPlotLegendBars :: (CairoFillStyle,Maybe CairoLineStyle) -> Rect -> CRender ()-renderPlotLegendBars (fstyle,mlstyle) r@(Rect p1 p2) = do- setFillStyle fstyle- rectPath r- c $ C.fill+ module Graphics.Rendering.Chart.Plot.Types,+ module Graphics.Rendering.Chart.Plot.Lines,+ module Graphics.Rendering.Chart.Plot.Vectors,+ module Graphics.Rendering.Chart.Plot.Points,+ module Graphics.Rendering.Chart.Plot.FillBetween,+ module Graphics.Rendering.Chart.Plot.ErrBars,+ module Graphics.Rendering.Chart.Plot.Candle,+ module Graphics.Rendering.Chart.Plot.Bars,+ module Graphics.Rendering.Chart.Plot.Hidden,+ module Graphics.Rendering.Chart.Plot.Annotation,+ module Graphics.Rendering.Chart.Plot.AreaSpots,+ module Graphics.Rendering.Chart.Plot.Pie,+ module Graphics.Rendering.Chart.Plot.Histogram,+) where -------------------------------------------------------------------------- Template haskell to derive an instance of Data.Accessor.Accessor for each field-$( deriveAccessors ''Plot )-$( deriveAccessors ''PlotLines )-$( deriveAccessors ''PlotPoints )-$( deriveAccessors ''PlotFillBetween )-$( deriveAccessors ''PlotErrBars )-$( deriveAccessors ''PlotBars )+import Graphics.Rendering.Chart.Plot.Types+import Graphics.Rendering.Chart.Plot.Lines+import Graphics.Rendering.Chart.Plot.Vectors+import Graphics.Rendering.Chart.Plot.Points+import Graphics.Rendering.Chart.Plot.FillBetween+import Graphics.Rendering.Chart.Plot.ErrBars+import Graphics.Rendering.Chart.Plot.Candle+import Graphics.Rendering.Chart.Plot.Bars+import Graphics.Rendering.Chart.Plot.Hidden+import Graphics.Rendering.Chart.Plot.Annotation+import Graphics.Rendering.Chart.Plot.AreaSpots+import Graphics.Rendering.Chart.Plot.Pie+import Graphics.Rendering.Chart.Plot.Histogram
+ Graphics/Rendering/Chart/Plot/Annotation.hs view
@@ -0,0 +1,101 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Annotation+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Show textual annotations on a chart.++{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Annotation(+ PlotAnnotation(..),++ plot_annotation_hanchor,+ plot_annotation_vanchor,+ plot_annotation_angle,+ plot_annotation_style,+ plot_annotation_background,+ plot_annotation_offset,+ plot_annotation_values+) where++import Control.Lens+import Data.Default.Class+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Renderable+import Graphics.Rendering.Chart.Plot.Types+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Plot.Types++-- | Value for describing a series of text annotations+-- to be placed at arbitrary points on the graph. Annotations+-- can be rotated and styled.++data PlotAnnotation x y = PlotAnnotation {+ _plot_annotation_hanchor :: HTextAnchor,+ _plot_annotation_vanchor :: VTextAnchor,+ _plot_annotation_angle :: Double,+ -- ^ Angle, in degrees, to rotate the annotation about the anchor point.+ _plot_annotation_style :: FontStyle,+ _plot_annotation_background :: Rectangle,+ -- ^ Rectangle which style determines the background of the annotation+ -- text and which '_rect_minsize' determines the additional width and+ -- height of the background area+ _plot_annotation_offset :: Vector,+ _plot_annotation_values :: [(x,y,String)]+}+++instance ToPlot PlotAnnotation where+ toPlot p = Plot {+ _plot_render = renderAnnotation p,+ _plot_legend = [],+ _plot_all_points = (map (^._1) vs , map (^._2) vs)+ }+ where+ vs = _plot_annotation_values p+++renderAnnotation :: PlotAnnotation x y -> PointMapFn x y -> BackendProgram ()+renderAnnotation p pMap = withFontStyle style $ do+ mapM_ drawRect values+ mapM_ drawOne values+ where hta = _plot_annotation_hanchor p+ vta = _plot_annotation_vanchor p+ values = _plot_annotation_values p+ angle = _plot_annotation_angle p+ style = _plot_annotation_style p+ offset = _plot_annotation_offset p+ rectangle = _plot_annotation_background p+ (x1,y1) = _rect_minsize rectangle+ drawRect (x,y,s) = do+ ts <- textSize s+ let (x2,y2) = (textSizeWidth ts, textSizeHeight ts)+ Point x3 y3 = point x y+ -- position of top-left vertex of the rectangle+ xvp HTA_Left = x3 - x1 / 2+ xvp HTA_Centre = x3 - (x1 + x2) / 2+ xvp HTA_Right = x3 - x2 - x1 / 2+ yvp VTA_Top = y3 - y1 / 2+ yvp VTA_Centre = y3 - (y1 + y2) / 2+ yvp VTA_Bottom = y3 - y2 - y1 / 2+ yvp VTA_BaseLine = y3 - y1 / 2 - textSizeAscent ts+ drawRectangle (Point (xvp hta) (yvp vta) `pvadd` offset) rectangle{ _rect_minsize = (x1+x2,y1+y2) }+ drawOne (x,y,s) = drawTextsR hta vta angle (point x y) s+ point x y = pMap (LValue x, LValue y) `pvadd` offset++instance Default (PlotAnnotation x y) where+ def = PlotAnnotation+ { _plot_annotation_hanchor = HTA_Centre+ , _plot_annotation_vanchor = VTA_Centre+ , _plot_annotation_angle = 0+ , _plot_annotation_style = def+ , _plot_annotation_background = def+ , _plot_annotation_values = []+ , _plot_annotation_offset = Vector 0 0+ }++$( makeLenses ''PlotAnnotation )
+ Graphics/Rendering/Chart/Plot/AreaSpots.hs view
@@ -0,0 +1,192 @@+-- |+-- Module : Graphics.Rendering.Chart.Plot.AreaSpots+-- Copyright : (c) Malcolm Wallace 2009+-- License : BSD-style (see COPYRIGHT file)+--+-- Area spots are a collection of unconnected filled circles,+-- with x,y position, and an independent z value to be represented+-- by the relative area of the spots.++{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.AreaSpots+ ( AreaSpots(..)++ , area_spots_title+ , area_spots_linethick+ , area_spots_linecolour+ , area_spots_fillcolour+ , area_spots_opacity+ , area_spots_max_radius+ , area_spots_values++ , AreaSpots4D(..)++ , area_spots_4d_title+ , area_spots_4d_linethick+ , area_spots_4d_palette+ , area_spots_4d_opacity+ , area_spots_4d_max_radius+ , area_spots_4d_values+ ) where++import Graphics.Rendering.Chart.Geometry hiding (scale, x0, y0)+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Graphics.Rendering.Chart.Axis+import Control.Lens+import Data.Colour hiding (over)+import Data.Colour.Names+import Data.Default.Class++import Control.Monad++-- | A collection of unconnected spots, with x,y position, and an+-- independent z value to be represented by the area of the spot.+data AreaSpots z x y = AreaSpots+ { _area_spots_title :: String+ , _area_spots_linethick :: Double+ , _area_spots_linecolour :: AlphaColour Double+ , _area_spots_fillcolour :: Colour Double+ , _area_spots_opacity :: Double+ , _area_spots_max_radius :: Double -- ^ the largest size of spot+ , _area_spots_values :: [(x,y,z)]+ }++instance Default (AreaSpots z x y) where+ def = AreaSpots+ { _area_spots_title = ""+ , _area_spots_linethick = 0.1+ , _area_spots_linecolour = opaque blue+ , _area_spots_fillcolour = blue+ , _area_spots_opacity = 0.2+ , _area_spots_max_radius = 20 -- in pixels+ , _area_spots_values = []+ }++instance (PlotValue z) => ToPlot (AreaSpots z) where+ toPlot p = Plot { _plot_render = renderAreaSpots p+ , _plot_legend = [(_area_spots_title p, renderSpotLegend p)]+ , _plot_all_points = ( map (^._1) (_area_spots_values p)+ , map (^._2) (_area_spots_values p) )+ }++renderAreaSpots :: (PlotValue z) => AreaSpots z x y -> PointMapFn x y -> BackendProgram ()+renderAreaSpots p pmap = + forM_ (scaleMax (_area_spots_max_radius p^(2::Integer))+ (_area_spots_values p))+ (\ (x,y,z)-> do+ let radius = sqrt z+ let psSpot = filledCircles radius $+ flip withOpacity + (_area_spots_opacity p) $+ _area_spots_fillcolour p+ drawPoint psSpot (pmap (LValue x, LValue y))+ let psOutline = hollowCircles radius+ (_area_spots_linethick p)+ (_area_spots_linecolour p)+ drawPoint psOutline (pmap (LValue x, LValue y))+ )+ where+ scaleMax :: PlotValue z => Double -> [(x,y,z)] -> [(x,y,Double)]+ scaleMax n points = let largest = maximum (map (^._3.to toValue) points)+ scale v = n * toValue v / largest+ in over (mapped._3) scale points++renderSpotLegend :: AreaSpots z x y -> Rect -> BackendProgram ()+renderSpotLegend p (Rect p1 p2) = do+ let radius = min (abs (p_y p1 - p_y p2)) (abs (p_x p1 - p_x p2))+ centre = linearInterpolate p1 p2+ psSpot = filledCircles radius $ withOpacity + (_area_spots_fillcolour p)+ (_area_spots_opacity p)+ psOutline = hollowCircles radius (_area_spots_linethick p)+ (_area_spots_linecolour p)+ drawPoint psSpot centre+ drawPoint psOutline centre+ where+ linearInterpolate (Point x0 y0) (Point x1 y1) =+ Point (x0 + abs(x1-x0)/2) (y0 + abs(y1-y0)/2)++-- | A collection of unconnected spots, with x,y position, an+-- independent z value to be represented by the area of the spot,+-- and in addition, a fourth variable t to be represented by a colour+-- from a given palette. (A linear transfer function from t to palette+-- is assumed.)+data AreaSpots4D z t x y = AreaSpots4D+ { _area_spots_4d_title :: String+ , _area_spots_4d_linethick :: Double+ , _area_spots_4d_palette :: [Colour Double]+ , _area_spots_4d_opacity :: Double+ , _area_spots_4d_max_radius :: Double -- ^ the largest size of spot+ , _area_spots_4d_values :: [(x,y,z,t)]+ }++instance Default (AreaSpots4D z t x y) where+ def = AreaSpots4D+ { _area_spots_4d_title = ""+ , _area_spots_4d_linethick = 0.1+ , _area_spots_4d_palette = [ blue, green, yellow, orange, red ]+ , _area_spots_4d_opacity = 0.2+ , _area_spots_4d_max_radius = 20 -- in pixels+ , _area_spots_4d_values = []+ }++instance (PlotValue z, PlotValue t, Show t) => ToPlot (AreaSpots4D z t) where+ toPlot p = Plot { _plot_render = renderAreaSpots4D p+ , _plot_legend = [ (_area_spots_4d_title p+ , renderSpotLegend4D p) ]+ , _plot_all_points = ( map (^._1) (_area_spots_4d_values p)+ , map (^._2) (_area_spots_4d_values p) )+ }++renderAreaSpots4D :: (PlotValue z, PlotValue t, Show t) =>+ AreaSpots4D z t x y -> PointMapFn x y -> BackendProgram ()+renderAreaSpots4D p pmap = + forM_ (scaleMax (_area_spots_4d_max_radius p^(2::Integer))+ (length (_area_spots_4d_palette p))+ (_area_spots_4d_values p))+ (\ (x,y,z,t)-> do+ let radius = sqrt z+ let colour = _area_spots_4d_palette p !! t + let psSpot+ = filledCircles radius $+ withOpacity colour (_area_spots_4d_opacity p)+ drawPoint psSpot (pmap (LValue x, LValue y))+ let psOutline+ = hollowCircles radius (_area_spots_4d_linethick p)+ (opaque colour)+ drawPoint psOutline (pmap (LValue x, LValue y))+ )+ where+ scaleMax :: (PlotValue z, PlotValue t, Show t) =>+ Double -> Int -> [(x,y,z,t)] -> [(x,y,Double,Int)]+ scaleMax n c points = let largest = maximum (map (^._3.to toValue) points)+ scale v = n * toValue v / largest+ colVals = map (^._4.to toValue) points+ colMin = minimum colVals+ colMax = maximum colVals+ select t = min (c-1) $ + truncate ( fromIntegral c+ * (toValue t-colMin)+ / (colMax-colMin))+ in map (\ (x,y,z,t) -> (x,y, scale z, select t))+ points++renderSpotLegend4D :: AreaSpots4D z t x y -> Rect -> BackendProgram ()+renderSpotLegend4D p (Rect p1 p2) = do+ let radius = min (abs (p_y p1 - p_y p2)) (abs (p_x p1 - p_x p2))+ centre = linearInterpolate p1 p2+ palCol = head $ _area_spots_4d_palette p+ psSpot = filledCircles radius $ withOpacity palCol+ (_area_spots_4d_opacity p)+ psOutline = hollowCircles radius (_area_spots_4d_linethick p)+ (opaque palCol)+ drawPoint psSpot centre+ drawPoint psOutline centre+ where+ linearInterpolate (Point x0 y0) (Point x1 y1) =+ Point (x0 + abs(x1-x0)/2) (y0 + abs(y1-y0)/2)++$( makeLenses ''AreaSpots )+$( makeLenses ''AreaSpots4D )
+ Graphics/Rendering/Chart/Plot/Bars.hs view
@@ -0,0 +1,411 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Bars+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Bar Charts+--+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}++module Graphics.Rendering.Chart.Plot.Bars(+ PlotBars(..),+ PlotBarsStyle(..),+ PlotBarsSpacing(..),+ PlotBarsAlignment(..),+ BarsPlotValue(..),+ BarHorizAnchor(..),+ BarVertAnchor(..),++ plotBars,+ plotHBars,++ plot_bars_style,+ plot_bars_item_styles,+ plot_bars_titles,+ plot_bars_spacing,+ plot_bars_alignment,+ plot_bars_singleton_width,+ plot_bars_label_bar_hanchor,+ plot_bars_label_bar_vanchor,+ plot_bars_label_text_hanchor,+ plot_bars_label_text_vanchor,+ plot_bars_label_angle,+ plot_bars_label_style,+ plot_bars_label_offset,++ plot_bars_values,++ plot_bars_settings,+ plot_bars_values_with_labels,++ addLabels+) where++import Control.Arrow+import Control.Lens+import Control.Monad+import Data.Colour (opaque)+import Data.Colour.Names (black)+import Data.Default.Class+import Data.Tuple(swap)+import Data.List(nub,sort)+import Graphics.Rendering.Chart.Axis+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Geometry hiding (x0, y0)+import Graphics.Rendering.Chart.Plot.Types+import Graphics.Rendering.Chart.Utils+class PlotValue a => BarsPlotValue a where+ barsIsNull :: a -> Bool+ -- | The starting level for the chart, a function of some statistic+ -- (normally the lowest value or just const 0).+ barsReference :: [a] -> a+ barsAdd :: a -> a -> a++instance BarsPlotValue Double where+ barsIsNull a = a == 0.0+ barsReference = const 0+ barsAdd = (+)++instance BarsPlotValue Int where+ barsIsNull a = a == 0+ barsReference = const 0+ barsAdd = (+)++instance BarsPlotValue LogValue where+ barsIsNull (LogValue a) = a == 0.0+ barsReference as =+ 10.0 ^^ (floor (log10 $ minimum $ filter (/= 0.0) as) :: Integer)+ barsAdd = (+)++data PlotBarsStyle+ = BarsStacked -- ^ Bars for a fixed x are stacked vertically+ -- on top of each other.+ | BarsClustered -- ^ Bars for a fixed x are put horizontally+ -- beside each other.+ deriving (Show)++data PlotBarsSpacing+ = BarsFixWidth Double -- ^ All bars have the same width in pixels.+ | BarsFixGap Double Double -- ^ (BarsFixGap g mw) means make the gaps between+ -- the bars equal to g, but with a minimum bar width+ -- of mw+ deriving (Show)++-- | How bars for a given (x,[y]) are aligned with respect to screen+-- coordinate corresponding to x (deviceX).+data PlotBarsAlignment = BarsLeft -- ^ The left edge of bars is at deviceX+ | BarsCentered -- ^ Bars are centered around deviceX+ | BarsRight -- ^ The right edge of bars is at deviceX+ deriving (Show)++data BarHorizAnchor+ = BHA_Left+ | BHA_Centre+ | BHA_Right+ deriving (Show)++data BarVertAnchor+ = BVA_Bottom+ | BVA_Centre+ | BVA_Top+ deriving (Show)++-- | Value describing how to plot a set of bars.+-- Note that the input data is typed [(x,[y])], ie for each x value+-- we plot several y values. Typically the size of each [y] list would+-- be the same.+data BarsSettings = BarsSettings {+ -- | This value specifies whether each value from [y] should be+ -- shown beside or above the previous value.+ _bars_settings_style :: PlotBarsStyle,++ -- | The style in which to draw each element of [y]. A fill style+ -- is required, and if a linestyle is given, each bar will be+ -- outlined.+ _bars_settings_item_styles :: [ (FillStyle,Maybe LineStyle) ],++ -- | This value controls how the widths of the bars are+ -- calculated. Either the widths of the bars, or the gaps between+ -- them can be fixed.+ _bars_settings_spacing :: PlotBarsSpacing,++ -- | This value controls how bars for a fixed x are aligned with+ -- respect to the device coordinate corresponding to x.+ _bars_settings_alignment :: PlotBarsAlignment,++ _bars_settings_singleton_width :: Double,++ -- | The point on the bar to horizontally anchor the label to+ _bars_settings_label_bar_hanchor :: BarHorizAnchor,++ -- | The point on the bar to vertically anchor the label to+ _bars_settings_label_bar_vanchor :: BarVertAnchor,++ -- | The anchor point on the label.+ _bars_settings_label_text_hanchor :: HTextAnchor,++ -- | The anchor point on the label.+ _bars_settings_label_text_vanchor :: VTextAnchor,++ -- | Angle, in degrees, to rotate the label about the anchor point.+ _bars_settings_label_angle :: Double,++ -- | The style to use for the label.+ _bars_settings_label_style :: FontStyle,++ -- | The offset from the anchor point to display the label at.+ _bars_settings_label_offset :: Vector+}+instance Default BarsSettings where+ def = BarsSettings+ { _bars_settings_style = BarsClustered+ , _bars_settings_item_styles = cycle istyles+ , _bars_settings_spacing = BarsFixGap 10 2+ , _bars_settings_alignment = BarsCentered+ , _bars_settings_singleton_width = 20+ , _bars_settings_label_bar_hanchor = BHA_Centre+ , _bars_settings_label_bar_vanchor = BVA_Top+ , _bars_settings_label_text_hanchor = HTA_Centre+ , _bars_settings_label_text_vanchor = VTA_Bottom+ , _bars_settings_label_angle = 0+ , _bars_settings_label_style = def+ , _bars_settings_label_offset = Vector 0 0+ }+ where+ istyles = map mkstyle defaultColorSeq+ mkstyle c = (solidFillStyle c, Just (solidLine 1.0 $ opaque black))+data PlotBars x y = PlotBars {+ _plot_bars_settings :: BarsSettings,+ -- | The title of each element of [y]. These will be shown in the legend.+ _plot_bars_titles :: [String],+ -- | The actual points to be plotted, and their labels+ _plot_bars_values_with_labels :: [(x, [(y, String)])]+}+instance Default (PlotBars x y) where+ def = PlotBars+ { _plot_bars_settings = def+ , _plot_bars_titles = []+ , _plot_bars_values_with_labels = []+ }++plotBars :: (BarsPlotValue y) => PlotBars x y -> Plot x y+plotBars p = Plot {+ _plot_render = \pmap -> renderBars s vals yref0+ (barRect pmap) (mapX pmap),+ _plot_legend = zip (_plot_bars_titles p)+ (map renderPlotLegendBars+ (_bars_settings_item_styles s)),+ _plot_all_points = allBarPoints s vals+ }+ where+ s = _plot_bars_settings p+ vals = _plot_bars_values_with_labels p+ yref0 = refVal s vals++ barRect pmap xos width x y0 y1 = Rect (Point (x'+xos) y0') (Point (x'+xos+width) y') where+ Point x' y' = mapXY pmap (x,y1)+ Point _ y0' = mapXY pmap (x,y0)++ mapX pmap x = p_x (mapXY pmap (x, yref0))++plotHBars :: (BarsPlotValue x) => PlotBars y x -> Plot x y+plotHBars p = Plot {+ _plot_render = \pmap -> renderBars s vals xref0+ (barRect pmap) (mapY pmap),+ _plot_legend = zip (_plot_bars_titles p)+ (map renderPlotLegendBars+ (_bars_settings_item_styles s)),+ _plot_all_points = swap $ allBarPoints s vals+ }+ where+ s = _plot_bars_settings p+ vals = _plot_bars_values_with_labels p+ xref0 = refVal s vals++ barRect pmap yos height y x0 x1 = Rect (Point x0' (y'+yos)) (Point x' (y'+yos+height)) where+ Point x' y' = mapXY pmap (x1,y)+ Point x0' _ = mapXY pmap (x0,y)++ mapY pmap y = p_y (mapXY pmap (xref0, y))++renderBars :: (BarsPlotValue v) =>+ BarsSettings+ -> [(k, [(v, String)])]+ -> v+ -> (Double -> Double -> k -> v -> v -> Rect)+ -> (k -> Double)+ -> BackendProgram ()+renderBars p vals vref0 r mapk = case _bars_settings_style p of+ BarsClustered -> forM_ vals clusteredBars+ BarsStacked -> forM_ vals stackedBars+ where+ clusteredBars (k,vs) = do+ let offset i = case _bars_settings_alignment p of+ BarsLeft -> fromIntegral i * bsize+ BarsRight -> fromIntegral (i-nvs) * bsize+ BarsCentered -> fromIntegral (2*i-nvs) * bsize/2+ forM_ (zip3 [0,1..] vs styles) $ \(i, (v, _), (fstyle,_)) ->+ unless (barsIsNull v) $+ withFillStyle fstyle $+ alignFillPath (barPath (offset i) k vref0 v)+ >>= fillPath+ forM_ (zip3 [0,1..] vs styles) $ \(i, (v, _), (_,mlstyle)) ->+ unless (barsIsNull v) $+ whenJust mlstyle $ \lstyle ->+ withLineStyle lstyle $+ alignStrokePath (barPath (offset i) k vref0 v)+ >>= strokePath+ withFontStyle (_bars_settings_label_style p) $+ forM_ (zip [0,1..] vs) $ \(i, (v, txt)) ->+ unless (null txt) $ do+ let ha = _bars_settings_label_bar_hanchor p+ let va = _bars_settings_label_bar_vanchor p+ let pt = rectCorner ha va (r (offset i) bsize k vref0 v)+ drawTextR+ (_bars_settings_label_text_hanchor p)+ (_bars_settings_label_text_vanchor p)+ (_bars_settings_label_angle p)+ (pvadd pt $ _bars_settings_label_offset p)+ txt++ stackedBars (k,vs) = do+ let (vs', lbls) = unzip vs+ let vs'' = map (\v -> if barsIsNull v then vref0 else v) (stack vs')+ let v2s = zip (vref0:vs'') vs''+ let ofs = case _bars_settings_alignment p of+ BarsLeft -> 0+ BarsRight -> -bsize+ BarsCentered -> -(bsize/2)+ forM_ (zip v2s styles) $ \((v0,v1), (fstyle,_)) ->+ unless (v0 >= v1) $+ withFillStyle fstyle $+ alignFillPath (barPath ofs k v0 v1)+ >>= fillPath+ forM_ (zip v2s styles) $ \((v0,v1), (_,mlstyle)) ->+ unless (v0 >= v1) $+ whenJust mlstyle $ \lstyle ->+ withLineStyle lstyle $+ alignStrokePath (barPath ofs k v0 v1)+ >>= strokePath+ withFontStyle (_bars_settings_label_style p) $+ forM_ (zip v2s lbls) $ \((v0, v1), txt) ->+ unless (null txt) $ do+ let ha = _bars_settings_label_bar_hanchor p+ let va = _bars_settings_label_bar_vanchor p+ let pt = rectCorner ha va (r ofs bsize k v0 v1)+ drawTextR+ (_bars_settings_label_text_hanchor p)+ (_bars_settings_label_text_vanchor p)+ (_bars_settings_label_angle p)+ (pvadd pt $ _bars_settings_label_offset p)+ txt++ styles = _bars_settings_item_styles p++ barPath os k v0 v1 = rectPath $ r os bsize k v0 v1++ bsize = case _bars_settings_spacing p of+ BarsFixGap gap minw -> let w = max (minKInterval - gap) minw in+ case _bars_settings_style p of+ BarsClustered -> w / fromIntegral nvs+ BarsStacked -> w+ BarsFixWidth width' -> width'++ minKInterval = let diffs = zipWith (-) (tail mks) mks+ in if null diffs+ then _bars_settings_singleton_width p+ else minimum diffs+ where+ mks = nub $ sort $ map (mapk . fst) vals++ nvs = maximum $ map (length . snd) vals++rectCorner :: BarHorizAnchor -> BarVertAnchor -> Rect -> Point+rectCorner h v (Rect (Point x0 y0) (Point x1 y1)) = Point x' y' where+ x' = case h of+ BHA_Left -> x0+ BHA_Right -> x1+ BHA_Centre -> (x0 + x1) / 2+ y' = case v of+ BVA_Bottom -> y0+ BVA_Top -> y1+ BVA_Centre -> (y0 + y1) / 2++-- Helper function for printing bar values as labels+addLabels :: Show y => [(x, [y])] -> [(x, [(y, String)])]+addLabels = map . second $ map (\y -> (y, show y))++refVal :: (BarsPlotValue y) => BarsSettings -> [(x, [(y, String)])] -> y+refVal p vals = barsReference $ case _bars_settings_style p of+ BarsClustered -> concatMap (map fst . snd) vals+ BarsStacked -> concatMap (take 1 . dropWhile barsIsNull . stack . map fst . snd) vals++allBarPoints :: (BarsPlotValue y) => BarsSettings -> [(x, [(y, String)])] -> ([x],[y])+allBarPoints p vals = case _bars_settings_style p of+ BarsClustered ->+ let ys = concatMap (map fst) yls in+ ( xs, barsReference ys:ys )+ BarsStacked ->+ let ys = map (stack . map fst) yls in+ ( xs, barsReference (concatMap (take 1 . dropWhile barsIsNull) ys):concat ys)+ where (xs, yls) = unzip vals++stack :: (BarsPlotValue y) => [y] -> [y]+stack = scanl1 barsAdd++renderPlotLegendBars :: (FillStyle,Maybe LineStyle) -> Rect -> BackendProgram ()+renderPlotLegendBars (fstyle,_) r =+ withFillStyle fstyle $+ fillPath (rectPath r)++$( makeLenses ''BarsSettings )+$( makeLenses ''PlotBars )++-- Lens provided for backward compat.++-- Note that this one does not satisfy the lens laws, as it discards/overwrites the labels.+plot_bars_values :: Lens' (PlotBars x y) [(x, [y])]+plot_bars_values = lens getter setter+ where+ getter = mapYs fst . _plot_bars_values_with_labels+ setter pb vals' = pb { _plot_bars_values_with_labels = mapYs (, "") vals' }+ mapYs :: (a -> b) -> [(c, [a])] -> [(c, [b])]+ mapYs f = map (over _2 $ map f)++plot_bars_style :: Lens' (PlotBars x y) PlotBarsStyle+plot_bars_style = plot_bars_settings . bars_settings_style++plot_bars_item_styles :: Lens' (PlotBars x y) [(FillStyle, Maybe LineStyle)]+plot_bars_item_styles = plot_bars_settings . bars_settings_item_styles++plot_bars_spacing :: Lens' (PlotBars x y) PlotBarsSpacing+plot_bars_spacing = plot_bars_settings . bars_settings_spacing++plot_bars_alignment :: Lens' (PlotBars x y) PlotBarsAlignment+plot_bars_alignment = plot_bars_settings . bars_settings_alignment++plot_bars_singleton_width :: Lens' (PlotBars x y) Double+plot_bars_singleton_width = plot_bars_settings . bars_settings_singleton_width++plot_bars_label_bar_hanchor :: Lens' (PlotBars x y) BarHorizAnchor+plot_bars_label_bar_hanchor = plot_bars_settings . bars_settings_label_bar_hanchor++plot_bars_label_bar_vanchor :: Lens' (PlotBars x y) BarVertAnchor+plot_bars_label_bar_vanchor = plot_bars_settings . bars_settings_label_bar_vanchor++plot_bars_label_text_hanchor :: Lens' (PlotBars x y) HTextAnchor+plot_bars_label_text_hanchor = plot_bars_settings . bars_settings_label_text_hanchor++plot_bars_label_text_vanchor :: Lens' (PlotBars x y) VTextAnchor+plot_bars_label_text_vanchor = plot_bars_settings . bars_settings_label_text_vanchor++plot_bars_label_angle :: Lens' (PlotBars x y) Double+plot_bars_label_angle = plot_bars_settings . bars_settings_label_angle++plot_bars_label_style :: Lens' (PlotBars x y) FontStyle+plot_bars_label_style = plot_bars_settings . bars_settings_label_style++plot_bars_label_offset :: Lens' (PlotBars x y) Vector+plot_bars_label_offset = plot_bars_settings . bars_settings_label_offset
+ Graphics/Rendering/Chart/Plot/Candle.hs view
@@ -0,0 +1,151 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Candle+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Candlestick charts for financial plotting+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Candle(+ PlotCandle(..),+ Candle(..),++ plot_candle_title,+ plot_candle_line_style,+ plot_candle_tick_length,+ plot_candle_width,+ plot_candle_centre,+ plot_candle_fill,+ plot_candle_rise_fill_style,+ plot_candle_fall_fill_style,+ plot_candle_values,+) where++import Control.Lens hiding (op)++import Graphics.Rendering.Chart.Geometry hiding (close)+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Control.Monad+import Data.Colour (opaque)+import Data.Colour.Names (white, blue)+import Data.Default.Class++-- | Value defining a financial interval: opening and closing prices, with+-- maxima and minima; and a style in which to render them.+-- By convention, there are different fill styles depending on whether+-- the price rises (open < close) or falls (close < open).+-- (This plot type can also be re-purposed for statistical intervals, e.g.+-- minimum, first quartile, median, third quartile, maximum.)+data PlotCandle x y = PlotCandle {+ _plot_candle_title :: String,+ _plot_candle_line_style :: LineStyle,+ _plot_candle_fill :: Bool,+ _plot_candle_rise_fill_style :: FillStyle,+ _plot_candle_fall_fill_style :: FillStyle,+ _plot_candle_tick_length :: Double,+ _plot_candle_width :: Double,+ _plot_candle_centre :: Double,+ _plot_candle_values :: [Candle x y]+}++-- | A Value holding price intervals for a given x-coord.+-- An alternative view is that these are statistical intervals: the+-- 0th, 25th, 50th, 75th, and 100th percentiles.+data Candle x y = Candle { candle_x :: x+ , candle_low :: y+ , candle_open :: y+ , candle_mid :: y+ , candle_close :: y+ , candle_high :: y+ } deriving (Show)++instance ToPlot PlotCandle where+ toPlot p = Plot {+ _plot_render = renderPlotCandle p,+ _plot_legend = [(_plot_candle_title p, renderPlotLegendCandle p)],+ _plot_all_points = ( map candle_x pts+ , concat [ [candle_low c, candle_high c]+ | c <- pts ] )+ }+ where+ pts = _plot_candle_values p++renderPlotCandle :: PlotCandle x y -> PointMapFn x y -> BackendProgram ()+renderPlotCandle p pmap =+ mapM_ (drawCandle p . candlemap) (_plot_candle_values p)+ where+ candlemap (Candle x lo op mid cl hi) =+ Candle x' lo' op' mid' cl' hi'+ where (Point x' mid') = pmap' (x,mid)+ (Point _ lo') = pmap' (x,lo)+ (Point _ op') = pmap' (x,op)+ (Point _ cl') = pmap' (x,cl)+ (Point _ hi') = pmap' (x,hi)+ pmap' = mapXY pmap++drawCandle :: PlotCandle x y -> Candle Double Double -> BackendProgram ()+drawCandle ps (Candle x lo open mid close hi) = do+ let tl = _plot_candle_tick_length ps+ let wd = _plot_candle_width ps+ let ct = _plot_candle_centre ps+ let f = _plot_candle_fill ps+ -- the pixel coordinate system is inverted wrt the value coords.+ when f $ withFillStyle (if open >= close+ then _plot_candle_rise_fill_style ps+ else _plot_candle_fall_fill_style ps) $+ fillPath $ moveTo' (x-wd) open+ <> lineTo' (x-wd) close+ <> lineTo' (x+wd) close+ <> lineTo' (x+wd) open+ <> lineTo' (x-wd) open++ withLineStyle (_plot_candle_line_style ps) $ do+ strokePath $ moveTo' (x-wd) open+ <> lineTo' (x-wd) close+ <> lineTo' (x+wd) close+ <> lineTo' (x+wd) open+ <> lineTo' (x-wd) open++ strokePath $ moveTo' x (min lo hi)+ <> lineTo' x (min open close)+ <> moveTo' x (max open close)+ <> lineTo' x (max hi lo)++ when (tl > 0) $ strokePath $ moveTo' (x-tl) lo+ <> lineTo' (x+tl) lo+ <> moveTo' (x-tl) hi+ <> lineTo' (x+tl) hi++ when (ct > 0) $ strokePath $ moveTo' (x-ct) mid+ <> lineTo' (x+ct) mid++renderPlotLegendCandle :: PlotCandle x y -> Rect -> BackendProgram ()+renderPlotLegendCandle pc (Rect p1 p2) = do+ drawCandle pc2 (Candle (xwid*1/4) lo open mid close hi)+ drawCandle pc2 (Candle (xwid*2/3) lo close mid open hi)+ where+ pc2 = pc { _plot_candle_width = 2 }+ xwid = p_x p1 + p_x p2+ lo = max (p_y p1) (p_y p2)+ mid = (p_y p1 + p_y p2)/2+ hi = min (p_y p1) (p_y p2)+ open = (lo + mid) / 2+ close = (mid + hi) / 2++instance Default (PlotCandle x y) where+ def = PlotCandle+ { _plot_candle_title = ""+ , _plot_candle_line_style = solidLine 1 $ opaque blue+ , _plot_candle_fill = False+ , _plot_candle_rise_fill_style = solidFillStyle $ opaque white+ , _plot_candle_fall_fill_style = solidFillStyle $ opaque blue+ , _plot_candle_tick_length = 2+ , _plot_candle_width = 5+ , _plot_candle_centre = 0+ , _plot_candle_values = []+ }++$( makeLenses ''PlotCandle )
+ Graphics/Rendering/Chart/Plot/ErrBars.hs view
@@ -0,0 +1,127 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.ErrBars+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Plot series of points with associated error bars.+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.ErrBars(+ PlotErrBars(..),+ ErrPoint(..),+ ErrValue(..),+ symErrPoint,++ -- * Accessors+ -- | These accessors are generated by template haskell++ plot_errbars_title,+ plot_errbars_line_style,+ plot_errbars_tick_length,+ plot_errbars_overhang,+ plot_errbars_values,+) where++import Control.Lens++import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Data.Colour (opaque)+import Data.Colour.Names (blue)+import Data.Default.Class++-- | Value for holding a point with associated error bounds for each axis.++data ErrValue x = ErrValue {+ ev_low :: x,+ ev_best :: x,+ ev_high :: x+} deriving Show++data ErrPoint x y = ErrPoint {+ ep_x :: ErrValue x,+ ep_y :: ErrValue y+} deriving Show++-- | When the error is symmetric, we can simply pass in dx for the error.+symErrPoint :: (Num a, Num b) => a -> b -> a -> b -> ErrPoint a b+symErrPoint x y dx dy = ErrPoint (ErrValue (x-dx) x (x+dx))+ (ErrValue (y-dy) y (y+dy))++-- | Value defining a series of error intervals, and a style in+-- which to render them.+data PlotErrBars x y = PlotErrBars {+ _plot_errbars_title :: String,+ _plot_errbars_line_style :: LineStyle,+ _plot_errbars_tick_length :: Double,+ _plot_errbars_overhang :: Double,+ _plot_errbars_values :: [ErrPoint x y]+}+++instance ToPlot PlotErrBars where+ toPlot p = Plot {+ _plot_render = renderPlotErrBars p,+ _plot_legend = [(_plot_errbars_title p, renderPlotLegendErrBars p)],+ _plot_all_points = ( concat [ [ev_low x,ev_high x]+ | ErrPoint x _ <- pts ]+ , concat [ [ev_low y,ev_high y]+ | ErrPoint _ y <- pts ] )+ }+ where+ pts = _plot_errbars_values p++renderPlotErrBars :: PlotErrBars x y -> PointMapFn x y -> BackendProgram ()+renderPlotErrBars p pmap =+ mapM_ (drawErrBar.epmap) (_plot_errbars_values p)+ where+ epmap (ErrPoint (ErrValue xl x xh) (ErrValue yl y yh)) =+ ErrPoint (ErrValue xl' x' xh') (ErrValue yl' y' yh')+ where (Point x' y') = pmap' (x,y)+ (Point xl' yl') = pmap' (xl,yl)+ (Point xh' yh') = pmap' (xh,yh)+ drawErrBar = drawErrBar0 p+ pmap' = mapXY pmap++drawErrBar0 :: PlotErrBars x y -> ErrPoint Double Double -> BackendProgram ()+drawErrBar0 ps (ErrPoint (ErrValue xl x xh) (ErrValue yl y yh)) = do+ let tl = _plot_errbars_tick_length ps+ let oh = _plot_errbars_overhang ps+ withLineStyle (_plot_errbars_line_style ps) $+ strokePath $ moveTo' (xl-oh) y+ <> lineTo' (xh+oh) y+ <> moveTo' x (yl-oh)+ <> lineTo' x (yh+oh)+ <> moveTo' xl (y-tl)+ <> lineTo' xl (y+tl)+ <> moveTo' (x-tl) yl+ <> lineTo' (x+tl) yl+ <> moveTo' xh (y-tl)+ <> lineTo' xh (y+tl)+ <> moveTo' (x-tl) yh+ <> lineTo' (x+tl) yh++renderPlotLegendErrBars :: PlotErrBars x y -> Rect -> BackendProgram ()+renderPlotLegendErrBars p (Rect p1 p2) = do+ drawErrBar (symErrPoint (p_x p1) y dx dx)+ drawErrBar (symErrPoint ((p_x p1 + p_x p2)/2) y dx dx)+ drawErrBar (symErrPoint (p_x p2) y dx dx)++ where+ drawErrBar = drawErrBar0 p+ dx = min ((p_x p2 - p_x p1)/6) ((p_y p2 - p_y p1)/2)+ y = (p_y p1 + p_y p2)/2++instance Default (PlotErrBars x y) where+ def = PlotErrBars+ { _plot_errbars_title = ""+ , _plot_errbars_line_style = solidLine 1 $ opaque blue+ , _plot_errbars_tick_length = 3+ , _plot_errbars_overhang = 0+ , _plot_errbars_values = []+ }++$( makeLenses ''PlotErrBars )
+ Graphics/Rendering/Chart/Plot/FillBetween.hs view
@@ -0,0 +1,89 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.FillBetween+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Plots that fill the area between two lines.+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.FillBetween(+ PlotFillBetween(..),++ -- * Accessors+ -- | These accessors are generated by template haskell+ plot_fillbetween_title,+ plot_fillbetween_style,+ plot_fillbetween_line,+ plot_fillbetween_values,+) where++import Control.Lens+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Data.Colour (opaque)+import Data.Colour.SRGB (sRGB)+import Data.Default.Class++-- | Value specifying a plot filling the area between two sets of Y+-- coordinates, given common X coordinates.++data PlotFillBetween x y = PlotFillBetween {+ _plot_fillbetween_title :: String,+ _plot_fillbetween_style :: FillStyle,+ _plot_fillbetween_line :: Maybe LineStyle,+ _plot_fillbetween_values :: [ (x, (y,y))]+}+++instance ToPlot PlotFillBetween where+ toPlot p = Plot {+ _plot_render = renderPlotFillBetween p,+ _plot_legend = [(_plot_fillbetween_title p,renderPlotLegendFill p)],+ _plot_all_points = plotAllPointsFillBetween p+ }++renderPlotFillBetween :: PlotFillBetween x y -> PointMapFn x y -> BackendProgram ()+renderPlotFillBetween p =+ renderPlotFillBetween' p (_plot_fillbetween_values p)++renderPlotFillBetween' :: + PlotFillBetween x y + -> [(a, (b, b))]+ -> ((Limit a, Limit b) -> Point)+ -> BackendProgram ()+renderPlotFillBetween' _ [] _ = return ()+renderPlotFillBetween' p vs pmap = + withFillStyle (_plot_fillbetween_style p) $ do+ ps <- alignFillPoints $ [p0] ++ p1s ++ reverse p2s ++ [p0]+ fillPointPath ps+ case _plot_fillbetween_line p of+ Nothing -> return ()+ Just lineStyle -> withLineStyle lineStyle $ strokePointPath ps+ where+ pmap' = mapXY pmap+ (p0:p1s) = map pmap' [ (x,y1) | (x,(y1,_)) <- vs ]+ p2s = map pmap' [ (x,y2) | (x,(_,y2)) <- vs ]++renderPlotLegendFill :: PlotFillBetween x y -> Rect -> BackendProgram ()+renderPlotLegendFill p r = + withFillStyle (_plot_fillbetween_style p) $ + fillPath (rectPath r)++plotAllPointsFillBetween :: PlotFillBetween x y -> ([x],[y])+plotAllPointsFillBetween p = ( [ x | (x,(_,_)) <- pts ]+ , concat [ [y1,y2] | (_,(y1,y2)) <- pts ] )+ where+ pts = _plot_fillbetween_values p++instance Default (PlotFillBetween x y) where+ def = PlotFillBetween + { _plot_fillbetween_title = ""+ , _plot_fillbetween_style = solidFillStyle (opaque $ sRGB 0.5 0.5 1.0)+ , _plot_fillbetween_line = Nothing+ , _plot_fillbetween_values = []+ }++$( makeLenses ''PlotFillBetween )
+ Graphics/Rendering/Chart/Plot/Hidden.hs view
@@ -0,0 +1,36 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Hidden+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Plots that don't show, but occupy space so as to effect axis+-- scaling+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Hidden(+ PlotHidden(..),+ + plot_hidden_x_values,+ plot_hidden_y_values+) where++import Control.Lens+import Graphics.Rendering.Chart.Plot.Types++-- | Value defining some hidden x and y values. The values are+-- not displayed, but they still affect axis scaling.+data PlotHidden x y = PlotHidden {+ _plot_hidden_x_values :: [x],+ _plot_hidden_y_values :: [y]+}++instance ToPlot PlotHidden where+ toPlot ph = Plot {+ _plot_render = \_ -> return (),+ _plot_legend = [],+ _plot_all_points = (_plot_hidden_x_values ph, _plot_hidden_y_values ph)+ }++$( makeLenses ''PlotHidden )
+ Graphics/Rendering/Chart/Plot/Histogram.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE TemplateHaskell, FlexibleInstances #-}++module Graphics.Rendering.Chart.Plot.Histogram+ ( -- * Histograms+ PlotHist (..)+ , histToPlot+ , defaultPlotHist+ , defaultFloatPlotHist+ , defaultNormedPlotHist+ , histToBins+ -- * 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+ ) where++import Control.Monad (when)+import Data.Maybe (fromMaybe)+import qualified Data.Foldable as F+import qualified Data.Vector as V++import Control.Lens+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 Numeric.Histogram++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+ }++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+ }++-- | @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, Num y, Ord y) => PlotHist x y -> Plot x y+histToPlot p = Plot {+ _plot_render = renderPlotHist p,+ _plot_legend = [(_plot_hist_title p, renderPlotLegendHist p)],+ _plot_all_points = unzip+ $ concatMap (\((x1,x2), y)->[ (x1,y)+ , (x2,y)+ , (x1,0)+ , (x2,0)+ ])+ $ histToBins p+ }++buildHistPath :: (RealFrac x, Num y)+ => PointMapFn x y -> [((x,x), y)] -> 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)++renderPlotHist :: (RealFrac x, Num y, Ord y)+ => PlotHist x y -> PointMapFn x y -> BackendProgram ()+renderPlotHist p pmap+ | null bins = return ()+ | otherwise = do+ withFillStyle (_plot_hist_fill_style p) $+ alignFillPath (buildHistPath pmap bins) >>= fillPath+ withLineStyle (_plot_hist_line_style p) $ do+ when (_plot_hist_drop_lines p) $+ alignStrokePath dropLinesPath >>= strokePath+ alignStrokePath (buildHistPath pmap bins) >>= strokePath+ where bins = histToBins p+ pt x y = pmap (LValue x, LValue y)+ dropLinesPath = F.foldMap (\((x1,_), y)->moveTo (pt x1 0)+ <> lineTo (pt x1 y)+ ) $ tail bins++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++-- | Obtain the bin dimensions of a given @PlotHist@.+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 )
+ Graphics/Rendering/Chart/Plot/Lines.hs view
@@ -0,0 +1,102 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Lines+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Line plots+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Lines(+ PlotLines(..),+ defaultPlotLineStyle,+ hlinePlot,+ vlinePlot,++ plot_lines_title,+ plot_lines_style,+ plot_lines_values,+ plot_lines_limit_values,+) where++import Control.Lens+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Data.Colour (opaque)+import Data.Colour.Names (blue)+import Data.Default.Class++-- | Value defining a series of (possibly disjointed) lines,+-- and a style in which to render them.+data PlotLines x y = PlotLines {+ _plot_lines_title :: String,+ _plot_lines_style :: LineStyle,++ -- | The lines to be plotted+ _plot_lines_values :: [[(x,y)]],++ -- | Additional lines to be plotted, specified using+ -- the Limit type to allow referencing the edges of+ -- the plot area.+ _plot_lines_limit_values :: [[(Limit x, Limit y)]]+}++instance ToPlot PlotLines where+ toPlot p = Plot {+ _plot_render = renderPlotLines p,+ _plot_legend = [(_plot_lines_title p, renderPlotLegendLines p)],+ _plot_all_points = ( map fst pts ++ xs, map snd pts ++ ys )+ }+ where+ pts = concat (_plot_lines_values p)+ xs = [ x | (LValue x,_) <- concat (_plot_lines_limit_values p)]+ ys = [ y | (_,LValue y) <- concat (_plot_lines_limit_values p)]++renderPlotLines :: PlotLines x y -> PointMapFn x y -> BackendProgram ()+renderPlotLines p pmap = + withLineStyle (_plot_lines_style p) $ do+ mapM_ (drawLines (mapXY pmap)) (_plot_lines_values p)+ mapM_ (drawLines pmap) (_plot_lines_limit_values p)+ where+ drawLines mapfn pts = alignStrokePoints (map mapfn pts) >>= strokePointPath ++renderPlotLegendLines :: PlotLines x y -> Rect -> BackendProgram ()+renderPlotLegendLines p (Rect p1 p2) = + withLineStyle (_plot_lines_style p) $ do+ let y = (p_y p1 + p_y p2) / 2+ ps <- alignStrokePoints [Point (p_x p1) y, Point (p_x p2) y]+ strokePointPath ps++defaultPlotLineStyle :: LineStyle+defaultPlotLineStyle = (solidLine 1 $ opaque blue){+ _line_cap = LineCapRound,+ _line_join = LineJoinRound+ }++instance Default (PlotLines x y) where+ def = PlotLines + { _plot_lines_title = ""+ , _plot_lines_style = defaultPlotLineStyle+ , _plot_lines_values = []+ , _plot_lines_limit_values = []+ }++-- | Helper function to plot a single horizontal line.+hlinePlot :: String -> LineStyle -> b -> Plot a b+hlinePlot t ls v = toPlot def {+ _plot_lines_title = t,+ _plot_lines_style = ls,+ _plot_lines_limit_values = [[(LMin, LValue v),(LMax, LValue v)]]+ }++-- | Helper function to plot a single vertical line.+vlinePlot :: String -> LineStyle -> a -> Plot a b+vlinePlot t ls v = toPlot def {+ _plot_lines_title = t,+ _plot_lines_style = ls,+ _plot_lines_limit_values = [[(LValue v,LMin),(LValue v,LMax)]]+ }++$( makeLenses ''PlotLines )
+ Graphics/Rendering/Chart/Plot/Pie.hs view
@@ -0,0 +1,228 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Pie+-- Copyright : (c) Tim Docker 2008, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- A basic pie chart.+--+-- Pie charts are handled different to other plots, in that they+-- have their own layout, and can't be composed with other plots. A+-- pie chart is rendered with code in the following form:+--+-- @+-- values :: [PieItem]+-- values = [...]+-- layout :: PieLayout+-- layout = pie_plot ^: pie_data ^= values+-- $ def+-- renderable = toRenderable layout+-- @+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Pie(+ PieLayout(..),+ PieChart(..),+ PieItem(..),++ pieToRenderable,+ pieChartToRenderable,++ pie_title,+ pie_title_style,+ pie_plot,+ pie_background,+ pie_margin,+ pie_data,+ pie_colors,+ pie_label_style,+ pie_label_line_style,+ pie_start_angle,+ pitem_label,+ pitem_offset,+ pitem_value,++) where+-- original code thanks to Neal Alexander++-- see ../Drawing.hs for why we do not use hiding (moveTo) for+-- lens < 4+import Control.Lens+import Data.Colour+import Data.Colour.Names (white)+import Data.Default.Class+import Control.Monad++import Graphics.Rendering.Chart.Geometry hiding (moveTo)+import qualified Graphics.Rendering.Chart.Geometry as G+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Renderable+import Graphics.Rendering.Chart.Grid++data PieLayout = PieLayout {+ _pie_title :: String,+ _pie_title_style :: FontStyle,+ _pie_plot :: PieChart,+ _pie_background :: FillStyle,+ _pie_margin :: Double+}++data PieChart = PieChart {+ _pie_data :: [PieItem],+ _pie_colors :: [AlphaColour Double],+ _pie_label_style :: FontStyle,+ _pie_label_line_style :: LineStyle,+ _pie_start_angle :: Double++}++data PieItem = PieItem {+ _pitem_label :: String,+ _pitem_offset :: Double,+ _pitem_value :: Double+}++instance Default PieChart where+ def = PieChart+ { _pie_data = []+ , _pie_colors = defaultColorSeq+ , _pie_label_style = def+ , _pie_label_line_style = solidLine 1 $ opaque black+ , _pie_start_angle = 0+ }++instance Default PieItem where+ def = PieItem "" 0 0++instance Default PieLayout where+ def = PieLayout+ { _pie_background = solidFillStyle $ opaque white+ , _pie_title = ""+ , _pie_title_style = def { _font_size = 15+ , _font_weight = FontWeightBold }+ , _pie_plot = def+ , _pie_margin = 10+ }++instance ToRenderable PieLayout where+ toRenderable = setPickFn nullPickFn . pieToRenderable++pieChartToRenderable :: PieChart -> Renderable (PickFn a)+pieChartToRenderable p = Renderable { minsize = minsizePie p+ , render = renderPie p+ }++instance ToRenderable PieChart where+ toRenderable = setPickFn nullPickFn . pieChartToRenderable++pieToRenderable :: PieLayout -> Renderable (PickFn a)+pieToRenderable p = fillBackground (_pie_background p) (+ gridToRenderable $ aboveN+ [ tval $ addMargins (lm/2,0,0,0) (setPickFn nullPickFn title)+ , weights (1,1) $ tval $ addMargins (lm,lm,lm,lm)+ (pieChartToRenderable $ _pie_plot p)+ ] )+ where+ title = label (_pie_title_style p) HTA_Centre VTA_Top (_pie_title p)+ lm = _pie_margin p++extraSpace :: PieChart -> BackendProgram (Double, Double)+extraSpace p = do+ textSizes <- mapM (textDimension . _pitem_label) (_pie_data p)+ let maxw = foldr (max.fst) 0 textSizes+ let maxh = foldr (max.snd) 0 textSizes+ let maxo = foldr (max._pitem_offset) 0 (_pie_data p)+ let extra = label_rgap + label_rlength + maxo+ return (extra + maxw, extra + maxh )++minsizePie :: PieChart -> BackendProgram (Double, Double)+minsizePie p = do+ (extraw,extrah) <- extraSpace p+ return (extraw * 2, extrah * 2)++renderPie :: PieChart -> (Double, Double) -> BackendProgram (PickFn a)+renderPie p (w,h) = do+ (extraw,extrah) <- extraSpace p+ -- let (w,h) = (p_x p2 - p_x p1, p_y p2 - p_y p1)+ -- let center = Point (p_x p1 + w/2) (p_y p1 + h/2)+ --+ let center = Point (w/2) (h/2)+ let radius = min (w - 2*extraw) (h - 2*extrah) / 2++ foldM_ (paint center radius) (_pie_start_angle p)+ (zip (_pie_colors p) content)+ return nullPickFn++ where+ -- p1 = Point 0 0+ -- p2 = Point w h+ content = let total = sum (map _pitem_value (_pie_data p))+ in [ pitem{_pitem_value=_pitem_value pitem/total}+ | pitem <- _pie_data p ]++ paint :: Point -> Double -> Double -> (AlphaColour Double, PieItem)+ -> BackendProgram Double+ paint center radius a1 (color,pitem) = do+ let ax = 360.0 * _pitem_value pitem+ let a2 = a1 + (ax / 2)+ let a3 = a1 + ax+ let offset = _pitem_offset pitem++ pieSlice (ray a2 offset) a1 a3 color+ pieLabel (_pitem_label pitem) a2 offset++ return a3++ where+ pieLabel :: String -> Double -> Double -> BackendProgram ()+ pieLabel name angle offset =+ withFontStyle (_pie_label_style p) $+ withLineStyle (_pie_label_line_style p) $ do+ let p1 = ray angle (radius+label_rgap+label_rlength+offset)+ p1a <- alignStrokePoint p1+ (tw,_) <- textDimension name+ let (offset',anchor) = if angle < 90 || angle > 270+ then ((0+),HTA_Left)+ else ((0-),HTA_Right)+ p0 <- alignStrokePoint $ ray angle (radius + label_rgap+offset)+ strokePath $ G.moveTo p0+ <> lineTo p1a+ <> lineTo' (p_x p1a + offset' (tw + label_rgap)) (p_y p1a)++ let p2 = p1 `pvadd` Vector (offset' label_rgap) 0+ drawTextA anchor VTA_Bottom p2 name++ pieSlice :: Point -> Double -> Double -> AlphaColour Double -> BackendProgram ()+ pieSlice (Point x y) arc1 arc2 pColor = do+ let path = arc' x y radius (radian arc1) (radian arc2)+ <> lineTo' x y+ <> lineTo' x y+ <> close++ withFillStyle (FillStyleSolid pColor) $+ fillPath path+ withLineStyle (def { _line_color = withOpacity white 0.1 }) $+ strokePath path++ ray :: Double -> Double -> Point+ ray angle r = Point x' y'+ where+ x' = x + (cos' * x'')+ y' = y + (sin' * x'')+ cos' = (cos . radian) angle+ sin' = (sin . radian) angle+ -- TODO: is x'' defined in this way to try and avoid+ -- numerical rounding?+ x'' = (x + r) - x+ x = p_x center+ y = p_y center++ radian = (*(pi / 180.0))++label_rgap, label_rlength :: Double+label_rgap = 5+label_rlength = 15++$( makeLenses ''PieLayout )+$( makeLenses ''PieChart )+$( makeLenses ''PieItem )
+ Graphics/Rendering/Chart/Plot/Points.hs view
@@ -0,0 +1,69 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Points+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Functions to plot sets of points, marked in various styles.++{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Points(+ PlotPoints(..),++ -- * Accessors+ -- | These accessors are generated by template haskell++ plot_points_title,+ plot_points_style,+ plot_points_values,+) where+ +import Control.Lens+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Plot.Types+import Data.Default.Class++-- | Value defining a series of datapoints, and a style in+-- which to render them.+data PlotPoints x y = PlotPoints {+ _plot_points_title :: String,+ _plot_points_style :: PointStyle,+ _plot_points_values :: [(x,y)]+}++instance ToPlot PlotPoints where+ toPlot p = Plot {+ _plot_render = renderPlotPoints p,+ _plot_legend = [(_plot_points_title p, renderPlotLegendPoints p)],+ _plot_all_points = (map fst pts, map snd pts)+ }+ where+ pts = _plot_points_values p++renderPlotPoints :: PlotPoints x y -> PointMapFn x y -> BackendProgram ()+renderPlotPoints p pmap = + mapM_ (drawPoint ps . pmap') (_plot_points_values p)+ where+ pmap' = mapXY pmap+ ps = _plot_points_style p++renderPlotLegendPoints :: PlotPoints x y -> Rect -> BackendProgram ()+renderPlotLegendPoints p (Rect p1 p2) = do+ drawPoint ps (Point (p_x p1) y)+ drawPoint ps (Point ((p_x p1 + p_x p2)/2) y)+ drawPoint ps (Point (p_x p2) y)++ where+ ps = _plot_points_style p+ y = (p_y p1 + p_y p2)/2++instance Default (PlotPoints x y) where+ def = PlotPoints + { _plot_points_title = ""+ , _plot_points_style = def+ , _plot_points_values = []+ }++$( makeLenses ''PlotPoints )
+ Graphics/Rendering/Chart/Plot/Types.hs view
@@ -0,0 +1,79 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Types+-- Copyright : (c) Tim Docker 2006, 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Datatypes and functions common to the implementation of the various+-- plot types.+--+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Types(+ Plot(..),+ joinPlot,+ ToPlot(..),++ mapXY,++ plot_render,+ plot_legend,+ plot_all_points,++ ) where++import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Control.Lens++-- | Interface to control plotting on a 2D area.+data Plot x y = Plot {++ -- | Given the mapping between model space coordinates and device+ -- coordinates, render this plot into a chart.+ _plot_render :: PointMapFn x y -> BackendProgram (),++ -- | Details for how to show this plot in a legend. For each item+ -- the string is the text to show, and the function renders a+ -- graphical sample of the plot.+ _plot_legend :: [ (String, Rect -> BackendProgram ()) ],++ -- | All of the model space coordinates to be plotted. These are+ -- used to autoscale the axes where necessary.+ _plot_all_points :: ([x],[y])+}++-- | A type class abstracting the conversion of a value to a Plot.+class ToPlot a where+ toPlot :: a x y -> Plot x y++instance ToPlot Plot where+ toPlot p = p++-- | Join any two plots together (they will share a legend).+joinPlot :: Plot x y -> Plot x y -> Plot x y+joinPlot Plot{ _plot_render = renderP+ , _plot_legend = legendP+ , _plot_all_points = (xsP,ysP) }+ Plot{ _plot_render = renderQ+ , _plot_legend = legendQ+ , _plot_all_points = (xsQ,ysQ) }++ = Plot{ _plot_render = \a-> renderP a >> renderQ a+ , _plot_legend = legendP ++ legendQ+ , _plot_all_points = ( xsP++xsQ, ysP++ysQ )+ }+++----------------------------------------------------------------------++mapXY :: PointMapFn x y -> (x,y) -> Point+mapXY f (x,y) = f (LValue x, LValue y)++----------------------------------------------------------------------++++----------------------------------------------------------------------++$( makeLenses ''Plot )
+ Graphics/Rendering/Chart/Plot/Vectors.hs view
@@ -0,0 +1,145 @@+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Plot.Vectors+-- Copyright : (c) Anton Vorontsov <anton@enomsg.org> 2014+-- License : BSD-style (see chart/COPYRIGHT)+--+-- Vector plots+--+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++module Graphics.Rendering.Chart.Plot.Vectors(+ PlotVectors(..),+ VectorStyle(..),+ plotVectorField,+ plot_vectors_mapf,+ plot_vectors_grid,+ plot_vectors_title,+ plot_vectors_style,+ plot_vectors_scale,+ plot_vectors_values,+ vector_line_style,+ vector_head_style,+) where++import Control.Lens+import Control.Monad+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Data.Tuple+import Data.Colour hiding (over)+import Data.Colour.Names+import Data.Default.Class+import Graphics.Rendering.Chart.Axis+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Plot.Types++data VectorStyle = VectorStyle+ { _vector_line_style :: LineStyle+ , _vector_head_style :: PointStyle+ }++$( makeLenses ''VectorStyle )++data PlotVectors x y = PlotVectors+ { _plot_vectors_title :: String+ , _plot_vectors_style :: VectorStyle+ -- | Set to 1 (default) to normalize the length of vectors to a space+ -- between them (so that the vectors never overlap on the graph).+ -- Set to 0 to disable any scaling.+ -- Values in between 0 and 1 are also permitted to adjust scaling.+ , _plot_vectors_scale :: Double+ -- | Provide a square-tiled regular grid.+ , _plot_vectors_grid :: [(x,y)]+ -- | Provide a vector field (R^2 -> R^2) function.+ , _plot_vectors_mapf :: (x,y) -> (x,y)+ -- | Provide a prepared list of (start,vector) pairs.+ , _plot_vectors_values :: [((x,y),(x,y))]+ }++$( makeLenses ''PlotVectors )++mapGrid :: (PlotValue y, PlotValue x)+ => [(x,y)] -> ((x,y) -> (x,y)) -> [((x,y),(x,y))]+mapGrid grid f = zip grid (f <$> grid)++plotVectorField :: (PlotValue x, PlotValue y) => PlotVectors x y -> Plot x y+plotVectorField pv = Plot+ { _plot_render = renderPlotVectors pv+ , _plot_legend = [(_plot_vectors_title pv, renderPlotLegendVectors pv)]+ , _plot_all_points = (map fst pts, map snd pts)+ }+ where+ pvals = _plot_vectors_values pv+ mvals = mapGrid (_plot_vectors_grid pv) (_plot_vectors_mapf pv)+ pts = concatMap (\(a,b) -> [a,b]) (pvals ++ mvals)++renderPlotVectors :: (PlotValue x, PlotValue y)+ => PlotVectors x y -> PointMapFn x y -> BackendProgram ()+renderPlotVectors pv pmap = do+ let pvals = _plot_vectors_values pv+ mvals = mapGrid (_plot_vectors_grid pv) (_plot_vectors_mapf pv)+ trans = translateToStart <$> (pvals ++ mvals)+ pvecs = filter (\v -> vlen' v > 0) $ over both (mapXY pmap) <$> trans+ mgrid = take 2 $ fst <$> pvecs+ maxLen = maximum $ vlen' <$> pvecs+ spacing = (!!1) $ (vlen <$> zipWith psub mgrid (reverse mgrid)) ++ [maxLen]+ sfactor = spacing/maxLen -- Non-adjusted scale factor+ afactor = sfactor + (1 - sfactor)*(1 - _plot_vectors_scale pv)+ tails = pscale afactor <$> pvecs -- Paths of arrows' tails+ angles = (vangle . psub' . swap) <$> pvecs -- Angles of the arrows+ centers = snd <$> tails -- Where to draw arrow heads+ mapM_ (drawTail radius) tails+ zipWithM_ (drawArrowHead radius) centers angles+ where+ psub' = uncurry psub+ vlen' = vlen . psub'+ pvs = _plot_vectors_style pv+ radius = _point_radius $ _vector_head_style pvs+ hs angle = _vector_head_style pvs & point_shape+ %~ (\(PointShapeArrowHead a) -> PointShapeArrowHead $ a+angle)+ translateToStart (s@(x,y),(vx,vy)) = (s,(tr x vx,tr y vy))+ where tr p t = fromValue $ toValue p + toValue t+ pscale w v@(s,_) = (s,translateP (vscale w . psub' $ swap v) s)+ drawTail r v = withLineStyle (_vector_line_style pvs) $+ strokePointPath $ (^..each) v'+ where+ v' = pscale (1-(3/2)*r/l) v+ l = vlen' v+ drawArrowHead r (Point x y) theta =+ withTranslation (Point (-r*cos theta) (-r*sin theta))+ (drawPoint (hs theta) (Point x y))++renderPlotLegendVectors :: (PlotValue x, PlotValue y)+ => PlotVectors x y -> Rect -> BackendProgram ()+renderPlotLegendVectors pv (Rect p1 p2) = do+ let y = (p_y p1 + p_y p2)/2+ pv' = plot_vectors_grid .~ []+ $ plot_vectors_values .~ [((fromValue $ p_x p1, fromValue y),+ (fromValue $ p_x p2, fromValue 0))]+ $ pv+ renderPlotVectors pv' pmap+ where+ pmap (LValue x,LValue y) = Point (toValue x) (toValue y)+ pmap _ = Point 0 0++instance Default VectorStyle where+ def = VectorStyle+ { _vector_line_style = (solidLine lw $ opaque blue)+ { _line_cap = LineCapSquare }+ , _vector_head_style = PointStyle (opaque red) transparent lw (2*lw)+ (PointShapeArrowHead 0)+ } where lw = 2++instance Default (PlotVectors x y) where+ def = PlotVectors+ { _plot_vectors_title = ""+ , _plot_vectors_style = def+ , _plot_vectors_scale = 1+ , _plot_vectors_grid = []+ , _plot_vectors_mapf = id+ , _plot_vectors_values = []+ }
Graphics/Rendering/Chart/Renderable.hs view
@@ -1,26 +1,25 @@ ----------------------------------------------------------------------------- -- | -- Module : Graphics.Rendering.Chart.Renderable--- Copyright : (c) Tim Docker 2006+-- Copyright : (c) Tim Docker 2006, 2014 -- License : BSD-style (see chart/COPYRIGHT) -- -- This module contains the definition of the 'Renderable' type, which -- is a composable drawing element, along with assorted functions to -- them. --+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE TemplateHaskell #-} module Graphics.Rendering.Chart.Renderable( Renderable(..), ToRenderable(..), PickFn,-- renderableToPNGFile,- renderableToPDFFile,- renderableToPSFile,- renderableToSVGFile,+ Rectangle(..),+ RectCornerStyle(..), - vectorEnv,- bitmapEnv,+ rectangleToRenderable,+ drawRectangle, fillBackground, addMargins,@@ -31,6 +30,7 @@ spacer, spacer1, setPickFn,+ mapMaybePickFn, mapPickFn, nullPickFn, @@ -40,82 +40,77 @@ rect_cornerStyle, ) where -import qualified Graphics.Rendering.Cairo as C import Control.Monad-import Data.Accessor-import Data.List ( nub, transpose, sort )+import Control.Lens+import Data.Default.Class -import Graphics.Rendering.Chart.Types+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Utils --- | A function that maps a point in device coordinates--- to some value.+-- | A function that maps a point in device coordinates to some value. ----- Perhaps it might be generalised from Maybe a to--- (MonadPlus m ) => m a in the future.-type PickFn a = Point -> (Maybe a)+-- Perhaps it might be generalised from Maybe a to+-- (MonadPlus m ) => m a in the future.+type PickFn a = Point -> Maybe a nullPickFn :: PickFn a nullPickFn = const Nothing -- | A Renderable is a record of functions required to layout a--- graphic element.+-- graphic element. data Renderable a = Renderable { - -- | a Cairo action to calculate a minimum size,- minsize :: CRender RectSize,+ -- | Calculate the minimum size of the renderable.+ minsize :: BackendProgram RectSize, - -- | a Cairo action for drawing it within a rectangle.- -- The rectangle is from the origin to the given point.+ -- | Draw the renderable with a rectangle, which covers+ -- the origin to a given point. --- -- The resulting "pick" function maps a point in the image to- -- a value.- render :: RectSize -> CRender (PickFn a)+ -- The resulting "pick" function maps a point in the image to a value.+ render :: RectSize -> BackendProgram (PickFn a) }---- | A type class abtracting the conversion of a value to a--- Renderable.+ deriving (Functor) +-- | A type class abtracting the conversion of a value to a Renderable. class ToRenderable a where- toRenderable :: a -> Renderable ()+ toRenderable :: a -> Renderable () +instance ToRenderable (Renderable a) where+ toRenderable = void++emptyRenderable :: Renderable a emptyRenderable = spacer (0,0) --- | Create a blank renderable with a specified minimum size-spacer :: RectSize -> Renderable a -spacer sz = Renderable {+-- | Create a blank renderable with a specified minimum size.+spacer :: RectSize -> Renderable a+spacer sz = Renderable { minsize = return sz, render = \_ -> return nullPickFn } -- | Create a blank renderable with a minimum size the same as--- some other renderable.+-- some other renderable. spacer1 :: Renderable a -> Renderable b-spacer1 r = Renderable {- minsize = minsize r,- render = \_ -> return nullPickFn-}+spacer1 r = r{ render = \_ -> return nullPickFn } --- | Replace the pick function of a renderable with another+-- | Replace the pick function of a renderable with another. setPickFn :: PickFn b -> Renderable a -> Renderable b-setPickFn pickfn r = Renderable {- minsize=minsize r,- render = \sz -> do { render r sz; return pickfn; }- }+setPickFn pickfn r = r{ render = \sz -> render r sz >> return pickfn } --- | Map a function over result of a renderables pickfunction.+-- | Map a function over the result of a renderable's pickfunction, keeping only 'Just' results.+mapMaybePickFn :: (a -> Maybe b) -> Renderable a -> Renderable b+mapMaybePickFn f r = r{ render = \sz -> do pf <- render r sz+ return (join . fmap f . pf) }++-- | Map a function over result of a renderable's pickfunction. mapPickFn :: (a -> b) -> Renderable a -> Renderable b-mapPickFn f r = Renderable {- minsize=minsize r,- render = \sz -> do {- pf <- render r sz;- return (\p -> fmap f (pf p));- }- }+mapPickFn f = mapMaybePickFn (Just . f) -- | Add some spacing at the edges of a renderable.-addMargins :: (Double,Double,Double,Double) -- ^ The spacing to be added- -> Renderable a -- ^ The source renderable+addMargins :: (Double,Double,Double,Double) -- ^ The spacing to be added.+ -> Renderable a -- ^ The source renderable. -> Renderable a addMargins (t,b,l,r) rd = Renderable { minsize = mf, render = rf } where@@ -123,122 +118,78 @@ (w,h) <- minsize rd return (w+l+r,h+t+b) - rf (w,h) = do- preserveCState $ do- c $ C.translate l t- pickf <- render rd (w-l-r,h-t-b)- mtx <- c $ C.getMatrix- return (mkpickf pickf (w,h) mtx)-- mkpickf pickf (w,h) mtx pt | within pt' rect = pickf pt'- | otherwise = Nothing- where- pt' = transform mtx pt- rect = (Rect (Point 0 0) (Point w h))+ rf (w,h) =+ withTranslation (Point l t) $ do+ pickf <- render rd (w-l-r,h-t-b)+ return (mkpickf pickf (t,b,l,r) (w,h)) -transform :: C.Matrix -> Point -> Point-transform = undefined+ mkpickf pickf (t',b',l',r') (w,h) (Point x y)+ | x >= l' && x <= w-r' && y >= t' && t' <= h-b' = pickf (Point (x-l') (y-t'))+ | otherwise = Nothing -- | Overlay a renderable over a solid background fill.-fillBackground :: CairoFillStyle -> Renderable a -> Renderable a-fillBackground fs r = Renderable { minsize = minsize r, render = rf }+fillBackground :: FillStyle -> Renderable a -> Renderable a+fillBackground fs r = r{ render = rf } where rf rsize@(w,h) = do- preserveCState $ do- setClipRegion (Point 0 0) (Point w h)- setFillStyle fs- c $ C.paint- render r rsize---- | Output the given renderable to a PNG file of the specifed size--- (in pixels), to the specified file.-renderableToPNGFile :: Renderable a -> Int -> Int -> FilePath -> IO ()-renderableToPNGFile chart width height path = - C.withImageSurface C.FormatARGB32 width height $ \result -> do- C.renderWith result $ runCRender rfn bitmapEnv- C.surfaceWriteToPNG result path- where- rfn = do- render chart (fromIntegral width, fromIntegral height)--renderableToFile withSurface chart width height path = - withSurface path (fromIntegral width) (fromIntegral height) $ \result -> do- C.renderWith result $ runCRender rfn vectorEnv- C.surfaceFinish result- where- rfn = do- render chart (fromIntegral width, fromIntegral height)- c $ C.showPage---- | Output the given renderable to a PDF file of the specifed size--- (in points), to the specified file.-renderableToPDFFile :: Renderable a -> Int -> Int -> FilePath -> IO ()-renderableToPDFFile = renderableToFile C.withPDFSurface---- | Output the given renderable to a postscript file of the specifed size--- (in points), to the specified file.-renderableToPSFile :: Renderable a -> Int -> Int -> FilePath -> IO ()-renderableToPSFile = renderableToFile C.withPSSurface---- | Output the given renderable to an SVG file of the specifed size--- (in points), to the specified file.-renderableToSVGFile :: Renderable a -> Int -> Int -> FilePath -> IO ()-renderableToSVGFile = renderableToFile C.withSVGSurface--bitmapEnv = CEnv (adjfn 0.5) (adjfn 0.0)- where- adjfn offset (Point x y) = Point (adj x) (adj y)- where- adj v = (fromIntegral.round) v +offset--vectorEnv = CEnv id id+ withFillStyle fs $ do+ p <- alignFillPath $ rectPath (Rect (Point 0 0) (Point w h))+ fillPath p+ render r rsize -- | Helper function for using a renderable, when we generate it--- in the CRender monad.-embedRenderable :: CRender (Renderable a) -> Renderable a+-- in the BackendProgram monad.+embedRenderable :: BackendProgram (Renderable a) -> Renderable a embedRenderable ca = Renderable { minsize = do { a <- ca; minsize a },- render = \ r -> do { a <- ca; render a r }+ render = \ r -> do { a <- ca; render a r } } ---------------------------------------------------------------------- -- Labels --- | Construct a renderable from a text string, aligned with the axes-label :: CairoFontStyle -> HTextAnchor -> VTextAnchor -> String -> Renderable a+-- | Construct a renderable from a text string, aligned with the axes.+label :: FontStyle -> HTextAnchor -> VTextAnchor -> String -> Renderable String label fs hta vta = rlabel fs hta vta 0 -- | Construct a renderable from a text string, rotated wrt to axes. The angle--- of rotation is in degrees.-rlabel :: CairoFontStyle -> HTextAnchor -> VTextAnchor -> Double -> String -> Renderable a+-- of rotation is in degrees, measured clockwise from the horizontal.+rlabel :: FontStyle -> HTextAnchor -> VTextAnchor -> Double -> String -> Renderable String rlabel fs hta vta rot s = Renderable { minsize = mf, render = rf } where- mf = preserveCState $ do- setFontStyle fs- (w,h) <- textSize s- return (w*acr+h*asr,w*asr+h*acr)- rf (w0,h0) = preserveCState $ do- setFontStyle fs- sz@(w,h) <- textSize s- fe <- c $ C.fontExtents- c $ C.translate 0 (-C.fontExtentsDescent fe)- c $ C.translate (xadj sz hta 0 w0) (yadj sz vta 0 h0)- c $ C.rotate rot'- c $ C.moveTo (-w/2) (h/2)- c $ C.showText s- return nullPickFn- xadj (w,h) HTA_Left x1 x2 = x1 +(w*acr+h*asr)/2- xadj (w,h) HTA_Centre x1 x2 = (x1 + x2)/2- xadj (w,h) HTA_Right x1 x2 = x2 -(w*acr+h*asr)/2- yadj (w,h) VTA_Top y1 y2 = y1 +(w*asr+h*acr)/2- yadj (w,h) VTA_Centre y1 y2 = (y1+y2)/2- yadj (w,h) VTA_Bottom y1 y2 = y2 - (w*asr+h*acr)/2+ mf = withFontStyle fs $ do+ ts <- textSize s+ let sz = (textSizeWidth ts, textSizeHeight ts)+ return (xwid sz, ywid sz) - rot' = rot / 180 * pi- (cr,sr) = (cos rot', sin rot')+ rf (w0,h0) = withFontStyle fs $ do+ ts <- textSize s+ let sz@(w,h) = (textSizeWidth ts, textSizeHeight ts)+ descent = textSizeDescent ts++ xadj HTA_Left = xwid sz/2+ xadj HTA_Centre = w0/2+ xadj HTA_Right = w0 - xwid sz/2++ yadj VTA_Top = ywid sz/2+ yadj VTA_Centre = h0/2+ yadj VTA_Bottom = h0 - ywid sz/2+ yadj VTA_BaseLine = h0 - ywid sz/2 + descent*acr++ withTranslation (Point 0 (-descent)) $+ withTranslation (Point (xadj hta) (yadj vta)) $+ withRotation rot' $ do+ drawText (Point (-w/2) (h/2)) s+ return (\_-> Just s) -- PickFn String++ rot' = rot / 180 * pi+ (cr,sr) = (cos rot', sin rot') (acr,asr) = (abs cr, abs sr) + xwid (w,h) = w*acr + h*asr+ ywid (w,h) = w*asr + h*acr+ ---------------------------------------------------------------------- -- Rectangles @@ -247,80 +198,74 @@ | RCornerRounded Double data Rectangle = Rectangle {- rect_minsize_ :: RectSize,- rect_fillStyle_ :: Maybe CairoFillStyle,- rect_lineStyle_ :: Maybe CairoLineStyle,- rect_cornerStyle_ :: RectCornerStyle+ _rect_minsize :: RectSize,+ _rect_fillStyle :: Maybe FillStyle,+ _rect_lineStyle :: Maybe LineStyle,+ _rect_cornerStyle :: RectCornerStyle } --- | Accessor for field rect_minsize_-rect_minsize = accessor (\v->rect_minsize_ v) (\a v -> v{rect_minsize_=a})---- | Accessor for field rect_fillStyle_-rect_fillStyle = accessor (\v->rect_fillStyle_ v) (\a v -> v{rect_fillStyle_=a})---- | Accessor for field rect_lineStyle_-rect_lineStyle = accessor (\v->rect_lineStyle_ v) (\a v -> v{rect_lineStyle_=a})+instance Default Rectangle where+ def = Rectangle+ { _rect_minsize = (0,0)+ , _rect_fillStyle = Nothing+ , _rect_lineStyle = Nothing+ , _rect_cornerStyle = RCornerSquare+ } --- | Accessor for field rect_cornerStyle_-rect_cornerStyle = accessor (\v->rect_cornerStyle_ v) (\a v -> v{rect_cornerStyle_=a})+instance ToRenderable Rectangle where+ toRenderable = rectangleToRenderable +rectangleToRenderable :: Rectangle -> Renderable a+rectangleToRenderable rectangle = Renderable mf rf+ where+ mf = return (_rect_minsize rectangle)+ rf = \rectSize -> drawRectangle (Point 0 0)+ rectangle{ _rect_minsize = rectSize } -defaultRectangle = Rectangle {- rect_minsize_ = (0,0),- rect_fillStyle_ = Nothing,- rect_lineStyle_ = Nothing,- rect_cornerStyle_ = RCornerSquare-}+-- | Draw the specified rectangle such that its top-left vertex is placed at+-- the given position+drawRectangle :: Point -> Rectangle -> BackendProgram (PickFn a)+drawRectangle point rectangle = do+ maybeM () (fill point size) (_rect_fillStyle rectangle)+ maybeM () (stroke point size) (_rect_lineStyle rectangle)+ return nullPickFn+ where+ size = _rect_minsize rectangle -instance ToRenderable Rectangle where- toRenderable rectangle = Renderable mf rf- where- mf = return (rect_minsize_ rectangle)- rf sz = preserveCState $ do- maybeM () (fill sz) (rect_fillStyle_ rectangle)- maybeM () (stroke sz) (rect_lineStyle_ rectangle)- return nullPickFn+ fill p sz fs =+ withFillStyle fs $+ fillPath $ strokeRectangleP p sz (_rect_cornerStyle rectangle) - fill sz fs = do- setFillStyle fs- strokeRectangle sz (rect_cornerStyle_ rectangle)- c $ C.fill+ stroke p sz ls =+ withLineStyle ls $+ strokePath $ strokeRectangleP p sz (_rect_cornerStyle rectangle) - stroke sz ls = do- setLineStyle ls- strokeRectangle sz (rect_cornerStyle_ rectangle)- c $ C.stroke+ strokeRectangleP (Point x1 y1) (x2,y2) RCornerSquare =+ let (x3,y3) = (x1+x2,y1+y2) in moveTo' x1 y1+ <> lineTo' x1 y3+ <> lineTo' x3 y3+ <> lineTo' x3 y1+ <> lineTo' x1 y1 - strokeRectangle (x2,y2) RCornerSquare = c $ do- let (x1,y1) = (0,0)- C.moveTo x1 y1- C.lineTo x1 y2- C.lineTo x2 y2- C.lineTo x2 y1- C.lineTo x1 y1- C.lineTo x1 y2- - strokeRectangle (x2,y2) (RCornerBevel s) = c $ do- let (x1,y1) = (0,0)- C.moveTo x1 (y1+s)- C.lineTo x1 (y2-s)- C.lineTo (x1+s) y2- C.lineTo (x2-s) y2- C.lineTo x2 (y2-s)- C.lineTo x2 (y1+s)- C.lineTo (x2-s) y1- C.lineTo (x1+s) y1- C.lineTo x1 (y1+s)- C.lineTo x1 (y2-s)+ strokeRectangleP (Point x1 y1) (x2,y2) (RCornerBevel s) =+ let (x3,y3) = (x1+x2,y1+y2) in moveTo' x1 (y1+s)+ <> lineTo' x1 (y3-s)+ <> lineTo' (x1+s) y3+ <> lineTo' (x3-s) y3+ <> lineTo' x3 (y3-s)+ <> lineTo' x3 (y1+s)+ <> lineTo' (x3-s) y1+ <> lineTo' (x1+s) y1+ <> lineTo' x1 (y1+s) - strokeRectangle (x2,y2) (RCornerRounded s) = c $ do- let (x1,y1) = (0,0)- C.arcNegative (x1+s) (y2-s) s (pi2*2) pi2 - C.arcNegative (x2-s) (y2-s) s pi2 0- C.arcNegative (x2-s) (y1+s) s 0 (pi2*3)- C.arcNegative (x1+s) (y1+s) s (pi2*3) (pi2*2)- C.lineTo x1 (y2-s)+ strokeRectangleP (Point x1 y1) (x2,y2) (RCornerRounded s) =+ let (x3,y3) = (x1+x2,y1+y2) in+ arcNeg (Point (x1+s) (y3-s)) s (pi2*2) pi2+ <> arcNeg (Point (x3-s) (y3-s)) s pi2 0+ <> arcNeg (Point (x3-s) (y1+s)) s 0 (pi2*3)+ <> arcNeg (Point (x1+s) (y1+s)) s (pi2*3) (pi2*2)+ <> lineTo' x1 (y3-s) pi2 = pi / 2 +$( makeLenses ''Rectangle )
− Graphics/Rendering/Chart/Simple.hs
@@ -1,304 +0,0 @@--------------------------------------------------------------------------------- |--- Module : Graphics.Rendering.Chart.Simple--- Copyright : (c) David Roundy 2007--- License : BSD-style (see chart/COPYRIGHT)------ An even simpler framework for creating 2D charts in Haskell.------ The basic idea is to make it as easy to plot as octave, which means that--- you provide no more information than you wish to provide. We provide--- four plotting functions, which differ only in their output. One--- produces a "Layout1" that you can customize using other--- Graphics.Rendering.Chart functions. The other three produce their--- output directly. All three accept the same input (except for the--- filename required by plotPDF and plotPS), and produce the same plots.------ The plot functions accept a variable number of arguments. You must--- provide a [Double] which defines the points on the x axis, which must--- precede any of the "y" values. The y values may either be [Double] or--- functions. After any given y value, you can give either Strings or--- PlotKinds describing how you'd like that y printed.------ Examples:------ > renderableToWindow (toRenderable $ plotLayout $ plot [0,0.1..10] sin "sin(x)") 640 480------ > plotWindow [0,1,3,4,8] [12,15,1,5,8] "o" "points"------ > plotPDF "foo.pdf" [0,0.1..10] sin "- " cos ". " cos "o"------ > plotPS "foo.ps" [0,0.1..10] (sin . exp) "- " (sin . exp) "o-"-------------------------------------------------------------------------------module Graphics.Rendering.Chart.Simple( plot, PlotKind(..), xcoords,- plotWindow, plotPDF, plotPS,- plotLayout- ) where--import Data.Maybe ( catMaybes )-import Data.Colour-import Data.Colour.Names--import Graphics.Rendering.Chart-import Graphics.Rendering.Chart.Gtk--styleColor :: Int -> AlphaColour Double-styleColor ind = colorSequence !! ind- where colorSequence = cycle $ map opaque [blue, red, green, yellow, cyan, magenta, black]--styleSymbol :: Int -> PlotKind-styleSymbol ind = symbolSequence !! ind- where symbolSequence = cycle [ Ex, HollowCircle, Square, Diamond,- Triangle, DownTriangle, Plus, Star, FilledCircle ]---- When defaultLayout1 has been generalized, change this signature to --- [InternalPlot x y] -> Layout1 x y z-iplot :: [InternalPlot Double Double] -> Layout1 Double Double-iplot foobar = defaultLayout1 {- layout1_plots_ = concat $ zipWith toplot (ip foobar) [0..]- }- where ip (xs@(IPX _ _):xyss) = map (\ys -> (xs,ys)) yss ++ ip rest- where yss = takeWhile isIPY xyss- rest = dropWhile isIPY xyss- ip (_:xyss) = ip xyss- ip [] = []- isIPY (IPY _ _) = True- isIPY _ = False- toplot (IPX xs _, IPY ys yks) ind = map Left plots- where vs = zip xs ys- plots = case catMaybes $ map plotas yks of- [] -> [toPlot $ defaultPlotLines- { plot_lines_title_ = name yks,- plot_lines_values_ = [vs],- plot_lines_style_ = solidLine 1 (styleColor ind) }]- xs -> xs- plotas Solid = Just $ toPlot $ defaultPlotLines- { plot_lines_title_ = name yks,- plot_lines_values_ = [vs],- plot_lines_style_ = solidLine 1 (styleColor ind) }- plotas Dashed = Just $ toPlot $ defaultPlotLines- { plot_lines_title_ = name yks,- plot_lines_values_ = [vs],- plot_lines_style_ = dashedLine 1 [10,10] (styleColor ind) }- plotas Dotted = Just $ toPlot $ defaultPlotLines- { plot_lines_title_ = name yks,- plot_lines_values_ = [vs],- plot_lines_style_ = dashedLine 1 [1,11] (styleColor ind) }- plotas FilledCircle = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=filledCircles 4 (styleColor ind) }- plotas HollowCircle = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=hollowCircles 5 1 (styleColor ind) }- plotas Triangle = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=hollowPolygon 7 1 3 False (styleColor ind) }- plotas DownTriangle = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=hollowPolygon 7 1 3 True (styleColor ind) }- plotas Square = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=hollowPolygon 7 1 4 False (styleColor ind) }- plotas Diamond = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=hollowPolygon 7 1 4 True (styleColor ind) }- plotas Plus = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=plusses 7 1 (styleColor ind) }- plotas Ex = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=exes 7 1 (styleColor ind) }- plotas Star = Just $ toPlot $ defaultPlotPoints- { plot_points_title_ = name yks,- plot_points_values_ = vs,- plot_points_style_=stars 7 1 (styleColor ind) }- plotas Symbols = plotas (styleSymbol ind)- plotas _ = Nothing--name :: [PlotKind] -> String-name (Name s:_) = s-name (_:ks) = name ks-name [] = ""--str2k :: String -> [PlotKind]-str2k "" = []-str2k ". " = [Dotted]-str2k s@('?':_) = str2khelper s Symbols-str2k s@('@':_) = str2khelper s FilledCircle-str2k s@('#':_) = str2khelper s Square-str2k s@('v':_) = str2khelper s DownTriangle-str2k s@('^':_) = str2khelper s Triangle-str2k s@('o':_) = str2khelper s HollowCircle-str2k s@('+':_) = str2khelper s Plus-str2k s@('x':_) = str2khelper s Ex-str2k s@('*':_) = str2khelper s Star-str2k s@('.':_) = str2khelper s LittleDot-str2k "- " = [Dashed]-str2k "-" = [Solid]-str2k n = [Name n]--str2khelper :: String -> PlotKind -> [PlotKind]-str2khelper s@(_:r) x = case str2k r of- [] -> [x]- [Name _] -> [Name s]- xs -> x:xs---- | Type to define a few simple properties of each plot.-data PlotKind = Name String | FilledCircle | HollowCircle- | Triangle | DownTriangle | Square | Diamond | Plus | Ex | Star | Symbols- | LittleDot | Dashed | Dotted | Solid- deriving ( Eq, Show, Ord )-data InternalPlot x y = IPY [y] [PlotKind] | IPX [x] [PlotKind]--newtype Layout1DDD = Layout1DDD { plotLayout :: Layout1 Double Double }-instance ToRenderable Layout1DDD where- toRenderable = toRenderable . plotLayout--uplot :: [UPlot] -> Layout1DDD-uplot us = Layout1DDD $ iplot $ nameDoubles $ evalfuncs us- where nameDoubles :: [UPlot] -> [InternalPlot Double Double]- nameDoubles (X xs:uus) = case grabName uus of- (ks,uus') -> IPX (filter isValidNumber xs) ks : nameDoubles uus'- nameDoubles (UDoubles xs:uus) = case grabName uus of- (ks,uus') -> IPY (filter isValidNumber xs) ks : nameDoubles uus'- nameDoubles (_:uus) = nameDoubles uus- nameDoubles [] = []- evalfuncs :: [UPlot] -> [UPlot]- evalfuncs (UDoubles xs:uus) = X xs : map ef (takeWhile (not.isX) uus)- ++ evalfuncs (dropWhile (not.isX) uus)- where ef (UFunction f) = UDoubles (map f xs)- ef u = u- evalfuncs (X xs:uus) = X xs : map ef (takeWhile (not.isX) uus)- ++ evalfuncs (dropWhile (not.isX) uus)- where ef (UFunction f) = UDoubles (map f xs)- ef u = u- evalfuncs (u:uus) = u : evalfuncs uus- evalfuncs [] = []- grabName :: [UPlot] -> ([PlotKind],[UPlot])- grabName (UString n:uus) = case grabName uus of- (ks,uus') -> (str2k n++ks,uus')- grabName (UKind ks:uus) = case grabName uus of- (ks',uus') -> (ks++ks',uus')- grabName uus = ([],uus)- isX (X _) = True- isX _ = False---- | The main plotting function. The idea behind PlotType is shamelessly--- copied from Text.Printf (and is not exported). All you need to know is--- that your arguments need to be in class PlotArg. And PlotArg consists--- of functions and [Double] and String and PlotKind or [PlotKind].--plot :: PlotType a => a-plot = pl []-class PlotType t where- pl :: [UPlot] -> t-instance (PlotArg a, PlotType r) => PlotType (a -> r) where- pl args = \ a -> pl (toUPlot a ++ args)-instance PlotType Layout1DDD where- pl args = uplot (reverse args)---- | Display a plot on the screen.--plotWindow :: PlotWindowType a => a-plotWindow = plw []-class PlotWindowType t where- plw :: [UPlot] -> t-instance (PlotArg a, PlotWindowType r) => PlotWindowType (a -> r) where- plw args = \ a -> plw (toUPlot a ++ args)-instance PlotWindowType (IO a) where- plw args = do renderableToWindow (toRenderable $ uplot (reverse args)) 640 480- return undefined---- | Save a plot as a PDF file.--plotPDF :: PlotPDFType a => String -> a-plotPDF fn = pld fn []-class PlotPDFType t where- pld :: FilePath -> [UPlot] -> t-instance (PlotArg a, PlotPDFType r) => PlotPDFType (a -> r) where- pld fn args = \ a -> pld fn (toUPlot a ++ args)-instance PlotPDFType (IO a) where- pld fn args = do renderableToPDFFile (toRenderable $ uplot (reverse args)) 640 480 fn- return undefined---- | Save a plot as a postscript file.--plotPS :: PlotPSType a => String -> a-plotPS fn = pls fn []-class PlotPSType t where- pls :: FilePath -> [UPlot] -> t-instance (PlotArg a, PlotPSType r) => PlotPSType (a -> r) where- pls fn args = \ a -> pls fn (toUPlot a ++ args)-instance PlotPSType (IO a) where- pls fn args = do renderableToPSFile (toRenderable $ uplot (reverse args)) 640 480 fn- return undefined---- | Save a plot as a png file-plotPNG :: PlotPNGType a => String -> a-plotPNG fn = plp fn []--class PlotPNGType t where- plp :: FilePath -> [UPlot] -> t-instance (PlotArg a, PlotPNGType r) => PlotPNGType (a -> r) where- plp fn args = \ a -> plp fn (toUPlot a ++ args)-instance PlotPNGType (IO a) where- plp fn args = do renderableToPNGFile (toRenderable $ uplot (reverse args)) 640 480 fn- return undefined----data UPlot = UString String | UDoubles [Double] | UFunction (Double -> Double)- | UKind [PlotKind] | X [Double]--xcoords :: [Double] -> UPlot-xcoords = X--class PlotArg a where- toUPlot :: a -> [UPlot]--instance IsPlot p => PlotArg [p] where- toUPlot = toUPlot'--instance (Real a, Real b, Fractional a, Fractional b) => PlotArg (a -> b) where- toUPlot x = [UFunction (realToFrac . x . realToFrac)]--instance (Real a, Real b, Fractional a, Fractional b) => IsPlot (a -> b) where- toUPlot' = reverse . concatMap f- where f x = [UFunction (realToFrac . x . realToFrac)]--instance PlotArg UPlot where- toUPlot = (:[])--instance PlotArg PlotKind where- toUPlot = (:[]) . UKind . (:[])--class IsPlot c where- toUPlot' :: [c] -> [UPlot]--instance IsPlot PlotKind where- toUPlot' = (:[]) . UKind--instance IsPlot Double where- toUPlot' = (:[]) . UDoubles--instance IsPlot Char where- toUPlot' = (:[]) . UString--instance IsPlot p => IsPlot [p] where- toUPlot' = reverse . concatMap toUPlot'--instance (IsPlot p, IsPlot q, IsPlot r) => IsPlot (p,q,r) where- toUPlot' = reverse . concatMap f- where f (p,q,r) = toUPlot' [p] ++ toUPlot' [q] ++ toUPlot' [r]--instance (IsPlot p, IsPlot q) => IsPlot (p,q) where- toUPlot' = reverse . concatMap f- where f (p,q) = toUPlot' [p] ++ toUPlot' [q]
+ Graphics/Rendering/Chart/SparkLine.hs view
@@ -0,0 +1,169 @@+---------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.Chart.Sparkline+-- Copyright : (c) Hitesh Jasani, 2008, Malcolm Wallace 2011, Tim Docker 2014+-- License : BSD3+--+-- Sparklines are mini graphs inspired by Edward Tufte; see+-- <http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001OR>+-- and+-- <http://en.wikipedia.org/wiki/Sparkline> for more information.+--+-- The original implementation (by Hitesh Jasani) used the gd+-- package as a backend renderer, and is still available at+-- <http://hackage.haskell.org/package/hsparklines>.+--+-- The present version integrates with+-- the Chart package, in the sense that Sparklines are just another+-- kind of (@ToRenderable a => a@), so they can be composed into grids+-- and used with the rest of Chart.+--+-- > dp :: [Double]+-- > dp = [24,21,32.3,24,15,34,43,55,57,72,74,75,73,72,55,44]+-- >+-- > sl = SparkLine barSpark dp+-- > fopts = FileOptions (sparkSize sl) PNG+-- > renderableToFile fopts (sparkLineToRenderable sl) "bar_spark.png" +-- >+---------------------------------------------------------------++module Graphics.Rendering.Chart.SparkLine+ ( -- * SparkLine type+ SparkLine(..)+ -- * Drawing options+ , SparkOptions(..)+ , smoothSpark+ , barSpark+ -- * Size calculation+ , sparkSize+ -- * Rendering function+ , renderSparkLine+ , sparkLineToRenderable+ , sparkWidth+ ) where++import Control.Monad+import Data.List+import Data.Ord+import Graphics.Rendering.Chart.Geometry+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Renderable+import Data.Colour+import Data.Colour.Names++-- | A sparkline is a single sequence of data values, treated as y-values.+-- The x-values are anonymous and implicit in the sequence.+data SparkLine = SparkLine { sl_options :: SparkOptions+ , sl_data :: [Double]+ }++-- | Options to render the sparklines in different ways.+data SparkOptions = SparkOptions+ { so_smooth :: Bool -- ^ smooth or bars+ , so_step :: Int -- ^ step size+ , so_height :: Int -- ^ graph height (pixels)+ , so_limits :: (Double,Double) -- ^ data point limits+ , so_bgColor :: Colour Double -- ^ background color+ , so_minColor :: Colour Double -- ^ color of minimum datapoint+ , so_maxColor :: Colour Double -- ^ color of maximum datapoint+ , so_lastColor :: Colour Double -- ^ color of last datapoint+ , so_minMarker :: Bool -- ^ display minimum marker+ , so_maxMarker :: Bool -- ^ display maximum marker+ , so_lastMarker :: Bool -- ^ display last marker+ } deriving (Show)++-- | Default options for a smooth sparkline.+smoothSpark :: SparkOptions+smoothSpark = SparkOptions+ { so_smooth = True+ , so_step = 2+ , so_height = 20+ , so_limits = (0,100)+ , so_bgColor = white+ , so_minColor = red+ , so_maxColor = green+ , so_lastColor = blue+ , so_minMarker = True+ , so_maxMarker = True+ , so_lastMarker = True+ }++-- | Default options for a barchart sparkline.+barSpark :: SparkOptions+barSpark = smoothSpark { so_smooth=False }++-- | Create a renderable from a SparkLine.+sparkLineToRenderable :: SparkLine -> Renderable ()+sparkLineToRenderable sp = Renderable+ { minsize = let (w,h) = sparkSize sp in return (fromIntegral w , fromIntegral h)+ , render = \_rect-> renderSparkLine sp+ }++instance ToRenderable SparkLine where+ toRenderable = sparkLineToRenderable++-- | Compute the width of a SparkLine, for rendering purposes.+sparkWidth :: SparkLine -> Int+sparkWidth SparkLine{sl_options=opt, sl_data=ds} =+ let w = 4 + (so_step opt) * (length ds - 1) + extrawidth+ extrawidth | so_smooth opt = 0+ | otherwise = bw * length ds+ bw | so_smooth opt = 0+ | otherwise = 2+ in w++-- | Return the width and height of the SparkLine.+sparkSize :: SparkLine -> (Int,Int)+sparkSize s = (sparkWidth s, so_height (sl_options s))++-- | Render a SparkLine to a drawing surface.+renderSparkLine :: SparkLine -> BackendProgram (PickFn ())+renderSparkLine SparkLine{sl_options=opt, sl_data=ds} =+ let w = 4 + (so_step opt) * (length ds - 1) + extrawidth+ extrawidth | so_smooth opt = 0+ | otherwise = bw * length ds+ bw | so_smooth opt = 0+ | otherwise = 2+ h = so_height opt + dmin = fst (so_limits opt)+ dmax = snd (so_limits opt)+ coords = zipWith (\x y-> Point (fi x) y)+ [1,(1+bw+so_step opt)..(1+(so_step opt+bw)*(length ds))]+ [ fi h - ( (y-dmin) /+ ((dmax-dmin+1) / fi (h-4)) )+ | y <- ds ]+ -- remember y increases as we go down the page+ minpt = maximumBy (comparing p_y) coords+ maxpt = minimumBy (comparing p_y) coords+ endpt = last coords+ boxpt :: Point -> Rect+ boxpt (Point x y) = Rect (Point (x-1)(y-1)) (Point (x+1)(y+1))+ fi :: (Num b, Integral a) => a -> b+ fi = fromIntegral+ in do++ withFillStyle (solidFillStyle (opaque (so_bgColor opt))) $ do+ fillPath (rectPath (Rect (Point 0 0) (Point (fi w) (fi h))))+ if so_smooth opt+ then do+ withLineStyle (solidLine 1 (opaque grey)) $ do+ p <- alignStrokePoints coords+ strokePointPath p+ else do+ withFillStyle (solidFillStyle (opaque grey)) $ do+ forM_ coords $ \ (Point x y) ->+ fillPath (rectPath (Rect (Point (x-1) y) (Point (x+1) (fi h))))+ when (so_minMarker opt) $ do+ withFillStyle (solidFillStyle (opaque (so_minColor opt))) $ do+ p <- alignFillPath (rectPath (boxpt minpt))+ fillPath p+ when (so_maxMarker opt) $ do+ withFillStyle (solidFillStyle (opaque (so_maxColor opt))) $ do+ p <- alignFillPath (rectPath (boxpt maxpt))+ fillPath p+ when (so_lastMarker opt) $ do+ withFillStyle (solidFillStyle (opaque (so_lastColor opt))) $ do+ p <- alignFillPath (rectPath (boxpt endpt))+ fillPath p+ return nullPickFn+
+ Graphics/Rendering/Chart/State.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE TemplateHaskell, FlexibleInstances #-}+module Graphics.Rendering.Chart.State(+ plot,+ plotLeft,+ plotRight,++ takeColor,+ takeShape,++ CState,+ colors,+ shapes,++ EC,+ execEC,+ liftEC,+ liftCState,+ ) where++import Control.Lens+import Control.Monad.State+import Data.Default.Class++import Data.Colour+import Data.Colour.Names++import Graphics.Rendering.Chart.Layout+import Graphics.Rendering.Chart.Plot+import Graphics.Rendering.Chart.Drawing+import Graphics.Rendering.Chart.Renderable++-- | The state held when monadically constructing a graphical element+data CState = CState {+ _colors :: [AlphaColour Double], -- ^ An infinite source of colors, for use in plots+ _shapes :: [PointShape] -- ^ An infinite source of shapes, for use in plots+ }++$( makeLenses ''CState )++-- | We use nested State monads to give nice syntax. The outer state+-- is the graphical element being constructed (typically a+-- layout). The inner state contains any additional state+-- reqired. This approach means that lenses and the state monad lens+-- operators can be used directly on the value being constructed.+type EC l a = StateT l (State CState) a++instance Default CState where+ def = CState defColors defShapes+ where+ defColors = cycle (map opaque [blue,green,red,orange,yellow,violet])+ defShapes = cycle [PointShapeCircle,PointShapePlus,PointShapeCross,PointShapeStar]++instance (Default a,ToRenderable a) => ToRenderable (EC a b) where+ toRenderable = toRenderable . execEC+ +-- | Run the monadic `EC` computation, and return the graphical+-- element (ie the outer monad' state)+execEC :: (Default l) => EC l a -> l+execEC ec = evalState (execStateT ec def) def++-- | Nest the construction of a graphical element within+-- the construction of another.+liftEC :: (Default l1) => EC l1 a -> EC l2 l1+liftEC ec = do+ cs <- lift get+ let (l,cs') = runState (execStateT ec def) cs+ lift (put cs')+ return l++-- | Lift a a computation over `CState`+liftCState :: State CState a -> EC l a+liftCState = lift++-- | Add a plot to the `Layout` being constructed.+plot :: (ToPlot p) => EC (Layout x y) (p x y) -> EC (Layout x y) ()+plot pm = do+ p <- pm+ layout_plots %= (++[toPlot p])++-- | Add a plot against the left axis to the `LayoutLR` being constructed.+plotLeft :: (ToPlot p) => EC (LayoutLR x y1 y2) (p x y1) -> EC (LayoutLR x y1 y2) ()+plotLeft pm = do+ p <- pm+ layoutlr_plots %= (++[Left (toPlot p)])++-- | Add a plot against the right axis tof the `LayoutLR` being constructed.+plotRight :: (ToPlot p) => EC (LayoutLR x y1 y2) (p x y2) -> EC (LayoutLR x y1 y2) ()+plotRight pm = do+ p <- pm+ layoutlr_plots %= (++[Right (toPlot p)])++-- | Pop and return the next color from the state+takeColor :: EC l (AlphaColour Double)+takeColor = liftCState $ do+ (c,cs) <- fromInfiniteList `fmap` use colors+ colors .= cs+ return c++-- | Pop and return the next shape from the state+takeShape :: EC l PointShape+takeShape = liftCState $ do+ (c,cs) <- fromInfiniteList `fmap` use shapes+ shapes .= cs+ return c++fromInfiniteList :: [a] -> (a, [a])+fromInfiniteList [] = error "fromInfiniteList (takeColor or takeShape): empty list"+fromInfiniteList (x:xs) = (x, xs)
− Graphics/Rendering/Chart/Types.hs
@@ -1,507 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# OPTIONS_GHC -XTemplateHaskell #-}---------------------------------------------------------------------------------- |--- Module : Graphics.Rendering.Chart.Types--- Copyright : (c) Tim Docker 2006--- License : BSD-style (see chart/COPYRIGHT)------ This module contains basic types and functions used for drawing.------ Note that template haskell is used to derive accessor functions--- (see 'Data.Accessor') for each field of the following data types:------ * 'CairoLineStyle'------ * 'CairoFontStyle'------ These accessors are not shown in this API documentation. They have--- the same name as the field, but with the trailing underscore--- dropped. Hence for data field f_::F in type D, they have type------ @--- f :: Data.Accessor.Accessor D F--- @-----module Graphics.Rendering.Chart.Types(- Rect(..),- Point(..),- Vector(..),-- RectSize,- Range,-- mkrect,- pvadd,- pvsub,- psub,- vscale,- within,-- RectEdge(..),- Limit(..),- PointMapFn,-- preserveCState,- setClipRegion,- strokeLines,- moveTo,- lineTo,- rectPath,-- isValidNumber,- maybeM,-- defaultColorSeq,-- setSourceColor,-- CairoLineStyle(..),- solidLine,- dashedLine,- setLineStyle,-- CairoFillStyle(..),- defaultPointStyle,- solidFillStyle,- setFillStyle,-- CairoFontStyle(..),- defaultFontStyle,- setFontStyle,-- CairoPointStyle(..),- filledPolygon,- hollowPolygon,- filledCircles,- hollowCircles,- plusses,- exes,- stars,-- HTextAnchor(..),- VTextAnchor(..),- drawText,- textSize,-- CRender(..),- CEnv(..),- runCRender,- c,- alignp,- alignc,- - line_width,- line_color,- line_dashes,- line_cap,- line_join,-- font_name,- font_size,- font_slant,- font_weight,- font_color,--) where--import qualified Graphics.Rendering.Cairo as C-import Control.Monad.Reader-import Data.Accessor-import Data.Accessor.Template-import Data.Colour-import Data.Colour.SRGB-import Data.Colour.Names---- | A point in two dimensions-data Point = Point {- p_x :: Double,- p_y :: Double-} deriving Show--data Vector = Vector {- v_x :: Double,- v_y :: Double-} deriving Show---- | scale a vector by a constant-vscale :: Double -> Vector -> Vector-vscale c (Vector x y) = (Vector (x*c) (y*c))---- | add a point and a vector-pvadd :: Point -> Vector -> Point-pvadd (Point x1 y1) (Vector x2 y2) = (Point (x1+x2) (y1+y2))---- | subtract a vector from a point-pvsub :: Point -> Vector -> Point-pvsub (Point x1 y1) (Vector x2 y2) = (Point (x1-x2) (y1-y2))---- | subtract two points-psub :: Point -> Point -> Vector-psub (Point x1 y1) (Point x2 y2) = (Vector (x1-x2) (y1-y2))--data Limit a = LMin | LValue a | LMax- deriving Show---- | a function mapping between points-type PointMapFn x y = (Limit x, Limit y) -> Point---- | A rectangle is defined by two points-data Rect = Rect Point Point- deriving Show--data RectEdge = E_Top | E_Bottom | E_Left | E_Right---- | Create a rectangle based upon the coordinates of 4 points-mkrect :: Point -> Point -> Point -> Point -> Rect-mkrect (Point x1 _) (Point _ y2) (Point x3 _) (Point _ y4) =- Rect (Point x1 y2) (Point x3 y4)---- | Test if a point is within a rectangle-within :: Point -> Rect -> Bool-within (Point x y) (Rect (Point x1 y1) (Point x2 y2)) =- x >= x1 && x <= x2 && y >= y1 && y <= y2----------------------------------------------------------------------------- | The environment present in the CRender Monad.-data CEnv = CEnv {- -- | An adjustment applied immediately prior to points- -- being displayed in device coordinates- --- -- When device coordinates correspond to pixels, a cleaner- -- image is created if this transform rounds to the nearest- -- pixel. With higher-resolution output, this transform can- -- just be the identity function.- cenv_point_alignfn :: Point -> Point,-- -- | A adjustment applied immediately prior to coordinates- -- being transformed- cenv_coord_alignfn :: Point -> Point-}---- | The reader monad containing context information to control--- the rendering process.-newtype CRender a = DR (ReaderT CEnv C.Render a)- deriving (Functor, Monad, MonadReader CEnv)--runCRender :: CRender a -> CEnv -> C.Render a-runCRender (DR m) e = runReaderT m e--c :: C.Render a -> CRender a-c = DR . lift- --------------------------------------------------------------------------- | Abstract data type for the style of a plotted point------ The contained Cairo action draws a point in the desired--- style, at the supplied device coordinates.-newtype CairoPointStyle = CairoPointStyle (Point -> CRender ())---- | Data type for the style of a line-data CairoLineStyle = CairoLineStyle {- line_width_ :: Double,- line_color_ :: AlphaColour Double,- line_dashes_ :: [Double],- line_cap_ :: C.LineCap,- line_join_ :: C.LineJoin-}---- | Abstract data type for a fill style------ The contained Cairo action sets the required fill--- style in the Cairo rendering state.-newtype CairoFillStyle = CairoFillStyle (CRender ())---- | Data type for a font-data CairoFontStyle = CairoFontStyle {- font_name_ :: String,- font_size_ :: Double,- font_slant_ :: C.FontSlant,- font_weight_ :: C.FontWeight,- font_color_ :: AlphaColour Double-}--type Range = (Double,Double)-type RectSize = (Double,Double)--defaultColorSeq :: [AlphaColour Double]-defaultColorSeq = cycle $ map opaque [blue, red, green, yellow, cyan, magenta]--------------------------------------------------------------------------- Assorted helper functions in Cairo Usage--moveTo, lineTo :: Point -> CRender ()-moveTo p = do- p' <- alignp p- c $ C.moveTo (p_x p') (p_y p')--alignp :: Point -> CRender Point-alignp p = do - alignfn <- fmap cenv_point_alignfn ask- return (alignfn p)--alignc :: Point -> CRender Point-alignc p = do - alignfn <- fmap cenv_coord_alignfn ask- return (alignfn p)--lineTo p = do- p' <- alignp p- c $ C.lineTo (p_x p') (p_y p')--setClipRegion p2 p3 = do - c $ C.moveTo (p_x p2) (p_y p2)- c $ C.lineTo (p_x p2) (p_y p3)- c $ C.lineTo (p_x p3) (p_y p3)- c $ C.lineTo (p_x p3) (p_y p2)- c $ C.lineTo (p_x p2) (p_y p2)- c $ C.clip---- | stroke the lines between successive points-strokeLines :: [Point] -> CRender ()-strokeLines (p1:ps) = do- c $ C.newPath- moveTo p1- mapM_ lineTo ps- c $ C.stroke-strokeLines _ = return ()---- | make a path from a rectangle-rectPath :: Rect -> CRender ()-rectPath (Rect p1@(Point x1 y1) p3@(Point x2 y2)) = do- c $ C.newPath- moveTo p1 >> lineTo p2 >> lineTo p3 >> lineTo p4 >> lineTo p1- where- p2 = (Point x1 y2)- p4 = (Point x2 y1)--setFontStyle f = do- c $ C.selectFontFace (font_name_ f) (font_slant_ f) (font_weight_ f)- c $ C.setFontSize (font_size_ f)- c $ setSourceColor (font_color_ f)--setLineStyle ls = do- c $ C.setLineWidth (line_width_ ls)- c $ setSourceColor (line_color_ ls)- c $ C.setLineCap (line_cap_ ls)- c $ C.setLineJoin (line_join_ ls)- case line_dashes_ ls of- [] -> return ()- ds -> c $ C.setDash ds 0--setFillStyle (CairoFillStyle s) = s--colourChannel :: (Floating a, Ord a) => AlphaColour a -> Colour a-colourChannel c = darken (recip (alphaChannel c)) (c `over` black)--setSourceColor c = let (RGB r g b) = toSRGB $ colourChannel c- in C.setSourceRGBA r g b (alphaChannel c)---- | Return the bounding rectancgle for a text string rendered--- in the current context.-textSize :: String -> CRender RectSize-textSize s = c $ do- te <- C.textExtents s- fe <- C.fontExtents- return (C.textExtentsWidth te, C.fontExtentsHeight fe)--data HTextAnchor = HTA_Left | HTA_Centre | HTA_Right-data VTextAnchor = VTA_Top | VTA_Centre | VTA_Bottom | VTA_BaseLine---- | Function to draw a textual label anchored by one of it's corners--- or edges.-drawText :: HTextAnchor -> VTextAnchor -> Point -> String -> CRender ()-drawText hta vta (Point x y) s = c $ do- te <- C.textExtents s- fe <- C.fontExtents- let lx = xadj hta (C.textExtentsWidth te)- let ly = yadj vta te fe- C.moveTo (x+lx) (y+ly)- C.showText s- where- xadj HTA_Left w = 0- xadj HTA_Centre w = (-w/2)- xadj HTA_Right w = (-w)- yadj VTA_Top te fe = C.fontExtentsAscent fe- yadj VTA_Centre te fe = - (C.textExtentsYbearing te) / 2- yadj VTA_BaseLine te fe = 0- yadj VTA_Bottom te fe = -(C.fontExtentsDescent fe)---- | Execute a rendering action in a saved context (ie bracketed--- between C.save and C.restore)-preserveCState :: CRender a -> CRender a-preserveCState a = do - c $ C.save- v <- a- c $ C.restore- return v--------------------------------------------------------------------------filledCircles ::- Double -- ^ radius of circle- -> AlphaColour Double -- ^ colour- -> CairoPointStyle-filledCircles radius cl = CairoPointStyle rf- where- rf p = do- (Point x y) <- alignp p- c $ setSourceColor cl- c $ C.newPath- c $ C.arc x y radius 0 (2*pi)- c $ C.fill--hollowCircles ::- Double -- ^ radius of circle- -> Double -- ^ thickness of line- -> AlphaColour Double- -> CairoPointStyle-hollowCircles radius w cl = CairoPointStyle rf- where- rf p = do- (Point x y) <- alignp p- c $ C.setLineWidth w- c $ setSourceColor cl- c $ C.newPath- c $ C.arc x y radius 0 (2*pi)- c $ C.stroke--hollowPolygon ::- Double -- ^ radius of circle- -> Double -- ^ thickness of line- -> Int -- ^ Number of vertices- -> Bool -- ^ Is right-side-up?- -> AlphaColour Double- -> CairoPointStyle-hollowPolygon radius w sides isrot cl = CairoPointStyle rf- where rf p =- do (Point x y ) <- alignp p- c $ C.setLineWidth w- c $ setSourceColor cl- c $ C.newPath- let intToAngle n = if isrot- then fromIntegral n * 2*pi / fromIntegral sides- else (0.5 + fromIntegral n)*2*pi/fromIntegral sides- angles = map intToAngle [0 .. sides-1]- (p:ps) = map (\a -> Point (x + radius * sin a) (y + radius * cos a)) angles- moveTo p- mapM_ lineTo (ps++[p])- c $ C.stroke--filledPolygon ::- Double -- ^ radius of circle- -> Int -- ^ Number of vertices- -> Bool -- ^ Is right-side-up?- -> AlphaColour Double- -> CairoPointStyle-filledPolygon radius sides isrot cl = CairoPointStyle rf- where rf p =- do (Point x y ) <- alignp p- c $ setSourceColor cl- c $ C.newPath- let intToAngle n = if isrot- then fromIntegral n * 2*pi / fromIntegral sides- else (0.5 + fromIntegral n)*2*pi/fromIntegral sides- angles = map intToAngle [0 .. sides-1]- (p:ps) = map (\a -> Point (x + radius * sin a) (y + radius * cos a)) angles- moveTo p- mapM_ lineTo (ps++[p])- c $ C.fill--plusses ::- Double -- ^ radius of circle- -> Double -- ^ thickness of line- -> AlphaColour Double- -> CairoPointStyle-plusses radius w cl = CairoPointStyle rf- where rf p = do (Point x y ) <- alignp p- c $ C.setLineWidth w- c $ setSourceColor cl- c $ C.newPath- c $ C.moveTo (x+radius) y- c $ C.lineTo (x-radius) y- c $ C.moveTo x (y-radius)- c $ C.lineTo x (y+radius)- c $ C.stroke--exes ::- Double -- ^ radius of circle- -> Double -- ^ thickness of line- -> AlphaColour Double- -> CairoPointStyle-exes radius w cl = CairoPointStyle rf- where rad = radius / sqrt 2- rf p = do (Point x y ) <- alignp p- c $ C.setLineWidth w- c $ setSourceColor cl- c $ C.newPath- c $ C.moveTo (x+rad) (y+rad)- c $ C.lineTo (x-rad) (y-rad)- c $ C.moveTo (x+rad) (y-rad)- c $ C.lineTo (x-rad) (y+rad)- c $ C.stroke--stars ::- Double -- ^ radius of circle- -> Double -- ^ thickness of line- -> AlphaColour Double- -> CairoPointStyle-stars radius w cl = CairoPointStyle rf- where rad = radius / sqrt 2- rf p = do (Point x y ) <- alignp p- c $ C.setLineWidth w- c $ setSourceColor cl- c $ C.newPath- c $ C.moveTo (x+radius) y- c $ C.lineTo (x-radius) y- c $ C.moveTo x (y-radius)- c $ C.lineTo x (y+radius)- c $ C.moveTo (x+rad) (y+rad)- c $ C.lineTo (x-rad) (y-rad)- c $ C.moveTo (x+rad) (y-rad)- c $ C.lineTo (x-rad) (y+rad)- c $ C.stroke--solidLine ::- Double -- ^ width of line- -> AlphaColour Double- -> CairoLineStyle-solidLine w cl = CairoLineStyle w cl [] C.LineCapButt C.LineJoinMiter--dashedLine ::- Double -- ^ width of line- -> [Double] -- ^ the dash pattern in device coordinates- -> AlphaColour Double- -> CairoLineStyle-dashedLine w ds cl = CairoLineStyle w cl ds C.LineCapButt C.LineJoinMiter--solidFillStyle ::- AlphaColour Double- -> CairoFillStyle-solidFillStyle cl = CairoFillStyle fn- where fn = c $ setSourceColor cl--defaultPointStyle = filledCircles 1 $ opaque white--defaultFontStyle = CairoFontStyle {- font_name_ = "sans",- font_size_ = 10,- font_slant_ = C.FontSlantNormal,- font_weight_ = C.FontWeightNormal,- font_color_ = opaque black-}--isValidNumber v = not (isNaN v) && not (isInfinite v)--maybeM v = maybe (return v)--------------------------------------------------------------------------- Template haskell to derive an instance of Data.Accessor.Accessor for each field-$( deriveAccessors ''CairoLineStyle )-$( deriveAccessors ''CairoFontStyle )-
+ Graphics/Rendering/Chart/Utils.hs view
@@ -0,0 +1,24 @@+-- | Non chart specific utility functions.+module Graphics.Rendering.Chart.Utils(+ isValidNumber,+ log10,+ maybeM,+ whenJust,+ ) where++-- | Checks if the given value is and actual numeric value and not+-- a concept like NaN or infinity.+isValidNumber :: (RealFloat a) => a -> Bool+isValidNumber v = not (isNaN v) && not (isInfinite v)++-- | Shorthand for the decimal logarithm+log10 :: (Floating a) => a -> a+log10 = logBase 10++-- | Version of 'Prelude.maybe' that returns a monadic value.+maybeM :: (Monad m) => b -> (a -> m b) -> Maybe a -> m b+maybeM v = maybe (return v)++-- | Specialization to ()+whenJust :: (Monad m) => Maybe a -> (a -> m ()) -> m ()+whenJust m f = maybeM () f m
+ Numeric/Histogram.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE PatternGuards #-}++module Numeric.Histogram ( 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
− tests/Prices.hs
@@ -1,296 +0,0 @@-module Prices where--prices :: [(Int,Int,Int,Double,Double)]-prices = [- (03,05,2005, 16.18, 42.02),- (04,05,2005, 16.25, 42.31),- (05,05,2005, 16.50, 42.95),- (06,05,2005, 16.52, 43.50),- (09,05,2005, 16.84, 44.91),- (10,05,2005, 16.70, 44.55),- (11,05,2005, 16.43, 43.63),- (12,05,2005, 16.29, 43.18),- (13,05,2005, 15.95, 42.40),- (16,05,2005, 15.55, 41.65),- (17,05,2005, 15.71, 42.17),- (18,05,2005, 15.93, 42.37),- (19,05,2005, 16.20, 42.85),- (20,05,2005, 15.88, 42.30),- (23,05,2005, 15.92, 42.53),- (24,05,2005, 16.25, 42.81),- (25,05,2005, 16.20, 42.67),- (26,05,2005, 16.05, 42.35),- (27,05,2005, 16.45, 43.12),- (30,05,2005, 16.85, 43.24),- (31,05,2005, 16.68, 42.57),- (01,06,2005, 16.85, 43.22),- (02,06,2005, 17.17, 43.85),- (03,06,2005, 17.42, 44.15),- (06,06,2005, 17.50, 44.07),- (07,06,2005, 17.37, 43.76),- (08,06,2005, 17.15, 43.21),- (09,06,2005, 17.12, 43.42),- (10,06,2005, 17.15, 43.27),- (14,06,2005, 17.34, 43.30),- (15,06,2005, 17.46, 43.90),- (16,06,2005, 17.71, 44.43),- (17,06,2005, 18.25, 45.45),- (20,06,2005, 18.23, 45.16),- (21,06,2005, 18.26, 45.54),- (22,06,2005, 18.11, 45.06),- (23,06,2005, 17.79, 44.65),- (24,06,2005, 17.76, 44.43),- (27,06,2005, 17.63, 44.22),- (28,06,2005, 18.08, 44.94),- (29,06,2005, 18.13, 44.80),- (30,06,2005, 18.15, 44.82),- (01,07,2005, 18.09, 45.12),- (04,07,2005, 18.20, 45.20),- (05,07,2005, 18.45, 45.85),- (06,07,2005, 18.40, 45.91),- (07,07,2005, 18.60, 46.35),- (08,07,2005, 18.38, 45.76),- (11,07,2005, 18.80, 46.41),- (12,07,2005, 18.60, 45.86),- (13,07,2005, 18.73, 46.00),- (14,07,2005, 18.70, 46.44),- (15,07,2005, 18.67, 46.25),- (18,07,2005, 18.54, 45.77),- (19,07,2005, 18.20, 45.21),- (20,07,2005, 18.65, 46.13),- (21,07,2005, 19.00, 46.87),- (22,07,2005, 19.09, 47.32),- (25,07,2005, 19.22, 47.36),- (26,07,2005, 19.19, 47.66),- (27,07,2005, 19.17, 47.64),- (28,07,2005, 19.15, 47.98),- (29,07,2005, 19.36, 49.12),- (01,08,2005, 19.40, 49.30),- (02,08,2005, 19.30, 49.17),- (03,08,2005, 19.52, 49.91),- (04,08,2005, 19.80, 50.60),- (05,08,2005, 19.60, 50.40),- (08,08,2005, 19.92, 50.94),- (09,08,2005, 20.34, 52.09),- (10,08,2005, 20.45, 51.42),- (11,08,2005, 20.74, 52.30),- (12,08,2005, 21.05, 53.01),- (15,08,2005, 21.16, 53.02),- (16,08,2005, 20.90, 52.74),- (17,08,2005, 20.55, 51.93),- (18,08,2005, 20.28, 51.42),- (19,08,2005, 20.68, 52.20),- (22,08,2005, 21.22, 53.10),- (23,08,2005, 21.07, 53.01),- (24,08,2005, 20.56, 52.72),- (25,08,2005, 19.90, 50.45),- (26,08,2005, 20.60, 51.42),- (29,08,2005, 20.03, 50.10),- (30,08,2005, 20.47, 50.74),- (31,08,2005, 20.46, 50.31),- (01,09,2005, 20.93, 51.80),- (02,09,2005, 20.83, 51.60),- (05,09,2005, 20.46, 51.56),- (06,09,2005, 20.25, 50.60),- (07,09,2005, 20.55, 51.54),- (08,09,2005, 20.03, 50.55),- (09,09,2005, 20.19, 50.90),- (12,09,2005, 20.20, 51.27),- (13,09,2005, 20.47, 51.91),- (14,09,2005, 20.59, 51.59),- (15,09,2005, 20.70, 52.44),- (16,09,2005, 20.99, 53.77),- (19,09,2005, 21.39, 55.45),- (20,09,2005, 21.53, 55.88),- (21,09,2005, 20.89, 54.63),- (22,09,2005, 21.41, 55.50),- (23,09,2005, 21.30, 55.56),- (26,09,2005, 21.86, 57.55),- (27,09,2005, 22.01, 58.56),- (28,09,2005, 21.81, 58.26),- (29,09,2005, 22.48, 60.01),- (30,09,2005, 22.25, 59.14),- (03,10,2005, 22.30, 58.93),- (04,10,2005, 22.20, 58.61),- (05,10,2005, 21.45, 57.10),- (06,10,2005, 20.76, 56.30),- (07,10,2005, 20.47, 56.50),- (10,10,2005, 20.72, 57.25),- (11,10,2005, 20.33, 57.00),- (12,10,2005, 20.83, 57.48),- (13,10,2005, 20.47, 56.69),- (14,10,2005, 19.98, 55.21),- (17,10,2005, 20.05, 55.95),- (18,10,2005, 20.75, 57.90),- (19,10,2005, 20.05, 56.19),- (20,10,2005, 20.01, 56.30),- (21,10,2005, 20.03, 55.82),- (24,10,2005, 19.77, 54.27),- (25,10,2005, 20.09, 54.82),- (26,10,2005, 20.42, 55.58),- (27,10,2005, 20.49, 55.80),- (28,10,2005, 20.10, 54.94),- (31,10,2005, 20.75, 56.31),- (01,11,2005, 20.89, 57.00),- (02,11,2005, 20.70, 57.11),- (03,11,2005, 21.28, 58.39),- (04,11,2005, 21.35, 58.60),- (07,11,2005, 21.09, 58.18),- (08,11,2005, 21.35, 59.80),- (09,11,2005, 21.03, 59.25),- (10,11,2005, 21.11, 59.21),- (11,11,2005, 21.12, 59.71),- (14,11,2005, 21.60, 61.24),- (15,11,2005, 21.53, 60.96),- (16,11,2005, 21.42, 60.60),- (17,11,2005, 21.40, 60.79),- (18,11,2005, 21.85, 62.45),- (21,11,2005, 21.71, 62.60),- (22,11,2005, 21.67, 61.70),- (23,11,2005, 21.55, 60.70),- (24,11,2005, 21.86, 61.89),- (25,11,2005, 22.02, 62.21),- (28,11,2005, 22.22, 62.09),- (29,11,2005, 21.83, 61.35),- (30,11,2005, 21.87, 61.76),- (01,12,2005, 21.50, 60.40),- (02,12,2005, 21.95, 61.92),- (05,12,2005, 22.03, 63.33),- (06,12,2005, 21.83, 62.99),- (07,12,2005, 21.85, 63.84),- (08,12,2005, 21.56, 63.10),- (09,12,2005, 21.80, 63.55),- (12,12,2005, 21.92, 63.60),- (13,12,2005, 21.65, 63.35),- (14,12,2005, 21.72, 63.15),- (15,12,2005, 21.69, 63.16),- (16,12,2005, 21.60, 62.63),- (19,12,2005, 21.87, 63.81),- (20,12,2005, 22.10, 65.50),- (21,12,2005, 22.50, 67.18),- (22,12,2005, 22.49, 67.75),- (23,12,2005, 22.58, 68.50),- (28,12,2005, 22.59, 68.25),- (29,12,2005, 22.93, 69.10),- (30,12,2005, 22.75, 69.00),- (03,01,2006, 23.18, 69.90),- (04,01,2006, 23.85, 71.06),- (05,01,2006, 23.60, 69.80),- (06,01,2006, 23.35, 68.80),- (09,01,2006, 24.06, 70.18),- (10,01,2006, 23.85, 69.15),- (11,01,2006, 23.88, 69.35),- (12,01,2006, 23.80, 70.19),- (13,01,2006, 23.73, 70.50),- (16,01,2006, 23.74, 71.05),- (17,01,2006, 23.96, 71.94),- (18,01,2006, 23.73, 70.25),- (19,01,2006, 24.45, 72.50),- (20,01,2006, 24.66, 74.00),- (23,01,2006, 24.47, 73.25),- (24,01,2006, 24.84, 74.25),- (25,01,2006, 25.08, 73.96),- (27,01,2006, 26.05, 76.10),- (30,01,2006, 26.58, 78.45),- (31,01,2006, 25.80, 75.82),- (01,02,2006, 25.99, 75.21),- (02,02,2006, 25.50, 74.98),- (03,02,2006, 25.53, 74.75),- (06,02,2006, 25.85, 75.57),- (07,02,2006, 25.70, 75.06),- (08,02,2006, 24.37, 72.75),- (09,02,2006, 24.68, 72.77),- (13,02,2006, 23.88, 70.95),- (14,02,2006, 24.16, 72.37),- (15,02,2006, 24.35, 71.84),- (16,02,2006, 24.29, 71.89),- (17,02,2006, 23.88, 71.65),- (20,02,2006, 24.54, 74.90),- (21,02,2006, 24.98, 75.50),- (22,02,2006, 24.90, 73.24),- (23,02,2006, 25.28, 74.69),- (24,02,2006, 24.55, 72.20),- (27,02,2006, 24.66, 73.00),- (28,02,2006, 24.25, 71.20),- (01,03,2006, 24.03, 70.25),- (02,03,2006, 24.45, 70.50),- (03,03,2006, 24.34, 70.35),- (06,03,2006, 24.51, 70.85),- (08,03,2006, 23.60, 67.95),- (09,03,2006, 23.70, 68.65),- (10,03,2006, 23.37, 67.50),- (13,03,2006, 23.93, 70.36),- (14,03,2006, 23.64, 69.45),- (15,03,2006, 23.90, 69.40),- (16,03,2006, 24.46, 70.90),- (17,03,2006, 24.70, 71.25),- (20,03,2006, 25.24, 72.85),- (21,03,2006, 25.32, 73.08),- (22,03,2006, 25.18, 72.99),- (23,03,2006, 25.57, 74.34),- (24,03,2006, 25.92, 75.23),- (27,03,2006, 26.78, 77.05),- (28,03,2006, 26.85, 77.13),- (30,03,2006, 27.60, 77.96),- (31,03,2006, 28.00, 78.85),- (03,01,2007, 23.18, 69.90),- (04,01,2007, 23.85, 71.06),- (05,01,2007, 23.60, 69.80),- (06,01,2007, 23.35, 68.80),- (09,01,2007, 24.06, 70.18),- (10,01,2007, 23.85, 69.15),- (11,01,2007, 23.88, 69.35),- (12,01,2007, 23.80, 70.19),- (13,01,2007, 23.73, 70.50),- (16,01,2007, 23.74, 71.05),- (17,01,2007, 23.96, 71.94),- (18,01,2007, 23.73, 70.25),- (19,01,2007, 24.45, 72.50),- (20,01,2007, 24.66, 74.00),- (23,01,2007, 24.47, 73.25),- (24,01,2007, 24.84, 74.25),- (25,01,2007, 25.08, 73.96),- (27,01,2007, 26.05, 76.10),- (30,01,2007, 26.58, 78.45),- (31,01,2007, 25.80, 75.82),- (01,02,2007, 25.99, 75.21),- (02,02,2007, 25.50, 74.98),- (03,02,2007, 25.53, 74.75),- (06,02,2007, 25.85, 75.57),- (07,02,2007, 25.70, 75.06),- (08,02,2007, 24.37, 72.75),- (09,02,2007, 24.68, 72.77),- (13,02,2007, 23.88, 70.95),- (14,02,2007, 24.16, 72.37),- (15,02,2007, 24.35, 71.84),- (16,02,2007, 24.29, 71.89),- (17,02,2007, 23.88, 71.65),- (20,02,2007, 24.54, 74.90),- (21,02,2007, 24.98, 75.50),- (22,02,2007, 24.90, 73.24),- (23,02,2007, 25.28, 74.69),- (24,02,2007, 24.55, 72.20),- (27,02,2007, 24.66, 73.00),- (28,02,2007, 24.25, 71.20),- (01,03,2007, 24.03, 70.25),- (02,03,2007, 24.45, 70.50),- (03,03,2007, 24.34, 70.35),- (06,03,2007, 24.51, 70.85),- (08,03,2007, 23.60, 67.95),- (09,03,2007, 23.70, 68.65),- (10,03,2007, 23.37, 67.50),- (13,03,2007, 23.93, 70.36),- (14,03,2007, 23.64, 69.45),- (15,03,2007, 23.90, 69.40),- (16,03,2007, 24.46, 70.90),- (17,03,2007, 24.70, 71.25),- (20,03,2007, 25.24, 72.85),- (21,03,2007, 25.32, 73.08),- (22,03,2007, 25.18, 72.99),- (23,03,2007, 25.57, 74.34),- (24,03,2007, 25.92, 75.23),- (27,03,2007, 26.78, 77.05),- (28,03,2007, 26.85, 77.13),- (30,03,2007, 27.60, 77.96),- (31,03,2007, 28.00, 78.85)- ]
− tests/test.hs
@@ -1,456 +0,0 @@-import qualified Graphics.Rendering.Cairo as C-import Graphics.Rendering.Chart-import Graphics.Rendering.Chart.Simple-import Graphics.Rendering.Chart.Gtk-import Graphics.Rendering.Chart.Grid--import System.Environment(getArgs)-import System.Time-import System.Random-import Data.Time.Calendar-import Data.Time.LocalTime-import Data.Accessor-import Data.Accessor.Tuple-import Data.Colour-import Data.Colour.Names-import Data.Colour.SRGB-import Data.List(sort,nub,scanl1)-import qualified Data.Map as Map-import Control.Monad-import Prices--data OutputType = Window | PNG | PS | PDF | SVG--chooseLineWidth Window = 1.0-chooseLineWidth PNG = 1.0-chooseLineWidth PDF = 0.25-chooseLineWidth PS = 0.25-chooseLineWidth SVG = 0.25--green1 = opaque $ sRGB 0.5 1 0.5-red1 = opaque $ sRGB 0.5 0.5 1-fwhite = solidFillStyle $ opaque white-fparchment = solidFillStyle $ opaque $ sRGB 1.0 0.99 0.90-------------------------------------------------------------------------test1Layout otype = layout- where- am :: Double -> Double- am x = (sin (x*pi/45) + 1) / 2 * (sin (x*pi/5))-- sinusoid1 = plot_lines_values ^= [[ (x,(am x)) | x <- [0,(0.5)..400]]]- $ plot_lines_style ^= solidLine lineWidth (opaque blue)- $ plot_lines_title ^="am"- $ defaultPlotLines-- sinusoid2 = plot_points_style ^= filledCircles 2 (opaque red)- $ plot_points_values ^= [ (x,(am x)) | x <- [0,7..400]]- $ plot_points_title ^="am points"- $ defaultPlotPoints-- layout = layout1_plots ^= [Left (toPlot sinusoid1),- Left (toPlot sinusoid2)]- $ layout1_background ^= fparchment- $ layout1_plot_background ^= Just fwhite- $ defaultLayout1-- lineWidth = chooseLineWidth otype--test1 :: OutputType -> Renderable ()-test1 otype = toRenderable layout- where- layout = layout1_title ^= "Amplitude Modulation"- $ (test1Layout otype)--test1a :: OutputType -> Renderable ()-test1a otype = fillBackground fwhite $ (gridToRenderable t)- where- t = weights (1,1) $ aboveN [ besideN [rf g1, rf g2, rf g3], - besideN [rf g4, rf g5, rf g6] ]-- g1 = layout1_title ^= "minimal"- $ layout1_bottom_axis ^: laxis_override ^= (axisGridHide.axisTicksHide)- $ layout1_left_axis ^: laxis_override ^= (axisGridHide.axisTicksHide)- $ test1Layout otype-- g2 = layout1_title ^= "with borders"- $ layout1_bottom_axis ^: laxis_override ^= (axisGridHide.axisTicksHide)- $ layout1_left_axis ^: laxis_override ^= (axisGridHide.axisTicksHide)- $ layout1_top_axis ^: axisBorderOnly- $ layout1_right_axis ^: axisBorderOnly- $ test1Layout otype-- g3 = layout1_title ^= "default"- $ test1Layout otype-- g4 = layout1_title ^= "tight grid"- $ layout1_left_axis ^: laxis_generate ^= axis- $ layout1_left_axis ^: laxis_override ^= axisGridAtTicks- $ layout1_bottom_axis ^: laxis_generate ^= axis- $ layout1_bottom_axis ^: laxis_override ^= axisGridAtTicks- $ test1Layout otype- where- axis = autoScaledAxis (- la_nLabels ^= 5 - $ la_nTicks ^= 20- $ defaultLinearAxis- )-- g5 = layout1_title ^= "y linked"- $ layout1_yaxes_control ^= linkAxes- $ test1Layout otype-- g6 = layout1_title ^= "everything"- $ layout1_yaxes_control ^= linkAxes- $ layout1_top_axis ^: laxis_visible ^= const True- $ test1Layout otype-- rf = tval.toRenderable-- axisBorderOnly = (laxis_visible ^= const True)- . (laxis_override ^= (axisGridHide.axisTicksHide.axisLabelsHide))-------------------------------------------------------------------------test2 :: [(Int,Int,Int,Double,Double)] -> Bool -> OutputType -> Renderable ()-test2 prices showMinMax otype = toRenderable layout- where-- lineStyle c = line_width ^= 3 * chooseLineWidth otype- $ line_color ^= c- $ defaultPlotLines ^. plot_lines_style-- limitLineStyle c = line_width ^= chooseLineWidth otype- $ line_color ^= opaque c- $ line_dashes ^= [5,10]- $ defaultPlotLines ^. plot_lines_style-- price1 = plot_lines_style ^= lineStyle (opaque blue)- $ plot_lines_values ^= [[ ((date d m y), v) | (d,m,y,v,_) <- prices]]- $ plot_lines_title ^= "price 1"- $ defaultPlotLines-- price2 = plot_lines_style ^= lineStyle (opaque green)- $ plot_lines_values ^= [[ ((date d m y), v) | (d,m,y,_,v) <- prices]]- $ plot_lines_title ^= "price 2"- $ defaultPlotLines-- (min1,max1) = (minimum [v | (_,_,_,v,_) <- prices],maximum [v | (_,_,_,v,_) <- prices])- (min2,max2) = (minimum [v | (_,_,_,_,v) <- prices],maximum [v | (_,_,_,_,v) <- prices])- limits | showMinMax = [ Left $ hlinePlot "min/max" (limitLineStyle blue) min1,- Left $ hlinePlot "" (limitLineStyle blue) max1,- Right $ hlinePlot "min/max" (limitLineStyle green) min2,- Right $ hlinePlot "" (limitLineStyle green) max2 ]- | otherwise = []-- bg = opaque $ sRGB 0 0 0.25- fg = opaque white- fg1 = opaque $ sRGB 0.0 0.0 0.15-- layout = layout1_title ^="Price History"- $ layout1_background ^= solidFillStyle bg- $ updateAllAxesStyles (axis_grid_style ^= solidLine 1 fg1)- $ layout1_left_axis ^: laxis_override ^= axisGridHide- $ layout1_right_axis ^: laxis_override ^= axisGridHide- $ layout1_bottom_axis ^: laxis_override ^= axisGridHide- $ layout1_plots ^= ([Left (toPlot price1), Right (toPlot price2)] ++ limits)- $ layout1_grid_last ^= False- $ setLayout1Foreground fg- $ defaultLayout1--date dd mm yyyy = (LocalTime (fromGregorian (fromIntegral yyyy) mm dd) midnight)-------------------------------------------------------------------------test3 :: OutputType -> Renderable ()-test3 otype = toRenderable layout- where-- price1 = plot_fillbetween_style ^= solidFillStyle green1- $ plot_fillbetween_values ^= [ (date d m y,(0,v2)) | (d,m,y,v1,v2) <- prices]- $ plot_fillbetween_title ^= "price 1"- $ defaultPlotFillBetween -- price2 = plot_fillbetween_style ^= solidFillStyle red1- $ plot_fillbetween_values ^= [ (date d m y,(0,v1)) | (d,m,y,v1,v2) <- prices]- $ plot_fillbetween_title ^= "price 2"- $ defaultPlotFillBetween-- layout = layout1_title ^= "Price History"- $ layout1_grid_last ^= True- $ layout1_plots ^= [Left (toPlot price1),- Left (toPlot price2)]- $ defaultLayout1------------------------------------------------------------------------ -test4 :: Bool -> Bool -> OutputType -> Renderable ()-test4 xrev yrev otype = toRenderable layout- where-- points = plot_points_style ^= filledCircles 3 (opaque red)- $ plot_points_values ^= [ (x, LogValue (10**x)) | x <- [0.5,1,1.5,2,2.5] ]- $ plot_points_title ^= "values"- $ defaultPlotPoints-- lines = plot_lines_values ^= [ [(x, LogValue (10**x)) | x <- [0,3]] ]- $ plot_lines_title ^= "values"- $ defaultPlotLines-- layout = layout1_title ^= "Log/Linear Example"- $ layout1_bottom_axis ^: laxis_title ^= "horizontal"- $ layout1_bottom_axis ^: laxis_reverse ^= xrev- $ layout1_left_axis ^: laxis_title ^= "vertical"- $ layout1_left_axis ^: laxis_reverse ^= yrev- $ layout1_plots ^= [Left (toPlot points), Left (toPlot lines) ]- $ defaultLayout1--------------------------------------------------------------------------- Example thanks to Russell O'Connor--test5 :: OutputType -> Renderable ()-test5 otype = toRenderable (layout 1001 (trial bits) :: Layout1 Double LogValue)- where- bits = randoms $ mkStdGen 0-- layout n t = layout1_title ^= "Simulation of betting on a biased coin"- $ layout1_plots ^= [- Left (toPlot (plot "f=0.05" s1 n 0 (t 0.05))),- Left (toPlot (plot "f=0.1" s2 n 0 (t 0.1)))- ]- $ defaultLayout1-- plot tt s n m t = plot_lines_style ^= s- $ plot_lines_values ^=- [[(fromIntegral x, LogValue y) | (x,y) <-- filter (\(x,_)->x `mod` (m+1)==0) $ take n $ zip [0..] t]]- $ plot_lines_title ^= tt- $ defaultPlotLines -- b = 0.1-- trial bits frac = scanl (*) 1 (map f bits)- where - f True = (1+frac*(1+b))- f False = (1-frac)-- s1 = solidLine lineWidth $ opaque green- s2 = solidLine lineWidth $ opaque blue-- lineWidth = chooseLineWidth otype------------------------------------------------------------------------- --- Test the Simple interface--test6 :: OutputType -> Renderable ()-test6 otype = toRenderable (plotLayout pp){layout1_title_="Graphics.Rendering.Chart.Simple example"}- where- pp = plot xs sin "sin"- cos "cos" "o"- (sin.sin.cos) "sin.sin.cos" "."- (/3) "- "- (const 0.5)- [0.1,0.7,0.5::Double] "+"- xs = [0,0.3..3] :: [Double]-------------------------------------------------------------------------test7 :: OutputType -> Renderable ()-test7 otype = toRenderable layout- where- vals :: [(Double,Double,Double,Double)]- vals = [ (x,sin (exp x),sin x/2,cos x/10) | x <- [1..20]]- bars = plot_errbars_values ^= [symErrPoint x y dx dy | (x,y,dx,dy) <- vals]- $ plot_errbars_title ^="test"- $ defaultPlotErrBars-- points = plot_points_style ^= filledCircles 2 (opaque red)- $ plot_points_values ^= [(x,y) | (x,y,dx,dy) <- vals]- $ plot_points_title ^= "test"- $ defaultPlotPoints-- layout = layout1_title ^= "errorbars example"- $ layout1_plots ^= [Left (toPlot bars),- Left (toPlot points)]- $ defaultLayout1--------------------------------------------------------------------------test8 :: OutputType -> Renderable ()-test8 otype = toRenderable layout- where- values = [ ("eggs",38,e), ("milk",45,e), ("bread",11,e1), ("salmon",8,e) ]- e = 0- e1 = 25- layout = pie_title ^= "Pie Chart Example"- $ pie_plot ^: pie_data ^= [ defaultPieItem{pitem_value_=v,pitem_label_=s,pitem_offset_=o} - | (s,v,o) <- values ]- $ defaultPieLayout---------------------------------------------------------------------------test9 :: OutputType -> Renderable ()-test9 otype = fillBackground fwhite $ (gridToRenderable t)- where- t = weights (1,1) $ aboveN [ besideN [rf g0, rf g1, rf g2],- besideN [rf g3, rf g4, rf g5] ]-- g0 = layout "clustered 1"- $ plot_bars_style ^= BarsClustered- $ plot_bars_spacing ^= BarsFixWidth 25- $ bars1-- g1 = layout "clustered / fix width "- $ plot_bars_style ^= BarsClustered- $ plot_bars_spacing ^= BarsFixWidth 25- $ bars2-- g2 = layout "clustered / fix gap "- $ plot_bars_style ^= BarsClustered- $ plot_bars_spacing ^= BarsFixGap 10- $ bars2-- g3 = layout "stacked 1"- $ plot_bars_style ^= BarsStacked- $ plot_bars_spacing ^= BarsFixWidth 25- $ bars1-- g4 = layout "stacked / fix width"- $ plot_bars_style ^= BarsStacked- $ plot_bars_spacing ^= BarsFixWidth 25- $ bars2-- g5 = layout "stacked / fix gap"- $ plot_bars_style ^= BarsStacked- $ plot_bars_spacing ^= BarsFixGap 10- $ bars2-- rf = tval.toRenderable-- alabels = [ "Jun", "Jul", "Aug", "Sep", "Oct" ]--- layout title bars = layout1_title ^= title- $ layout1_bottom_axis ^: laxis_generate ^= autoIndexAxis alabels- $ layout1_left_axis ^: laxis_override ^= (axisGridHide.axisTicksHide)- $ layout1_plots ^= [ Left (plotBars bars) ]- $ defaultLayout1 :: Layout1 PlotIndex Double-- bars1 = plot_bars_titles ^= ["Cash"]- $ plot_bars_values ^= addIndexes [[20],[45],[30],[70]]- $ defaultPlotBars-- bars2 = plot_bars_titles ^= ["Cash","Equity"]- $ plot_bars_values ^= addIndexes [[20,45],[45,30],[30,20],[70,25]]- $ defaultPlotBars-----------------------------------------------------------------------------------test10 :: [(Int,Int,Int,Double,Double)] -> OutputType -> Renderable ()-test10 prices otype = toRenderable layout- where-- lineStyle c = line_width ^= 3 * chooseLineWidth otype- $ line_color ^= c- $ defaultPlotLines ^. plot_lines_style-- price1 = plot_lines_style ^= lineStyle (opaque blue)- $ plot_lines_values ^= [[ ((date d m y), v) | (d,m,y,v,_) <- prices]]- $ plot_lines_title ^= "price 1"- $ defaultPlotLines-- price1_area = plot_fillbetween_values ^= [((date d m y), (v * 0.95, v * 1.05)) | (d,m,y,v,_) <- prices]- $ plot_fillbetween_style ^= solidFillStyle (withOpacity blue 0.2)- $ defaultPlotFillBetween-- price2 = plot_lines_style ^= lineStyle (opaque red)- $ plot_lines_values ^= [[ ((date d m y), v) | (d,m,y,_,v) <- prices]]- $ plot_lines_title ^= "price 2"- $ defaultPlotLines-- price2_area = plot_fillbetween_values ^= [((date d m y), (v * 0.95, v * 1.05)) | (d,m,y,_,v) <- prices]- $ plot_fillbetween_style ^= solidFillStyle (withOpacity red 0.2)- $ defaultPlotFillBetween-- fg = opaque black- fg1 = opaque $ sRGB 0.0 0.0 0.15-- layout = layout1_title ^="Price History"- $ layout1_background ^= solidFillStyle (opaque white)- $ layout1_right_axis ^: laxis_override ^= axisGridHide- $ layout1_plots ^= [ Left (toPlot price1_area), Right (toPlot price2_area)- , Left (toPlot price1), Right (toPlot price2)- ]- $ setLayout1Foreground fg- $ defaultLayout1--------------------------------------------------------------------------- a quick test to display labels with all combinations--- of anchors-misc1 rot otype = fillBackground fwhite $ (gridToRenderable t)- where- t = weights (1,1) $ aboveN [ besideN [tval (lb h v) | h <- hs] | v <- vs ]- lb h v = addMargins (20,20,20,20) $ fillBackground fblue $ crossHairs $ rlabel fs h v rot s- s = "Labelling"- hs = [HTA_Left, HTA_Centre, HTA_Right]- vs = [VTA_Top, VTA_Centre, VTA_Bottom]- fwhite = solidFillStyle $ opaque white- fblue = solidFillStyle $ opaque $ sRGB 0.8 0.8 1- fs = defaultFontStyle{font_size_=20,font_weight_=C.FontWeightBold}- crossHairs r =Renderable {- minsize = minsize r,- render = \sz@(w,h) -> do- let xa = w / 2- let ya = h / 2- strokeLines [Point 0 ya,Point w ya]- strokeLines [Point xa 0,Point xa h]- render r sz- }------------------------------------------------------------------------ -allTests :: [ (String, OutputType -> Renderable ()) ]-allTests =- [ ("test1", test1)- , ("test1a", test1a)- , ("test2a", test2 prices False)- , ("test2b", test2 (filterPrices (date 1 1 2005) (date 31 12 2005)) False)- , ("test2c", test2 (filterPrices (date 1 5 2005) (date 1 7 2005)) False)- , ("test2d", test2 (filterPrices (date 1 1 2006) (date 10 1 2006)) False)- , ("test2e", test2 (filterPrices (date 1 8 2005) (date 31 8 2005)) True)- , ("test3", test3)- , ("test4a", test4 False False)- , ("test4b", test4 True False)- , ("test4c", test4 False True)- , ("test5", test5)- , ("test6", test6)- , ("test7", test7)- , ("test8", test8)- , ("test9", test9)- , ("test10", test10 (filterPrices (date 1 1 2005) (date 31 12 2005)))- , ("misc1", misc1 0)- , ("misc1a", misc1 45)- ]--filterPrices t1 t2 = [ v | v@(d,m,y,_,_) <- prices, let t = date d m y in t >= t1 && t <= t2]--main = do- args <- getArgs- main1 args--main1 :: [String] -> IO ()-main1 ("--png":tests) = showTests tests renderToPNG-main1 ("--pdf":tests) = showTests tests renderToPDF-main1 ("--svg":tests) = showTests tests renderToSVG-main1 ("--ps":tests) = showTests tests renderToPS-main1 tests = showTests tests renderToWindow--showTests :: [String] -> ((String,OutputType -> Renderable ()) -> IO()) -> IO ()-showTests tests ofn = mapM_ ofn (filter (match tests) allTests)--match :: [String] -> (String,a) -> Bool-match [] t = True-match ts t = (fst t) `elem` ts--renderToWindow (n,ir) = renderableToWindow (ir Window) 640 480-renderToPNG (n,ir) = renderableToPNGFile (ir PNG) 640 480 (n ++ ".png")-renderToPS (n,ir) = renderableToPSFile (ir PS) 640 480 (n ++ ".ps")-renderToPDF (n,ir) = renderableToPDFFile (ir PDF) 640 480 (n ++ ".pdf")-renderToSVG (n,ir) = renderableToSVGFile (ir SVG) 640 480 (n ++ ".svg")