packages feed

plot-light 0.1.0.3 → 0.1.0.4

raw patch · 6 files changed

+157/−65 lines, 6 files

Files

README.md view
@@ -3,3 +3,17 @@ [![Build Status](https://travis-ci.org/ocramz/plot-light.png)](https://travis-ci.org/ocramz/plot-light)  A lightweight plotting library, exporting to SVG+++## Usage++Import the user-facing module: `Graphics.Rendering.Plot.Light.Internal`++`plot-light` provides functionality for rendering vector graphics as SVG.+It is geared in particular towards scientific plotting, and it is termed "light" because it only requires native Haskell dependencies.++It builds upon `blaze-svg` by adding type-safe combinators, geometry primitives and functionality++To use this project you just need to import this module qualified (to avoid name clashes with any other modules you might have loaded on the side), for example as follows :++@import Graphics.Rendering.Plot.Light as P@
plot-light.cabal view
@@ -1,5 +1,5 @@ name:                plot-light-version:             0.1.0.3+version:             0.1.0.4 synopsis:            A lightweight plotting library, exporting to SVG description:         A lightweight plotting library, exporting to SVG homepage:            https://github.com/ocramz/plot-light@@ -27,7 +27,8 @@                        Graphics.Rendering.Plot.Light.IO.Text                        Graphics.Rendering.Plot.Light.PlotTypes.Heatmap                        Graphics.Rendering.Plot.Light.PlotTypes.TimeSeries-                       Graphics.Rendering.Plot.Light.PlotTypes.TimeSeries.Candlestick                          Data.TimeSeries.Forex+                       Graphics.Rendering.Plot.Light.PlotTypes.TimeSeries.Candlestick+                       Data.TimeSeries.Forex                        Graphics.Rendering.Plot.Light.Internal.Types                        Graphics.Rendering.Plot.Light.Internal.Geometry   build-depends:       base >= 4.7 && < 5@@ -40,6 +41,8 @@                      , colour                      , palette                      , blaze-svg+                     , hspec+                     , QuickCheck                       executable plot-light   default-language:    Haskell2010
src/Graphics/Rendering/Plot/Light.hs view
@@ -1,6 +1,51 @@ {-# LANGUAGE OverloadedStrings #-}-module Graphics.Rendering.Plot.Light (module Plot) where +-- |+-- Module      :  Graphics.Rendering.Plot.Light+-- Copyright   :  Marco Zocca 2017+-- License     :  BSD3+-- Maintainer  :  Marco Zocca <zocca marco gmail>+--+-- `plot-light` provides functionality for rendering vector+-- graphics in SVG format. It is geared in particular towards scientific plotting,+-- and it is termed "light" because it only requires few native Haskell dependencies.+--+-- It builds upon `blaze-svg` by adding type-safe combinators,+-- geometry primitives and related functions.+--+-- == Usage+--+-- To use this project you just need to import this module qualified (to avoid name clashes with any other modules you might have loaded on the side), for example as follows :+--+-- @import Graphics.Rendering.Plot.Light as P@+--+-- If you wish to try out the examples in this page, you will need to have `renderSvg` in scope as well:+--+-- @import Text.Blaze.Svg.Renderer.String (renderSvg)@++module Graphics.Rendering.Plot.Light (+  -- * Graphical elements+  rectCentered, line, axis, text, polyline,+  -- * Types+  FigureData(..), Point(..), LabeledPoint(..), Axis(..),+  mkFigureData, svgHeader,+  -- * Geometry+  -- ** Vectors+  V2(..),+  -- ** Matrices+  Mat2(..), DiagMat2(..), diagMat2,+  -- ** Primitive elements+  origin, e1, e2,+  -- ** Vector operations +  norm2, normalize2,+  -- ** Vector construction+  mkV2fromEndpoints, v2fromPoint, +  -- ** Operations on points+  movePoint, moveLabeledPointV2, fromUnitSquare, toUnitSquare,+  -- ** Typeclasses+  AdditiveGroup(..), VectorSpace(..), Hermitian(..), LinearMap(..), MultiplicativeSemigroup(..), MatrixGroup(..)+  ) where+ -- import Data.Foldable import qualified Data.Text as T @@ -15,7 +60,7 @@ import qualified Data.Colour.SRGB as C  -import Graphics.Rendering.Plot.Light.Internal as Plot+import Graphics.Rendering.Plot.Light.Internal   
src/Graphics/Rendering/Plot/Light/Internal.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}-module Graphics.Rendering.Plot.Light.Internal (FigureData(..), Point(..), LabeledPoint(..), Axis(..), mkFigureData, figure, rectCentered, line, tick, ticks, axis, text, polyline, V2(..), Mat2, DiagMat2, diagMat2, AdditiveGroup(..), VectorSpace(..), Hermitian(..), LinearMap(..), MultiplicativeSemigroup(..), MatrixGroup(..), norm2, normalize2, mkV2fromEndpoints, v2fromPoint, origin, movePoint, moveLabeledPointV2, fromUnitSquare, toUnitSquare, e1, e2) where+module Graphics.Rendering.Plot.Light.Internal (FigureData(..), Point(..), LabeledPoint(..), Axis(..), mkFigureData, svgHeader, rectCentered, line, tick, ticks, axis, text, polyline, V2(..), Mat2(..), DiagMat2(..), diagMat2, AdditiveGroup(..), VectorSpace(..), Hermitian(..), LinearMap(..), MultiplicativeSemigroup(..), MatrixGroup(..), norm2, normalize2, mkV2fromEndpoints, v2fromPoint, origin, movePoint, moveLabeledPointV2, fromUnitSquare, toUnitSquare, e1, e2) where  import Data.Monoid ((<>)) import Control.Arrow ((&&&), (***))@@ -28,15 +28,20 @@   -mkFigureData :: Num a => a -> a -> a -> a -> FigureData a-mkFigureData xmin ymin xlen ylen =-  FigData xlen ylen xmin (xmin + xlen) ymin (ymin + ylen)+-- | Create a `FigureData` structure from the top-left corner point and its side lengths+mkFigureData :: Num a =>+          Point a      +       -> a+       -> a+       -> FigureData a+mkFigureData (Point xmi ymi) xlen ylen =+  FigData xlen ylen xmi (xmi + xlen) ymi (ymi + ylen)   --- | Header for a Figure-figure :: FigureData Int -> Svg -> Svg-figure fd =+-- | Create the SVG header from `FigureData`+svgHeader :: FigureData Int -> Svg -> Svg+svgHeader fd =   S.docTypeSvg   ! SA.version "1.1"   ! SA.width (vi $ _width fd)@@ -46,62 +51,63 @@  -- | A filled rectangle, centered at (x0, y0) rectCentered-  :: Double -> Double -> Double -> Double -> C.Colour Double -> Svg-rectCentered x0 y0 wid hei col = S.g ! SA.transform (S.translate x0c y0c) $ +  :: Point Double    -- ^ Center coordinates           +  -> Double          -- ^ Width+  -> Double          -- ^ Height+  -> C.Colour Double -- ^ Colour+  -> Svg+rectCentered (Point x0 y0) wid hei col = S.g ! SA.transform (S.translate x0c y0c) $    S.rect ! SA.width (vd wid) ! SA.height (vd hei) ! SA.fill (colourAttr col) where    x0c = x0 - (wid / 2)    y0c = y0 - (hei / 2)    --- centeredAt x0 y0 = S.g ! A.transform (translate x0 y0)  --- | Line segment------ e.g. --- > putStrLn $ renderSvg (line 0 0 1 1 0.1 C.blueviolet)+-- | Line segment between two `Point`s -- --- <line x1="0.0" y1="0.0" x2="1.0" y2="1.0" stroke="#8a2be2" stroke-width="0.1" />-line :: Double -> Double -> Double -> Double -> Double -> C.Colour Double -> Svg-line x1 y1 x2 y2 sw col = S.line ! SA.x1 (vd x1) ! SA.y1 (vd y1) ! SA.x2 (vd x2)  ! SA.y2 (vd y2) ! SA.stroke (colourAttr col )! SA.strokeWidth (vd sw)+-- > > putStrLn $ renderSvg (line 0 0 1 1 0.1 C.blueviolet)+-- > <line x1="0.0" y1="0.0" x2="1.0" y2="1.0" stroke="#8a2be2" stroke-width="0.1" />+line :: Point Double -> Point Double -> Double -> C.Colour Double -> Svg+line (Point x1 y1) (Point x2 y2) sw col = S.line ! SA.x1 (vd x1) ! SA.y1 (vd y1) ! SA.x2 (vd x2)  ! SA.y2 (vd y2) ! SA.stroke (colourAttr col )! SA.strokeWidth (vd sw)  tick :: Axis -> Double -> Double -> C.Colour Double -> Point Double -> Svg-tick ax len sw col (Point x y) = line x1 y1 x2 y2 sw col where+tick ax len sw col (Point x y) = line (Point x1 y1) (Point x2 y2) sw col where   lh = len / 2   (x1, y1, x2, y2)     | ax == Y = (x, y-lh, x, y+lh)     | otherwise = (x-lh, y, x+lh, y)  tickX, tickY ::-     Double          -- | Length-  -> Double          -- | Stroke width-  -> C.Colour Double -- | Stroke colour-  -> Point Double    -- | Center coordinates+     Double          -- ^ Length+  -> Double          -- ^ Stroke width+  -> C.Colour Double -- ^ Stroke colour+  -> Point Double    -- ^ Center coordinates   -> Svg tickX = tick X tickY = tick Y  -- | An array of axis-aligned identical segments (to be used as axis tickmarks), with centers given by the array of `Point`s ticks :: Foldable t =>-               Axis                -- | Axis -               -> Double           -- | Length         -               -> Double           -- | Stroke width-               -> C.Colour Double  -- | Stroke colour-               -> t (Point Double) -- | Center coordinates+               Axis                -- ^ Axis +               -> Double           -- ^ Length         +               -> Double           -- ^ Stroke width+               -> C.Colour Double  -- ^ Stroke colour+               -> t (Point Double) -- ^ Center coordinates                -> Svg ticks ax len sw col ps = forM_ ps (tick ax len sw col)   -- | An axis with tickmarks ----- λ> putStrLn $ renderSvg $ axis X 200 2 C.red 0.05 (Point 150 10) [Point 50 1, Point 60 1, Point 70 1]--- <line x1="50.0" y1="10.0" x2="250.0" y2="10.0" stroke="#ff0000" stroke-width="2.0" /><line x1="50.0" y1="5.0" x2="50.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" /><line x1="60.0" y1="5.0" x2="60.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" /><line x1="70.0" y1="5.0" x2="70.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" />+-- > > putStrLn $ renderSvg $ axis X 200 2 C.red 0.05 (Point 150 10) [Point 50 1, Point 60 1, Point 70 1]+-- > <line x1="50.0" y1="10.0" x2="250.0" y2="10.0" stroke="#ff0000" stroke-width="2.0" /><line x1="50.0" y1="5.0" x2="50.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" /><line x1="60.0" y1="5.0" x2="60.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" /><line x1="70.0" y1="5.0" x2="70.0" y2="15.0" stroke="#ff0000" stroke-width="2.0" /> axis :: (Functor t, Foldable t) =>-              Axis-              -> Double-              -> Double-              -> C.Colour Double-              -> Double-              -> Point Double-              -> t (Point Double)+              Axis                  -- ^ Axis (i.e. either `X` or `Y`)+              -> Double             -- ^ Length+              -> Double             -- ^ Stroke width+              -> C.Colour Double    -- ^ Stroke colour+              -> Double             -- ^ Tick length fraction (w.r.t axis length)+              -> Point Double       -- ^ Axis center coordinate+              -> t (Point Double)   -- ^ Tick center coordinates               -> Svg axis ax len sw col tickLenFrac p@(Point x y) ps = do   tick ax len sw col p@@ -114,18 +120,20 @@  -- * text --- | `text` renders text onto the SVG canvas. It is also possible to rotate and move the text, however the order of these modifier matters.+-- | `text` renders text onto the SVG canvas. It is also possible to rotate and move the text, however the order of these modifiers matters.+--  -- NB1: `x` and `y` determine the position of the bottom-left corner of the text box. If a nonzero rotation is applied, the whole text box will move in a circle of radius || x^2 + y^2 ||--- NB2: the `rotate` and `translate` apply to the _center_ of the text box instead ----- > putStrLn $ renderSvg $ text (-45) C.red "hullo!" (V2 (-30) 0) (Point 0 20)--- <text x="-30" y="0" transform="translate(0 20)rotate(-45)" fill="#ff0000">hullo!</text>+-- NB2: the `rotate` and `translate` attributes apply to the _center_ of the text box instead+--+-- > > putStrLn $ renderSvg $ text (-45) C.red "hullo!" (V2 (-30) 0) (Point 0 20)+-- > <text x="-30" y="0" transform="translate(0 20)rotate(-45)" fill="#ff0000">hullo!</text> text :: (Show a, Show a1, S.ToValue a2) =>-              a1        -- | Rotation angle -     -> C.Colour Double -- | Font colour-     -> T.Text          -- | Text -     -> V2 a2           -- | Displacement-     -> Point a         -- | Initial center position of the text box+              a1        -- ^ Rotation angle +     -> C.Colour Double -- ^ Font colour+     -> T.Text          -- ^ Text +     -> V2 a2           -- ^ Displacement+     -> Point a         -- ^ Initial center position of the text box      -> Svg text rot col te (V2 x y) (Point dx dy) =    S.text_ (S.toMarkup te) ! SA.x (S.toValue x) ! SA.y (S.toValue y) ! SA.transform (S.translate dx dy <> S.rotate rot) ! SA.fill (colourAttr col)@@ -137,11 +145,14 @@ -- <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" style="fill:none;stroke:black;stroke-width:3" />  -- | Polyline (piecewise straight line)--- e.g.--- > putStrLn $ renderSvg (polyline [(1,1), (2,1), (2,2), (3,4)] 0.1 C.red)------ <polyline points="1,1 2,1 2,2 3,4" fill="none" stroke="#ff0000" stroke-width="0.1" />-polyline :: (Show a1, Show a) => [(a1, a)] -> Double -> C.Colour Double -> Svg+-- +-- > > putStrLn $ renderSvg (polyline [(1,1), (2,1), (2,2), (3,4)] 0.1 C.red)+-- > <polyline points="1,1 2,1 2,2 3,4" fill="none" stroke="#ff0000" stroke-width="0.1" />+polyline :: (Show a1, Show a) =>+            [(a1, a)]       -- ^ Data+         -> Double          -- ^ Stroke width+         -> C.Colour Double -- ^ Stroke colour+         -> Svg polyline lis sw col = S.polyline ! SA.points (S.toValue $ unwords $ map showP2 lis) ! SA.fill none ! SA.stroke (colourAttr col )! SA.strokeWidth (vd sw) ! SA.strokeLinejoin (S.toValue ("round" :: String))  showP2 :: (Show a, Show a1) => (a1, a) -> String
src/Graphics/Rendering/Plot/Light/Internal/Geometry.hs view
@@ -19,14 +19,20 @@   mempty = V2 0 0   (V2 a b) `mappend` (V2 c d) = V2 (a + b) (c + d) --- | Additive group : --- v ^+^ zero = zero ^+^ v = v--- v ^-^ v == zero+-- | Additive group :+-- +-- > v ^+^ zero == zero ^+^ v == v+--+-- > v ^-^ v == zero class AdditiveGroup v where+  -- | Identity element   zero :: v+  -- | Group action ("sum")   (^+^) :: v -> v -> v+  -- | Inverse group action ("subtraction")   (^-^) :: v -> v -> v +-- | Vectors form an additive group instance Num a => AdditiveGroup (V2 a) where   zero = mempty   (^+^) = mappend@@ -35,6 +41,7 @@ -- | Vector space : multiplication by a scalar quantity class AdditiveGroup v => VectorSpace v where   type Scalar v :: *+  -- | Scalar multiplication   (.*) :: Scalar v -> v -> v    instance Num a => VectorSpace (V2 a) where@@ -44,6 +51,7 @@ -- | Hermitian space : inner product class VectorSpace v => Hermitian v where   type InnerProduct v :: *+  -- | Inner product   (<.>) :: v -> v -> InnerProduct v  instance Num a => Hermitian (V2 a) where@@ -71,17 +79,22 @@ origin = Point 0 0  --- | A Mat2 can be seen as a linear operator V2 -> V2+-- | A Mat2 can be seen as a linear operator that acts on points in the plane data Mat2 a = Mat2 a a a a deriving (Eq, Show)  -- | Linear maps, i.e. linear transformations of vectors class Hermitian v => LinearMap m v where+  -- | Matrix action, i.e. linear transformation of a vector   (#>) :: m -> v -> v  -- | Multiplicative matrix semigroup ("multiplying" two matrices together) class MultiplicativeSemigroup m where+  -- | Matrix product   (##) :: m -> m -> m +instance Num a => MultiplicativeSemigroup (Mat2 a) where+  Mat2 a00 a01 a10 a11 ## Mat2 b00 b01 b10 b11 = Mat2 (a00*b00+a01*b10) (a00*b01+a01*b11) (a10*b00+a11*b10) (a10*b01+a11*b11)+ instance Num a => LinearMap (Mat2 a) (V2 a) where   (Mat2 a00 a01 a10 a11) #> (V2 vx vy) = V2 (a00 * vx + a01 * vy) (a10 * vx + a11 * vy) @@ -95,14 +108,16 @@  -- | The class of invertible linear transformations class LinearMap m v => MatrixGroup m v where+  -- | Inverse matrix action on a vector   (<\>) :: m -> v -> v    instance Num a => MultiplicativeSemigroup (DiagMat2 a) where   DMat2 a b ## DMat2 c d = DMat2 (a*c) (b*d) --- | Diagonal matrices can always be inverted instance Num a => LinearMap (DiagMat2 a) (V2 a) where   DMat2 d1 d2 #> V2 vx vy = V2 (d1 * vx) (d2 * vy)++-- | Diagonal matrices can always be inverted instance Fractional a => MatrixGroup (DiagMat2 a) (V2 a) where   DMat2 d1 d2 <\> V2 vx vy = V2 (vx / d1) (vy / d2) @@ -130,7 +145,7 @@     o1 = _fpmin from     vmove = mm <\> (p -. o1) --- | The vector translation from a `Point` contained in the unit square onto a `Frame`+-- | The vector translation from a `Point` `p` contained in the unit square onto a `Frame` -- -- NB: we do not check that `p` is actually contained in [0,1] x [0,1], This has to be supplied correctly by the user fromUnitSquare :: Num a => Frame a -> Point a -> Point a@@ -141,9 +156,11 @@     vmove = (mm #> v2fromPoint p) ^+^ vo  --- | Coordinate unit vectors-e1, e2 :: Num a => V2 a+-- | X-aligned unit vector+e1 :: Num a => V2 a e1 = V2 1 0+-- | Y-aligned unit vector+e2 :: Num a => V2 a e2 = V2 0 1  
src/Graphics/Rendering/Plot/Light/Internal/Types.hs view
@@ -27,11 +27,12 @@ setPointX = setPointCoord X setPointY = setPointCoord Y --- | A `LabeledPoint` carries the information of where a point should be plotted, what label should it carry (e.g. for labelling the axes) and its function value +-- | A `LabeledPoint` carries a "label" (i.e. any additional information such as a text tag, or any other data structure), in addition to position information. Data points on a plot are `LabeledPoint`s. data LabeledPoint l a =   LabeledPoint {    _lp :: Point a,-   _lplabel :: l } deriving (Eq, Show)+   _lplabel :: l+   } deriving (Eq, Show)  moveLabeledPoint :: (Point a -> Point b) -> LabeledPoint l a -> LabeledPoint l b moveLabeledPoint f (LabeledPoint p l) = LabeledPoint (f p) l@@ -39,7 +40,8 @@ -- | A frame, i.e. a bounding box for objects data Frame a = Frame {    _fpmin :: Point a,-   _fpmax :: Point a} deriving (Eq, Show)+   _fpmax :: Point a+   } deriving (Eq, Show)  -- | Frame corner coordinates xmin, xmax, ymin, ymax :: Frame a -> a