packages feed

timeplot-1.0.32: Graphics/Rendering/Chart/Event.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-}
module Graphics.Rendering.Chart.Event (
    PlotEvent(..),
    Event(..),

    eventStart,
    eventEnd,

    plot_event_title,
    plot_event_data,
    plot_event_long_fillstyle,
    plot_event_long_linestyle,
    plot_event_pulse_linestyle,
    plot_event_track_linestyle,
    plot_event_label,
) where

import Control.Lens
import Graphics.Rendering.Chart
import Data.Colour
import Data.Colour.Names
import Data.Default
import Control.Monad

#if ! MIN_VERSION_Chart(1,7,0)
-- legacy map for Chart<1.7
#    define BackendProgram ChartBackend
#endif

data Event t e = LongEvent (t,Bool) (t,Bool) e  -- ^ An event that has a beginning and an end. 
                                                --   True = "known explicitly", False = "implicit" (e.g. imposed by axis bounds)
               | PulseEvent t e   -- ^ A zero-length event
               deriving (Show)

eventStart :: Event t e -> t
eventStart (LongEvent (t0,_) (_,_) _) = t0
eventStart (PulseEvent t _) = t

eventEnd :: Event t e -> t
eventEnd (LongEvent (_,_) (t1,_) _) = t1
eventEnd (PulseEvent t _) = t

-- | A chart that depict events.
-- There are two kinds of events: long and impulse events. A long event
-- is drawn like "--[=====]---" and has a beginning and ending moment, and
-- an impulse event is drawn like "---|----" and has an occurence moment.
data PlotEvent t e = PlotEvent {
   _plot_event_title           :: String,
   _plot_event_data            :: [Event t e],
   -- | Linestyle with which marks for pulse events will be drawn
   _plot_event_pulse_linestyle :: e -> LineStyle,
   -- | Linestyle with which borders of rectangles for long events will be drawn
   _plot_event_long_linestyle  :: e -> LineStyle,
   -- | Fillstyle with which interiors of rectangles for long events will be filled
   _plot_event_long_fillstyle  :: e -> FillStyle,
   -- | Linestyle with which the "track line" will be drawn
   _plot_event_track_linestyle :: LineStyle,
   _plot_event_label           :: e -> String
}
makeLenses ''PlotEvent

instance Default (PlotEvent t e) where
  def = PlotEvent {
    _plot_event_title           = "",
    _plot_event_data            = [],
    _plot_event_pulse_linestyle = const $ solidLine 2 (opaque red),
    _plot_event_long_linestyle  = const $ solidLine 1 (opaque black),
    _plot_event_long_fillstyle  = const $ solidFillStyle (opaque lightgray),
    _plot_event_track_linestyle = solidLine 1 (opaque black),
    _plot_event_label           = const ""
  }

instance ToPlot PlotEvent where
    toPlot p = Plot {
      _plot_render = renderPlotEvent p,
      _plot_legend = [(_plot_event_title p, renderPlotLegendEvent p)],
      _plot_all_points = plotAllPointsEvent p
    }

renderPlotLegendEvent :: PlotEvent t e -> Rect -> BackendProgram ()
renderPlotLegendEvent p r = return ()


filledRect :: FillStyle -> Rect -> BackendProgram ()
filledRect fs r = withFillStyle fs $ fillPath (rectPath r)

framedRect :: LineStyle -> Rect -> BackendProgram ()
framedRect ls r = withLineStyle ls $ strokePath (rectPath r)

barHeight = 7
pulseHeight = 15

renderPlotEvent :: PlotEvent t e -> PointMapFn t e  -> BackendProgram ()
renderPlotEvent p pmap = do
      withLineStyle (p ^. plot_event_track_linestyle) $ do
        strokePointPath [Point x0 cy, Point x1 cy]
        mapM_ drawEventFill  (p ^. plot_event_data)
        mapM_ drawEventFrame (p ^. plot_event_data)
    where
      (Point x0 y0) = pmap (LMin,LMin)
      (Point x1 y1) = pmap (LMax,LMax)
      (cx,cy) = ((x0+x1)/2, (y0+y1)/2)
      
      drawEventFill (PulseEvent t e) = return ()
      drawEventFill (LongEvent (t1,_) (t2,_) e) = do
        let (Point x1 cy)  = pmap (LValue t1, LValue e)
        let (Point x2 cy') = pmap (LValue t2, LValue e) -- Assume cy' == cy (pmap is coordinate-wise)
        filledRect (p ^. plot_event_long_fillstyle $ e) $ Rect
            (Point x1 (cy-barHeight/2)) (Point x2 (cy+barHeight/2))

      drawEventFrame (PulseEvent t e) = do
        withLineStyle (p ^. plot_event_pulse_linestyle $ e) $ do
          let (Point x y) = pmap (LValue t, LValue e)
          strokePointPath [Point x (y-pulseHeight/2), Point x (y+pulseHeight/2)]
          let label = p ^. plot_event_label $ e
          unless (null label) $ do
            textSize <- textSize label
            withLineStyle (solidLine 2 $ opaque black) $ do
              drawText (Point x (y - pulseHeight/2 - textSizeHeight textSize - textSizeYBearing textSize - 1)) label
      drawEventFrame (LongEvent (t1,_) (t2,_) e) = do
        let (Point x1 cy)  = pmap (LValue t1, LValue e)
        let (Point x2 cy') = pmap (LValue t2, LValue e) -- Assume cy' == cy (pmap is coordinate-wise)
        framedRect (p ^. plot_event_long_linestyle $ e) $ Rect
            (Point x1 (cy-barHeight/2)) (Point x2 (cy+barHeight/2))

plotAllPointsEvent :: PlotEvent t e -> ([t], [e])
plotAllPointsEvent p = (concat ts, es)
  where
    decomp (PulseEvent t             e) = ([t],     e)
    decomp (LongEvent  (t1,_) (t2,_) e) = ([t1,t2], e)
    (ts, es) = unzip $ p ^.. plot_event_data . traverse . to decomp