A simple plot of data series with shapes and colours.
import Diagrams.Backend.SVG.CmdLine
import Diagrams.Prelude
import Graphics.SVGFonts
import Graphics.SVGFonts.ReadFont (PreparedFont)
import Diagrams.Core.Points -- needed to work around bug in GHC 7.4
Each series is a label and a list of points (x-y pairs). Each series will be drawn as a separate line, with its own combination of colour, dashing pattern and shape.
type Pt = (Double, Double)
type Points = [Pt]
dataSeries :: [(String,Points)]
=
dataSeries "upward", zip [0.0, 1.0 .. 10.0] [0.0, 1.0 .. 10.0])
[ ("downward", zip [0.0, 1.0 .. 10.0] [10.0, 9.0 .. 0.0])
, ("cycle", zip [0.0, 1.0 .. 10.0] (cycle [3,4,5]))
, ("arbitrary", [(2,4), (4,2), (5,4), (10,5)])
, ("sin", map (\x -> (x, 8+sin x)) [0.0, 0.5 .. 10.0])
, (
]
type Dia = Diagram B
The final diagram is the chart with the legend next to it.
example :: IO Dia
= do
example <- lin2
lin2_ return . centerXY $
map snd dataSeries) plotStyles [0,2,4,6,8,10] [0,2,4,6,8,10])
(centerY (chart lin2_ (||| strutX 1
||| centerY (legend lin2_ plotStyles (map fst dataSeries)))
`atop` square 12 # translateX 5 # scaleY 0.85 -- border
The size of the chart, in logical units.
w :: Double
h,= 7
h = 7 w
The chart is made of the data series, the outer box, and the horizontal and vertical axes markings.
“dataToFrac” converts points from the “data” space [0..10] into the [0..1] range.
chart :: PreparedFont Double -> [Points] -> [(Dia, Dia -> Dia)] -> [Double] -> [Double] -> Dia
= mconcat
chart font series styles xs ys
[ plotMany styles series dataToFracmap (\x -> ((x-minx)/xrange, showFloor x)) xs)
, horizticks font (map (\y -> ((y-miny)/yrange, showFloor y)) ys)
, vertticks font (
, box
]where maxx = last xs
= head xs
minx = last ys
maxy = head ys
miny = maxx-minx
xrange = maxy-miny
yrange = ((x-minx)/xrange, (y-miny)/yrange)
dataToFrac (x,y) = show . (floor :: Double -> Integer) showFloor
Plot a single data series. A “shape” is drawn at every data point, and straight lines are drawn between points.
plot :: ((Double, Double) -> (Double, Double)) -> Dia -> (Dia -> Dia) -> Points -> Dia
=
plot dataToFrac shape lineStyle ps let scalify (x,y) = (x*w,y*h)
= map (p2 . scalify . dataToFrac) ps
ps' in (strokeP $ fromVertices ps') # lineStyle
`beneath` mconcat [ shape # moveTo p | p <- ps' ]
Plot many data series using the given list of styles.
plotMany :: [(Dia, Dia -> Dia)] -> [Points] -> (Pt -> Pt) -> Dia
=
plotMany styles seriesList dataToFrac mconcat $ zipWith (uncurry (plot dataToFrac)) (styles ++ plotStyles) seriesList
A string of text, converted to a path and filled.
text' :: PreparedFont Double -> String -> Dia
text' font s= (set_envelope . fit_height 0.4 . svgText def { textFont = font } $ s)
# fc black # lw none
The chart’s legend. Each label is drawn next to a little example of how the line looks in the chart.
legend :: PreparedFont Double -> [(Dia, Dia -> Dia)] -> [String] -> Dia
= centerXY $
legend font styles labels =0.15} $
vcat' with {_sepmap (\(l,s) -> littleLine s ||| strutX 0.4 ||| text' font l # alignL)
zip labels (styles ++ plotStyles))
(where littleLine (d,l) = (strokeP $ fromVertices [ 0^&0, 1^&0 ]) # l
<> d # moveTo (0.5^&0)
The outer box is just a rectangle.
box :: Dia
= strokeLoop . closeLine . fromVertices $ [ 0^&0, 0^&h, w^&h, w^&0 ] box
Each tick on the vertical axis has a text part, a solid line on the left, a solid line on the right, and a long dashed line from left to right.
vertticks :: PreparedFont Double -> [(Double, String)] -> Dia
=
vertticks font pairs let textBits = mconcat [ text' font t # alignR # moveTo ((-0.2)^&(y*h)) | (y,t) <- pairs ]
= mconcat [ fromVertices [ 0^&(y*h), 0.1 ^&(y*h) ] | (y,_) <- pairs ]
tickBits <> mconcat [ fromVertices [ w^&(y*h), (w-0.1)^&(y*h) ] | (y,_) <- pairs ]
<> mconcat [ fromVertices [ 0^&(y*h), w^&(y*h) ] # lc gray # dashingG [ 0.1, 0.1 ] 0 | (y,_) <- pairs ]
in textBits <> tickBits
(Similar for the horizontal axis.)
horizticks :: PreparedFont Double -> [(Double, String)] -> Dia
=
horizticks font pairs let textBits = mconcat [ text' font t # moveTo ((x*w)^&(-0.3)) | (x,t) <- pairs ]
= mconcat [ fromVertices [ (x*w)^&0, (x*w)^&0.1 ] | (x,_) <- pairs ]
tickBits <> mconcat [ fromVertices [ (x*w)^&h, (x*w)^&(h-0.1) ] | (x,_) <- pairs ]
<> mconcat [ fromVertices [ (x*w)^&0, (x*w)^&h ] # lc gray # dashingG [ 0.1, 0.1 ] 0 | (x,_) <- pairs ]
in textBits <> tickBits
A dot style is a shape (any diagram) and a boolean indicating whether the shape should be filled, a line style is a dashing pattern, and a colour style is just a colour. These three combined give a “style”.
newtype Fill = Fill Bool
type Shape = Dia
type DotStyle = (Shape, Fill)
type LineStyle = Dia -> Dia
plotStyles :: [ (Shape, LineStyle) ]
= zipWith3 combineStyles dotStyles colourStyles lineStyles
plotStyles
combineStyles :: DotStyle -> Colour Double -> LineStyle -> (Shape, LineStyle)
Fill f) c l =
combineStyles (d,# (if f then fcA (c `withOpacity` 0.5) else id) # lc c, lc c . l ) ( d
The dot styles.
dotStyles :: [DotStyle]
= cycle $
dotStyles let shapes = map (strokeP)
0.07
[ circle 0.1
, square 0.1
, eqTriangle 0.1
, pentagon 0.07
, cross 0.07
, plus StarSkip 2) (pentagon 0.1)
, star (
]in [ (s, Fill b) | b <- [True,False], s <- shapes ]
Some custom shapes.
cross :: Double -> Path V2 Double
= fromVertices [ x^&(-x) , ((-x)^&x) ]
cross x <> fromVertices [ x^&x , ((-x)^&(-x)) ]
plus :: Double -> Path V2 Double
= cross x # rotate (45 @@ deg) plus x
The colour styles.
colourStyles :: [Colour Double]
= cycle $ [ red, green, blue, brown ] colourStyles
The line styles.
lineStyles :: [Dia -> Dia]
= cycle $
lineStyles id, dashingG [0.1,0.1] 0, dashingG [0.02,0.02] 0
[ 0.1,0.1,0.03,0.1] 0, dashingG [0.1,0.1,0.02,0.02,0.02,0.1] 0 ] , dashingG [
= mainWith (example :: Diagram B) main