{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Plots.Legend
(
Legend
, HasLegend (..)
, drawLegend
) where
import Control.Lens hiding (none, ( # ))
import Data.Default
import Data.Typeable
import Diagrams.TwoD.Text
import Diagrams.BoundingBox
import Diagrams.Prelude
import Plots.Types
data Legend b n = Legend
{ lPlacement :: Placement
, lGap :: n
, lStyle :: Style V2 n
, lSpacing :: n
, lTextWidth :: n
, lTextF :: String -> QDiagram b V2 n Any
, lTextStyle :: Style V2 n
, lOrientation :: Orientation
, lVisible :: Bool
} deriving Typeable
type instance V (Legend b n) = V2
type instance N (Legend b n) = n
class HasLegend a b | a -> b where
legend :: Lens' a (Legend b (N a))
legendPlacement :: Lens' a Placement
legendPlacement = legend . lens lPlacement (\l a -> l {lPlacement = a})
legendGap :: Lens' a (N a)
legendGap = legend . lens lGap (\l a -> l {lGap = a})
legendStyle :: Lens' a (Style V2 (N a))
legendStyle = legend . lens lStyle (\l a -> l {lStyle = a})
legendSpacing :: Lens' a (N a)
legendSpacing = legend . lens lSpacing (\l a -> l {lSpacing = a})
legendTextWidth :: Lens' a (N a)
legendTextWidth = legend . lens lTextWidth (\l a -> l {lTextWidth = a})
legendTextFunction :: Lens' a (String -> QDiagram b V2 (N a) Any)
legendTextFunction = legend . lens lTextF (\l a -> l {lTextF = a})
legendTextStyle :: Lens' a (Style V2 (N a))
legendTextStyle = legend . lens lTextStyle (\l a -> l {lTextStyle = a})
legendOrientation :: Lens' a Orientation
legendOrientation = legend . lens lOrientation (\l a -> l {lOrientation = a})
instance HasLegend (Legend b n) b where
legend = id
instance HasGap (Legend b n) where
gap = legendGap
instance HasPlacement (Legend b n) where
placement = legendPlacement
instance (TypeableFloat n, Renderable (Text n) b) => Default (Legend b n) where
def = Legend
{ lPlacement = rightTop
, lGap = 20
, lSpacing = 20
, lTextWidth = 60
, lStyle = mempty
, lTextF = mkText (BoxAlignedText 0 0.5)
, lTextStyle = mempty & fontSize (output 11)
, lOrientation = Vertical
, lVisible = True
}
instance HasVisibility (Legend b n) where
visible = lens lVisible (\l a -> l {lVisible = a})
instance TypeableFloat n => HasStyle (Legend b n) where
applyStyle sty = over legendStyle (applyStyle sty)
instance HasOrientation (Legend b n) where
orientation = legendOrientation
drawLegend
:: (TypeableFloat n,
Renderable (Path V2 n) b)
=> BoundingBox V2 n
-> [(QDiagram b V2 n Any, String)]
-> Legend b n
-> QDiagram b V2 n Any
drawLegend bb entries l
| l ^. hidden || null entries = mempty
| otherwise = placeAgainst
bb
(l ^. legendPlacement)
(l ^. legendGap)
(ledge <> back)
where
w = l ^. legendTextWidth
h = l ^. legendSpacing
ledge = map mkLabels entries
# orient (l ^. legendOrientation) hcat vcat
# alignTL
back = backRect
# applyStyle (l ^. legendStyle)
# fcA transparent
# alignTL
backRect = orient (l ^. legendOrientation)
(rect (nEntries * entryWidth) h )
(rect entryWidth (h * nEntries))
nEntries = fromIntegral (length entries)
entryWidth = w + 10 + h
mkLabels (pic, txt) = strutX 5 ||| pic' ||| strutX 5 ||| label where
pic' = pic # withEnvelope (fromCorners (pure (-h/2)) (pure (h/2)))
label = view legendTextFunction l txt
# applyStyle (l ^. legendTextStyle)
# withEnvelope (fromCorners origin (mkP2 w h) # moveTo (mkP2 0 (-h/2)))