plots-0.1.1.2: src/Plots/Types.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module : Plots.Types
-- Copyright : (C) 2015 Christopher Chalmers
-- License : BSD-style (see the file LICENSE)
-- Maintainer : Christopher Chalmers
-- Stability : experimental
-- Portability : non-portable
--
-- This module defines the various types for holding plots:
--
-- [@'PlotOptions' b v n@]
-- Generic options all plots have.
--
-- [@'PlotMods' b v n@]
-- Includes 'PlotOptions' along with modifications to the 'PlotStyle'.
--
-- [@'Plot' p b@]
-- A 'rawPlot' @p@ grouped with a 'PlotMods'.
--
-- [@'DynamicPlot' b v n@]
-- A wrapped up 'Plot' so it can be stored in an 'Axis'.
--
-- [@'StyledPlot' b v n@]
-- A 'DynamicPlot' with a concrete 'PlotStyle', ready to be rendered.
--
-- As well as other things like the 'Plotable' class, 'LegendEntries',
-- 'HasOrientation' and 'HasVisibility'.
--
----------------------------------------------------------------------------
module Plots.Types
(
-- * Plot options
PlotOptions
, HasPlotOptions (..)
, key
, addLegendEntry
-- ** Plot modifications
, PlotMods
, plotMods
-- * Plotable class
, Plotable (..)
-- * Plot types
-- ** Parameterised plot
, Plot
, mkPlot
, rawPlot
-- ** Dynamic plot
, DynamicPlot (..)
, _DynamicPlot
, dynamicPlot
, dynamicPlotMods
-- ** Styled plot
, StyledPlot
, styledPlot
, styleDynamic
, renderStyledPlot
, singleStyledPlotLegend
, styledPlotLegends
-- * Miscellaneous
-- ** Visibility
, HasVisibility (..)
, hide
, display
-- ** Orientation
, Orientation (..)
, HasOrientation (..)
, orient
, horizontal
, vertical
-- ** Legend entries
, LegendEntry
, LegendPic (..)
, mkLegendEntry
, legendPicture
, legendText
, legendPrecedence
-- ** Axis spec
, AxisSpec (..)
, specTrans
, specBounds
, specScale
, scaleNum
, specPoint
, specColourMap
-- ** Positioning
, Placement (..)
, HasPlacement (..)
, HasGap (..)
, placeAgainst
-- *** Common positions
-- **** Inside positions
, topLeft, top, topRight, left, right, bottomLeft, bottom
, bottomRight
-- **** Outside positions
, leftAbove, leftTop, leftMid, leftBottom, leftBelow, midAbove, midBelow
, rightAbove, rightTop, rightMid, rightBottom, rightBelow
) where
import Control.Monad.State
import Data.Bool
import Data.List (sortBy)
import Data.Maybe (fromMaybe)
import Data.Ord (comparing)
import Data.Orphans ()
import Data.Typeable
import Diagrams.Prelude as D
import Plots.Axis.Scale
import Plots.Style
import Plots.Util
-- Orientation ---------------------------------------------------------
data Orientation = Horizontal | Vertical
deriving (Show, Eq, Ord, Typeable)
-- | Pick the first @a@ if the object has 'Horizontal' orientation and
-- the second @a@ if the object has a 'Vertical' orientation.
orient :: HasOrientation o => o -> a -> a -> a
orient o h v =
case view orientation o of
Horizontal -> h
Vertical -> v
-- | Class of things that have an orientation.
class HasOrientation a where
-- | Lens onto the orientation of an object.
orientation :: Lens' a Orientation
instance HasOrientation Orientation where
orientation = id
-- | Lens onto whether an object's orientation is horizontal.
horizontal :: HasOrientation a => Lens' a Bool
horizontal = orientation . iso (==Horizontal) (bool Vertical Horizontal)
-- | Lens onto whether an object's orientation is vertical.
vertical :: HasOrientation a => Lens' a Bool
vertical = horizontal . involuted not
------------------------------------------------------------------------
-- Placement
------------------------------------------------------------------------
class HasGap a where
-- | The value of the gap when rendering.
gap :: Lens' a (N a)
-- | A 'Position' is a point on an axis together with an anchor and a
-- direction for the gap.
data Placement = Placement
{ pAt :: V2 Rational
, pAnchor :: V2 Rational
, pGapDir :: Direction V2 Rational
}
deriving (Show, Read, Eq, Ord)
-- In the future the axis position may be replaced by a reader-like
-- anchor system where you can choose parts of the rendered axis as a
-- position.
-- we keep posGap and posGapDir as separate values to keep lens laws
-- for 0 gaps
-- I'm not sure this should work for a 3D axis
class HasPlacement a where
placement :: Lens' a Placement
-- | The position relative to the axis. @V2 0 0@ corresponds to the
-- bottom left corner, @V2 1 1@ is the top right corner.
placementAt :: Lens' a (V2 Rational)
placementAt = placement . lens pAt (\p a -> p {pAt = a})
-- | The anchor used for the object being positioned. @V2 0 0@
-- corresponds to the bottom left corner, @V2 1 1@ is the top right
-- corner.
placementAnchor :: Lens' a (V2 Rational)
placementAnchor = placement . lens pAnchor (\p a -> p {pAnchor = a})
-- | The direction to extend the 'gap' when positioning.
gapDirection :: Lens' a (Direction V2 Rational)
gapDirection = placement . lens pGapDir (\p a -> p {pGapDir = a})
instance HasPlacement Placement where
placement = id
-- Inside positions ----------------------------------------------------
-- Internal helper for all inside placements
pInside :: V2 Rational -> Placement
pInside v = Placement
{ pAt = v
, pAnchor = v
, pGapDir = dirBetween' (P v) origin
}
-- | @dirBetween p q@ returns the directions from @p@ to @q@
dirBetween' :: (Additive v, Num n) => Point v n -> Point v n -> Direction v n
dirBetween' p q = direction $ q .-. p
topLeft, top, topRight, left, right, bottomLeft, bottom, bottomRight :: Placement
topLeft = pInside (V2 (-1) 1 )
top = pInside (V2 0 1 )
topRight = pInside (V2 1 1 )
left = pInside (V2 (-1) 0 )
right = pInside (V2 (-1) 0 )
bottomLeft = pInside (V2 (-1) (-1))
bottom = pInside (V2 0 (-1))
bottomRight = pInside (V2 1 (-1))
-- Outside positions ---------------------------------------------------
leftAbove, leftTop, leftMid, leftBottom, leftBelow, midAbove, midBelow,
rightAbove, rightTop, rightMid, rightBottom, rightBelow :: Placement
leftAbove = Placement (V2 (-1) 1 ) (V2 (-1) (-1)) (direction (V2 0 1 ))
leftTop = Placement (V2 (-1) 1 ) (V2 1 1 ) (direction (V2 (-1) 0 ))
leftMid = Placement (V2 (-1) 0 ) (V2 1 0 ) (direction (V2 (-1) 0 ))
leftBottom = Placement (V2 (-1) (-1)) (V2 1 (-1)) (direction (V2 (-1) 0 ))
leftBelow = Placement (V2 (-1) (-1)) (V2 (-1) 1 ) (direction (V2 0 (-1)))
midAbove = Placement (V2 0 1 ) (V2 0 (-1)) (direction (V2 0 1 ))
midBelow = Placement (V2 0 (-1)) (V2 0 1 ) (direction (V2 0 (-1)))
rightAbove = Placement (V2 1 1 ) (V2 1 (-1)) (direction (V2 0 1 ))
rightTop = Placement (V2 1 1 ) (V2 (-1) 1 ) (direction (V2 1 0 ))
rightMid = Placement (V2 1 0 ) (V2 (-1) 0 ) (direction (V2 1 0 ))
rightBottom = Placement (V2 1 (-1)) (V2 (-1) (-1)) (direction (V2 1 0 ))
rightBelow = Placement (V2 1 (-1)) (V2 1 1 ) (direction (V2 0 (-1)))
-- Using positions -----------------------------------------------------
-- | A tool for aligned one object to another. Positions @b@ around the
-- bounding box of @a@ by translating @b@.
placeAgainst
:: (InSpace V2 n a, SameSpace a b, Enveloped a,
HasOrigin b, Alignable b)
=> a -> Placement -> n -> b -> b
placeAgainst a (Placement (V2 px py) (V2 ax ay) d) n b
= b # anchor
# moveTo (pos .+^ n *^ fromDirection (fmap fromRational d))
where
pos = mkP2 (lerp' px xu xl) (lerp' py yu yl)
anchor = alignBy unitX (fromRational ax) . alignBy unitY (fromRational ay)
(P (V2 xl yl), P (V2 xu yu)) = fromMaybe (origin, origin) (getCorners $ boundingBox a)
lerp' z u v = fromRational alpha * u + (1 - fromRational alpha) * v
where alpha = (z + 1) / 2
------------------------------------------------------------------------
-- Legend entries
------------------------------------------------------------------------
-- | Type allowing use of the default legend picture (depending on the
-- plot) or a custom legend picture with access to the 'PlotStyle'.
data LegendPic b v n
= DefaultLegendPic
| CustomLegendPic (PlotStyle b v n -> QDiagram b v n Any)
instance Default (LegendPic b v n) where
def = DefaultLegendPic
-- | Data type for holding a legend entry.
data LegendEntry b v n = LegendEntry
{ lPic :: LegendPic b v n
, lText :: String
, lPrecedence :: n
} deriving Typeable
-- | The picture used in the legend entry.
legendPicture :: Lens' (LegendEntry b v n) (LegendPic b v n)
legendPicture = lens lPic (\l pic -> l {lPic = pic})
-- | The text used in the legend entry.
legendText :: Lens' (LegendEntry b v n) String
legendText = lens lText (\l txt -> l {lText = txt})
-- | The order in which the legend entries are rendered. If precedences
-- are equal, the entries are put in the order they are added to the
-- axis.
--
-- Default is @0@.
legendPrecedence :: Lens' (LegendEntry b v n) n
legendPrecedence = lens lPrecedence (\l n -> l {lPrecedence = n})
type instance V (LegendEntry b v n) = v
type instance N (LegendEntry b v n) = n
-- | Make a legend entry with a default 'legendPicture' and
-- 'legendPrecedence' 0 using the string as the 'legendText'.
mkLegendEntry :: Num n => String -> LegendEntry b v n
mkLegendEntry x = LegendEntry DefaultLegendPic x 0
------------------------------------------------------------------------
-- Plot attributes
------------------------------------------------------------------------
-- Generic Plot info
-- | Data type for holding information all plots must contain.
data PlotOptions b v n = PlotOptions
{ poName :: Name
, poClipPlot :: Bool
, poLegend :: [LegendEntry b v n]
, poVisible :: Bool
, poTransform :: Transformation v n
-- , poPostPlotBoundingBox :: BoundingBox v n -> BoundingBox v n
-- , poPlotPostProduction :: QDiagram b v n Any -> QDiagram b v n Any
} deriving Typeable
type instance V (PlotOptions b v n) = v
type instance N (PlotOptions b v n) = n
-- | Class of things that have 'PlotOptions'.
class HasPlotOptions f a b | a -> b where
{-# MINIMAL plotOptions #-}
-- | Lens onto the 'PlotOptions'.
plotOptions :: LensLike' f a (PlotOptions b (V a) (N a))
-- | The 'Name' applied to the plot. This gives a way to reference a
-- specific plot in a rendered axis.
--
-- 'Default' is 'mempty'.
plotName :: Functor f => LensLike' f a Name
plotName = plotOptions . lens poName (\g a -> g {poName = a})
{-# INLINE plotName #-}
-- | Whether the plot should be clipped to the bounds of the axes.
--
-- 'Default' is 'True'.
clipPlot :: Functor f => LensLike' f a Bool
clipPlot = plotOptions . lens poClipPlot (\g a -> g {poClipPlot = a})
{-# INLINE clipPlot #-}
-- | The legend entries to be used for the current plot.
--
-- 'Default' is 'mempty'.
legendEntries :: Functor f => LensLike' f a [LegendEntry b (V a) (N a)]
legendEntries = plotOptions . lens poLegend (\g a -> g {poLegend = a})
{-# INLINE legendEntries #-}
-- | The transform applied to the plot once it's in the axis
-- coordinates.
--
-- 'Default' is 'mempty'.
plotTransform :: Functor f => LensLike' f a (Transformation (V a) (N a))
plotTransform = plotOptions . lens poTransform (\g a -> g {poTransform = a})
{-# INLINE plotTransform #-}
-- | Whether or not the plot should be shown. The 'BoundingBox' of the
-- plot will still affect the inferred axis bounds.
--
-- 'Default' is 'True'.
plotVisible :: Functor f => LensLike' f a Bool
plotVisible = plotOptions . lens poVisible (\po b -> po {poVisible = b})
{-# INLINE plotVisible #-}
instance (Additive v, Num n) => Default (PlotOptions b v n) where
def = PlotOptions
{ poName = mempty
, poClipPlot = True
, poLegend = []
, poVisible = True
, poTransform = mempty
-- , poPostPlotBoundingBox = id
-- , poPlotPostProduction = id
}
instance HasPlotOptions f (PlotOptions b v n) b where
plotOptions = id
{-# INLINE plotOptions #-}
instance (HasLinearMap v, Num n) => Transformable (PlotOptions b v n) where
transform = over plotTransform . transform
-- instance HasBounds (PlotOptions b v n) v where
-- bounds = plotBounds
-- | Move origin by applying to @plotTransform@.
instance (Additive v, Num n) => HasOrigin (PlotOptions b v n) where
moveOriginTo = over plotTransform . moveOriginTo
instance Qualifiable (PlotOptions b v n) where
n .>> p = over plotName (n .>>) p
-- XXX template haskell getting in the way
-- instance HasVisibility (PlotOptions b v n) where
-- visible = plotVisible
-- | Add a 'LegendEntry' to something with 'PlotOptions' using the
-- 'String' as the 'legendText' and a 'DefaultLegendPic'. Here are
-- some typical examples:
--
-- @
-- 'key' :: 'String' -> 'State' ('Plot' ('ScatterPlot' v n) b) ()
-- 'key' :: 'String' -> 'State' ('DynamicPlot' b v n) ()
-- 'key' :: 'String' -> 'State' ('PlotMods' b v n) ()
-- @
--
-- If you only care about the name of the legend, use 'key'.
key :: (HasPlotOptions Identity a b, MonadState a m, Num (N a)) => String -> m ()
key = addLegendEntry . mkLegendEntry
-- | Add a 'LegendEntry' to something with 'PlotOptions'. Here are some
-- typical examples:
--
-- @
-- 'addLegendEntry' :: 'LegendEntry' b v n -> 'State' ('Plot' ('ScatterPlot' v n) b) ()
-- 'addLegendEntry' :: 'LegendEntry' b v n -> 'State' ('DynamicPlot' b v n) ()
-- @
--
-- If you only care about the name of the legend, use 'key'.
addLegendEntry
:: (HasPlotOptions Identity a b, MonadState a m)
=> LegendEntry b (V a) (N a)
-> m ()
addLegendEntry l = legendEntries <>= [l]
-- zeroInt :: Additive v => v Int
-- zeroInt = zero
------------------------------------------------------------------------
-- AxisSpec
------------------------------------------------------------------------
-- | Information from the 'Plots.Axis.Axis' necessary to render a 'Plotable'.
data AxisSpec v n = AxisSpec
{ _specBounds :: v (n, n)
, _specTrans :: Transformation v n
, _specScale :: v LogScale
, _specColourMap :: ColourMap
}
makeLenses ''AxisSpec
type instance V (AxisSpec v n) = v
type instance N (AxisSpec v n) = n
-- | Scale a number by log10-ing it and linearly scaling it so it's
-- within the same range.
scaleNum :: Floating n => (n, n) -> LogScale -> n -> n
scaleNum (a,b) s x = case s of
LinearAxis -> x
LogAxis -> subtract a $ (b / logBase 10 d) * (logBase 10 x)
where d = b - a
-- | Apply log scaling and the transform to a point.
specPoint :: (Applicative v, Additive v, Floating n) => AxisSpec v n -> Point v n -> Point v n
specPoint (AxisSpec bs tr ss _) p =
papply tr $ over _Point (scaleNum <$> bs <*> ss <*>) p
------------------------------------------------------------------------
-- Plotable class
------------------------------------------------------------------------
-- | Class defining how plots should be rendered.
class (Typeable p, Enveloped p) => Plotable p b where
-- | Render a plot according to the 'AxisSpec', using the 'PlotStyle'.
renderPlotable
:: InSpace v n p
=> AxisSpec v n
-> PlotStyle b v n
-> p
-> QDiagram b v n Any
-- | The default legend picture when the 'LegendPic' is
-- 'DefaultLegendPic'.
defLegendPic
:: InSpace v n p
=> PlotStyle b v n
-> p
-> QDiagram b v n Any
defLegendPic = mempty
instance (Typeable b, Typeable v, Metric v, Typeable n, OrderedField n)
=> Plotable (QDiagram b v n Any) b where
renderPlotable s _ dia = dia # transform (s^.specTrans)
instance (TypeableFloat n, Renderable (Path V2 n) b) => Plotable (Path V2 n) b where
renderPlotable s sty path
= stroke path
# transform (s^.specTrans)
# applyLineStyle sty
defLegendPic sty _
= (p2 (-10,0) ~~ p2 (10,0))
# applyLineStyle sty
------------------------------------------------------------------------
-- Visibility
------------------------------------------------------------------------
-- | Class of objects that can be hidden.
class HasVisibility a where
-- | Lens onto whether an object should be visible when rendered.
visible :: Lens' a Bool
-- | The opposite of 'visible'.
hidden :: Lens' a Bool
hidden = visible . involuted not
{-# INLINE hidden #-}
instance HasVisibility (PlotOptions b v n) where
visible = plotVisible
instance HasVisibility (PlotMods b v n) where
visible = plotVisible
instance HasVisibility (Plot p b) where
visible = plotVisible
instance HasVisibility (DynamicPlot b v n) where
visible = plotVisible
instance HasVisibility (StyledPlot b v n) where
visible = plotVisible
-- | Set 'visible' to 'False' for the given setter.
--
-- @
-- 'hide' 'minorTicks' :: 'State' ('Axis' b v n) ()
-- 'hide' ('xAxis' . 'gridLines') :: 'State' ('Axis' b v n) ()
-- @
hide :: (MonadState s m, HasVisibility a) => ASetter' s a -> m ()
hide l = l . visible .= False
-- | Set 'visible' to 'True' for the given setter.
--
-- @
-- 'display' 'minorGridLines' :: 'State' ('Axis' b v n) ()
-- 'display' 'colourBar' :: 'State' ('Axis' b v n) ()
-- @
display :: (MonadState s m, HasVisibility a) => ASetter' s a -> m ()
display l = l . visible .= True
------------------------------------------------------------------------
-- Plot modification
------------------------------------------------------------------------
-- | A 'PlotOptions' with modifications to a 'PlotStyle'.
data PlotMods b v n
= PlotMods (PlotOptions b v n) (PlotStyle b v n -> PlotStyle b v n)
type instance V (PlotMods b v n) = v
type instance N (PlotMods b v n) = n
instance Functor f => HasPlotOptions f (PlotMods b v n) b where
plotOptions f (PlotMods opts sty) = f opts <&> \opts' -> PlotMods opts' sty
instance Settable f => HasPlotStyle f (PlotMods b v n) b where
plotStyle = sty . mapped where
sty f (PlotMods opts s) = f s <&> \s' -> PlotMods opts s'
instance (Additive v, Num n) => Default (PlotMods b v n) where
def = PlotMods def id
------------------------------------------------------------------------
-- Plot type
------------------------------------------------------------------------
-- | A parameterised plot, together with a 'PlotMods'. This type has an
-- instance of many classes for modifying specific plots.
data Plot p b =
Plot p
(PlotOptions b (V p) (N p))
(PlotStyle b (V p) (N p) -> PlotStyle b (V p) (N p))
deriving Typeable
type instance V (Plot p b) = V p
type instance N (Plot p b) = N p
instance Functor f => HasPlotOptions f (Plot p b) b where
plotOptions f (Plot p opts sty) = f opts <&> \opts' -> Plot p opts' sty
instance Settable f => HasPlotStyle f (Plot p b) b where
plotStyle = sty . mapped where
sty f (Plot p opts s) = f s <&> \s' -> Plot p opts s'
instance HasOrientation p => HasOrientation (Plot p b) where
orientation = rawPlot . orientation
-- | Make a 'Plot' with 'Default' 'PlotOptions'.
mkPlot :: (Additive (V p), Num (N p)) => p -> Plot p b
mkPlot p = Plot p def id
-- | Lens onto the raw 'Plotable' inside a 'Plot'.
rawPlot :: SameSpace p p' => Lens (Plot p b) (Plot p' b) p p'
rawPlot f (Plot p opts ps) = f p <&> \p' -> Plot p' opts ps
-- | The modifications to the 'PlotOptions' and 'PlotStyle' in a 'Plot'.
plotMods :: Lens' (Plot p b) (PlotMods b (V p) (N p))
plotMods f (Plot p opts ps) =
f (PlotMods opts ps) <&> \(PlotMods opts' ps') -> Plot p opts' ps'
------------------------------------------------------------------------
-- DynamicPlot
------------------------------------------------------------------------
-- | A wrapped up 'Plot', used to store plots in an 'Axis'.
data DynamicPlot b v n where
DynamicPlot :: (InSpace v n p, Plotable p b) => Plot p b -> DynamicPlot b v n
deriving Typeable
type instance V (DynamicPlot b v n) = v
type instance N (DynamicPlot b v n) = n
-- | Prism for a 'DynamicPlot'.
_DynamicPlot :: (Plotable p b, Typeable b) => Prism' (DynamicPlot b (V p) (N p)) (Plot p b)
_DynamicPlot = prism' DynamicPlot (\(DynamicPlot p) -> cast p)
-- | Traversal over the dynamic plot without the 'Plotable' constraint
-- '_DynamicPlot' has.
dynamicPlot :: forall p b. (Typeable p, Typeable b)
=> Traversal' (DynamicPlot b (V p) (N p)) (Plot p b)
dynamicPlot f d@(DynamicPlot p) =
case eq p of
Just Refl -> f p <&> \p' -> DynamicPlot p'
Nothing -> pure d
where eq :: Typeable a => a -> Maybe (a :~: Plot p b)
eq _ = eqT
instance Functor f => HasPlotOptions f (DynamicPlot b v n) b where
plotOptions f (DynamicPlot (Plot p opts sty)) =
f opts <&> \opts' -> DynamicPlot (Plot p opts' sty)
instance Settable f => HasPlotStyle f (DynamicPlot b v n) b where
plotStyle = sty . mapped where
sty :: Setter' (DynamicPlot b v n) (PlotStyle b v n -> PlotStyle b v n)
sty f (DynamicPlot (Plot p opts s)) = f s <&> \s' -> DynamicPlot (Plot p opts s')
-- | The modifications to the 'PlotOptions' and 'PlotStyle' in a 'DynamicPlot'.
dynamicPlotMods :: Lens' (DynamicPlot b v n) (PlotMods b v n)
dynamicPlotMods f (DynamicPlot (Plot p opts ps)) =
f (PlotMods opts ps) <&> \(PlotMods opts' ps') -> DynamicPlot (Plot p opts' ps')
------------------------------------------------------------------------
-- StyledPlot
------------------------------------------------------------------------
-- | A 'DynamicPlot' with a concrete style. This is suitable for being
-- rendered with 'renderStyledPlot' and get extract the legend entries
-- with 'styledPlotLegend'.
--
-- You can make a 'StyledPlot' with 'styleDynamic'
data StyledPlot b v n where
StyledPlot
:: Plotable p b
=> p
-> PlotOptions b (V p) (N p)
-> PlotStyle b (V p) (N p)
-> StyledPlot b (V p) (N p)
type instance V (StyledPlot b v n) = v
type instance N (StyledPlot b v n) = n
instance Functor f => HasPlotOptions f (StyledPlot b v n) b where
plotOptions f (StyledPlot p opts sty) =
f opts <&> \opts' -> StyledPlot p opts' sty
instance (Metric v, OrderedField n) => Enveloped (StyledPlot b v n) where
getEnvelope (StyledPlot p opts _) =
getEnvelope p & transform (poTransform opts)
instance Functor f => HasPlotStyle f (StyledPlot b v n) b where
plotStyle f (StyledPlot p opts sty) =
f sty <&> StyledPlot p opts
-- | Traversal over a raw plot of a styled plot. The type of the plot
-- must match for the traversal to be successful.
styledPlot :: forall p b. Typeable p => Traversal' (StyledPlot b (V p) (N p)) p
styledPlot f s@(StyledPlot p opts sty) =
case eq p of
Just Refl -> f p <&> \p' -> StyledPlot p' opts sty
Nothing -> pure s
where eq :: Typeable a => a -> Maybe (a :~: p)
eq _ = eqT
-- | Give a 'DynamicPlot' a concrete 'PlotStyle'.
styleDynamic :: PlotStyle b v n -> DynamicPlot b v n -> StyledPlot b v n
styleDynamic sty (DynamicPlot (Plot p opts styF)) = StyledPlot p opts (styF sty)
-- | Render a 'StyledPlot' given an and 'AxisSpec'.
renderStyledPlot
:: TypeableFloat n
=> AxisSpec V2 n
-> StyledPlot b V2 n
-> QDiagram b V2 n Any
renderStyledPlot aSpec (StyledPlot p opts sty)
= renderPlotable aSpec sty p
& whenever (opts^.hidden) phantom
& whenever (opts^.clipPlot) (clipTo $ specRect aSpec)
specRect :: TypeableFloat n => AxisSpec V2 n -> Path V2 n
specRect aSpec =
rect (xU - xL) (yU - yL)
# moveTo (mkP2 ((xU+xL)/2) ((yU+yL)/2))
# transform t
where
V2 (xL,xU) (yL,yU) = _specBounds aSpec
t = _specTrans aSpec
-- | Get the legend rendered entries from a single styled plot. The
-- resulting entries are in no particular order. See also
-- 'styledPlotLegends'.
singleStyledPlotLegend
:: StyledPlot b v n
-> [(n, QDiagram b v n Any, String)] -- ^ @(z-order, legend pic, legend text)@
singleStyledPlotLegend (StyledPlot p opts sty) =
map mk (opts ^. legendEntries)
where
mk entry = (entry ^. legendPrecedence, pic, entry ^. legendText)
where
pic = case lPic entry of
DefaultLegendPic -> defLegendPic sty p
CustomLegendPic f -> f sty
-- | Render a list of legend entries, in order.
styledPlotLegends
:: Ord n
=> [StyledPlot b v n]
-> [(QDiagram b v n Any, String)] -- ^ @[(legend pic, legend text)]@
styledPlotLegends
= map (\(_,p,t) -> (p,t))
. sortOn (view _1)
. concatMap singleStyledPlotLegend
-- XXX taken from "Data.List", defined here because it was only added in
-- base-4.8 (ghc-7.10)
sortOn :: Ord b => (a -> b) -> [a] -> [a]
sortOn f =
map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))