    ---------------------------------------------------------------
    -- |
    -- 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 -> ChartBackend (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