{-# OPTIONS_GHC -Wall #-} {-# LANGUAGE CPP #-} #if ( __GLASGOW_HASKELL__ < 820 ) {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} #endif {-# OPTIONS_GHC -Wno-unused-top-binds #-} -- | rectangular chart elements module Chart.Rect ( RectOptions(..) , blob , box , clear , bound , rect_ , rects , rectChart , rectChart_ , Pixel(..) , pixel_ , pixels , pixelChart , pixelChart_ , PixelationOptions(..) , pixelate , pixelateChart ) where import Chart.Core import Diagrams.Prelude hiding (Color, D, (<>), scaleX, scaleY) import NumHask.Pair import NumHask.Prelude import NumHask.Range import NumHask.Rect import NumHask.Space -- | Just about everything on a chart is a rectangle. data RectOptions = RectOptions { borderSize :: Double , borderColor :: UColor Double , color :: UColor Double } deriving (Show, Generic) instance Default RectOptions where def = RectOptions 0.005 ugrey ublue -- | solid rectangle, no border blob :: UColor Double -> RectOptions blob c = RectOptions 0 utrans c -- | clear and utrans rect clear :: RectOptions clear = RectOptions 0 utrans utrans -- | clear rect, with border box :: UColor Double -> RectOptions box c = RectOptions 0.015 c utrans -- | place a rect around an Chart, with a size equal to the chart range bound :: RectOptions -> Double -> Chart b -> Chart b bound (RectOptions bs bc c) p x = (boundingRect x' # lcA (acolor bc) # lwN bs # fcA (acolor c)) <> x' where x' = pad p x -- | A single rectangle specified using a Rect x z y w where -- (x,y) is location of lower left corner -- (z,w) is location of upper right corner -- -- > rect_Example :: Double -> Chart b -- > rect_Example n = -- > labelled (opts (Pair n 1)) "z,w" $ -- > labelled (opts (Pair n -1)) "z,y" $ -- > labelled (opts (Pair (-n) 1)) "x,w" $ -- > labelled (opts (Pair (-n) -1)) "x,y" $ -- > rect_ def (Ranges (n *. one) one) -- > where -- > opts :: Pair Double -> LabelOptions -- > opts o = -- > #text %~ -- > ( (#color .~ black `withOpacity` 0.8) . -- > (#size .~ 0.3)) $ -- > #orientation .~ o $ -- > def -- -- ![rect_ example](other/rect_Example.svg) -- rect_ :: ( N b ~ Double , V b ~ V2 , Transformable b , HasOrigin b , TrailLike b , HasStyle b ) => RectOptions -> Rect Double -> b rect_ (RectOptions bs bc c) (Rect x z y w) = unitSquare # moveTo (p2 (0.5, 0.5)) # scaleX (z - x) # scaleY (w - y) # moveTo (p2 (x, y)) # fcA (acolor c) # lcA (acolor bc) # lwN bs -- | Create rectangles (with the same configuration). -- -- > rects def (rectBars 0 [1, 2, 3, 5, 8, 0, -2, 11, 2, 1]) -- -- ![rects example](other/rectsExample.svg) -- rects :: ( V a ~ V2 , N a ~ Double , Functor t , HasStyle a , TrailLike a , HasOrigin a , Transformable a , Foldable t , Monoid a ) => RectOptions -> t (Rect Double) -> a rects opts xs = mconcat $ toList $ rect_ opts <$> xs -- | A chart of rects rectChart :: (Traversable f) => [RectOptions] -> Rect Double -> Rect Double -> [f (Rect Double)] -> Chart b rectChart optss asp r rs = mconcat . zipWith rects optss $ fmap (projectRect r asp) <$> rs -- | A chart of rectangles scaled to its own range -- -- > ropts :: [RectOptions] -- > ropts = -- > [ #borderSize .~ 0 $ def -- > , #borderSize .~ 0 $ #color .~ ucolor 0.3 0.3 0.3 0.2 $ def -- > ] -- > -- > rss :: [[Rect Double]] -- > rss = -- > [ rectXY (\x -> exp (-(x ** 2) / 2)) (Range -5 5) 50 -- > , rectXY (\x -> 0.5 * exp (-(x ** 2) / 8)) (Range -5 5) 50 -- > ] -- > -- > rectChart_Example :: Chart b -- > rectChart_Example = rectChart_ ropts widescreen rss -- -- ![rectChart_ example](other/rectChart_Example.svg) -- rectChart_ :: (Traversable f) => [RectOptions] -> Rect Double -> [f (Rect Double)] -> Chart b rectChart_ optss asp rs = rectChart optss asp (fold $ fold <$> rs) rs -- | At some point, a color of a rect becomes more about data than stylistic option, hence the pixel. Echewing rect border leaves a Pixel with no stylistic options to choose. data Pixel = Pixel { pixelRect :: Rect Double , pixelColor :: UColor Double } deriving (Show, Generic) -- | A pixel is a rectangle with a color. -- -- > pixel_Example :: Chart b -- > pixel_Example = text_ opt "I'm a pixel!" <> pixel_ (Pixel one ublue) -- > where -- > opt = -- > #color .~ withOpacity black 0.8 $ -- > #size .~ 0.2 $ -- > def -- -- ![pixel_ example](other/pixel_Example.svg) -- pixel_ :: Pixel -> Chart b pixel_ (Pixel (Rect x z y w) c) = unitSquare # moveTo (p2 (0.5, 0.5)) # scaleX (z - x) # scaleY (w - y) # moveTo (p2 (x, y)) # fcA (acolor c) # lcA (acolor utrans) # lw 0 -- | Render multiple pixels -- -- > pixelsExample :: Chart b -- > pixelsExample = -- > pixels -- > [ Pixel -- > (Rect (5 * x) (5 * x + 0.1) (sin (10 * x)) (sin (10 * x) + 0.1)) -- > (dissolve (2 * x) ublue) -- > | x <- grid OuterPos (Range 0 1) 100 -- > ] -- -- ![pixels example](other/pixelsExample.svg) -- pixels :: (Traversable f) => f Pixel -> Chart b pixels ps = mconcat $ toList $ pixel_ <$> ps -- | A chart of pixels pixelChart :: (Traversable f) => Rect Double -> Rect Double -> [f Pixel] -> Chart b pixelChart asp r pss = mconcat $ pixels . projectPixels r asp . toList <$> pss where projectPixels r0 r1 ps = zipWith Pixel (projectRect r0 r1 . pixelRect <$> ps) (pixelColor <$> ps) -- | A chart of pixels scaled to its own range -- -- > pixelChart_Example :: Chart b -- > pixelChart_Example = -- > pixelChart_ asquare -- > [(\(r,c) -> -- > Pixel r -- > (blend c -- > (ucolor 0.47 0.73 0.86 1) -- > (ucolor 0.01 0.06 0.22 1) -- > )) <$> -- > rectF (\(Pair x y) -> (x+y)*(x+y)) -- > one (Pair 40 40)] -- -- ![pixelChart_ example](other/pixelChart_Example.svg) -- pixelChart_ :: (Traversable f) => Rect Double -> [f Pixel] -> Chart b pixelChart_ asp ps = pixelChart asp (fold $ fold . map pixelRect <$> ps) ps -- | Options to pixelate a Rect using a function data PixelationOptions = PixelationOptions { pixelationGradient :: Range (AlphaColour Double) , pixelationGrain :: Pair Int } instance Default PixelationOptions where def = PixelationOptions (Range (acolor $ UColor 0.47 0.73 0.86 1) (acolor $ UColor 0.01 0.06 0.22 1)) (Pair 40 40) -- | Transform a Rect into Pixels using a function over a Pair pixelate :: PixelationOptions -> Rect Double -> (Pair Double -> Double) -> [Pixel] pixelate (PixelationOptions (Range lc0 uc0) grain) xy f = zipWith Pixel g (ucolor <$> cs) where g = gridSpace xy grain xs = f . mid <$> g (Range lx ux) = space xs cs = (\x -> blend ((x - lx) / (ux - lx)) lc0 uc0) <$> xs -- | Chart pixels using a function -- This is a convenience function, and the example below is equivalent to the pixelChart_ example -- -- > pixelateChart def asquare one (\(Pair x y) -> (x+y)*(x+y)) -- pixelateChart :: PixelationOptions -> Rect Double -> Rect Double -> (Pair Double -> Double) -> Chart b pixelateChart opts asp xy f = pixelChart asp xy [pixelate opts xy f]