plots-0.1.1.1: src/Plots/Axis/Labels.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
-----------------------------------------------------------------------------
-- |
-- Module : Plots.Axis.Labels
-- Copyright : (C) 2015-2017 Christopher Chalmers
-- License : BSD-style (see the file LICENSE)
-- Maintainer : Christopher Chalmers
-- Stability : experimental
-- Portability : non-portable
--
-- There are two kinds of labels this module deals with: The 'AxisLabel'
-- labels are placed next to an axis line. The 'TickLabels' are the
-- numbers (usually) next to each major tick on an axis line.
--
----------------------------------------------------------------------------
module Plots.Axis.Labels
( -- * Axis line labels
HasAxisLabel (..)
, AxisLabel
, AxisLabelPosition (..)
, AxisLabelPlacement (..)
-- * Axis tick labels
, TickLabels
, HasTickLabels (..)
, tickLabelPositions
, atMajorTicks
-- * Misc
, TextFunction
) where
import Control.Lens hiding (( # ))
import Data.Data
import Data.Default
import Diagrams.Prelude hiding (view)
import Diagrams.TwoD.Text
import Plots.Types
-- | Function to render the axis label from a string. This is very basic
-- now and will be replace by a more sophisticated system.
type TextFunction v = TextAlignment Double -> String -> Diagram v
------------------------------------------------------------------------
-- Axis labels
------------------------------------------------------------------------
-- | The position of the 'AxisLabel' along the axis.
data AxisLabelPosition
= MiddleAxisLabel
| LowerAxisLabel
| UpperAxisLabel
-- | Whether the 'AxisLabel' should be inside or outside the axis.
data AxisLabelPlacement
= InsideAxisLabel
| OutsideAxisLabel
-- | 'AxisLabel' describes the label next to each axis line. They are
-- normally set with the 'Plots.Axis.xLabel' and 'Plots.Axis.yLabel'
-- helper function:
--
-- @
-- myAxis = r2Axis &~ do
-- 'Plots.Axis.xLabel' .= "time (s)"
-- 'Plots.Axis.yLabel' .= "height (m)"
-- @
--
-- See 'HasAxisLabel' for more advanced settings.
data AxisLabel v = AxisLabel
{ alFun :: TextFunction v
, alText :: String
, alStyle :: Style v Double
, alGap :: Double
, alPos :: AxisLabelPosition
, alPlacement :: AxisLabelPlacement
, alVisible :: Bool
}
type instance V (AxisLabel v) = v
type instance N (AxisLabel v) = Double
class HasAxisLabel f a where
-- | The options for the label of the axis. This can be used on
-- various levels of the axis:
--
-- @
-- 'axisLabel' :: 'Traversal'' ('Axis' b c n) ('AxisLabel' ('BaseSpace' c) n)
-- 'axisLabel' :: 'Lens'' ('SingleAxis' v) ('AxisLabel' v n)
-- 'axisLabel' :: 'Lens'' ('AxisLabel' v n) ('AxisLabel' v n)
-- @
axisLabel :: LensLike' f a (AxisLabel (V a))
-- | The text to use when labeling the axis.
axisLabelText :: Functor f => LensLike' f a String
axisLabelText = axisLabel . lens alText (\al txt -> al {alText = txt})
-- | The 'TextFunction' to render the text of the axis label.
axisLabelTextFunction :: Functor f => LensLike' f a (TextFunction (V a))
axisLabelTextFunction = axisLabel . lens alFun (\al f -> al {alFun = f})
-- | The gap between the axis and the labels, in the direction
-- corresponding to the 'axisLabelPosition'.
axisLabelGap :: Functor f => LensLike' f a Double
axisLabelGap = axisLabel . lens alGap (\al sty -> al {alGap = sty})
-- | The 'Style' to use on the rendered text.
axisLabelStyle :: Functor f => LensLike' f a (Style (V a) Double)
axisLabelStyle = axisLabel . lens alStyle (\al sty -> al {alStyle = sty})
-- | The position the label will be placed parallel to the axis.
axisLabelPosition :: Functor f => LensLike' f a AxisLabelPosition
axisLabelPosition = axisLabel . lens alPos (\al sty -> al {alPos = sty})
-- | Whether the axis label should be placed inside or outside the
-- axis.
axisLabelPlacement :: Functor f => LensLike' f a AxisLabelPosition
axisLabelPlacement = axisLabel . lens alPos (\al sty -> al {alPos = sty})
instance HasAxisLabel f (AxisLabel v) where
axisLabel = id
instance ApplyStyle (AxisLabel v)
instance HasStyle (AxisLabel v) where
style = axisLabelStyle
instance HasVisibility (AxisLabel v) where
visible = lens alVisible (\al b -> al {alVisible = b})
instance HasGap (AxisLabel v) where
gap = axisLabelGap
instance Default (AxisLabel V2) where
def = AxisLabel
{ alFun = mkText
, alText = ""
, alStyle = mempty & fontSize (output 11)
& backupFillColor black
, alGap = 30
, alPos = MiddleAxisLabel
, alPlacement = OutsideAxisLabel
, alVisible = True
}
instance Default (AxisLabel V3) where
def = AxisLabel
{ alFun = mempty
, alText = ""
, alStyle = mempty & fontSize (output 11)
-- & backupFillColor black
, alGap = 30
, alPos = MiddleAxisLabel
, alPlacement = OutsideAxisLabel
, alVisible = True
}
------------------------------------------------------------------------
-- Tick labels
------------------------------------------------------------------------
-- | 'TickLabels' describes how to draw the labels next to ticks. See
-- 'HasTickLabels' for more options.
data TickLabels v = TickLabels
{ tlFun :: [Double] -> (Double,Double) -> [(Double, String)]
, tlTextFun :: TextFunction v
, tlStyle :: Style v Double
, tlGap :: Double
, tlVisible :: Bool
} deriving Typeable
type instance V (TickLabels v) = v
type instance N (TickLabels v) = Double
class HasTickLabels f a where
-- | The options for the label of ticks. This can be used on various
-- levels of the axis:
--
-- @
-- 'tickLabel' :: 'Traversal'' ('Tick' c) ('TickLabels' ('BaseSpace' c))
-- 'tickLabel' :: 'Lens'' ('SingleAxis' v) ('TickLabels' v)
-- 'tickLabel' :: 'Lens'' ('TickLabel' v) ('TickLabels' v)
-- @
tickLabel :: LensLike' f a (TickLabels (V a))
-- | The 'TextFunction' to render the text.
--
-- 'Default' is 'mkText'.
tickLabelTextFunction :: Functor f => LensLike' f a (TextFunction (V a))
tickLabelTextFunction = tickLabel . lens tlTextFun (\tl f -> tl {tlTextFun = f})
-- | Tick labels functions are used to draw the tick labels. They have
-- access to the major ticks and the current bounds. Returns the
-- position of the tick and label to use at that position.
--
-- 'Default' is @'atMajorTicks' 'floatShow'@
tickLabelFunction :: Functor f => LensLike' f a ([Double] -> (Double, Double) -> [(Double, String)])
tickLabelFunction = tickLabel . lens tlFun (\tl f -> tl {tlFun = f})
-- | The 'Style' to use on the rendered text.
--
-- 'Default' is @'fontSize' ('output' 11)@.
tickLabelStyle :: Functor f => LensLike' f a (Style (V a) Double)
tickLabelStyle = tickLabel . lens tlStyle (\tl sty -> tl {tlStyle = sty})
-- | The gap between the axis and the tick labels.
--
-- 'Default' is @12@.
tickLabelGap :: Functor f => LensLike' f a Double
tickLabelGap = tickLabel . lens tlGap (\tl n -> tl {tlGap = n})
instance HasTickLabels f (TickLabels v) where
tickLabel = id
instance HasGap (TickLabels v) where
gap = tickLabelGap
instance Default (TickLabels V2) where
def = TickLabels
{ tlFun = atMajorTicks floatShow
, tlTextFun = mkText
, tlStyle = mempty & fontSize (output 11)
& backupFillColor black
, tlGap = 12
, tlVisible = True
}
instance Default (TickLabels V3) where
def = TickLabels
{ tlFun = atMajorTicks floatShow
, tlTextFun = mempty
, tlStyle = mempty & fontSize (output 11)
-- & sc black
, tlGap = 12
, tlVisible = True
}
instance HasVisibility (TickLabels v) where
visible = lens tlVisible (\tl b -> tl {tlVisible = b})
-- | Setter over the final positions the major ticks. This is not as
-- general as 'tickLabelFunction' because you don't have access to the
-- bounds but it can be useful when you know exactly what ticks you
-- want to add or modify existing tick positions or to add an extra
-- value:
--
-- @
-- xAxis . tickLabelPositions .= [(1, "apples"), (2, "oranges"), (3, "bananas"]
-- yAxis . tickLabelPositions <>= [(1.5, "critial mass")]
-- @
--
-- If you want to change or add normal ticks see 'majorTicksFunction'.
--
tickLabelPositions
:: (HasTickLabels f a, Settable f) => LensLike' f a [(Double, String)]
tickLabelPositions = tickLabelFunction . mapped . mapped
-- | Numbers are shown as 'Float's to reduce the chance of numbers like
-- 1.30000000008. (This is not an ideal solution.)
floatShow :: Real n => n -> String
floatShow = show . (realToFrac :: Real n => n -> Float)
-- | Make a 'TickLabelFunction' by specifying how to draw a single label
-- from a position on the axis.
atMajorTicks :: (Double -> String) -> [Double] -> (Double,Double) -> [(Double, String)]
atMajorTicks f ticks _ = map ((,) <*> f) ticks
-- -- | Use the list of strings as the labels for the axis, starting at 1
-- -- and going to 2, 3, 4 ... .
-- stringLabels :: Num n => [(n, String)] -> TickLabelFunction n
-- stringLabels nms _ _ = iover (each . itraversed) (\i l -> (fromIntegral (i + 1), l)) nms