Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New workflow again, Typesafe data source implementation #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ Experimental bindings for generating Bokeh Graphs in Haskell. Project originally

## Roadmap

1. *Safe* helper functions for inserting lines, possibly monoidal
1. ~~*Safe* helper functions for inserting lines, possibly monoidal~~

2. More models, particularly glyphs

3. More helper functions for those models

4. Better file generation, plot composition

5. Performace optimizations, type-safety improvements
5. Performace/Space efficiency optimizations, type-safety improvements.

6. As a subproject of the above, implementing the Python implementations of DataSource and DataSpec in a typesafe manner.
1 change: 1 addition & 0 deletions bokeHS.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ library

other-modules: Graphics.BokeHS.Serialize
Graphics.BokeHS.Models
Graphics.BokeHS.CDS
Graphics.BokeHS.Helpers
Graphics.BokeHS.Prim
Graphics.BokeHS.GlyphConfig
Expand Down
80 changes: 50 additions & 30 deletions example.hs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}

import Graphics.BokeHS
import Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy as BS
import Control.Monad
import System.Process
import Data.Scientific
import GHC.TypeLits

myPlot :: Plot
myPlot = plt
$> addLinearAxis BBelow
|> addLinearAxis BLeft
|> addLine myData2 fst snd def
%> addLinearAxis BBelow
%> addLinearAxis BLeft
%> addLine myData2 (Key :: Key "x") (Key :: Key "y") def
{ lineColor = red
, lineAlpha = 0.6
, lineWidth = 6
, lineCap = Rounded
}
|> addLine myData fst snd def
%> addLine myData (Key :: Key "x") (Key :: Key "y2") def
{ lineColor = green
, lineDash = DotDash
, lineWidth = 3
Expand All @@ -33,32 +39,14 @@ main = do
BS.writeFile "sample.html" plotHTML
void $ system "firefox --new-window sample.html"

myData :: [(BNum, BNum)]
myData = Prelude.zip xcols ycols
where
xcols = [-0.5,
1.8333333333333335,
4.166666666666667,
6.5,
8.833333333333334,
11.166666666666668,
13.5,
15.833333333333336,
18.166666666666668,
20.5]
ycols = [2.75,
3.916666666666667,
5.083333333333334,
6.25,
10.416666666666667,
2.583333333333334,
7.75,
5.916666666666668,
19.083333333333334,
13.25]
-- Automatic deriving for record types

data CustomData = CD
{ x :: Scientific
, y :: Scientific }

myData2 :: [(BNum, BNum)]
myData2 = Prelude.zip xcols ycols
myData2 :: [CustomData]
myData2 = zipWith CD xcols ycols
where
xcols = [-0.5,
1.8333333333333335,
Expand All @@ -80,3 +68,35 @@ myData2 = Prelude.zip xcols ycols
11.916666666666668,
13.083333333333334,
14.25]

-- Or define your own:

instance HasColumn (Scientific, Double) "x" Scientific where
getValue _ (x, y) = x

instance HasColumn (Scientific, Double) "y2" Scientific where
getValue _ (x, y) = fromFloatDigits y

myData :: [(Scientific, Double)]
myData = zip xcols ycols
where
xcols = [-0.5,
1.8333333333333335,
4.166666666666667,
6.5,
8.833333333333334,
11.166666666666668,
13.5,
15.833333333333336,
18.166666666666668,
20.5]
ycols = [2.75,
3.916666666666667,
5.083333333333334,
6.25,
10.416666666666667,
2.583333333333334,
7.75,
5.916666666666668,
19.083333333333334,
13.25]
2 changes: 1 addition & 1 deletion sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</div>

<script type="application/json" id="container_obj">
{"top_obj":{"roots":{"root_ids":["plot_id"],"references":[{"attributes":{"background_fill_color":{"value":"#ffffff"},"x_range":{"id":"3","type":"Range1d"},"left":[{"id":"21","type":"LinearAxis"}],"y_scale":{"id":"6","type":"LinearScale"},"y_range":{"id":"4","type":"Range1d"},"above":[],"plot_height":1000,"x_scale":{"id":"5","type":"LinearScale"},"right":[],"title":{"id":"1","type":"Title"},"below":[{"id":"24","type":"LinearAxis"}],"renderers":[{"id":"12","type":"GlyphRenderer"},{"id":"18","type":"GlyphRenderer"},{"id":"21","type":"LinearAxis"},{"id":"24","type":"LinearAxis"}],"plot_width":1000,"toolbar":{"id":"2","type":"Toolbar"}},"id":"plot_id","type":"Plot"},{"attributes":{"plot":{"id":"plot_id","type":"Plot"},"ticker":{"id":"23","type":"BasicTicker"},"formatter":{"id":"22","type":"BasicTickFormatter"}},"id":"24","type":"LinearAxis"},{"attributes":{},"id":"23","type":"BasicTicker"},{"attributes":{},"id":"22","type":"BasicTickFormatter"},{"attributes":{"plot":{"id":"plot_id","type":"Plot"},"ticker":{"id":"20","type":"BasicTicker"},"formatter":{"id":"19","type":"BasicTickFormatter"}},"id":"21","type":"LinearAxis"},{"attributes":{},"id":"20","type":"BasicTicker"},{"attributes":{},"id":"19","type":"BasicTickFormatter"},{"attributes":{"glyph":{"id":"16","type":"Line"},"data_source":{"id":"15","type":"ColumnDataSource"},"muted_glyph":null,"hover_glyph":null,"view":{"id":"17","type":"CDSView"}},"id":"18","type":"GlyphRenderer"},{"attributes":{"source":{"id":"15","type":"ColumnDataSource"}},"id":"17","type":"CDSView"},{"attributes":{"line_dash":[],"line_width":6,"line_dash_offset":0,"line_alpha":0.6,"line_join":"miter","line_color":"#ff0000","x":{"field":"x"},"line_cap":"round","y":{"field":"y"}},"id":"16","type":"Line"},{"attributes":{"data":{"x":[-0.5,1.8333333333333335,4.166666666666667,6.5,8.833333333333334,11.166666666666668,13.5,15.833333333333336,18.166666666666668,20.5],"y":[3.75,4.916666666666667,6.083333333333334,7.25,8.416666666666667,9.583333333333334,10.75,11.916666666666668,13.083333333333334,14.25]},"selected":{"id":"13","type":"Selection"},"selection_policy":{"id":"14","type":"UnionRenderers"},"callback":null},"id":"15","type":"ColumnDataSource"},{"attributes":{},"id":"14","type":"UnionRenderers"},{"attributes":{},"id":"13","type":"Selection"},{"attributes":{"glyph":{"id":"10","type":"Line"},"data_source":{"id":"9","type":"ColumnDataSource"},"muted_glyph":null,"hover_glyph":null,"view":{"id":"11","type":"CDSView"}},"id":"12","type":"GlyphRenderer"},{"attributes":{"source":{"id":"9","type":"ColumnDataSource"}},"id":"11","type":"CDSView"},{"attributes":{"line_dash":[2,4,6,4],"line_width":3,"line_dash_offset":0,"line_alpha":1,"line_join":"miter","line_color":"#008000","x":{"field":"x"},"line_cap":"butt","y":{"field":"y"}},"id":"10","type":"Line"},{"attributes":{"data":{"x":[-0.5,1.8333333333333335,4.166666666666667,6.5,8.833333333333334,11.166666666666668,13.5,15.833333333333336,18.166666666666668,20.5],"y":[2.75,3.916666666666667,5.083333333333334,6.25,10.416666666666667,2.583333333333334,7.75,5.916666666666668,19.083333333333334,13.25]},"selected":{"id":"7","type":"Selection"},"selection_policy":{"id":"8","type":"UnionRenderers"},"callback":null},"id":"9","type":"ColumnDataSource"},{"attributes":{},"id":"8","type":"UnionRenderers"},{"attributes":{},"id":"7","type":"Selection"},{"attributes":{},"id":"6","type":"LinearScale"},{"attributes":{},"id":"5","type":"LinearScale"},{"attributes":{"start":-0.5,"end":22,"callback":null},"id":"4","type":"Range1d"},{"attributes":{"start":-0.5,"end":22,"callback":null},"id":"3","type":"Range1d"},{"attributes":{"active_inspect":"auto","active_scroll":"auto","active_tap":"auto","active_drag":"auto"},"id":"2","type":"Toolbar"},{"attributes":{"text":"Sample BokeHS plot","plot":null},"id":"1","type":"Title"}]},"version":"0.12.16","title":"BokeHS Application"}}
{"top_obj":{"roots":{"root_ids":["plot_id"],"references":[{"attributes":{"background_fill_color":{"value":"#ffffff"},"x_range":{"id":"3","type":"Range1d"},"left":[{"id":"21","type":"LinearAxis"}],"y_scale":{"id":"6","type":"LinearScale"},"y_range":{"id":"4","type":"Range1d"},"above":[],"plot_height":1000,"x_scale":{"id":"5","type":"LinearScale"},"right":[],"title":{"id":"1","type":"Title"},"below":[{"id":"24","type":"LinearAxis"}],"renderers":[{"id":"12","type":"GlyphRenderer"},{"id":"18","type":"GlyphRenderer"},{"id":"21","type":"LinearAxis"},{"id":"24","type":"LinearAxis"}],"plot_width":1000,"toolbar":{"id":"2","type":"Toolbar"}},"id":"plot_id","type":"Plot"},{"attributes":{"plot":{"id":"plot_id","type":"Plot"},"ticker":{"id":"23","type":"BasicTicker"},"formatter":{"id":"22","type":"BasicTickFormatter"}},"id":"24","type":"LinearAxis"},{"attributes":{},"id":"23","type":"BasicTicker"},{"attributes":{},"id":"22","type":"BasicTickFormatter"},{"attributes":{"plot":{"id":"plot_id","type":"Plot"},"ticker":{"id":"20","type":"BasicTicker"},"formatter":{"id":"19","type":"BasicTickFormatter"}},"id":"21","type":"LinearAxis"},{"attributes":{},"id":"20","type":"BasicTicker"},{"attributes":{},"id":"19","type":"BasicTickFormatter"},{"attributes":{"glyph":{"id":"16","type":"Line"},"data_source":{"id":"15","type":"ColumnDataSource"},"muted_glyph":null,"hover_glyph":null,"view":{"id":"17","type":"CDSView"}},"id":"18","type":"GlyphRenderer"},{"attributes":{"source":{"id":"15","type":"ColumnDataSource"}},"id":"17","type":"CDSView"},{"attributes":{"line_dash":[],"line_width":6,"line_dash_offset":0,"line_alpha":0.6,"line_join":"miter","line_color":"#ff0000","x":{"field":"x"},"line_cap":"round","y":{"field":"y"}},"id":"16","type":"Line"},{"attributes":{"data":{"x":[-0.5,1.8333333333333335,4.166666666666667,6.5,8.833333333333334,11.166666666666668,13.5,15.833333333333336,18.166666666666668,20.5],"y":[3.75,4.916666666666667,6.083333333333334,7.25,8.416666666666667,9.583333333333334,10.75,11.916666666666668,13.083333333333334,14.25]},"selected":{"id":"13","type":"Selection"},"selection_policy":{"id":"14","type":"UnionRenderers"},"callback":null},"id":"15","type":"ColumnDataSource"},{"attributes":{},"id":"14","type":"UnionRenderers"},{"attributes":{},"id":"13","type":"Selection"},{"attributes":{"glyph":{"id":"10","type":"Line"},"data_source":{"id":"9","type":"ColumnDataSource"},"muted_glyph":null,"hover_glyph":null,"view":{"id":"11","type":"CDSView"}},"id":"12","type":"GlyphRenderer"},{"attributes":{"source":{"id":"9","type":"ColumnDataSource"}},"id":"11","type":"CDSView"},{"attributes":{"line_dash":[2,4,6,4],"line_width":3,"line_dash_offset":0,"line_alpha":1,"line_join":"miter","line_color":"#008000","x":{"field":"x"},"line_cap":"butt","y":{"field":"y2"}},"id":"10","type":"Line"},{"attributes":{"data":{"x":[-0.5,1.8333333333333335,4.166666666666667,6.5,8.833333333333334,11.166666666666668,13.5,15.833333333333336,18.166666666666668,20.5],"y2":[2.75,3.916666666666667,5.083333333333334,6.25,10.416666666666667,2.583333333333334,7.75,5.916666666666668,19.083333333333334,13.25]},"selected":{"id":"7","type":"Selection"},"selection_policy":{"id":"8","type":"UnionRenderers"},"callback":null},"id":"9","type":"ColumnDataSource"},{"attributes":{},"id":"8","type":"UnionRenderers"},{"attributes":{},"id":"7","type":"Selection"},{"attributes":{},"id":"6","type":"LinearScale"},{"attributes":{},"id":"5","type":"LinearScale"},{"attributes":{"start":-0.5,"end":22,"callback":null},"id":"4","type":"Range1d"},{"attributes":{"start":-0.5,"end":22,"callback":null},"id":"3","type":"Range1d"},{"attributes":{"active_inspect":"auto","active_scroll":"auto","active_tap":"auto","active_drag":"auto"},"id":"2","type":"Toolbar"},{"attributes":{"text":"Sample BokeHS plot","plot":null},"id":"1","type":"Title"}]},"version":"0.12.16","title":"BokeHS Application"}}
</script>
<script type="text/javascript">
(function() {
Expand Down
5 changes: 4 additions & 1 deletion src/Graphics/BokeHS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ module Graphics.BokeHS(

--Serialize
emitPlotHTML,


module Graphics.BokeHS.CDS,
module Graphics.BokeHS.GlyphConfig,
module Graphics.BokeHS.Prim,

module Data.Colour.Names,


Default(..)
) where

import Graphics.BokeHS.CDS
import Graphics.BokeHS.Helpers
import Graphics.BokeHS.Prim
import Graphics.BokeHS.Models
Expand Down
42 changes: 42 additions & 0 deletions src/Graphics/BokeHS/CDS.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Graphics.BokeHS.CDS where

import Data.Text
import GHC.TypeLits
import GHC.Records
import Data.Aeson

import Graphics.BokeHS.Prim

data Key (name :: Symbol) where
Key :: Key name

fieldName :: KnownSymbol n => Key n -> Field
fieldName = Field . pack . symbolVal

class (ToJSON value, KnownSymbol name) => HasColumn row name value | row name -> value where
getValue :: Key name -> row -> value
default getValue :: (HasField name row value) => Key name -> row -> value
getValue _ = getField @name

instance {-# OVERLAPPABLE #-} (ToJSON v, KnownSymbol n, HasField n r v)
=> HasColumn r n v

data Name r where
Name :: (ToJSON v, KnownSymbol n, HasColumn r n v) => Key n -> Name r

toColumn :: forall r. [r] -> Name r -> (Field, Value)
toColumn xs (Name k) = (fieldName k, toJSON (toJSON . getValue k <$> xs))

allTheStuff :: [r] -> [Name r] -> [(Field, Value)]
allTheStuff rows cols = toColumn rows <$> cols
1 change: 1 addition & 0 deletions src/Graphics/BokeHS/GlyphConfig.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Configuration types for every Glyph (everything that's not a @Spec@)
Expand Down
36 changes: 22 additions & 14 deletions src/Graphics/BokeHS/Helpers.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}

module Graphics.BokeHS.Helpers(
(|>),
($>),
(%>),
addLine,
addLinearAxis,
defaultToolbar,
Expand All @@ -12,33 +14,39 @@ module Graphics.BokeHS.Helpers(
import Graphics.BokeHS.Models
import Graphics.BokeHS.Prim
import Graphics.BokeHS.GlyphConfig
import Graphics.BokeHS.CDS

import Data.Foldable
import Data.Colour.Names
import GHC.TypeLits

(|>) :: (a -> b) -> (b -> c) -> a -> c
(|>) = flip (.)

($>) :: a -> (a -> b) -> b
($>) = flip ($)
infixr 0 $>
-- | Flipped ($). Same as (&) from @Control.Lens@
(%>) :: a -> (a -> b) -> b
x %> f = f x
{-# INLINE (%>) #-}
infixl 1 %>

--sample glyph adder function
--r is a row type from which the data can be extracted
addLine :: Foldable t => t r -> (r -> BNum) -> (r -> BNum) -> LineConfig -> Plot -> Plot
addLine points getx gety config plt@Plot{renderers = rends} =
addLine ::
( Foldable t
, HasColumn r f1 BNum
, HasColumn r f2 BNum
, KnownSymbol f1
, KnownSymbol f2
)
=> t r -> Key f1 -> Key f2 -> LineConfig -> Plot -> Plot
addLine points k1 k2 config plt@Plot{renderers = rends} =
plt{renderers = lrend : rends}
where
lrend = GRend GlyphRenderer { hoverGlyph = Nothing, mutedGlyph = Nothing,
dataSource = src, glyph = lin, vie = CDSView}
lin = Line config (Field "x") (Field "y")
lin = Line config k1 k2
src = CDS {
cols = [(Field "x", xs), (Field "y", ys)],
rows = toList points,
selected = Selection,
selectionPolicy = UnionRenderers
}
xs = getx <$> foldr' (:) [] points
ys = gety <$> foldr' (:) [] points

--sample layout adder function
addLinearAxis :: Direction -> Plot -> Plot
Expand Down
62 changes: 33 additions & 29 deletions src/Graphics/BokeHS/Models.hs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE AutoDeriveTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}

module Graphics.BokeHS.Models where

import Data.Text (Text)
import GHC.Generics
import GHC.TypeLits
import Data.Aeson
import Data.String (IsString)

import Graphics.BokeHS.Prim
import Graphics.BokeHS.GlyphConfig
import Graphics.BokeHS.CDS

newtype Placeholder = Placeholder Value deriving (Show, Generic, Eq)
instance ToJSON Placeholder
Expand All @@ -28,61 +32,61 @@ data Plot = Plot {
yRange :: Range,
xScale :: Scale,
yScale :: Scale
} deriving Show
}

newtype Title = Title Text deriving (Show, IsString)
newtype Title = Title Text deriving IsString

data Renderer = ARend Direction Axis | GRend GlyphRenderer deriving Show
data Renderer
= ARend Direction Axis
| forall r. GRend (GlyphRenderer r)

data Axis = LinearAxis {
formatter :: Formatter
, ticker :: Ticker
} deriving Show
}

data DataSource = forall v. ToJSON v => CDS {
cols :: [(Field, [v])] -- FIXME use `Frames` instead
data DataSource r = CDS {
rows :: [r] -- FIXME use `Frames` instead
, selected :: Selection
, selectionPolicy :: SelectionPolicy
}
}

instance Show DataSource where
show CDS{} = "<CDS>"

data GlyphRenderer = GlyphRenderer {
data GlyphRenderer r = GlyphRenderer {
hoverGlyph :: Maybe Placeholder
, mutedGlyph :: Maybe Placeholder
, dataSource :: DataSource
, glyph :: Glyph
, vie :: View } deriving Show
, dataSource :: DataSource r
, glyph :: Glyph r
, vie :: View }

data View = CDSView | Views_ deriving Show
data ViewWrapper = VWrap Value View
data View = CDSView | Views_
data ViewWrapper = VWrap Value View

data Scale = LinearScale deriving Show
data Scale = LinearScale

data Ticker = BasicTicker deriving Show
data Ticker = BasicTicker

data Formatter = BasicTickFormatter deriving Show
data Formatter = BasicTickFormatter

data Range = Range1d {
start :: BNum,
end :: BNum } deriving Show
end :: BNum }

data SelectionPolicy = UnionRenderers | Policies_

data SelectionPolicy = UnionRenderers | Policies_ deriving Show
data Selection = Selection | Sels_

data Selection = Selection | Sels_ deriving Show
data Glyph r where
Line :: (KnownSymbol n0, HasColumn r n0 BNum, KnownSymbol n1, HasColumn r n1 BNum) =>
LineConfig -> Key n0 -> Key n1 -> Glyph r

data Glyph = Line {
lineConfig :: LineConfig
, xfield :: Field
, yfield :: Field }
deriving Show
getNames :: Glyph r -> [Name r]
getNames (Line _ x y) = [Name x, Name y]

data Auto a = Auto | NotAuto a deriving Show
data Auto a = Auto | NotAuto a

--active_drag, active_inspect, active_scroll, active_tap
data Toolbar = Toolbar {
activeDrag :: Auto Placeholder
, activeInspect :: Auto Placeholder
, activeScroll :: Auto Placeholder
, activeTap :: Auto Placeholder } deriving Show
, activeTap :: Auto Placeholder }
Loading