plot-light 0.2.6 → 0.2.7
raw patch · 10 files changed
+313/−56 lines, 10 filesdep ~basenew-component:exe:scatterbinary-addedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Graphics.Rendering.Plot.Light: Circle :: GlyphShape_
+ Graphics.Rendering.Plot.Light: Cross :: GlyphShape_
+ Graphics.Rendering.Plot.Light: Plus :: GlyphShape_
+ Graphics.Rendering.Plot.Light: ScatterPointData :: GlyphShape_ -> a -> a -> Colour Double -> ScatterPointData a
+ Graphics.Rendering.Plot.Light: Square :: GlyphShape_
+ Graphics.Rendering.Plot.Light: [spColour] :: ScatterPointData a -> Colour Double
+ Graphics.Rendering.Plot.Light: [spGlyphShape] :: ScatterPointData a -> GlyphShape_
+ Graphics.Rendering.Plot.Light: [spSize] :: ScatterPointData a -> a
+ Graphics.Rendering.Plot.Light: [spStrokeWidth] :: ScatterPointData a -> a
+ Graphics.Rendering.Plot.Light: data GlyphShape_
+ Graphics.Rendering.Plot.Light: data ScatterPointData a
+ Graphics.Rendering.Plot.Light: scatter :: (Foldable t, Show a, RealFrac a) => ScatterPointData a -> t (Point a) -> Svg
+ Graphics.Rendering.Plot.Light: scatterLP :: (Foldable t, RealFrac a, Show a) => (l -> b -> a) -> (l -> b -> a) -> (l -> Colour Double -> Colour Double) -> ScatterPointData b -> t (LabeledPoint l a) -> Svg
+ Graphics.Rendering.Plot.Light: scatterLPBar :: (RealFrac t, Enum t, RealFrac b, Show b) => FigureData b -> b -> t -> t -> Int -> LegendPosition_ -> b -> (t -> b -> b) -> (t -> b -> b) -> (t -> Colour Double -> Colour Double) -> ScatterPointData b -> Svg
- Graphics.Rendering.Plot.Light: axes :: (Show a, RealFrac a) => FigureData a -> Frame Rational -> a -> Colour Double -> Int -> Int -> MarkupM ()
+ Graphics.Rendering.Plot.Light: axes :: (Show a, RealFrac a) => FigureData a -> Frame a -> a -> Colour Double -> Int -> Int -> Svg
Files
- app/scatter/Main.hs +47/−0
- doc/fig/heatmap.png binary
- doc/fig/scatter.png binary
- plot-light.cabal +14/−1
- src/Graphics/Rendering/Plot/Light.hs +53/−8
- src/Graphics/Rendering/Plot/Light/Internal.hs +56/−35
- src/Graphics/Rendering/Plot/Light/Internal/Geometry.hs +13/−7
- src/Graphics/Rendering/Plot/Light/Internal/Utils.hs +3/−0
- src/Graphics/Rendering/Plot/Light/PlotTypes/Heatmap.hs +30/−1
- src/Graphics/Rendering/Plot/Light/PlotTypes/Scatter.hs +97/−4
+ app/scatter/Main.hs view
@@ -0,0 +1,47 @@+module Main where++import Control.Monad (forM_)++import qualified Data.Colour as C+import qualified Data.Colour.Names as C+import Data.Scientific (Scientific, toRealFloat)+import qualified Data.Text as T +import qualified Data.Text.IO as T (readFile, writeFile)+import Text.Blaze.Svg.Renderer.String (renderSvg)++import Graphics.Rendering.Plot.Light+import Graphics.Rendering.Plot.Light.PlotTypes++xPlot = 400+yPlot = 300+fnameOut = "data/scatter-1.svg"++fdat = FigureData xPlot yPlot 0.1 0.8 0.1 0.9 10++dats = zipWith LabeledPoint p_ l_ where+ l_ = [-5, -4 .. ]+ p_ = zipWith Point [4,7,12,23,90,34,24,5,6,12,3] [43,23,1,23,8,11,17,25,4,5]++spdata = ScatterPointData Circle 3 3 C.red+++main = do+ let+ frameTo = frameFromFigData fdat+ frameFrom = frameFromPoints $ _lp <$> dats+ vmin = minimum $ _lplabel <$> dats+ vmax = maximum $ _lplabel <$> dats + f l sz = 10/(1 + exp(-(0.3 * x)))+ where x = l + sz+ g _ w = w+ h l col = C.blend l' C.blue col+ where+ l' = (l - vmin)/(vmax - vmin)+ dats' = moveLabeledPointBwFrames frameFrom frameTo False True <$> dats+ svg_t = svgHeader xPlot yPlot $ do+ axes fdat frameFrom 2 C.black 10 10+ scatterLP f g h spdata dats'+ scatterLPBar fdat 50 vmin vmax 3 TopRight 100 f g h spdata+ -- putStrLn $ renderSvg svg_t+ T.writeFile fnameOut $ T.pack $ renderSvg svg_t+
doc/fig/heatmap.png view
binary file changed (41701 → 39966 bytes)
+ doc/fig/scatter.png view
binary file changed (absent → 13903 bytes)
plot-light.cabal view
@@ -1,5 +1,5 @@ name: plot-light-version: 0.2.6+version: 0.2.7 synopsis: A lightweight plotting library, exporting to SVG description: This library provides drawing and plotting datastructures and functions; it is aimed in particular at scientific visualization, but it also exposes its plotting primitives and a general purpose 2D geometry library. homepage: https://github.com/ocramz/plot-light@@ -47,6 +47,19 @@ -- , QuickCheck +executable scatter+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ hs-source-dirs: app/scatter+ main-is: Main.hs+ build-depends: base+ , plot-light+ , attoparsec+ , text+ , colour+ , blaze-svg+ , scientific+ executable timeseries default-language: Haskell2010 ghc-options: -threaded -rtsopts -with-rtsopts=-N
src/Graphics/Rendering/Plot/Light.hs view
@@ -16,11 +16,12 @@ -- -- == Examples -- --- If you wish to try out the examples in this page, you will need to have these additional statements :+-- If you wish to try out the examples in this page, you will need to have these import statements as well : ----- @import Text.Blaze.Svg.Renderer.String (renderSvg)@--- --- @import qualified Data.Colour.Names as C@+-- > import Text.Blaze.Svg.Renderer.String (renderSvg) +-- > import qualified Data.Colour.Names as C+-- > import qualified Data.Text.IO as T (readFile, writeFile)+-- > import qualified Data.Text as T -- -- === 1. Heatmap plot of a 2D function --@@ -34,9 +35,7 @@ -- where -- \( \rho^2 = x^2 + y^2 \) and \( \theta = \arctan(y/x) \). ----- > import qualified Data.Text.IO as T (readFile, writeFile)--- > import qualified Data.Text as T--- >+-- -- > xPlot = 400 -- > yPlot = 300 -- >@@ -80,11 +79,57 @@ -- Next, we create the legend; in this case this is a `colourBar` element that requires the data bounds `vmin`, `vmax`. -- -- As a last step, the SVG content is wrapped in the appropriate markdown by `svgHeader` and written to file.+--+--+-- === 2. Scatter plot of 3D data+--+-- <<doc/fig/scatter.png>>+--+-- This example shows how to plot a collection of labelled points in the plane. Each sample row is represented by a `LabeledPoint`, in which the label is a scalar quantity.+--+-- The `scatterLP` function renders each data row as a glyph, by modifying a `ScatterPointData` record of default values via three functions that control the glyph size, contour line thickness and colour. This functionality can be exploited in creative ways to achieve effective infographics.+--+--+-- > xPlot = 400+-- > yPlot = 300+-- > fnameOut = "data/scatter-1.svg"+-- >+-- > fdat = FigureData xPlot yPlot 0.1 0.8 0.1 0.9 10+-- >+-- > dats = zipWith LabeledPoint p_ l_ where+-- > l_ = [-5, -4 .. ]+-- > p_ = zipWith Point [4,7,12,23,90,34,24,5,6,12,3] [43,23,1,23,8,11,17,25,4,5]+-- >+-- > spdata = ScatterPointData Circle 3 3 C.red+-- >+-- >+-- > main = do+-- > let+-- > frameTo = frameFromFigData fdat+-- > frameFrom = frameFromPoints $ _lp <$> dats+-- > vmin = minimum $ _lplabel <$> dats+-- > vmax = maximum $ _lplabel <$> dats +-- > f l sz = 10/(1 + exp(-(0.3 * x)))+-- > where x = l + sz+-- > g _ w = w+-- > h l col = C.blend l' C.blue col+-- > where+-- > l' = (l - vmin)/(vmax - vmin)+-- > dats' = moveLabeledPointBwFrames frameFrom frameTo False True <$> dats+-- > svg_t = svgHeader xPlot yPlot $ do+-- > axes fdat frameFrom 2 C.black 10 10+-- > scatterLP f g h spdata dats'+-- > scatterLPBar fdat 50 vmin vmax 3 TopRight 100 f g h spdata+-- > T.writeFile fnameOut $ T.pack $ renderSvg svg_t+--+-- module Graphics.Rendering.Plot.Light ( -- * Plot types -- ** Heatmap- heatmap, heatmap', plotFun2, colourBar, + heatmap, heatmap', plotFun2, colourBar,+ -- ** Scatter+ scatter, scatterLP, scatterLPBar, ScatterPointData(..), GlyphShape_(..), -- * Plot elements -- ** Geometrical primitives rect, rectCentered, squareCentered, circle, line, text, polyline, filledPolyline, pixel, pixel',
src/Graphics/Rendering/Plot/Light/Internal.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE OverloadedStrings, DeriveFunctor #-}-module Graphics.Rendering.Plot.Light.Internal (FigureData(..), Frame(..), mkFrame, mkFrameOrigin, frameToFrame, frameToFrameValue, frameFromPoints, frameFromFigData, xmin,xmax,ymin,ymax, width, height, figFWidth, figFHeight, Point(..), mkPoint, LabeledPoint(..), mkLabeledPoint, labelPoint, mapLabel, Axis(..), axes, meshGrid, subdivSegment, svgHeader, rect, rectCentered, squareCentered, circle, line, tick, ticks, axis, toPlot, text, pixel, pixel', pickColour, colourBar, plusGlyph, crossGlyph, polyline, filledPolyline, filledBand, candlestick, strokeLineJoin, LineStroke_(..), StrokeLineJoin_(..), TextAnchor_(..), LegendPosition_(..), V2(..), Mat2(..), DiagMat2(..), diagMat2, AdditiveGroup(..), VectorSpace(..), Hermitian(..), LinearMap(..), MultiplicativeSemigroup(..), MatrixGroup(..), Eps(..), norm2, normalize2, v2fromEndpoints, v2fromPoint, origin, (-.), pointRange, movePoint, moveLabeledPointV2, moveLabeledPointBwFrames, translateSvg, toSvgFrame, toSvgFrameLP, e1, e2, toFloat, wholeDecimal, blendTwo, palette) where+module Graphics.Rendering.Plot.Light.Internal (FigureData(..), Frame(..), mkFrame, mkFrameOrigin, frameToFrame, frameToFrameValue, frameFromPoints, frameFromFigData, xmin,xmax,ymin,ymax, width, height, figFWidth, figFHeight, Point(..), mkPoint, LabeledPoint(..), mkLabeledPoint, labelPoint, mapLabel, Axis(..), axes, meshGrid, subdivSegment, svgHeader, rect, rectCentered, squareCentered, circle, line, tick, ticks, axis, toPlot, text, pixel, pixel', pickColour, colourBar, legendBar, plusGlyph, crossGlyph, polyline, filledPolyline, filledBand, candlestick, strokeLineJoin, LineStroke_(..), StrokeLineJoin_(..), TextAnchor_(..), LegendPosition_(..), V2(..), Mat2(..), DiagMat2(..), diagMat2, AdditiveGroup(..), VectorSpace(..), Hermitian(..), LinearMap(..), MultiplicativeSemigroup(..), MatrixGroup(..), Eps(..), norm2, normalize2, v2fromEndpoints, v2fromPoint, origin, (-.), pointRange, movePoint, moveLabeledPointV2, moveLabeledPointBwFrames, translateSvg, toSvgFrame, toSvgFrameLP, e1, e2, toFloat, wholeDecimal, blendTwo, palette) where import Data.Monoid ((<>)) import qualified Data.Foldable as F (toList)@@ -56,7 +56,6 @@ - -- | Create the SVG header svgHeader :: Real a => a -- ^ Image width (X axis)@@ -247,14 +246,15 @@ | otherwise = setPointX ox --- -- | A pair of Cartesian axes--- axes :: (Show a, RealFrac a) =>--- FigureData a--- -> a--- -> C.Colour Double--- -> Int--- -> Int--- -> Svg+-- | A pair of Cartesian axes+axes :: (Show a, RealFrac a) =>+ FigureData a+ -> Frame a+ -> a+ -> C.Colour Double+ -> Int+ -> Int+ -> Svg axes fdat (Frame (Point xmi ymi) (Point xma yma)) sw col nx ny = do axis o X lenx sw col 0.01 Continuous fontsize (-45) TAEnd showlabf (V2 (-10) 0) plabx_ axis o Y (- leny) sw col 0.01 Continuous fontsize 0 TAEnd showlabf (V2 (-10) 0) plaby_@@ -267,7 +267,7 @@ fontsize = figLabelFontSize fdat lenx = figFWidth fdat leny = figFHeight fdat- showlabf = T.pack . show . fromRational+ showlabf x = T.pack $ show (fromRational x :: Fixed E2) @@ -583,36 +583,58 @@ -> LegendPosition_ -- ^ Legend position in the figure -> a -- ^ Colour bar length -> Svg-colourBar fdat pal w vmin vmax n legpos legh = forM_ lps (colBarPx fdat pal w h vmin vmax) where- (legx, legy) = posCoeff legpos- legendX = fromRational $ figWidth fdat * legx - legendY = fromRational $ figHeight fdat * legy- p1 = Point legendX (legendY + legh)- p2 = Point legendX legendY- lps = zipWith LabeledPoint (pointRange n p1 p2) v_- h = norm2 (p1 -. p2) / fromIntegral n- v_ = take (n+1) [vmin, vmin + dv ..]- dv = (vmax - vmin)/fromIntegral n+colourBar fdat pal w vmin vmax n legpos legh =+ legendBar (fromRational <$> fdat) w vmin vmax n legpos legh (colBarPx pal) +legendBar+ :: (Monad m, Enum t, Fractional t, Fractional a) =>+ FigureData a+ -> a+ -> t+ -> t+ -> Int+ -> LegendPosition_+ -> a+ -> (FigureData a -> a -> a -> t -> t -> LabeledPoint t a -> m b)+ -> m ()+legendBar fdat w vmin vmax n legpos legh fun = do+ -- rect wrect hrect 1 (Just C.black) (Just C.white) prect+ forM_ lps (fun fdat w h vmin vmax) where+ wrect = 0.95 * (1 - figRightMFrac fdat) * figWidth fdat+ hrect = 1.5 * legh+ prect = movePoint (V2 (-0.5 * w) (-0.5 * w)) p2+ (legx, legy) = posCoeff legpos+ legendX = figWidth fdat * legx + legendY = figHeight fdat * legy+ p1 = Point legendX (legendY + legh)+ p2 = Point legendX legendY+ lps = zipWith LabeledPoint (pointRange n p1 p2) v_+ h = legh / fromIntegral n+ v_ = take (n+1) [vmin, vmin + dv ..]+ dv = (vmax - vmin)/fromIntegral n --- colBarPx--- :: (Show a, RealFrac a, RealFrac t) =>--- FigureData a1--- -> [C.Colour Double]--- -> a--- -> a--- -> t--- -> t--- -> LabeledPoint t a--- -> Svg-colBarPx fdat pal w h vmin vmax (LabeledPoint p val) = do- text 0 (figLabelFontSize fdat) C.black TAStart (T.pack $ show (rr val :: Fixed E6)) (V2 (1.1*w) (0.5*h)) p++colBarPx+ :: (Show a, RealFrac a, RealFrac t) =>+ [C.Colour Double] + -> FigureData a1+ -> a+ -> a+ -> t+ -> t+ -> LabeledPoint t a+ -> Svg+colBarPx pal fdat w h vmin vmax (LabeledPoint p val) = do+ text 0 (figLabelFontSize fdat) C.black TAStart (T.pack $ show (rr val :: Fixed E3)) (V2 (1.1*w) (0.5*h)) p rectCentered w h 0 Nothing (Just $ pickColour pal vmin vmax val) p ++ + -- * Helpers @@ -623,8 +645,7 @@ -- ** -rr :: (Real a, Fractional c) => a -> c-rr = fromRational . toRational+
src/Graphics/Rendering/Plot/Light/Internal/Geometry.hs view
@@ -274,7 +274,7 @@ moveLabeledPointV2 = moveLabeledPoint . movePoint --- | `pointRange n p q` returns a list of `n+1` equi-spaced `Point`s between `p` and `q` (i.e. the input points are included as the first and last points in the list)+-- | `pointRange n p q` returns a list of equi-spaced `Point`s between `p` and `q`. pointRange :: (Fractional a, Integral n) => n -> Point a -> Point a -> [Point a] pointRange n p q = [ movePoint (fromIntegral x .* vnth) p | x <- [0 .. n]]@@ -298,14 +298,20 @@ x <- take nx $ subdivSegment xmi xma nx, y <- take ny $ subdivSegment ymi yma ny] -subdivSegment- :: (Enum a, RealFrac a) => a -> a -> Int -> [a]-subdivSegment x1 x2 n = [xmin, xmin + dx ..] where- dx = fromRational . toRational $ l / fromIntegral n+-- subdivSegment+-- :: (Enum a, RealFrac a) => a -> a -> Int -> [a]+-- subdivSegment x1 x2 n = [xmin, xmin + dx ..] where+-- dx = fromRational . toRational $ l / fromIntegral n+-- xmin = min x1 x2+-- xmax = max x1 x2+-- l = xmax - xmin++subdivSegment :: (Real a, Enum b, RealFrac b) => a -> a -> Int -> [b]+subdivSegment x1 x2 n = f <$> [0, 1 ..] where xmin = min x1 x2 xmax = max x1 x2- l = xmax - xmin-+ l = fromRational $ toRational (xmax - xmin)+ f x = x * l/fromIntegral n + fromRational (toRational xmin) fromFrame :: Fractional a => Frame a -> V2 a -> V2 a
src/Graphics/Rendering/Plot/Light/Internal/Utils.hs view
@@ -22,6 +22,9 @@ -- ** Numeric formats +rr :: (Real a, Fractional c) => a -> c+rr = fromRational . toRational+ toFloat :: Scientific -> Float toFloat x = toRealFloat x :: Float
src/Graphics/Rendering/Plot/Light/PlotTypes/Heatmap.hs view
@@ -1,5 +1,7 @@+{-# language RecordWildCards #-} module Graphics.Rendering.Plot.Light.PlotTypes.Heatmap (heatmap, heatmap', plotFun2) where +import Control.Monad.State import Data.Scientific (Scientific, toRealFloat) import Graphics.Rendering.Plot.Light.Internal @@ -8,10 +10,37 @@ import Control.Arrow ((***), (&&&)) import Control.Monad (forM_) --- import qualified Data.Colour.Names as C+ import qualified Data.Colour as C+import qualified Data.Colour.Names as C import Data.Colour.Palette.BrewerSet -- import Text.Blaze.Svg++++data MeshGrid2d a = MeshGrid2d {+ mgFrame :: Frame a+ , mgNx :: Int+ , mgNy :: Int+ } deriving (Eq, Show)++data Heatmap a = Heatmap {+ hmMesh :: MeshGrid2d a+ , hmPalette :: [C.Colour Double]+ , hmValMin :: a+ , hmValMax :: a+ } deriving (Eq, Show)++heatmapDefaults :: Num a => Heatmap a+heatmapDefaults = Heatmap mesh pal 0 1+ where+ pal = palette [C.red, C.white, C.blue] 20+ pmin = Point 0 0+ pmax = Point 1 1+ mesh = MeshGrid2d (Frame pmin pmax) 20 20++ +
src/Graphics/Rendering/Plot/Light/PlotTypes/Scatter.hs view
@@ -1,24 +1,118 @@ module Graphics.Rendering.Plot.Light.PlotTypes.Scatter where import Graphics.Rendering.Plot.Light.Internal+import Graphics.Rendering.Plot.Light.Internal.Utils +import Data.Fixed import Data.Maybe (fromMaybe) import Control.Monad (forM_) import Text.Blaze.Svg import qualified Data.Colour as C import qualified Data.Colour.Palette.BrewerSet as CP import qualified Data.Colour.Names as C+import qualified Data.Text as T +-- | Scatter plot+--+-- Every point in the plot has the same parameters, as declared in the `ScatterPointData` record+scatter+ :: (Foldable t, Show a, RealFrac a) =>+ ScatterPointData a+ -> t (Point a)+ -> Svg+scatter (ScatterPointData glshape w sw fcol) ps = + forM_ ps $ glyph w sw glshape Nothing (Just fcol) -data GlyphShape = Square | Circle | Cross | Plus deriving (Eq, Show, Enum) ++++-- | Parametric scatter plot+--+-- The parameters of every point in the scatter plot are modulated according to the label, using the three functions.+--+-- This can be used to produce rich infographics, in which e.g. the colour and size of the glyphs carry additional information.+scatterLP+ :: (Foldable t, RealFrac a, Show a) =>+ (l -> b -> a)+ -> (l -> b -> a)+ -> (l -> C.Colour Double -> C.Colour Double)+ -> ScatterPointData b+ -> t (LabeledPoint l a)+ -> Svg+scatterLP f g h spdat lps = forM_ lps (scatterLP1 f g h spdat)+++scatterLP1+ :: (Show a, RealFrac a) =>+ (l -> b -> a)+ -> (l -> b -> a)+ -> (l -> C.Colour Double -> C.Colour Double)+ -> ScatterPointData b+ -> LabeledPoint l a+ -> Svg+scatterLP1 f g h spdat lp = glyph w' sw' sh Nothing (Just col') (_lp lp)+ where+ ScatterPointData sh w' sw' col' = modifyScatterPoint f g h spdat lp++++scatterLPBar+ :: (RealFrac t, Enum t, RealFrac b, Show b) =>+ FigureData b+ -> b+ -> t+ -> t+ -> Int+ -> LegendPosition_+ -> b+ -> (t -> b -> b)+ -> (t -> b -> b)+ -> (t -> C.Colour Double -> C.Colour Double)+ -> ScatterPointData b+ -> Svg+scatterLPBar fdat w vmin vmax n legpos legh f g h spdat = legendBar fdat w vmin vmax n legpos legh fun where+ wglyph = spSize spdat+ fun _ _ _ _ _ lp@(LabeledPoint p val) = do+ scatterLP1 f g h spdat lp+ text 0 (figLabelFontSize fdat) C.black TAStart (T.pack $ show (rr val :: Fixed E3)) (V2 (2*wglyph) (0.5*wglyph)) p+++ + +-- | Parameters for a scatterplot glyph+data ScatterPointData a = ScatterPointData+ {+ spGlyphShape :: GlyphShape_+ , spSize :: a+ , spStrokeWidth :: a+ , spColour :: C.Colour Double+ } deriving (Eq, Show)+++modifyScatterPoint+ :: (a -> b -> c)+ -> (a -> b -> c)+ -> (a -> C.Colour Double -> C.Colour Double)+ -> ScatterPointData b+ -> LabeledPoint a d+ -> ScatterPointData c+modifyScatterPoint f g h (ScatterPointData glsh sz w col) lp =+ ScatterPointData glsh (f lab sz) (g lab w) (h lab col)+ where+ lab = _lplabel lp+++-- | Glyph shape+data GlyphShape_ = Square | Circle | Cross | Plus deriving (Eq, Show, Enum)+ -- | Scatterplot glyph shapes glyph :: (Show a, RealFrac a) => a -> a- -> GlyphShape+ -> GlyphShape_ -> Maybe (C.Colour Double) -> Maybe (C.Colour Double) -> Point a@@ -30,11 +124,10 @@ -- | Utility function for cycling glyph colours and shapes (i.e. unique combinations of these make it easy to tell different datasets apart)-cycleGlyphCols :: (CP.ColorCat, Int) -> Int -> [(CP.Kolor, GlyphShape)]+cycleGlyphCols :: (CP.ColorCat, Int) -> Int -> [(CP.Kolor, GlyphShape_)] cycleGlyphCols (pal, n) nsets = take nsets $ zip (cycle $ CP.brewerSet pal n ) (cycle [Square, Circle ..]) -scatter figdata = forM_