{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Plots.Types.Scatter -- Copyright : (C) 2015 Christopher Chalmers -- License : BSD-style (see the file LICENSE) -- Maintainer : Christopher Chalmers -- Stability : experimental -- Portability : non-portable -- -- A scatter plot is a type of mathematical diagram using Cartesian -- coordinates to display values for typically two variables for a set -- of data. -- -- <<diagrams/src_Plots_Types_Scatter_scatterExample.svg#diagram=scatterExample&height=350>> -- -- (see 'scatterPlot' example for code to make this plot) -- ---------------------------------------------------------------------------- module Plots.Types.Scatter ( -- * Scatter plot ScatterPlot -- * Scatter plot lenses , ScatterOptions , HasScatterOptions (..) , HasConnectingLine (..) -- * Basic scatter plot -- ** Add plots to the axis , scatterPlot , scatterPlot' , scatterPlotOf , scatterPlotOf' -- * Scatter options , scatterOptions -- * Bubble plots , bubblePlot , bubblePlot' , bubblePlotOf , bubblePlotOf' -- ** Bubble options , BubbleOptions , bubbleOptions , bubbleTransform , bubbleStyle -- ** General scatter plot , gscatterPlot , gscatterOptionsFor -- * Low level construction , mkScatterOptions ) where import Control.Lens hiding (lmap, transform, ( # )) import Control.Monad.State.Lazy import qualified Data.Foldable as F import Data.Typeable import Diagrams.Coordinates.Isomorphic import Diagrams.Prelude hiding (view) import Plots.Axis import Plots.Style import Plots.Types import Diagrams.Types (mkQD, Prim (..)) -- config -- > import Plots ------------------------------------------------------------------------ -- General scatter plot ------------------------------------------------------------------------ -- | A general data type for scatter plots. Allows storing different -- types of data as well as allowing transforms depending on the data. data ScatterPlot v where ScatterPlot :: Typeable a => ScatterOptions v a -> ScatterPlot v deriving Typeable type instance V (ScatterPlot v) = v type instance N (ScatterPlot v) = Double -- | A general data type for scatter plots. Allows storing different -- types of data as well as allowing transforms depending on the data. data ScatterOptions v a = ScatterOptions { oData :: [a] , oPos :: a -> Point v Double , oTr :: a -> Transformation v Double , oSty :: a -> Style v Double , oLine :: Bool } deriving Typeable type instance V (ScatterOptions v a) = v type instance N (ScatterOptions v a) = Double instance Metric v => Enveloped (ScatterPlot v) where getEnvelope (ScatterPlot (ScatterOptions {..})) = getEnvelope (map oPos oData) instance Plotable (ScatterPlot V2) where renderPlotable s sty (ScatterPlot (ScatterOptions {..})) = markers <> line where markers = F.foldMap mk oData # applyMarkerStyle sty -- mk a = marker # transform (oTr a) # applyStyle (oSty a) # moveTo (specPoint s $ oPos a) marker = sty ^. plotMarker -- line | not oLine = mempty | otherwise = fromVertices points # applyLineStyle sty points = map (specPoint s . oPos) oData defLegendPic sty (ScatterPlot (ScatterOptions {..})) = sty ^. plotMarker & applyMarkerStyle sty strokeV3 :: Path V3 Double -> Diagram V3 strokeV3 path = mkQD (Prim path) (getEnvelope path) mempty mempty instance Plotable (ScatterPlot V3) where renderPlotable s sty (ScatterPlot (ScatterOptions {..})) = markers <> line where markers = F.foldMap mk oData # applyMarkerStyle sty -- mk a = marker # transform (oTr a) # applyStyle (oSty a) # moveTo (specPoint s $ oPos a) marker = sty ^. plotMarker -- line | not oLine = mempty | otherwise = strokeV3 (fromVertices points) # applyLineStyle sty points = map (specPoint s . oPos) oData defLegendPic sty (ScatterPlot (ScatterOptions {..})) = sty ^. plotMarker & applyMarkerStyle sty ------------------------------------------------------------------------ -- Generating scatter options ------------------------------------------------------------------------ -- | Low level construction of 'ScatterOptions'. mkScatterOptions :: (PointLike v Double p, F.Foldable f) => f a -> (a -> p) -> ScatterOptions v a mkScatterOptions xs pf = ScatterOptions { oData = F.toList xs , oPos = view unpointLike . pf , oTr = mempty , oSty = const mempty , oLine = False } ------------------------------------------------------------------------ -- Scatter plot lenses ------------------------------------------------------------------------ -- | Class of things that have a 'LensLike' for a 'ScatterPlot' \'s -- connecting line. class HasConnectingLine f a where -- | 'LensLike' onto whether the scatter plot should have a connecting -- line between points. If the line is present, it uses the -- 'lineStyle' from the 'PlotStyle'. connectingLine :: Functor f => LensLike' f a Bool instance HasConnectingLine f (ScatterOptions v a) where connectingLine = lens oLine (\o b -> o {oLine = b}) instance HasConnectingLine f (ScatterPlot v) where connectingLine f (ScatterPlot o@(ScatterOptions {..})) = f oLine <&> \b -> ScatterPlot o {oLine = b} instance HasConnectingLine f p => HasConnectingLine f (Plot p) where connectingLine = rawPlot . connectingLine instance (Applicative f, Typeable v) => HasConnectingLine f (DynamicPlot v) where connectingLine = (dynamicPlot :: Traversal' (DynamicPlot v) (Plot (ScatterPlot v))) . connectingLine instance (Applicative f, Typeable v) => HasConnectingLine f (StyledPlot v) where connectingLine = (styledPlot :: Traversal' (StyledPlot v) (ScatterPlot v)) . connectingLine instance (Settable f, Typeable (BaseSpace c)) => HasConnectingLine f (Axis c) where connectingLine = finalPlots . connectingLine -- Options ------------------------------------------------------------- class HasScatterOptions f a d where -- | Lens onto the 'ScatterOptions' for a general scatter plot. gscatterOptions :: LensLike' f a (ScatterOptions (V a) d) -- | Apply a transform to the markers using the associated data. scatterTransform :: Functor f => LensLike' f a (d -> Transformation (V a) Double) scatterTransform = gscatterOptions . lens oTr (\o tr -> o {oTr = tr}) -- | Apply a style to the markers using the associated data. scatterStyle :: Functor f => LensLike' f a (d -> Style (V a) Double) scatterStyle = gscatterOptions . lens oSty (\o sty -> o {oSty = sty}) -- | Change the position of the markers depending on the data. scatterPosition :: Functor f => LensLike' f a (d -> Point (V a) Double) scatterPosition = gscatterOptions . lens oPos (\o pos -> o {oPos = pos}) instance d ~ d' => HasScatterOptions f (ScatterOptions v d) d' where gscatterOptions = id instance (Applicative f, Typeable v, Typeable d) => HasScatterOptions f (ScatterPlot v) d where gscatterOptions f s@(ScatterPlot p) = case eq p of Just Refl -> ScatterPlot <$> f p Nothing -> pure s where eq :: Typeable a => a -> Maybe (a :~: ScatterOptions v d) eq _ = eqT instance (Functor f, HasScatterOptions f p a) => HasScatterOptions f (Plot p) a where gscatterOptions = rawPlot . gscatterOptions instance (Applicative f, Typeable v, Typeable a) => HasScatterOptions f (DynamicPlot v) a where gscatterOptions = dynamicPlot . rawPlot instance (Applicative f, Typeable (BaseSpace c), Typeable a) => HasScatterOptions f (Axis c) a where gscatterOptions = axisPlots . traverse . gscatterOptions -- Pure scatter lenses ------------------------------------------------- -- | Lens onto a scatter plot of points. scatterOptions :: (InSpace v Double a, HasScatterOptions f a (Point v Double)) => LensLike' f a (ScatterOptions v (Point v Double)) scatterOptions = gscatterOptions -- -- | Lens onto a transform of a scatter plot of points. This is a -- -- specialised version of 'scatterTransform' for better type -- -- inference. -- _scatterTransform -- :: (InSpace v a, PointLike v p, Functor f, HasScatterOptions f a (Point v)) -- => LensLike' f a (p -> Transformation v) -- _scatterTransform = scatterTransform . lmapping unpointLike -- -- | Lens onto a transform of a scatter plot of points. This is a -- -- specialised version of 'scatterPosition' for better type inference. -- _scatterPosition -- :: (InSpace v a, PointLike v p, Functor f, HasScatterOptions f a (Point v)) -- => LensLike' f a (p -> p) -- _scatterPosition = scatterPos . dimapping unpointLike pointLike -- -- | Lens onto a style function of a scatter plot of points. This is a -- -- specialised version of 'scatterStyle' for better type inference. -- _scatterStyle -- :: (InSpace v a, PointLike v p, Functor f, HasScatterOptions f a (Point v)) -- => LensLike' f a (p -> Style v) -- _scatterStyle = scatterStyle . lmapping unpointLike ------------------------------------------------------------------------ -- Scatter plot ------------------------------------------------------------------------ -- $ scatter -- Scatter plots display data as dots. There are several representations -- for scatter plots for extra parameters. Scatter plots have the -- following lenses: -- -- * 'connectingLine' - line between points -- * -- -- | Add a 'ScatterPlot' to the 'AxisState' from a data set. -- -- @ -- 'scatterPlot' :: [('Double', 'Double')] -> 'State' ('Plot' ('ScatterOptions' 'V2' ('P2' 'Double')) b) () -> 'State' ('Axis' 'V2') () -- 'scatterPlot' :: ['V2' 'Double'] -> 'State' ('Plot' ('ScatterOptions' 'V2' ('P2' 'Double')) b) () -> 'State' ('Axis' 'V2') () -- 'scatterPlot' :: ['P2' 'Double'] -> 'State' ('Plot' ('ScatterOptions' 'V2' ('P2' 'Double')) b) () -> 'State' ('Axis' 'V2') () -- @ -- -- === __Example__ -- -- <<diagrams/src_Plots_Types_Scatter_scatterExample.svg#diagram=scatterExample&height=350>> -- -- > import Plots -- > mydata1 = [(1,3), (2,5.5), (3.2, 6), (3.5, 6.1)] -- > mydata2 = mydata1 & each . _1 *~ 0.5 -- > mydata3 = [V2 1.2 2.7, V2 2 5.1, V2 3.2 2.6, V2 3.5 5] -- -- > scatterAxis :: Axis V2 -- > scatterAxis = r2Axis &~ do -- > scatterPlot mydata1 $ key "data 1" -- > scatterPlot mydata2 $ key "data 2" -- > scatterPlot mydata3 $ key "data 3" -- -- > scatterExample = renderAxis scatterAxis scatterPlot :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v), F.Foldable f) => f p -- ^ points to plot -> State (Plot (ScatterOptions v (Point v Double))) () -- ^ changes to plot options -> m () -- ^ add plot to 'Axis' scatterPlot xs = gscatterPlot (xs ^.. folded . unpointLike) id -- | Version of 'scatterPlot' without any changes to the -- 'ScatterOptions'. -- -- @ -- 'scatterPlot'' :: [('Double', 'Double')] -> 'State' ('Axis' b 'V2' 'Double') () -- 'scatterPlot'' :: ['V2' 'Double'] -> 'State' ('Axis' b 'V2' 'Double') () -- 'scatterPlot'' :: ['P2' 'Double'] -> 'State' ('Axis' b 'V2' 'Double') () -- @ -- -- === __Example__ -- -- <<diagrams/src_Plots_Types_Scatter_scatterExample'.svg#diagram=scatterExample'&height=350>> -- -- > import Plots -- > mydata4 = [(1,3), (2,5.5), (3.2, 6), (3.5, 6.1)] -- > mydata5 = mydata1 & each . _1 *~ 0.5 -- > mydata6 = [V2 1.2 2.7, V2 2 5.1, V2 3.2 2.6, V2 3.5 5] -- -- > scatterAxis' :: Axis B V2 Double -- > scatterAxis' = r2Axis &~ do -- > scatterPlot' mydata4 -- > scatterPlot' mydata5 -- > scatterPlot' mydata6 -- -- > scatterExample' = renderAxis scatterAxis' scatterPlot' :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v), F.Foldable f) => f p -- ^ points to plot -> m () -- ^ add plot to 'Axis' scatterPlot' xs = scatterPlot xs (return ()) -- | Version of 'scatterPlot' that accepts a 'Fold' over the data. scatterPlotOf :: (BaseSpace c ~ v, PointLike v Double p, Plotable (ScatterPlot v), MonadState (Axis c) m) => Fold s p -- ^ fold over points -> s -- ^ data to fold -> State (Plot (ScatterOptions v (Point v Double))) () -- ^ changes to plot options -> m () -- ^ add plot to 'Axis' scatterPlotOf f s = scatterPlot (toListOf f s) -- | Version of 'scatterPlot' that accepts a 'Fold' over the data -- without any changes to the 'ScatterOptions'. scatterPlotOf' :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v)) => Fold s p -- ^ fold over points -> s -- ^ data to fold -> m () -- ^ add plot to axis scatterPlotOf' f s = scatterPlot' (toListOf f s) ------------------------------------------------------------------------ -- Bubble plot -- ------------------------------------------------------------------------ -- | A bubble plot is a scatter plot using point together with a scalar. type BubbleOptions v = ScatterOptions v (Double, Point v Double) -- | Scatter plots with extra numeric parameter. By default the extra -- parameter is the scale of the marker but this can be changed. -- -- @ -- 'bubblePlot' :: [('Double', ('Double', 'Double'))] -> 'State' ('Plot' ('BubbleOptions' v) b) () -> 'State' ('Axis' b 'V2' 'Double') () -- 'bubblePlot' :: [('Double', 'V2' 'Double')] -> 'State' ('Plot' ('BubbleOptions' v) b) () -> 'State' ('Axis' b 'V2' 'Double') () -- 'bubblePlot' :: [('Double', 'P2' 'Double')] -> 'State' ('Plot' ('BubbleOptions' v) b) () -> 'State' ('Axis' b 'V2' 'Double') () -- @ -- -- === __Example__ -- -- <<diagrams/src_Plots_Types_Scatter_bubbleExample.svg#diagram=bubbleExample&height=350>> -- -- > import Plots -- > myweights = [2, 1.3, 1.8, 0.7] -- > mydata7 = zip myweights [(1,3), (2,5.5), (3.2, 6), (3.5, 6.1)] -- > mydata8 = mydata7 & each._2._2 *~ 0.5 & each._1 *~ 0.5 -- > mydata9 = [(1, V2 1.2 2.7), (3, V2 2 5.1), (0.9, V2 3.2 2.6), (2, V2 3.5 5)] -- -- > bubbleAxis :: Axis B V2 Double -- > bubbleAxis = r2Axis &~ do -- > bubblePlot mydata7 $ key "data 7" -- > bubblePlot mydata8 $ key "data 8" -- > bubblePlot mydata9 $ key "data 9" -- -- > bubbleExample = renderAxis bubbleAxis bubblePlot :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v), F.Foldable f) => f (Double, p) -- ^ fold over points with a size -> State (Plot (BubbleOptions v)) () -- ^ changes to the options -> m () -- ^ add plot to 'Axis' bubblePlot xs s = gscatterPlot (xs ^.. folded . mapping unpointLike) snd $ do bubbleTransform .= scaling s -- | Simple version of 'bubblePlot' without any changes to the 'Plot'. -- -- @ -- 'bubblePlot'' :: [('Double', ('Double', 'Double'))] -> 'State' ('Axis' b 'V2' 'Double') () -- 'bubblePlot'' :: [('Double', 'V2' 'Double')] -> 'State' ('Axis' b 'V2' 'Double') () -- @ -- bubblePlot' :: (v ~ BaseSpace c, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v), F.Foldable f) => f (Double, p) -- ^ fold over points with a size -> m () -- ^ add plot to 'Axis' bubblePlot' xs = bubblePlot xs (return ()) -- | Version of 'bubblePlot' using a 'Fold' over the data. bubblePlotOf :: (BaseSpace c ~ v, PointLike v Double p, Plotable (ScatterPlot v), MonadState (Axis c) m) => Fold s (Double,p) -- ^ fold over the data -> s -- ^ data -> State (Plot (BubbleOptions v)) () -- ^ changes to the options -> m () -- ^ add plot to 'Axis' bubblePlotOf f s = bubblePlot (toListOf f s) -- | Version of 'bubblePlot' using a 'Fold' over the data without any -- changes to the 'BubbleOptions'. bubblePlotOf' :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Plotable (ScatterPlot v)) => Fold s (Double,p) -- ^ fold over the data -> s -- ^ data -> State (Plot (BubbleOptions v)) () -- ^ changes to the options -> m () -- ^ add plot to 'Axis' bubblePlotOf' f s = bubblePlot (toListOf f s) -- Bubble scatter lenses ----------------------------------------------- -- | LensLike onto into a 'ScatterOptions' made up of a scaler @n@, and -- a point, @'Point' v@ -- -- @ -- 'bubbleOptions' :: 'Lens'' ('Plot' ('BubbleOptions' v) v) ('BubbleOptions' v) -- @ bubbleOptions :: (InSpace v Double a, HasScatterOptions f a (Double, Point v Double)) => LensLike' f a (BubbleOptions v) bubbleOptions = gscatterOptions -- | Setter over the transform function for a 'bubblePlot'. Default is 'scale'. -- -- @ -- 'bubbleOptions' :: 'Setter'' ('Plot' ('BubbleOptions' v) v) (n -> 'Transformation' v) -- @ -- -- Note that this is the less general version of @'bubblePlot' . -- 'scatterTransform'@, which would give a 'LensLike' onto @(n, -- 'Point' v) -> 'Transformation' v@. -- bubbleTransform :: (InSpace v Double a, HasScatterOptions f a (Double, Point v Double), Settable f) => LensLike' f a (Double -> Transformation v Double) bubbleTransform = bubbleOptions . scatterTransform . sets nOnly where nOnly f g (n,p) = f (\n' -> g (n', p)) n -- | Setter over the style function for a 'bubblePlot'. Default is 'mempty'. -- -- @ -- 'bubbleStyle' :: 'Setter'' ('Plot' ('BubbleOptions' v) v) (n -> 'Style' v) -- @ -- -- Note that this is the less general version of @'bubblePlot' . -- 'scatterTransform'@, which would give a 'LensLike' onto @(n, -- 'Point' v) -> 'Style' v@. -- bubbleStyle :: (InSpace v Double a, Settable f, HasScatterOptions f a (Double, Point v Double)) => LensLike' f a (Double -> Style v Double) bubbleStyle = bubbleOptions . scatterStyle . sets nOnly where nOnly f g (n,p) = f (\n' -> g (n', p)) n ------------------------------------------------------------------------ -- General scatter plot ------------------------------------------------------------------------ -- | A general scatter plot allow using any data type @d@ to determine -- the 'scatterTransform' and 'scatterStyle'. gscatterPlot :: (BaseSpace c ~ v, PointLike v Double p, MonadState (Axis c) m, Typeable d, Plotable (ScatterPlot v), F.Foldable f) => f d -- ^ data -> (d -> p) -- ^ extract point from data -> State (Plot (ScatterOptions v d)) () -- ^ options for plot -> m () -- ^ add plot to 'Axis' gscatterPlot xs pf s = addPlot $ over rawPlot ScatterPlot p1 where p1 = execState s p0 p0 = mkPlot $ mkScatterOptions xs (view unpointLike . pf) -- | Helper to traverse over a general scatter plot where the type of d -- is not infered. gscatterOptionsFor :: (InSpace v Double a, HasScatterOptions f a d) => proxy d -> LensLike' f a (ScatterOptions v d) gscatterOptionsFor _ = gscatterOptions