--------------------------------------------------------------- -- | -- Module : Graphics.Rendering.HSparklines -- Copyright : (c) Hitesh Jasani, 2008 -- License : BSD3 -- -- Maintainer : Hitesh Jasani -- Stability : experimental -- Portability : portable -- -- Created : 2008-02-26 -- Version : 0.1 -- -- Sparklines implementation in Haskell. Sparklines are -- mini graphs inspired by Edward Tufte. -- -- > dp :: [Float] -- > dp = [24,21,32.3,24,15,34,43,55,57,72,74,75,73,72,55,44] -- > -- > make barSpark dp >>= savePngFile "bar_spark.png" -- > --------------------------------------------------------------- module Graphics.Rendering.HSparklines ( -- * Types SparkOptions(..) ,rgb -- * Drawing functions ,make ,smoothSpark ,barSpark -- * Saving functions ,savePngFile ,encodePngAsDataUrl ) where import Codec.Binary.Base64 ( encode ) import Control.Monad import Data.ByteString ( unpack ) import Data.List import Data.Ord import Graphics.GD make :: SparkOptions -> [Float] -> IO Image make so@(SmoothOptions {}) dp = renderSmooth so dp make so@(BarOptions {}) dp = renderBar so dp data SparkOptions = SmoothOptions { step :: Int -- ^ step size ,height :: Int -- ^ graph height (pixels) ,limits :: (Int,Int) -- ^ data point limits ,bgColor :: Color -- ^ background color ,minColor :: Color -- ^ color of minimum datapoint ,maxColor :: Color -- ^ color of maximum datapoint ,lastColor :: Color -- ^ color of last datapoint ,minMarker :: Bool -- ^ display minimum marker ,maxMarker :: Bool -- ^ display maximum marker ,lastMarker :: Bool -- ^ display last marker } | BarOptions { step :: Int -- ^ step size ,height :: Int -- ^ graph height (pixels) ,limits :: (Int,Int) -- ^ data point limits ,bgColor :: Color -- ^ background color ,minColor :: Color -- ^ color of minimum datapoint ,maxColor :: Color -- ^ color of maximum datapoint ,lastColor :: Color -- ^ color of last datapoint ,minMarker :: Bool -- ^ display minimum marker ,maxMarker :: Bool -- ^ display maximum marker ,lastMarker :: Bool -- ^ display last marker } deriving (Show) smoothSpark :: SparkOptions smoothSpark = SmoothOptions { step = 2 ,height = 20 ,limits = (0,100) ,bgColor = white ,minColor = red ,maxColor = green ,lastColor = blue ,minMarker = True ,maxMarker = True ,lastMarker = True } barSpark :: SparkOptions barSpark = BarOptions { step = 2 ,height = 20 ,limits = (0,100) ,bgColor = white ,minColor = red ,maxColor = green ,lastColor = blue ,minMarker = True ,maxMarker = True ,lastMarker = True } renderSmooth :: SparkOptions -> [Float] -> IO Image renderSmooth opt ds = do let w = 4 + (step opt) * (length ds - 1) h = height opt dmin = fst (limits opt) dmax = snd (limits opt) coords = zip [1,(1+(step opt))..(1+(step opt)*(length ds))] [h - round( (y-(fi dmin)) / ((fi (dmax-dmin+1)) / (fi (h-4))) ) | y <- ds ] minpt = maximumBy (comparing snd) coords -- because y increases as we go down maxpt = minimumBy (comparing snd) coords endpt = last coords img <- newImage (w,h) drawFilledRectangle (0,0) (w,h) (bgColor opt) img zipWithM_ (\p1 p2 -> antiAliased (drawLine p1 p2) grey img) coords (drop 1 coords) when (minMarker opt) (uncurry drawFilledRectangle (boxpt minpt) (minColor opt) img) when (maxMarker opt) (uncurry drawFilledRectangle (boxpt maxpt) (maxColor opt) img) when (lastMarker opt) (uncurry drawFilledRectangle (boxpt endpt) (lastColor opt) img) return img renderBar :: SparkOptions -> [Float] -> IO Image renderBar opt ds = do let w = 4 + (step opt) * (length ds - 1) + bw2 * length ds h = height opt dmin = fst (limits opt) dmax = snd (limits opt) bw = 1 bw2 = 2 * bw coords = zip [1,(1+(step opt)+bw2)..(1+((step opt)+bw2)*(length ds))] [h - round( (y-(fi dmin)) / ((fi (dmax-dmin+1)) / (fi (h-4))) ) | y <- ds ] minpt = maximumBy (comparing snd) coords -- because y increases as we go down maxpt = minimumBy (comparing snd) coords endpt = last coords img <- newImage (w,h) drawFilledRectangle (0,0) (w,h) (bgColor opt) img forM_ coords $ \(x,y) -> antiAliased (drawFilledRectangle (x-bw,y) (x+bw,h)) grey img when (minMarker opt) (uncurry drawFilledRectangle (boxpt minpt) (minColor opt) img) when (maxMarker opt) (uncurry drawFilledRectangle (boxpt maxpt) (maxColor opt) img) when (lastMarker opt) (uncurry drawFilledRectangle (boxpt endpt) (lastColor opt) img) return img encodePngAsDataUrl :: Image -> IO String encodePngAsDataUrl img = savePngByteString img >>= return . encode . unpack fi :: (Num b, Integral a) => a -> b fi x = fromIntegral x boxpt :: (Num a) => (a,a) -> ((a,a),(a,a)) boxpt (x,y) = (,) (x-1,y-1) (x+1,y+1) white,grey,red,green,blue :: Color white = rgb 0xff 0xff 0xff grey = rgb 0x88 0x88 0x88 red = rgb 0xff 0x00 0x00 green = rgb 0x00 0xff 0x00 blue = rgb 0x00 0x00 0xff