--------------------------------------------------------------- -- | -- 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 -- -- and -- for more information. -- -- The original implementation (by Hitesh Jasani) used the gd -- package as a backend renderer, and is still available at -- . -- -- 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