module Graphics.Rendering.Chart.Renderable(
Renderable(..),
ToRenderable(..),
PickFn,
rectangleToRenderable,
fillBackground,
addMargins,
emptyRenderable,
embedRenderable,
label,
rlabel,
spacer,
spacer1,
setPickFn,
mapMaybePickFn,
mapPickFn,
nullPickFn,
rect_minsize,
rect_fillStyle,
rect_lineStyle,
rect_cornerStyle,
) where
import Control.Monad
import Control.Lens
import Data.List ( nub, transpose, sort )
import Data.Monoid
import Data.Default.Class
import Graphics.Rendering.Chart.Geometry
import Graphics.Rendering.Chart.Drawing
import Graphics.Rendering.Chart.Utils
type PickFn a = Point -> (Maybe a)
nullPickFn :: PickFn a
nullPickFn = const Nothing
data Renderable a = Renderable {
minsize :: ChartBackend RectSize,
render :: RectSize -> ChartBackend (PickFn a)
}
class ToRenderable a where
toRenderable :: a -> Renderable ()
emptyRenderable :: Renderable a
emptyRenderable = spacer (0,0)
spacer :: RectSize -> Renderable a
spacer sz = Renderable {
minsize = return sz,
render = \_ -> return nullPickFn
}
spacer1 :: Renderable a -> Renderable b
spacer1 r = r{ render = \_ -> return nullPickFn }
setPickFn :: PickFn b -> Renderable a -> Renderable b
setPickFn pickfn r = r{ render = \sz -> do { render r sz; return pickfn; } }
mapMaybePickFn :: (a -> Maybe b) -> Renderable a -> Renderable b
mapMaybePickFn f r = r{ render = \sz -> do pf <- render r sz
return (join . fmap f . pf) }
mapPickFn :: (a -> b) -> Renderable a -> Renderable b
mapPickFn f = mapMaybePickFn (Just . f)
addMargins :: (Double,Double,Double,Double)
-> Renderable a
-> Renderable a
addMargins (t,b,l,r) rd = Renderable { minsize = mf, render = rf }
where
mf = do
(w,h) <- minsize rd
return (w+l+r,h+t+b)
rf (w,h) = do
withTranslation (Point l t) $ do
pickf <- render rd (wlr,htb)
return (mkpickf pickf (t,b,l,r) (w,h))
mkpickf pickf (t,b,l,r) (w,h) (Point x y)
| x >= l && x <= wr && y >= t && t <= hb = pickf (Point (xl) (yt))
| otherwise = Nothing
fillBackground :: FillStyle -> Renderable a -> Renderable a
fillBackground fs r = r{ render = rf }
where
rf rsize@(w,h) = do
withFillStyle fs $ do
p <- alignFillPath $ rectPath (Rect (Point 0 0) (Point w h))
fillPath p
render r rsize
embedRenderable :: ChartBackend (Renderable a) -> Renderable a
embedRenderable ca = Renderable {
minsize = do { a <- ca; minsize a },
render = \ r -> do { a <- ca; render a r }
}
label :: FontStyle -> HTextAnchor -> VTextAnchor -> String -> Renderable String
label fs hta vta = rlabel fs hta vta 0
rlabel :: FontStyle -> HTextAnchor -> VTextAnchor -> Double -> String -> Renderable String
rlabel fs hta vta rot s = Renderable { minsize = mf, render = rf }
where
mf = withFontStyle fs $ do
ts <- textSize s
let (w,h) = (textSizeWidth ts, textSizeHeight ts)
return (w*acr+h*asr,w*asr+h*acr)
rf (w0,h0) = withFontStyle fs $ do
ts <- textSize s
let sz@(w,h) = (textSizeWidth ts, textSizeHeight ts)
let descent = textSizeDescent ts
withTranslation (Point 0 (descent)) $ do
withTranslation (Point (xadj sz hta 0 w0) (yadj sz vta 0 h0)) $ do
withRotation rot' $ do
drawText (Point (w/2) (h/2)) s
return (\_-> Just s)
xadj (w,h) HTA_Left x1 x2 = x1 +(w*acr+h*asr)/2
xadj (w,h) HTA_Centre x1 x2 = (x1 + x2)/2
xadj (w,h) HTA_Right x1 x2 = x2 (w*acr+h*asr)/2
yadj (w,h) VTA_Top y1 y2 = y1 +(w*asr+h*acr)/2
yadj (w,h) VTA_Centre y1 y2 = (y1+y2)/2
yadj (w,h) VTA_Bottom y1 y2 = y2 (w*asr+h*acr)/2
rot' = rot / 180 * pi
(cr,sr) = (cos rot', sin rot')
(acr,asr) = (abs cr, abs sr)
data RectCornerStyle = RCornerSquare
| RCornerBevel Double
| RCornerRounded Double
data Rectangle = Rectangle {
_rect_minsize :: RectSize,
_rect_fillStyle :: Maybe FillStyle,
_rect_lineStyle :: Maybe LineStyle,
_rect_cornerStyle :: RectCornerStyle
}
defaultRectangle :: Rectangle
defaultRectangle = def
instance Default Rectangle where
def = Rectangle
{ _rect_minsize = (0,0)
, _rect_fillStyle = Nothing
, _rect_lineStyle = Nothing
, _rect_cornerStyle = RCornerSquare
}
instance ToRenderable Rectangle where
toRenderable = rectangleToRenderable
rectangleToRenderable :: Rectangle -> Renderable a
rectangleToRenderable rectangle = Renderable mf rf
where
mf = return (_rect_minsize rectangle)
rf sz = do
maybeM () (fill sz) (_rect_fillStyle rectangle)
maybeM () (stroke sz) (_rect_lineStyle rectangle)
return nullPickFn
fill sz fs = do
withFillStyle fs $ do
fillPath $ strokeRectangleP sz (_rect_cornerStyle rectangle)
stroke sz ls = do
withLineStyle ls $ do
strokePath $ strokeRectangleP sz (_rect_cornerStyle rectangle)
strokeRectangleP (x2,y2) RCornerSquare =
let (x1,y1) = (0,0) in moveTo' x1 y1
<> lineTo' x1 y2
<> lineTo' x2 y2
<> lineTo' x2 y1
<> lineTo' x1 y1
<> lineTo' x1 y2
strokeRectangleP (x2,y2) (RCornerBevel s) =
let (x1,y1) = (0,0) in moveTo' x1 (y1+s)
<> lineTo' x1 (y2s)
<> lineTo' (x1+s) y2
<> lineTo' (x2s) y2
<> lineTo' x2 (y2s)
<> lineTo' x2 (y1+s)
<> lineTo' (x2s) y1
<> lineTo' (x1+s) y1
<> lineTo' x1 (y1+s)
<> lineTo' x1 (y2s)
strokeRectangleP (x2,y2) (RCornerRounded s) =
let (x1,y1) = (0,0) in arcNeg (Point (x1+s) (y2s)) s (pi2*2) pi2
<> arcNeg (Point (x2s) (y2s)) s pi2 0
<> arcNeg (Point (x2s) (y1+s)) s 0 (pi2*3)
<> arcNeg (Point (x1+s) (y1+s)) s (pi2*3) (pi2*2)
<> lineTo' x1 (y2s)
pi2 = pi / 2
$( makeLenses ''Rectangle )