{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
module Plots.Axis.ColourBar
(
ColourBar
, HasColourBar (..)
, defColourBar
, gradientColourBar
, pathColourBar
, renderColourBar
, addColourBar
) where
import Data.Bool (bool)
import qualified Data.Foldable as F
import Data.Typeable
import Diagrams.Core.Transform (fromSymmetric)
import Diagrams.Prelude hiding (gap)
import Diagrams.TwoD.Text
import Plots.Axis.Grid
import Plots.Axis.Labels
import Plots.Axis.Ticks
import Plots.Style
import Plots.Types
import Plots.Util
data ColourBar b n = ColourBar
{ cbPlacement :: Placement
, cbVisible :: Bool
, cbTicks :: MajorTicks V2 n
, cbGridLines :: MajorGridLines V2 n
, cbTickLabels :: TickLabels b V2 n
, cbDraw :: ColourMap -> QDiagram b V2 n Any
, cbWidth :: n
, cbLengthFun :: n -> n
, cbGap :: n
, cbStyle :: Style V2 n
}
type instance V (ColourBar b n) = V2
type instance N (ColourBar b n) = n
defColourBar :: (Renderable (Text n) b, Renderable (Path V2 n) b, TypeableFloat n)
=> ColourBar b n
defColourBar = ColourBar
{ cbPlacement = rightMid
, cbVisible = False
, cbTicks = def
, cbGridLines = def
, cbTickLabels = def
, cbDraw = gradientColourBar
, cbWidth = 20
, cbLengthFun = id
, cbGap = 20
, cbStyle = mempty
}
class HasColourBar a b | a -> b where
colourBar :: Lens' a (ColourBar b (N a))
colourBarDraw :: Lens' a (ColourMap -> QDiagram b V2 (N a) Any)
colourBarDraw = colourBar . lens cbDraw (\c a -> c {cbDraw = a})
colourBarWidth :: Lens' a (N a)
colourBarWidth = colourBar . lens cbWidth (\c a -> c {cbWidth = a})
colourBarLengthFunction :: Lens' a (N a -> N a)
colourBarLengthFunction = colourBar . lens cbLengthFun (\c a -> c {cbLengthFun = a})
colourBarGap :: Lens' a (N a)
colourBarGap = colourBar . lens cbGap (\c a -> c {cbGap = a})
colourBarStyle :: Lens' a (Style V2 (N a))
colourBarStyle = colourBar . lens cbStyle (\c a -> c {cbStyle = a})
instance HasColourBar (ColourBar b n) b where
colourBar = id
instance HasGap (ColourBar b n) where
gap = colourBarGap
instance HasPlacement (ColourBar b n) where
placement = lens cbPlacement (\c p -> c {cbPlacement = p})
instance HasOrientation (ColourBar b n) where
orientation = lens getter setter where
getter p
| north || south = Horizontal
| east || west = Vertical
| northEast = bool Horizontal Vertical (dx > dy)
| southEast = bool Horizontal Vertical (dx > -dy)
| southWest = bool Horizontal Vertical (dx < dy)
| northWest = bool Horizontal Vertical (dx < -dy)
| otherwise = error $ "internal error: get colourBar orientation: "
++ show (p ^. placement)
where
V2 x y = p ^. placementAt
V2 dx dy = p ^. gapDirection . _Dir
north = x < y && x > (-y)
east = x > y && x > (-y)
south = x > y && x < (-y)
west = x < y && x < (-y)
northEast = x == y && x > 0
southEast = x == (-y) && x > 0
southWest = x == y && x < 0
northWest = x == (-y) && x < 0
setter p o
| getter p == o = p
| otherwise = p & placementAt %~ flipX_Y
& placementAnchor %~ flipX_Y
& gapDirection ._Dir %~ flipX_Y
instance Typeable n => HasStyle (ColourBar b n) where
applyStyle sty = colourBarStyle %~ applyStyle sty
instance Functor f => HasMajorTicks f (ColourBar b n) where
majorTicks = lens cbTicks (\c a -> c {cbTicks = a})
instance Functor f => HasTickLabels f (ColourBar b n) b where
tickLabel = lens cbTickLabels (\c a -> c {cbTickLabels = a})
instance HasVisibility (ColourBar b n) where
visible = lens cbVisible (\c a -> c {cbVisible = a})
addColourBar
:: (TypeableFloat n, Renderable (Path V2 n) b)
=> BoundingBox V2 n
-> ColourBar b n
-> ColourMap
-> (n,n)
-> QDiagram b V2 n Any
addColourBar bb cbo@ColourBar {..} cm bnds
| cbVisible = placeAgainst bb cbPlacement cbGap cb
| otherwise = mempty
where
cb = renderColourBar cbo cm bnds l
l = cbLengthFun bbl
bbl = orient cbo bx by
V2 bx by = boxExtents bb
renderColourBar
:: (TypeableFloat n, Renderable (Path V2 n) b)
=> ColourBar b n
-> ColourMap
-> (n,n)
-> n
-> QDiagram b V2 n Any
renderColourBar cb@ColourBar {..} cm bnds@(lb,ub) l
| cbVisible = bar # xy id reflectY
# o id (reflectY . _reflectX_Y)
<> tLbs
| otherwise = mempty
where
o, xy :: a -> a -> a
o = orient cb
xy a b = if let V2 x y = cb^.placementAt in x > y
then a else b
w = cbWidth
f x = (x - (ub + lb)/2) / (ub - lb) * l
inRange x = x >= lb && x <= ub
bar = outline <> tks <> gLines <> colours
outline = rect l w # applyStyle (cbStyle & _fillTexture .~ _AC ## transparent)
colours = cbDraw cm # centerXY # scaleX l # scaleY w
tickXs = view majorTicksFunction cbTicks bnds
tickXs' = filter inRange tickXs
tks
| cbTicks ^. hidden = mempty
| otherwise = F.foldMap (\x -> aTick # translate (V2 (f x) (-w/2))) tickXs'
aTick = someTick (cbTicks ^. majorTicksAlignment) (cbTicks ^. majorTicksLength)
someTick tType d = case tType of
TickSpec (fromRational -> aa) (fromRational -> bb)
-> mkP2 0 (-d*bb) ~~ mkP2 0 (d*aa)
AutoTick -> mkP2 0 (-d) ~~ mkP2 0 d
gridXs = filter inRange $ view majorGridLinesFunction cbGridLines tickXs bnds
gLines
| cbGridLines ^. hidden = mempty
| otherwise = F.foldMap mkGridLine gridXs
# strokePath
# applyStyle (cbGridLines ^. majorGridLinesStyle)
mkGridLine x = mkP2 (f x) (-w/2) ~~ mkP2 (f x) (w/2)
tickLabelXs = view tickLabelFunction cbTickLabels tickXs' bnds
tLbs
| cbTickLabels ^. hidden = mempty
| otherwise = F.foldMap drawTickLabel tickLabelXs
drawTickLabel (x,label) =
view tickLabelTextFunction cbTickLabels tAlign label
# translate v
# applyStyle (cbTickLabels ^. tickLabelStyle)
where v = V2 (f x) (- w/2 - view tickLabelGap cbTickLabels)
# xy id (_y %~ negate)
# o id ((_y %~ negate) . flipX_Y)
tAlign = o (xy (BoxAlignedText 0.5 1) (BoxAlignedText 0.5 0))
(xy (BoxAlignedText 0 0.5) (BoxAlignedText 1 0.5))
gradientColourBar :: (TypeableFloat n, Renderable (Path V2 n) b) => ColourMap -> QDiagram b V2 n Any
gradientColourBar cm =
rect 1 1
# fillTexture grad
# lw none
where
stops = map (\(x,c) -> GradientStop (SomeColor c) (fromRational x)) (colourList cm)
grad = defaultLG & _LG . lGradStops .~ stops
pathColourBar :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Int -> ColourMap -> QDiagram b V2 n Any
pathColourBar n cm = ifoldMap mkR xs
where
mkR i x = rect d' 1
# alignR
# fcA (cm ^. ixColour (x - 1/(2*fromIntegral n)))
# translateX (fromRational x)
# lw none
where
d' | i == 0 = d
| otherwise = d*1.5
xs = tail (enumFromToN 0 1 n)
d = 1 / fromIntegral n
flipX_Y :: Num n => V2 n -> V2 n
flipX_Y (V2 x y) = V2 (-y) (-x)
_reflectionX_Y :: (Additive v, R2 v, Num n) => Transformation v n
_reflectionX_Y = fromSymmetric $ (_xy %~ flipX_Y) <-> (_xy %~ flipX_Y)
_reflectX_Y :: (InSpace v n t, R2 v, Transformable t) => t -> t
_reflectX_Y = transform _reflectionX_Y