├── .purs-repl
├── src
├── D3
│ ├── Base
│ │ ├── Layout
│ │ │ ├── Trees.js
│ │ │ ├── Trees.purs
│ │ │ └── Simulation.purs
│ │ ├── Transition.purs
│ │ ├── Foreign.js
│ │ ├── Foreign.purs
│ │ ├── Element.purs
│ │ ├── Attributes.purs
│ │ └── Selection.purs
│ ├── Base.purs
│ └── Interpreter
│ │ ├── Interpreter.purs
│ │ ├── Types.purs
│ │ ├── Attributes.purs
│ │ ├── Foreign.purs
│ │ ├── Selection.purs
│ │ ├── Foreign.js
│ │ └── Layouts
│ │ └── Simulation.purs
├── examples
│ ├── WrappedJS
│ │ ├── Minimal.js
│ │ ├── Minimal.purs
│ │ ├── Force.purs
│ │ ├── Tree.purs
│ │ ├── oldGUP.js
│ │ ├── newGUP.js
│ │ ├── Force.js
│ │ └── Tree.js
│ └── NewSyntax
│ │ ├── Force.js
│ │ ├── Matrix.purs
│ │ ├── Tree.js
│ │ ├── GUP.purs
│ │ ├── Force.purs
│ │ └── Tree.purs
└── Main.purs
├── .gitignore
├── test
└── Main.purs
├── package.json
├── spago.dhall
├── design notes
├── nested_joins.md
├── grammar.md
└── IndexedExample.purs
├── dist
├── index.html
├── flare-2.json
└── miserables.json
├── LICENSE
├── README.md
└── packages.dhall
/.purs-repl:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
--------------------------------------------------------------------------------
/src/D3/Base/Layout/Trees.js:
--------------------------------------------------------------------------------
1 | exports.d3LinkRadial = angleFn => radiusFn => d3.linkRadial().angle(angleFn).radius(radiusFn)
2 |
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Minimal.js:
--------------------------------------------------------------------------------
1 |
2 | exports.chart = function (data) {
3 | d3.selectAll('div#minimal').append('svg')
4 | }
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Minimal.purs:
--------------------------------------------------------------------------------
1 | module D3.Example.Minimal (chart) where
2 |
3 | import Prelude
4 |
5 | foreign import chart :: Int -> Unit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | .psci_modules
3 | bower_components
4 | node_modules
5 |
6 | # Generated files
7 | .psci
8 | output
9 | /.spago
10 | .DS_Store
11 | /dist/bundle.js
12 | /.psc-ide-port
13 |
--------------------------------------------------------------------------------
/test/Main.purs:
--------------------------------------------------------------------------------
1 | module Test.Main where
2 |
3 | import Prelude
4 |
5 | import Effect (Effect)
6 | import Effect.Class.Console (log)
7 |
8 | main :: Effect Unit
9 | main = do
10 | log "🍝"
11 | log "You should add some tests."
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "http-server": "^0.12.3"
4 | },
5 | "scripts": {
6 | "bundle": "spago bundle-app -t dist/bundle.js",
7 | "serve": "http-server dist -a localhost --port 1234 -o",
8 | "example": "npm run bundle; npm run serve"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Force.purs:
--------------------------------------------------------------------------------
1 | module D3.Example.Force where
2 |
3 | import Prelude
4 |
5 | import Data.Tuple (Tuple(..))
6 |
7 | foreign import chartFFI :: String -> Int -> Int -> String -> Unit
8 |
9 | chart :: Tuple Int Int -> String -> Unit
10 | chart (Tuple width height) fileContents = chartFFI "div#force" width height fileContents
--------------------------------------------------------------------------------
/src/examples/NewSyntax/Force.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | exports.readJSONJS = filecontents => decodeFile(filecontents)
4 |
5 | const decodeFile = function (filecontents) {
6 | const json = JSON.parse(filecontents)
7 | const links = json.links.map(d => Object.create(d))
8 | const nodes = json.nodes.map(d => Object.create(d))
9 | return { links: links, nodes: nodes }
10 | }
--------------------------------------------------------------------------------
/src/D3/Base.purs:
--------------------------------------------------------------------------------
1 | module D3.Base (
2 | module D3.Base.Attributes
3 | , module D3.Base.Foreign
4 | , module D3.Base.Layout.Simulation
5 | , module D3.Base.Element
6 | , module D3.Base.Selection
7 | ) where
8 |
9 | import D3.Base.Attributes
10 | import D3.Base.Foreign
11 | import D3.Base.Selection
12 | import D3.Base.Element
13 | import D3.Base.Layout.Simulation
14 |
--------------------------------------------------------------------------------
/src/D3/Base/Transition.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Transition where
2 |
3 | import D3.Base.Attributes (Attr)
4 | import D3.Base.Selection (Selection(..))
5 |
6 | import Data.Maybe (Maybe(..))
7 |
8 | -- transition :: forall model. Number -> Array Attr -> Selection model
9 | -- transition duration attributes =
10 | -- Transition { label: Nothing, duration, attributes }
11 |
12 |
--------------------------------------------------------------------------------
/src/D3/Base/Foreign.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | // COLOR & SCALE functions
4 | // gross simplification here, scales can take ranges and allsorts
5 | // we just want to be able to pass d3.schemeCategory10 back in from Purescript to prove the idea tho
6 | const d3SchemeCategory10 = d3.scaleOrdinal(d3.schemeCategory10)
7 | exports.d3SchemeCategory10JS = value => d3SchemeCategory10(value)
8 |
--------------------------------------------------------------------------------
/src/D3/Base/Layout/Trees.purs:
--------------------------------------------------------------------------------
1 | module D3.Layout.Trees (radialLink) where
2 |
3 | import Prelude
4 |
5 | import D3.Base.Attributes (Attr(..))
6 | import D3.Base.Foreign (Datum)
7 |
8 | foreign import d3LinkRadial :: (Datum -> Number) -> (Datum -> Number) -> (Datum -> String)
9 | radialLink :: (Datum -> Number) -> (Datum -> Number) -> Attr
10 | radialLink angleFn radius_Fn = StringAttr "d" $ d3LinkRadial angleFn radius_Fn
11 |
12 |
--------------------------------------------------------------------------------
/src/D3/Interpreter/Interpreter.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter (
2 | module D3.Interpreter.Types
3 | , module D3.Interpreter.Layouts.Simulation
4 | , module D3.Interpreter.Selection
5 | ) where
6 |
7 | import D3.Interpreter.Types (D3, D3State(..), initialState)
8 | import D3.Interpreter.Layouts.Simulation (addAttrFnToTick, getNativeSelection, interpretDrag, interpretForce, interpretSimulation, interpretTickMap, startSimulation, stopSimulation)
9 | import D3.Interpreter.Selection
10 |
11 |
--------------------------------------------------------------------------------
/spago.dhall:
--------------------------------------------------------------------------------
1 | {-
2 | Welcome to a Spago project!
3 | You can edit this file as you like.
4 | -}
5 | { name = "my-project"
6 | , dependencies =
7 | [ "affjax"
8 | , "arrays"
9 | , "console"
10 | , "debug"
11 | , "effect"
12 | , "exists"
13 | , "foldable-traversable"
14 | , "indexed-monad"
15 | , "lists"
16 | , "nullable"
17 | , "psci-support"
18 | , "web-events"
19 | , "web-html"
20 | ]
21 | , packages = ./packages.dhall
22 | , sources = [ "src/**/*.purs", "test/**/*.purs" ]
23 | }
24 |
--------------------------------------------------------------------------------
/design notes/nested_joins.md:
--------------------------------------------------------------------------------
1 | # a useful example to consider
2 |
3 | ```javascript
4 | const matrix = [
5 | [11975, 5871, 8916, 2868],
6 | [ 1951, 10048, 2060, 6171],
7 | [ 8010, 16145, 8090, 8045],
8 | [ 1013, 990, 940, 6907]
9 | ];
10 |
11 | d3.select("body")
12 | .append("table")
13 | .selectAll("tr")
14 | .data(matrix)
15 | .join("tr")
16 | .selectAll("td")
17 | .data(d => d)
18 | .join("td")
19 | .text(d => d);
20 | ```
21 |
22 | ```javascript
23 | const div = d3.select("body")
24 | .selectAll("div")
25 | .data([4, 8, 15, 16, 23, 42])
26 | .enter().append("div")
27 | .text(d => d);
28 | ```
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Tree.purs:
--------------------------------------------------------------------------------
1 | module D3.Example.Tree where
2 |
3 | import Prelude
4 |
5 | import Affjax (Error)
6 | import Data.Either (Either(..))
7 | import Data.Tuple (Tuple(..))
8 |
9 | foreign import chartFFI :: String -> Number -> Number -> String -> Unit
10 |
11 | chart :: forall r. Tuple Number Number -> Either Error { body ∷ String | r } -> Unit
12 | chart (Tuple width height) (Right { body } ) = chartFFI "div#tree" width height body
13 | chart _ (Left error) = unit
14 |
15 | {- AS IS version of script
16 | process data using d3.hierarchy (NB non-PureScript data structure, no children field on )
17 |
18 | -}
--------------------------------------------------------------------------------
/src/D3/Interpreter/Types.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter.Types where
2 |
3 | import Control.Monad.State (StateT)
4 | import D3.Base (NativeSelection)
5 | import Data.Map (Map, empty)
6 | import Effect (Effect)
7 |
8 | -- we model the scope of the JavaScript "script" like this (see README for rationale)
9 | data D3State model = Context model (Map String NativeSelection)
10 |
11 | initialState :: forall model. model -> D3State model
12 | initialState model = Context model empty
13 |
14 | -- TODO alternatively, modify state with new model
15 | updateState :: forall model. model -> D3State model -> D3State model
16 | updateState model (Context _ scope) = Context model scope
17 |
18 | type D3 model t = StateT(D3State model) Effect t
19 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 | Document
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/examples/WrappedJS/oldGUP.js:
--------------------------------------------------------------------------------
1 | const svg = d3.create("svg")
2 | .attr("width", width)
3 | .attr("height", 33)
4 | .attr("viewBox", `0 -20 ${width} 33`);
5 |
6 | while (true) {
7 | const t = svg.transition()
8 | .duration(750);
9 |
10 | svg.selectAll("text")
11 | .data(randomLetters(), d => d)
12 | .join(
13 | enter => enter.append("text")
14 | .attr("fill", "green")
15 | .attr("x", (d, i) => i * 16)
16 | .attr("y", -30)
17 | .text(d => d)
18 | .call(enter => enter.transition(t)
19 | .attr("y", 0)),
20 | update => update.append("text")
21 | .attr("fill", "black")
22 | .attr("y", 0)
23 | .call(update => update.transition(t)
24 | .attr("x", (d, i) => i * 16)),
25 | exit => exit
26 | .attr("fill", "brown")
27 | .call(exit => exit.transition(t)
28 | .attr("y", 30)
29 | .remove())
30 | );
31 |
32 |
--------------------------------------------------------------------------------
/src/D3/Base/Foreign.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Foreign where
2 |
3 | -- | these foreign types allow us to work with some very funky typing without
4 | -- | adding tonnes of syntactic noise or type complexity
5 | -- | NativeSelection is an opaque type which we keep in order to feed it back to D3
6 | -- | when we look up nameSelection Selections, Transitions, Simulations, whatever
7 | foreign import data NativeSelection :: Type
8 |
9 | -- | The Datum models the (variable / polymorphic) type of the lambdas used in Attr
10 | foreign import data Datum :: Type
11 |
12 | -- | The SubModel is that portion of the Model that we give to a particular Join
13 | foreign import data SubModel :: Type
14 |
15 |
16 | -- TODO move to separate Scales module when more fleshed out
17 | type Scale = Number -> String
18 |
19 | foreign import d3SchemeCategory10JS :: Scale -- not modelling the scale / domain distinction yet
20 |
--------------------------------------------------------------------------------
/src/examples/NewSyntax/Matrix.purs:
--------------------------------------------------------------------------------
1 | module NewSyntax.Matrix where
2 |
3 | import D3.Base
4 |
5 | import Data.Tuple (Tuple(..))
6 | import Prelude hiding (join)
7 | import Unsafe.Coerce (unsafeCoerce)
8 |
9 | type D3DOMelement = Int
10 |
11 | type Model a = Array (Array a)
12 |
13 | matrix :: Model Int
14 | matrix = [
15 | [11975, 5871, 8916, 2868],
16 | [ 1951, 10048, 2060, 6171],
17 | [ 8010, 16145, 8090, 8045],
18 | [ 1013, 990, 940, 6907]
19 | ]
20 | {-
21 | d3.select("body")
22 | .append("table")
23 | .selectAll("tr")
24 | .data(matrix)
25 | .join("tr")
26 | .selectAll("td")
27 | .data(d => d)
28 | .join("td")
29 | .text(d => d);
30 | -}
31 | chartInit :: Tuple Number Number -> Selection (Model Int)
32 | chartInit (Tuple width height) = do
33 | let origin = { x: -width / 2.0, y: -height / 2.0 }
34 |
35 | selectInDOM "div#matrix" [] [
36 | table []
37 | [ join $
38 | Enter Td $ Child $
39 | join $
40 | Enter Tr $ Attrs [ computeText (\d -> unsafeCoerce d) ]
41 | ]
42 | ]
43 |
--------------------------------------------------------------------------------
/src/examples/WrappedJS/newGUP.js:
--------------------------------------------------------------------------------
1 | {
2 | const svg = d3.create("svg")
3 | .attr("width", width)
4 | .attr("height", 33)
5 | .attr("viewBox", `0 -20 ${width} 33`);
6 |
7 | while (true) {
8 | const t = svg.transition()
9 | .duration(750);
10 |
11 | svg.selectAll("text")
12 | .data(randomLetters(), d => d)
13 | .join(
14 | enter => enter.append("text")
15 | .attr("fill", "green")
16 | .attr("x", (d, i) => i * 16)
17 | .attr("y", -30)
18 | .text(d => d)
19 | .call(enter => enter.transition(t)
20 | .attr("y", 0)),
21 | update => update
22 | .attr("fill", "black")
23 | .attr("y", 0)
24 | .call(update => update.transition(t)
25 | .attr("x", (d, i) => i * 16)),
26 | exit => exit
27 | .attr("fill", "brown")
28 | .call(exit => exit.transition(t)
29 | .attr("y", 30)
30 | .remove())
31 | );
32 |
33 | yield svg.node();
34 | await Promises.tick(2500);
35 | }
36 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Andrew Condon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/examples/NewSyntax/Tree.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | exports.readJSONJS = filecontents => JSON.parse(filecontents)
4 |
5 | // foreign import d3HierarchyLinks :: D3Tree -> SubModel
6 | exports.d3HierarchyLinks = tree => tree.links()
7 |
8 | // foreign import d3HierarchyDescendants :: D3Tree -> SubModel
9 | exports.d3HierarchyDescendants = tree => tree.descendants()
10 |
11 | // foreign import d3Hierarchy :: forall a. Tree a -> D3Hierarchical
12 | exports.d3Hierarchy = json => d3.hierarchy(json).sort((a, b) => d3.ascending(a.data.name, b.data.name))
13 |
14 | // foreign import d3Hierarchy :: forall a. D3Hierarchical -> D3Tree
15 | exports.d3InitTree = config => hierarchy => d3.tree().size(config.size).separation(config.separation)(hierarchy)
16 |
17 | // foreign import hasChildren :: Datum -> Boolean
18 | exports.hasChildren = function(d) {
19 | return !d.children
20 | }
21 |
22 | // this REQUIRES that the data have been passed thru the d3.herarchy (and maybe d3.tree?) function
23 | // NB because this is called directly from deep in the bowels of D3 (tree first walk to be precise) it isn't a curried function
24 | exports.radialSeparationJS = (a,b) => (a.parent == b.parent ? 1 : 2) / a.depth
--------------------------------------------------------------------------------
/src/D3/Interpreter/Attributes.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter.Attributes where
2 |
3 | import D3.Interpreter.Foreign
4 |
5 | import D3.Base (Attr(..), NativeSelection)
6 | import Prelude (Unit, pure, ($))
7 | import D3.Interpreter.Types (D3)
8 |
9 | -- interprets and calls D3 directly, as opposed to storing attr associations on JS side,
10 | -- as is needed for, for example tick function
11 | applyAttr :: forall model. NativeSelection -> Attr -> D3 model Unit
12 | applyAttr selection = case _ of
13 | (StaticString attr value) -> pure $ runSimpleAttrJS selection attr (stringToNativeJS value)
14 | (StaticNumber attr value) -> pure $ runSimpleAttrJS selection attr (numberToNativeJS value)
15 | (StaticArrayNumber attr value) -> pure $ runSimpleAttrJS selection attr (arrayNumberToNativeJS value)
16 |
17 | (StringAttr attr fnD) -> pure $ runDatumAttrJS selection attr fnD
18 | (NumberAttr attr fnD) -> pure $ runDatumAttrJS selection attr fnD
19 | (ArrayNumberAttr attr fnD) -> pure $ runDatumAttrJS selection attr fnD
20 |
21 | (StringAttrI attr fnDI) -> pure $ runDatumIndexAttrJS selection attr fnDI
22 | (NumberAttrI attr fnDI) -> pure $ runDatumIndexAttrJS selection attr fnDI
23 | (ArrayNumberAttrI attr fnDI) -> pure $ runDatumIndexAttrJS selection attr fnDI
24 |
25 | (TextAttr fnD) -> pure $ runDatumTextJS selection fnD
26 |
--------------------------------------------------------------------------------
/src/D3/Base/Layout/Simulation.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Layout.Simulation where
2 |
3 | import D3.Base.Attributes (Attr)
4 | import D3.Base.Selection (Label)
5 | import Prelude (Unit)
6 |
7 | import Data.Map (Map)
8 |
9 | -- | a record to initialize / configure simulations
10 | type SimulationConfig = {
11 | alpha :: Number
12 | , alphaTarget :: Number
13 | , alphaMin :: Number
14 | , alphaDecay :: Number
15 | , velocityDecay :: Number
16 | }
17 |
18 | defaultConfigSimulation :: SimulationConfig
19 | defaultConfigSimulation = {
20 | alpha : 1.0
21 | , alphaTarget : 0.0
22 | , alphaMin : 0.0001
23 | , alphaDecay : 0.0228
24 | , velocityDecay: 0.4
25 | }
26 |
27 | -- this is the row that gets added ot your Model's nodes when initialized by D3
28 | type SimulationNodeRow :: forall k. k -> Type
29 | type SimulationNodeRow r = { x :: Number, y :: Number, group :: Number, vx :: Number, vy :: Number, index :: Number }
30 |
31 | -- | Force Layout core types
32 | type ID = Int -- TODO this needs to be polymorphic eventually
33 | type Link = forall r. { id :: ID, source :: ID, target :: ID | r }
34 | type Node = forall r i. { id :: i | r }
35 | type IdFn = Link -> ID
36 | data Force = Force Label ForceType
37 | data ForceType =
38 | ForceMany
39 | | ForceCenter Number Number
40 | -- | ForceLink (Array Link) IdFn
41 | | ForceCollide Number
42 | | ForceX Number
43 | | ForceY Number
44 | | ForceRadial Number Number
45 | | Custom
46 |
47 | type SimulationRecord = {
48 | label :: String
49 | , config :: SimulationConfig
50 | , nodes :: Array Node
51 | , links :: Array Link
52 | , forces :: Array Force
53 | , tick :: Unit -> Unit -- could be Effect Unit
54 | , drag :: Simulation -> Unit -- could be Effect Unit
55 | }
56 |
57 | data Simulation = Simulation SimulationRecord
58 | type TickMap :: forall k. k -> Type
59 | type TickMap model = Map String (Array Attr)
60 | data DragBehavior = DefaultDrag String String -- only one implementation rn and implemented on JS side
61 |
--------------------------------------------------------------------------------
/src/D3/Base/Element.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Element where
2 |
3 | import D3.Base.Attributes (Attr)
4 | import D3.Base.Selection (Element(..), Selection, append, append_)
5 |
6 | -- Group and Div are containers so the underscore versions elide attrs, not children
7 | -- for a deeper consistency
8 | group :: forall model. Array Attr -> Array (Selection model)
9 | -> Selection model
10 | group = append Group
11 |
12 | group_ :: forall model. Array (Selection model)
13 | -> Selection model
14 | group_ = append Group []
15 |
16 | div :: forall model. Array Attr -> Array (Selection model)
17 | -> Selection model
18 | div = append Div
19 |
20 | div_ :: forall model. Array (Selection model)
21 | -> Selection model
22 | div_ = append Div []
23 |
24 |
25 |
26 | svg :: forall model. Array Attr -> Array (Selection model)
27 | -> Selection model
28 | svg = append Svg
29 |
30 | svg_ :: forall model. Array Attr
31 | -> Selection model
32 | svg_ = append_ Svg
33 |
34 | line :: forall model. Array Attr -> Array (Selection model)
35 | -> Selection model
36 | line = append Line
37 |
38 | line_ :: forall model. Array Attr
39 | -> Selection model
40 | line_ = append_ Line
41 |
42 | circle :: forall model. Array Attr -> Array (Selection model)
43 | -> Selection model
44 | circle = append Circle
45 |
46 | circle_ :: forall model. Array Attr
47 | -> Selection model
48 | circle_ = append_ Circle
49 |
50 | path :: forall model. Array Attr -> Array (Selection model)
51 | -> Selection model
52 | path = append Path
53 |
54 | path_ :: forall model. Array Attr
55 | -> Selection model
56 | path_ = append_ Path
57 |
58 | text :: forall model. Array Attr -> Array (Selection model)
59 | -> Selection model
60 | text = append Text
61 |
62 | text_ :: forall model. Array Attr
63 | -> Selection model
64 | text_ = append_ Text
65 |
66 |
67 |
68 | table :: forall model. Array Attr -> Array (Selection model)
69 | -> Selection model
70 | table = append Table
71 |
72 | table_ :: forall model. Array Attr
73 | -> Selection model
74 | table_ = append_ Table
75 |
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Force.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.chartFFI = element => width => height => data => {
4 | const json = JSON.parse(data)
5 | const links = json.links.map(d => Object.create(d));
6 | const nodes = json.nodes.map(d => Object.create(d));
7 |
8 | const simulation = d3.forceSimulation(nodes)
9 | .force("link", d3.forceLink(links).id(d => d.id))
10 | .force("charge", d3.forceManyBody())
11 | .force("center", d3.forceCenter(width / 2, height / 2));
12 |
13 | const svg = d3.selectAll(element).append("svg")
14 | .attr("viewBox", [0, 0, width, height]);
15 |
16 | const link = svg.append("g")
17 | .attr("stroke", "#999")
18 | .attr("stroke-opacity", 0.6)
19 | .selectAll("line")
20 | .data(links)
21 | .join("line")
22 | .attr("stroke-width", d => Math.sqrt(d.value));
23 |
24 | const node = svg.append("g")
25 | .attr("stroke", "#fff")
26 | .attr("stroke-width", 1.5)
27 | .selectAll("circle")
28 | .data(nodes)
29 | .join("circle")
30 | .attr("r", 5) // altho static, can't promote
31 | .attr("fill", d => scale(d.group))
32 |
33 |
34 | node.call(drag(simulation));
35 |
36 | simulation.on("tick", () => {
37 | link
38 | .attr("x1", d => d.source.x)
39 | .attr("y1", d => d.source.y)
40 | .attr("x2", d => d.target.x)
41 | .attr("y2", d => d.target.y);
42 |
43 | node
44 | .attr("cx", d => d.x)
45 | .attr("cy", d => d.y);
46 | });
47 |
48 |
49 | return svg.node();
50 | }
51 |
52 | var scale = d3.scaleOrdinal(d3.schemeCategory10);
53 |
54 | var drag = function (simulation) {
55 |
56 | function dragstarted(event, d) {
57 | if (!event.active) simulation.alphaTarget(0.3).restart();
58 | d.fx = d.x;
59 | d.fy = d.y;
60 | }
61 |
62 | function dragged(event,d) {
63 | d.fx = event.x;
64 | d.fy = event.y;
65 | }
66 |
67 | function dragended(event,d) {
68 | if (!event.active) simulation.alphaTarget(0);
69 | d.fx = null;
70 | d.fy = null;
71 | }
72 |
73 | return d3.drag()
74 | .on("start", dragstarted)
75 | .on("drag", dragged)
76 | .on("end", dragended);
77 | }
--------------------------------------------------------------------------------
/src/examples/WrappedJS/Tree.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 |
4 | exports.chartFFI = element => width => height => data => {
5 | var treeH = d3.hierarchy(JSON.parse(data))
6 | .sort((a, b) => d3.ascending(a.data.name, b.data.name))
7 |
8 | var radius = width / 2
9 |
10 | var tree = d3.tree()
11 | .size([2 * Math.PI, radius])
12 | .separation((a, b) => (a.parent == b.parent ? 1 : 2) / a.depth)
13 |
14 | function autoBox() {
15 | document.body.appendChild(this);
16 | const {x, y, width, height} = this.getBBox();
17 | document.body.removeChild(this);
18 | return [x, y, width, height];
19 | }
20 |
21 | const root = tree(treeH);
22 |
23 | const svg =
24 | d3.selectAll(element)
25 | .append("svg")
26 | .attr("viewBox", [-width/2, -height/2, width, height])
27 |
28 | svg.append("g")
29 | .selectAll("path")
30 | .data(root.links())
31 | .join("path")
32 | .attr("fill", "none")
33 | .attr("stroke", "#555")
34 | .attr("stroke-opacity", 0.4)
35 | .attr("stroke-width", 1.5)
36 | .attr("d", d3.linkRadial()
37 | .angle(d => d.x)
38 | .radius(d => d.y));
39 |
40 | svg.append("g")
41 | .selectAll("circle")
42 | .data(root.descendants())
43 | .join("circle")
44 | .attr("transform", d => `
45 | rotate(${d.x * 180 / Math.PI - 90})
46 | translate(${d.y},0)
47 | `)
48 | .attr("fill", d => d.children ? "#555" : "#999")
49 | .attr("r", 2.5);
50 |
51 | svg.append("g")
52 | .attr("font-family", "sans-serif")
53 | .attr("font-size", 10)
54 | .attr("stroke-linejoin", "round")
55 | .attr("stroke-width", 3)
56 | .selectAll("text")
57 | .data(root.descendants())
58 | .join("text")
59 | .attr("transform", d => `
60 | rotate(${d.x * 180 / Math.PI - 90})
61 | translate(${d.y},0)
62 | rotate(${d.x >= Math.PI ? 180 : 0})
63 | `)
64 | .attr("dy", "0.31em")
65 | .attr("x", d => d.x < Math.PI === !d.children ? 6 : -6)
66 | .attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
67 | .text(d => d.data.name)
68 | .clone(true).lower()
69 | .attr("stroke", "white");
70 | }
71 |
--------------------------------------------------------------------------------
/design notes/grammar.md:
--------------------------------------------------------------------------------
1 |
2 | # analysis of d3 grammar
3 |
4 | ## top of mind ideas
5 | * maintain full expressivity of D3
6 | * no attempt to model imperative, chained model of D3
7 | * use StateT to hold the stateful elements like named selections, transitions, scales, interpolators etc
8 | * no performance / space optimizations required in Purs code, for example, entirely static attributes can be promoted from data-bound elements to parent as is done in D3 practice but this would be done by interpreter
9 |
10 | ## abstract grammar of force example
11 | ```
12 | make simulation w. [force] [config]
13 | add tick with reference to named selections
14 |
15 | make selection "div#chart"
16 | append 'svg' [props] [
17 | append 'g' "links" [props] -- and export selection
18 | insert Line links
19 | ( Join (Enter lines [props]) -- static props can be promoted to 'g'
20 | (Update [])
21 | (Exit []) )
22 | , append 'g' "nodes" [props] -- and export selection
23 | insert Circle nodes
24 | ( Join (Enter circles [props])
25 | (Update [])
26 | (Exit []) )
27 | call custom drag simulation -- causes simulation to wake up too
28 | ]
29 | ```
30 |
31 | ## abstract grammar of tree example
32 | ```
33 | make hierarchyData from data
34 | make treeFn [config]
35 | feed hierarchyData into treeFn
36 | make selection "div#app"
37 | append 'svg' props [
38 | append 'g' for links
39 | selectAll `path`
40 | data bind root.links
41 | join
42 | enter 'path' [props] -- one prop, 'd' is d3.linkRadial().angle(d=>d.x).radius(d=>d.y)
43 | , append 'g' for circles
44 | selectAll `circle`
45 | data bind root.descendants()
46 | join
47 | enter 'circle' props -- one prop, 'transform' is printf-ish thing
48 | , append 'g' for text
49 | selectAll `text`
50 | data bind root.descendants()
51 | join
52 | enter 'text' [props] -- one prop, 'transform' is printf-ish thing
53 | clone(true).lower() [props] -- clones whole text selection, lowers it, white outline
54 | ]
55 | ```
56 |
57 | ## abstract grammar of lsm.js
58 |
59 | ```
60 |
61 | ```
62 |
63 | ## abstract grammar of (new) GUP
64 |
65 | ```
66 | make selection "div#chart"
67 | append 'svg' w. [props] [ -- width, height, viewbox
68 | append
69 | ```
--------------------------------------------------------------------------------
/design notes/IndexedExample.purs:
--------------------------------------------------------------------------------
1 | module Data.Viz.Indexed where
2 |
3 | import Data.List as L
4 |
5 | -- || FFI for D3
6 | foreign import data D3Element :: Type
7 | -- the underlying D3 selection that is passed between calls
8 | foreign import data D3Selection :: Type
9 | -- a Selection that's passed back in some callbacks
10 | foreign import data Peers :: Type
11 | -- the `this` pointer in a callback, DOM element receiving an event
12 | foreign import data DomElement :: Type
13 |
14 |
15 | data Model a = Model a
16 |
17 | data D3Model a b = D3Model b
18 |
19 | data Empty
20 | data HasModel
21 | data HasSelection
22 | data HasDataBind
23 | data InTransition
24 |
25 | newtype IxDataViz i o a = IxDataViz (Model a)
26 |
27 | -- the function that will get your result out of the IndexedMonad
28 | runIxDataViz :: forall prev next spec. IxDataViz prev next spec -> spec
29 | runIxDataViz (IxDataViz model) = model
30 |
31 | instance ixMonadDataViz :: IxMonad IxDataViz where
32 | ipure = IxDataViz
33 | ibind (IxDataViz model) f = IxDataViz <<< runIxDataViz $ f model
34 |
35 | initDataViz :: forall a. Model a -> IxDataViz Empty HasModel (Model a)
36 | initDataViz model = IxDataViz model
37 |
38 | data EventType = MouseEnter | MouseLeave | MouseDown | MouseUp | Click
39 | | ContextMenu | DragStart | Drag | DragEnd
40 | data Property a = StaticString String String | StaticNumber String Number
41 | | DynamicString String (a -> String) | DynamicNumber String (a -> Number)
42 | | EventHandler EventType (Event -> Unit)
43 | type Selector = String -- could add more validation here
44 | type Properties = Array Property
45 | data Select = Select Selector Properties
46 | type Name = String
47 | type Duration = Int
48 | data Transition = Transition Name Duration Properties
49 |
50 | makeSelectionWithModel :: forall model. Select -> IxDataViz HasModel OpenSelection
51 |
52 | addPropertiesToSelection :: forall model. Properties -> IxDataViz OpenSelection OpenSelection
53 |
54 | recoverSelectionFromTransition :: forall model.
55 |
56 | simpleModel :: L.List Int
57 | simpleModel = L.fromFoldable [1,2,3,4]
58 |
59 | sampleViz :: forall model. model -> IxDataViz Empty HasDataBind model
60 | sampleViz model = initDataViz model
61 | :>>= makeSelectionWithModel (Select "div#chart" [])
62 | :>>= addPropertiesToSelection []
63 | :>>= bindModelToDOM
64 | :>>= addTransition (Transition "foo" 2000 [])
65 | :>>= addOnions
66 | :>>= noLettuce
67 | :>>= addTomato
68 | :>>= addTopBun
69 |
--------------------------------------------------------------------------------
/src/examples/NewSyntax/GUP.purs:
--------------------------------------------------------------------------------
1 | module NewSyntax.GUP where
2 |
3 | import D3.Base
4 | import Data.Tuple (Tuple(..))
5 | import Prelude (negate, ($), (*), (/))
6 | import Unsafe.Coerce (unsafeCoerce)
7 |
8 | -- with such simple data and simple data viz, the data in the DOM will be exactly same as Model data
9 | -- this means that a simple coerce suffices to turn Datum -> D3DOMelement and also that no Projection
10 | -- function is required, hence the use of joinElement_
11 | type D3DOMelement = Char
12 |
13 | type Model a = Array a
14 |
15 | chartInit :: Tuple Number Number -> Selection (Model Char)
16 | chartInit (Tuple width height) = do
17 | let origin = { x: -width / 2.0, y: -height / 2.0 }
18 | t = [ Duration 750 ]
19 | xPosition d i = i * 16.0
20 |
21 | selectInDOM "div#GUP" [] [-- this is an InitialSelect
22 | svg
23 | [ viewBox origin.x origin.y width height ]
24 | [ join $
25 | EnterUpdateExit Text
26 | -- Enter
27 | (Attrs [ fill "green"
28 | , y (-30.0) Px
29 | , computeXusingIndex Px xPosition
30 | , computeText (\d -> unsafeCoerce d )
31 | -- , transition t -- TODO maybe this can become proper selection child now?
32 | , y 0.0 Px ])
33 | -- Update
34 | (Attrs [ fill "black", y 0.0 Px
35 | -- , transition t -- TODO make this a proper child later
36 | , computeXusingIndex Px xPosition ])
37 | -- exit
38 | (Attrs [ fill "brown", y 30.0 Px
39 | -- , transition t -- TODO make this a proper child later
40 | , y 30.0 Px ])
41 | ]
42 | ]
43 |
44 |
45 | -- const t = svg.transition()
46 | -- .duration(750);
47 |
48 | -- svg.selectAll("text")
49 | -- .data(randomLetters(), d => d)
50 | -- .join(
51 | -- enter => enter.append("text")
52 | -- .attr("fill", "green")
53 | -- .attr("x", (d, i) => i * 16)
54 | -- .attr("y", -30)
55 | -- .text(d => d)
56 | -- .call(enter => enter.transition(t)
57 | -- .attr("y", 0)),
58 | -- update => update
59 | -- .attr("fill", "black")
60 | -- .attr("y", 0)
61 | -- .call(update => update.transition(t)
62 | -- .attr("x", (d, i) => i * 16)),
63 | -- exit => exit
64 | -- .attr("fill", "brown")
65 | -- .call(exit => exit.transition(t)
66 | -- .attr("y", 30)
67 | -- .remove())
68 | -- );
69 |
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # purescript-d3v6
2 |
3 | ## Approach taken / project goals
4 |
5 | The goal has been to add some type-safety and composability to the construction of visualizations with D3.
6 |
7 | The aim is to do this with ZERO compromise to D3js' graphical expressivity, ie want to be able to reproduce any D3js viz.
8 |
9 | A secondary, but important goal is to improve readability.
10 |
11 | *Non-goals* include replicating parts of D3js that would - IMHO - be better done in PureScript, data-processing and cleaning and so forth.
12 |
13 | Performance neither a goal or a non-goal, this library is only interpreting a kind of AST for the D3 chart, D3 is doing all the work under the hood including, importantly all the DOM updates and animations etc. The Purescript layer shouldn't be adding any overhead at all, practically speaking.
14 |
15 | # status as of this commit
16 |
17 | Abstract syntax tree of chart (for simple force layout only) now exists and also an interpreter (generic, not specific to the force layout) to do the effectful business of putting the chart into the DOM. The mainline is reading and decoding the exact same JSON file as in the standard D3 example and interpreting this chart using that model data.
18 |
19 |
20 | ## example of current syntax model
21 |
22 | ```purescript
23 | chart :: Tuple Number Number -> Selection Model
24 | chart (Tuple width height) =
25 | initialSelect "div#force" "forceLayout" [] $ [
26 | appendNamed "svg" Svg [ viewBox 0.0 0.0 width height ] [
27 | append Group [ strokeColor "#999", strokeOpacity 0.6 ]
28 | [ join Line modelLinks
29 | (appendNamed "link" Line [ strokeWidth_D (\d -> sqrt (d3Link d).value)] [])
30 | noUpdate noExit ]
31 |
32 | , append Group [ strokeColor "#fff", strokeOpacity 1.5 ]
33 | [ join Circle modelNodes
34 | (appendNamed "node" Circle [ radius 5.0, fill_D colorByGroup] [])
35 | noUpdate noExit ]
36 | ]
37 | ]
38 | ```
39 |
40 | ## Compare to this JS for example
41 |
42 | ```JavaScript
43 | const svg = d3.selectAll(element).append("svg")
44 | .attr("viewBox", [0, 0, width, height]);
45 |
46 | const link = svg.append("g")
47 | .attr("stroke", "#999")
48 | .attr("stroke-opacity", 0.6)
49 | .selectAll("line")
50 | .data(links)
51 | .join("line")
52 | .attr("stroke-width", d => Math.sqrt(d.value));
53 |
54 | const node = svg.append("g")
55 | .attr("stroke", "#fff")
56 | .attr("stroke-width", 1.5)
57 | .selectAll("circle")
58 | .data(nodes)
59 | .join("circle")
60 | .attr("r", 5) // altho static, can't promote
61 | .attr("fill", d => scale(d.group))
62 | ```
63 |
64 | # Quickstart to run an example
65 |
66 | Note that this is for a different example than the above, and assumes you already have `purescript` and `spago` installed.
67 | ```
68 | npm i
69 | npm run example
70 | ```
71 |
72 |
--------------------------------------------------------------------------------
/src/D3/Interpreter/Foreign.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter.Foreign where
2 |
3 | import D3.Base
4 | import Prelude
5 |
6 | import Data.Nullable (Nullable)
7 | import Unsafe.Coerce (unsafeCoerce)
8 |
9 | -- | right upfront we're just going to hand wave over the types given to D3
10 | -- | design decision here to prefer (aliased) `unsafeCoerce` to complex wrappers
11 | -- | D3 WILL modify any data you give it, it will also require types that CANNOT be
12 | -- | expressed in PureScript AND also transparently shared with JavaScript
13 | -- | (ie Variants, Exists, Nullable etc)
14 | -- | IMPORTANT - coercions should not be visible outside this module
15 | foreign import data NativeJS :: Type
16 |
17 | -- | foreign types associated with D3 Selection
18 | -- all this FFI stuff can be made Effect Unit later
19 | foreign import runSimpleAttrJS :: NativeSelection -> String -> NativeJS -> Unit
20 | foreign import runDatumAttrJS :: forall f. NativeSelection -> String -> f -> Unit
21 | foreign import runDatumTextJS :: forall f. NativeSelection -> f -> Unit
22 | foreign import runDatumIndexAttrJS :: forall f. NativeSelection -> String -> f -> Unit
23 | foreign import d3SelectAllJS :: String -> NativeSelection
24 | foreign import d3AppendElementJS :: NativeSelection -> String -> NativeSelection
25 | -- foreign import d3EnterElementJS :: NativeSelection -> String -> NativeSelection
26 | foreign import d3JoinJS :: forall d.
27 | NativeSelection
28 | -> String
29 | -> d
30 | -> { enter :: NativeSelection -> NativeSelection,
31 | update :: NativeSelection -> NativeSelection,
32 | exit :: NativeSelection -> NativeSelection }
33 | -> NativeSelection
34 |
35 | foreign import d3EnterWithIndexJS :: forall d. NativeSelection -> d -> (d -> NativeJS) -> NativeSelection
36 | foreign import nullSelectionJS :: NativeSelection
37 |
38 | -- | foreign types associated with Force Layout Simulation
39 | foreign import initSimulationJS :: SimulationConfig -> NativeSelection
40 | -- TODO tick functions should be nameSelection too, so this should be param'd with a tick NativeSelection too!
41 | foreign import addAttrFnToTickJS :: NativeSelection -> Attr -> Unit
42 | foreign import attachTickFnToSimulationJS :: NativeSelection -> Unit
43 | foreign import attachDefaultDragBehaviorJS :: NativeSelection -> NativeSelection -> Unit
44 | foreign import putNodesInSimulationJS :: NativeSelection -> Array NativeJS -> Array NativeJS
45 | foreign import putLinksInSimulationJS :: NativeSelection -> Array NativeJS -> Array NativeJS
46 | foreign import startSimulationJS :: NativeSelection -> Unit
47 | foreign import stopSimulationJS :: NativeSelection -> Unit
48 | foreign import setAlphaTargetJS :: NativeSelection -> Number -> Unit
49 | foreign import forceManyJS :: NativeSelection -> String -> Unit
50 | foreign import forceCenterJS :: NativeSelection -> String -> Number -> Number -> Unit
51 | foreign import forceCollideJS :: NativeSelection -> String -> Number -> Unit
52 | foreign import forceXJS :: NativeSelection -> String -> Number -> Unit
53 | foreign import forceYJS :: NativeSelection -> String -> Number -> Unit
54 | foreign import forceRadialJS :: NativeSelection -> String -> Number -> Number -> Unit
55 |
56 | stringToNativeJS :: String -> NativeJS
57 | stringToNativeJS = unsafeCoerce
58 |
59 | numberToNativeJS :: Number -> NativeJS
60 | numberToNativeJS = unsafeCoerce
61 |
62 | arrayNumberToNativeJS :: Array Number -> NativeJS
63 | arrayNumberToNativeJS = unsafeCoerce
64 |
--------------------------------------------------------------------------------
/src/Main.purs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Prelude
4 |
5 | import Affjax (get) as AJAX
6 | import Affjax (printError)
7 | import Affjax.ResponseFormat as ResponseFormat
8 | import Control.Monad.State (StateT, runStateT)
9 | import D3.Base (Selection)
10 | import D3.Interpreter (D3State(..), initialState, interpretDrag, runInitial, interpretSimulation, interpretTickMap, startSimulation)
11 | import D3.Interpreter.Types (updateState)
12 | import Data.Bifunctor (rmap)
13 | import Data.Either (Either(..))
14 | import Data.Int (toNumber)
15 | import Data.String.CodeUnits (toCharArray)
16 | import Data.Tuple (Tuple(..))
17 | import Effect (Effect)
18 | import Effect.Aff (Milliseconds(..), delay, launchAff_)
19 | import Effect.Class (liftEffect)
20 | import Effect.Class.Console (log)
21 | import NewSyntax.Force as Force
22 | import NewSyntax.GUP as GUP
23 | import NewSyntax.Tree as Tree
24 | import Web.HTML (window)
25 | import Web.HTML.Window (innerHeight, innerWidth)
26 |
27 | getWindowWidthHeight :: Effect (Tuple Number Number)
28 | getWindowWidthHeight = do
29 | win <- window
30 | width <- innerWidth win
31 | height <- innerHeight win
32 | pure $ Tuple (toNumber width) (toNumber height)
33 |
34 | forceInterpreter :: Selection Force.Model -> StateT (D3State Force.Model) Effect Unit
35 | forceInterpreter forceChart = do
36 | simulation <- interpretSimulation Force.simulation Force.getNodes Force.getLinks Force.makeModel
37 | _ <- runInitial forceChart
38 | interpretTickMap simulation Force.myTickMap
39 | interpretDrag Force.myDrag
40 | startSimulation simulation
41 | pure unit -- should be we returning the updated Selection? and the Simulation?
42 |
43 | -- TODO take the file reading stuff out so that we can see the essentials
44 | main :: Effect Unit
45 | main = launchAff_ do -- Aff
46 | widthHeight <- liftEffect getWindowWidthHeight
47 |
48 | let numberOfExamples = 3.0
49 | widthHeight' = rmap (\h -> h/numberOfExamples) widthHeight
50 |
51 | -- first, a force layout example
52 | log "Force layout example"
53 | forceJSON <- AJAX.get ResponseFormat.string "http://localhost:1234/miserables.json"
54 | let fileData = Force.readModelFromFileContents forceJSON
55 | let forceChart = Force.chart widthHeight'
56 | let forceModel = Force.makeModel fileData.links fileData.nodes
57 |
58 | _ <- liftEffect $
59 | runStateT (forceInterpreter forceChart) (initialState forceModel)
60 |
61 | -- then a radial tree
62 | log "Radial tree example"
63 | treeJSON <- AJAX.get ResponseFormat.string "http://localhost:1234/flare-2.json"
64 | let treeChart = Tree.chart widthHeight'
65 | case Tree.readModelFromFileContents widthHeight' treeJSON of
66 | (Left error) -> liftEffect $ log $ printError error
67 |
68 | (Right treeModel) -> liftEffect $
69 | runStateT (runInitial treeChart) (initialState treeModel) *> pure unit
70 |
71 | -- then the General Update Pattern example
72 | log "General Update Pattern example"
73 | let lettersChart = GUP.chartInit widthHeight'
74 | let letters1 = toCharArray "abcdefghijklmnopqrstuvwxyz"
75 | let letters2 = toCharArray "acefghiklnpqrtuwyz"
76 |
77 | (Tuple _ s) <- liftEffect $
78 | runStateT (runInitial lettersChart) (initialState letters1)
79 |
80 | _ <- delay $ Milliseconds 2000.0
81 |
82 | -- _ <- liftEffect $
83 | -- runStateT (runInitial lettersChart) (updateState letters2 s)
84 | -- TODO now we need to use the monadic context inside StateT to (repeatedly) add the GUP.chartUpdate
85 | -- and we really want the NativeSelection to be passed in via scope, right?
86 | -- _ <- liftEffect $ runStateT (interpretSelection GUP.chartUpdate ) (Context letters2 lettersScope)
87 | -- _ <- liftEffect $ runStateT (interpretSelection chartUpdate ) (Context letters2 lettersScope)
88 | -- _ <- liftEffect $ runStateT (interpretSelection chartUpdate ) (Context letters2 lettersScope)
89 | pure unit
--------------------------------------------------------------------------------
/src/D3/Interpreter/Selection.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter.Selection where
2 |
3 | import Control.Monad.State (get, modify_)
4 | import D3.Base (Element(..), NativeSelection, Selection(..))
5 | import D3.Interpreter.Attributes (applyAttr)
6 | import D3.Interpreter.Foreign (d3AppendElementJS, d3JoinJS, d3SelectAllJS, nullSelectionJS)
7 | import D3.Interpreter.Types (D3, D3State(..))
8 | import Data.Array (reverse)
9 | import Data.Foldable (traverse_)
10 | import Data.Map (insert)
11 | import Data.Maybe (Maybe(..))
12 | import Debug.Trace (spy)
13 | import Effect.Console (log)
14 | import Prelude (Unit, bind, discard, pure, show, unit, ($))
15 | import Unsafe.Coerce (unsafeCoerce)
16 |
17 | -- TODO fugly, fix later
18 | runInitial :: forall model. Selection model -> D3 model NativeSelection
19 | runInitial = case _ of
20 | -- selectInDOMion is the only one that we can start with (alt. use IxMonad)
21 | s@(InitialSelect r) -> run nullSelectionJS s
22 | -- TODO handle extend selection cases needed for update / re-entrancy
23 | -- TODO raise error, or structure differently
24 | _ -> pure nullSelectionJS
25 |
26 | run :: forall model. NativeSelection -> Selection model -> D3 model NativeSelection
27 | run activeSelection selection = do
28 | case selection of
29 | (InitialSelect r) -> do
30 | let root = spy "selectInDOM" $
31 | d3SelectAllJS r.selector
32 | traverse_ (run root) r.children
33 | pure root
34 |
35 | (Append r) -> do
36 | let appendSelection = spy "Append" $
37 | d3AppendElementJS activeSelection (show r.element)
38 | traverse_ (applyAttr appendSelection) r.attributes
39 | traverse_ (run appendSelection) (reverse r.children) -- TODO why reverse children here?
40 | pure appendSelection
41 |
42 | (Join { element, projection, selections }) -> do
43 | (Context model scope) <- get
44 | let
45 | enterFunction :: forall model. Selection model -> NativeSelection -> D3 model NativeSelection
46 | enterFunction selection nativeSelection = run nativeSelection (Append { attributes: [], children: [], element: Text })
47 | where
48 | _ = log "callback from join enter"
49 |
50 | updateFunction :: forall model. Maybe (Selection model) -> NativeSelection -> D3 model NativeSelection
51 | updateFunction selection nativeSelection = run nativeSelection (Update { attributes: [], children: [] })
52 | where
53 | _ = log "callback from join update"
54 |
55 | exitFunction :: forall model. Maybe (Selection model) -> NativeSelection -> D3 model NativeSelection
56 | exitFunction selection nativeSelection = run nativeSelection (Exit { attributes: [], children: [] })
57 | where
58 | _ = log "callback from join exit"
59 | dataForJoin =
60 | case projection of
61 | Nothing -> unsafeCoerce model
62 | (Just fn) -> fn model
63 |
64 | joinSelection = spy "Join: " $
65 | d3JoinJS activeSelection (show element) (spy "submodel: " dataForJoin)
66 | { enter: enterFunction selections.enter
67 | , update: updateFunction selections.update
68 | , exit: exitFunction selections.exit }
69 |
70 | pure joinSelection
71 |
72 |
73 | -- TODO lookup the selection given by name and run interpreter on it with
74 | (RunTimeSelection _ _) -> do
75 | pure activeSelection
76 |
77 | -- (Transition _) -> do
78 | -- pure activeSelection
79 | (Update _) -> do
80 | pure activeSelection
81 | (Exit _) -> do
82 | pure activeSelection
83 |
84 | NullSelection -> do
85 | pure activeSelection
86 |
87 |
88 |
89 | updateScope :: forall model. NativeSelection -> Maybe String -> D3 model Unit
90 | updateScope selection Nothing = pure unit
91 | updateScope selection (Just label) =
92 | modify_ (\(Context model scope) -> Context model (insert label selection scope))
93 |
94 |
--------------------------------------------------------------------------------
/packages.dhall:
--------------------------------------------------------------------------------
1 | {-
2 | Welcome to your new Dhall package-set!
3 |
4 | Below are instructions for how to edit this file for most use
5 | cases, so that you don't need to know Dhall to use it.
6 |
7 | ## Warning: Don't Move This Top-Level Comment!
8 |
9 | Due to how `dhall format` currently works, this comment's
10 | instructions cannot appear near corresponding sections below
11 | because `dhall format` will delete the comment. However,
12 | it will not delete a top-level comment like this one.
13 |
14 | ## Use Cases
15 |
16 | Most will want to do one or both of these options:
17 | 1. Override/Patch a package's dependency
18 | 2. Add a package not already in the default package set
19 |
20 | This file will continue to work whether you use one or both options.
21 | Instructions for each option are explained below.
22 |
23 | ### Overriding/Patching a package
24 |
25 | Purpose:
26 | - Change a package's dependency to a newer/older release than the
27 | default package set's release
28 | - Use your own modified version of some dependency that may
29 | include new API, changed API, removed API by
30 | using your custom git repo of the library rather than
31 | the package set's repo
32 |
33 | Syntax:
34 | Replace the overrides' "{=}" (an empty record) with the following idea
35 | The "//" or "⫽" means "merge these two records and
36 | when they have the same value, use the one on the right:"
37 | -------------------------------
38 | let overrides =
39 | { packageName =
40 | upstream.packageName // { updateEntity1 = "new value", updateEntity2 = "new value" }
41 | , packageName =
42 | upstream.packageName // { version = "v4.0.0" }
43 | , packageName =
44 | upstream.packageName // { repo = "https://www.example.com/path/to/new/repo.git" }
45 | }
46 | -------------------------------
47 |
48 | Example:
49 | -------------------------------
50 | let overrides =
51 | { halogen =
52 | upstream.halogen // { version = "master" }
53 | , halogen-vdom =
54 | upstream.halogen-vdom // { version = "v4.0.0" }
55 | }
56 | -------------------------------
57 |
58 | ### Additions
59 |
60 | Purpose:
61 | - Add packages that aren't already included in the default package set
62 |
63 | Syntax:
64 | Replace the additions' "{=}" (an empty record) with the following idea:
65 | -------------------------------
66 | let additions =
67 | { package-name =
68 | { dependencies =
69 | [ "dependency1"
70 | , "dependency2"
71 | ]
72 | , repo =
73 | "https://example.com/path/to/git/repo.git"
74 | , version =
75 | "tag ('v4.0.0') or branch ('master')"
76 | }
77 | , package-name =
78 | { dependencies =
79 | [ "dependency1"
80 | , "dependency2"
81 | ]
82 | , repo =
83 | "https://example.com/path/to/git/repo.git"
84 | , version =
85 | "tag ('v4.0.0') or branch ('master')"
86 | }
87 | , etc.
88 | }
89 | -------------------------------
90 |
91 | Example:
92 | -------------------------------
93 | let additions =
94 | { benchotron =
95 | { dependencies =
96 | [ "arrays"
97 | , "exists"
98 | , "profunctor"
99 | , "strings"
100 | , "quickcheck"
101 | , "lcg"
102 | , "transformers"
103 | , "foldable-traversable"
104 | , "exceptions"
105 | , "node-fs"
106 | , "node-buffer"
107 | , "node-readline"
108 | , "datetime"
109 | , "now"
110 | ]
111 | , repo =
112 | "https://github.com/hdgarrood/purescript-benchotron.git"
113 | , version =
114 | "v7.0.0"
115 | }
116 | }
117 | -------------------------------
118 | -}
119 | let upstream =
120 | https://github.com/purescript/package-sets/releases/download/psc-0.14.0-20210311/packages.dhall sha256:3da8be2b7b4a0e7de6186591167b363023695accffb98a8639e9e7d06e2070d6
121 |
122 | let overrides = {=}
123 |
124 | let additions = {=}
125 |
126 | in upstream // overrides // additions
127 |
--------------------------------------------------------------------------------
/src/examples/NewSyntax/Force.purs:
--------------------------------------------------------------------------------
1 | module NewSyntax.Force (
2 | chart, simulation
3 | , Model, GraphLink, GraphNode
4 | , getLinks, getNodes, makeModel
5 | , myTickMap, myDrag
6 | , readModelFromFileContents) where
7 |
8 | import D3.Base
9 |
10 | import Affjax (Error)
11 | import Data.Either (Either(..))
12 | import Data.Map (fromFoldable)
13 | import Data.Tuple (Tuple(..))
14 | import Math (sqrt)
15 | import Prelude (const, identity, unit, ($), (/))
16 | import Unsafe.Coerce (unsafeCoerce)
17 |
18 |
19 | -- this is the model used by this particular "chart" (ie force layout simulation)
20 | type Model = { links :: Array GraphLink, nodes :: Array GraphNode }
21 |
22 | type GraphNode = SimulationNodeRow { group :: Number }
23 | type GraphLink = { id :: ID, source :: ID, target :: ID, value :: Number }
24 |
25 | -- | express the additions that D3 makes in terms of rows for clarity and DRY
26 | -- after the GraphLink type has been bound in D3 it is changed to the following
27 | type D3GraphLink = { id :: ID, source :: GraphNode, target :: GraphNode, value :: Number }
28 |
29 | -- Projection functions to get subModels out of the Model for sub-selections
30 | modelLinks :: Projection Model
31 | modelLinks model = unsafeCoerce model.links
32 |
33 | modelNodes :: Projection Model
34 | modelNodes model = unsafeCoerce model.nodes
35 |
36 |
37 | -- | definition of the force layout, resulting Selection is run by Interpreter
38 | chart :: Tuple Number Number -> Selection Model
39 | chart (Tuple width height) =
40 | selectInDOM "div#force" [] [
41 | svg [ viewBox 0.0 0.0 width height ] [
42 | group
43 | [ strokeColor "#999"
44 | , strokeOpacity 0.6 ]
45 | [ joinUsing modelLinks $
46 | Enter Line $ Attrs [ computeStrokeWidth Px $ (\d -> sqrt (d3Link d).value) ]
47 | ]
48 |
49 | , group
50 | [ strokeColor "#fff"
51 | , strokeOpacity 1.5 ]
52 | [ joinUsing modelNodes $ -- we need to name this selection for Drag tho
53 | Enter Circle $ Attrs [ radius 5.0 Px, computeFill colorByGroup]
54 | ]
55 | ]
56 | ]
57 |
58 |
59 | -- | definition of the particular Simulation that we are going to run
60 | simulation :: Simulation
61 | simulation =
62 | Simulation {
63 | label: "simulation" -- TODO stringy label
64 | , config: defaultConfigSimulation
65 | , forces: [ Force "charge" ForceMany, centerForce 800.0 900.0 ]
66 | , nodes: []
67 | , links: []
68 | , tick: identity
69 | , drag: const unit
70 | }
71 |
72 | centerForce :: Number -> Number -> Force
73 | centerForce width height = Force "center" $ ForceCenter (width / 2.0) (height / 2.0)
74 |
75 | -- | function to build the tick function, quite tricky
76 | myTickMap :: TickMap Model
77 | myTickMap = fromFoldable
78 | [ Tuple "link" [ NumberAttr "x1" (\d -> (unsafeCoerce d).source.x)
79 | , NumberAttr "y1" (\d -> (unsafeCoerce d).source.y)
80 | , NumberAttr "x2" (\d -> (unsafeCoerce d).target.x)
81 | , NumberAttr "y2" (\d -> (unsafeCoerce d).target.y) ]
82 | , Tuple "node" [ NumberAttr "cx" (\d -> (unsafeCoerce d).x)
83 | , NumberAttr "cy" (\d -> (unsafeCoerce d).y) ]
84 | ]
85 |
86 | -- | utility functions and boilerplate
87 |
88 | myDrag :: DragBehavior
89 | myDrag = DefaultDrag "node" "simulation"
90 |
91 | makeModel :: Array GraphLink -> Array GraphNode -> Model
92 | makeModel links nodes = { links, nodes }
93 |
94 | getLinks :: Model -> Array GraphLink
95 | getLinks m = m.links
96 |
97 | getNodes :: Model -> Array GraphNode
98 | getNodes m = m.nodes
99 |
100 | -- TODO the decode on the Purescript side unless files are ginormous, this is just for prototyping
101 | foreign import readJSONJS :: String -> Model -- TODO no error handling at all here RN
102 |
103 | readModelFromFileContents :: forall r. Either Error { body ∷ String | r } -> Model
104 | readModelFromFileContents (Right { body } ) = readJSONJS body
105 | readModelFromFileContents (Left err) = { links: [], nodes: [] }
106 |
107 |
108 | -- we give the chart our Model type but behind the scenes it is mutated by D3 and additionally
109 | -- which projection of the "Model" is active in each Join varies so we can't have both strong
110 | -- static type representations AND lightweight syntax with JS compatible lambdas
111 | d3Link :: Datum -> D3GraphLink
112 | d3Link = unsafeCoerce
113 | d3Node :: Datum -> GraphNode
114 | d3Node = unsafeCoerce
115 |
116 | colorByGroup :: Datum -> String
117 | colorByGroup d = d3SchemeCategory10JS (unsafeCoerce d :: GraphNode).group
118 |
119 | -- drag =
120 | -- d3Drag "node" simulation {
121 | -- start: dragstarted
122 | -- , drag: dragged
123 | -- , end: dragended
124 | -- }
125 |
126 | -- attachTickMap :: (Unit -> Unit) -> Simulation -> Unit
127 | -- attachTickMap tick simulation =
128 |
129 |
--------------------------------------------------------------------------------
/src/D3/Interpreter/Foreign.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | // ATTR functions
4 |
5 | // :: NativeSelection -> String -> NativeJS -> Unit
6 | exports.runSimpleAttrJS = selection => attr => value => selection.attr(attr, value)
7 | // :: forall f. NativeSelection -> String -> f -> Unit
8 | exports.runDatumAttrJS = selection => attr => f => selection.attr(attr, f)
9 |
10 | // NB the (d,i) => f(d)(i) in runDatumIndexAttrJS - the attribute-setter
11 | // function is not curried, since it’s been written on the PureScript side
12 |
13 | // :: forall f. NativeSelection -> String -> f -> Unit
14 | exports.runDatumIndexAttrJS = selection => attr => f => selection.attr(attr, (d,i) => f(d)(i))
15 | // foreign import runDatumTextJS :: forall f. NativeSelection -> f -> Unit
16 | exports.runDatumTextJS = selection => f => selection.text(f)
17 |
18 | // SELECTION functions
19 | // :: String -> NativeSelection
20 | exports.d3SelectAllJS = selector => d3.selectAll(selector)
21 | // :: NativeSelection -> String -> NativeSelection
22 | exports.d3AppendElementJS = selection => element => selection.append(element)
23 | // :: forall d. d -> NativeSelection
24 | exports.d3JoinJS = selection => element => data => fns => {
25 | console.log("d3JoinJS");
26 | return selection.selectAll(element).data(data).join(fns.enter, fns.update, fns.exit)
27 | }
28 |
29 | exports.d3EnterWithIndexJS = selection => data => idFunction => selection.data(data, idFunction)
30 | // :: NativeSelection
31 | exports.nullSelectionJS = null // NB only used on init, maybe still a bad idea
32 |
33 |
34 | // SIMULATION functions
35 | exports.initSimulationJS = config => {
36 | return d3.forceSimulation()
37 | .alpha(config.alpha) // default is 1
38 | .alphaTarget(config.alphaTarget) // default is 0
39 | .alphaMin(config.alphaMin) // default is 0.0001
40 | .alphaDecay(config.alphaDecay) // default is 0.0228
41 | .velocityDecay(config.velocityDecay) // default is 0.4
42 | }
43 | // :: Simulation -> Array NativeNode -> Array NativeNode
44 | exports.putNodesInSimulationJS = simulation => nodes => {
45 | simulation.nodes(nodes)
46 | return nodes
47 | }
48 | // :: Simulation -> Array NativeLink -> Array NativeLink
49 | exports.putLinksInSimulationJS = simulation => links => {
50 | simulation.force("links", d3.forceLink(links).id(d => d.id))
51 | return links
52 | }
53 | // :: NativeSelection -> Number -> Unit
54 | exports.setAlphaTargetJS = simulation => target => simulation.alphaTarget(target)
55 | // :: NativeSelection -> Unit
56 | exports.startSimulationJS = simulation => simulation.restart()
57 | // :: NativeSelection -> Unit
58 | exports.stopSimulationJS = simulation => simulation.stop()
59 |
60 | var tickAttrArray = [] // TODO probably want API to reset this too, but defer til adding named tick functions
61 | exports.addAttrFnToTickJS = selection => pursAttr => {
62 | tickAttrArray.push({ selection: selection, attr: pursAttr.value0, fn: pursAttr.value1 })
63 | }
64 | // assumes we've already put all the things we want to happen into the tickAttrArray
65 | exports.attachTickFnToSimulationJS = simulation => simulation.on("tick", () => {
66 | tickAttrArray.forEach(element => (element.selection).attr(element.attr, element.fn))
67 | })
68 | // default drag function, useful in probably most simulations
69 | exports.attachDefaultDragBehaviorJS = selection => simulation => {
70 |
71 | var drag = function(simulation) {
72 | function dragstarted(event, d) {
73 | if (!event.active) simulation.alphaTarget(0.3).restart();
74 | d.fx = d.x;
75 | d.fy = d.y;
76 | }
77 |
78 | function dragged(event,d) {
79 | d.fx = event.x;
80 | d.fy = event.y;
81 | }
82 |
83 | function dragended(event,d) {
84 | if (!event.active) simulation.alphaTarget(0);
85 | d.fx = null;
86 | d.fy = null;
87 | }
88 |
89 | return d3.drag()
90 | .on("start", dragstarted)
91 | .on("drag", dragged)
92 | .on("end", dragended);
93 | }
94 |
95 | selection.call(drag(simulation))
96 | }
97 |
98 | // FORCE functions
99 | // :: Simulation -> Unit
100 | exports.forceManyJS = simulation => label => simulation.force(label, d3.forceManyBody())
101 | // :: Simulation -> Number -> Number -> Unit
102 | exports.forceCenterJS = simulation => label => cx => cy => simulation.force(label, d3.forceCenter(cx,cy))
103 | // :: Simulation -> Unit
104 | // exports.forceLinks = simulation => label => simulation.x()
105 | // :: Simulation -> Number -> Unit
106 | exports.forceCollideJS = simulation => label => radius => simulation.force(label, d3.forceCollide(radius))
107 | // :: Simulation -> Number -> Unit
108 | exports.forceXJS = simulation => label => cx => simulation.force(label, d3.forceX(cx))
109 | // :: Simulation -> Number -> Unit
110 | exports.forceYJS = simulation => label => cy => simulation.force(label, d3.forceY(cy))
111 | // :: Simulation -> Number -> Number -> Unit
112 | exports.forceRadialJS = simulation => label => cx => cy => simulation.force(label, d3.forceRadial(cx, cy))
113 |
114 |
--------------------------------------------------------------------------------
/src/D3/Interpreter/Layouts/Simulation.purs:
--------------------------------------------------------------------------------
1 | module D3.Interpreter.Layouts.Simulation where
2 |
3 | import D3.Interpreter.Foreign (NativeJS, addAttrFnToTickJS, attachDefaultDragBehaviorJS, attachTickFnToSimulationJS, forceCenterJS, forceCollideJS, forceManyJS, forceRadialJS, forceXJS, forceYJS, initSimulationJS, putLinksInSimulationJS, putNodesInSimulationJS, startSimulationJS, stopSimulationJS)
4 | import Prelude (Unit, bind, discard, flip, pure, unit, ($), (<$>))
5 |
6 | import Control.Monad.State (get, put)
7 | import D3.Base (Attr, DragBehavior(..), Force(..), NativeSelection, Simulation(..), TickMap)
8 | import D3.Base.Layout.Simulation (ForceType(..))
9 | import D3.Interpreter.Types (D3, D3State(..))
10 | import Data.Array (concatMap, foldl, fromFoldable, (:))
11 | import Data.Bifunctor (lmap)
12 | import Data.Map (Map, insert, lookup, toUnfoldable)
13 | import Data.Maybe (Maybe(..))
14 | import Data.Traversable (traverse_)
15 | import Data.Tuple (Tuple(..))
16 | import Unsafe.Coerce (unsafeCoerce)
17 |
18 | -- | FORCE LAYOUT (SIMULATION) interpreter
19 | startSimulation :: forall model. NativeSelection -> D3 model Unit
20 | startSimulation simulation = pure $ startSimulationJS simulation
21 |
22 | stopSimulation :: forall model. NativeSelection -> D3 model Unit
23 | stopSimulation simulation = pure $ stopSimulationJS simulation
24 |
25 | -- | get a reference to a simulation that we can then use elsewhere
26 | -- | ie having interpreted a Selection such that the DOM is set up to run a simulation
27 | -- | NB this is actually modifying the model in the Context
28 | interpretSimulation :: forall model node link. Simulation ->
29 | (model -> Array node) -> -- project nodes from model
30 | (model -> Array link) -> -- project links from model
31 | (Array link -> Array node -> model) -> -- repackage nodes & links as model
32 | D3 model NativeSelection
33 | interpretSimulation (Simulation r) getNodes getLinks repackage =
34 | do
35 | (Context model scope) <- get
36 | let sim = initSimulationJS r.config
37 | nodes = nativeNodes $ getNodes model
38 | links = nativeLinks $ getLinks model
39 | -- updateScope sim (Just r.label)
40 | traverse_ (interpretForce sim) r.forces
41 | let initializedNodes = unnativeNodes $ putNodesInSimulationJS sim nodes
42 | initializedLinks = unnativeLinks $ putLinksInSimulationJS sim links
43 | initializedModel = repackage initializedLinks initializedNodes
44 | put (Context initializedModel (insert r.label sim scope))
45 | pure sim
46 | where
47 | nativeNodes = unsafeCoerce :: Array node -> Array NativeJS
48 | nativeLinks = unsafeCoerce :: Array link -> Array NativeJS
49 | unnativeNodes = unsafeCoerce :: Array NativeJS -> Array node
50 | unnativeLinks = unsafeCoerce :: Array NativeJS -> Array link
51 |
52 | interpretForce :: forall model. NativeSelection -> Force -> D3 model Unit
53 | interpretForce simulation = do
54 | case _ of
55 | (Force label ForceMany) -> pure $ forceManyJS simulation label
56 | (Force label (ForceCenter cx cy)) -> pure $ forceCenterJS simulation label cx cy
57 | -- (Force (ForceLink links idFn)) -> pure $ forceLinks
58 | (Force label (ForceCollide radius_)) -> pure $ forceCollideJS simulation label radius_
59 | (Force label (ForceX x)) -> pure $ forceXJS simulation label x
60 | (Force label (ForceY y)) -> pure $ forceYJS simulation label y
61 | (Force label (ForceRadial cx cy)) -> pure $ forceRadialJS simulation label cx cy
62 | (Force label Custom) -> pure $ unit -- do this later as needed
63 |
64 | -- getNativeSelections :: Map String NativeSelection -> TickMap model -> Array (Tuple NativeSelection (Array (Tuple attr fn)))
65 | -- getNativeSelections scope []
66 |
67 |
68 | getNativeSelection :: forall x. (Map String NativeSelection) -> Map String x -> Array (Tuple NativeSelection x)
69 | getNativeSelection scopeMap tickMap = fromFoldable nativeTuples
70 | where
71 | tickTuples :: Array (Tuple String x)
72 | tickTuples = toUnfoldable tickMap
73 |
74 | maybeNativeTuple :: Tuple String x -> Tuple (Maybe NativeSelection) x
75 | maybeNativeTuple = lmap (flip lookup scopeMap)
76 |
77 | maybeNativeTuples :: Array (Tuple (Maybe NativeSelection) x)
78 | maybeNativeTuples = maybeNativeTuple <$> tickTuples
79 |
80 | nativeTuples :: Array (Tuple NativeSelection x)
81 | nativeTuples = foldl foldFn [] maybeNativeTuples
82 |
83 | foldFn list (Tuple (Just a) b) = (Tuple a b) : list
84 | foldFn list (Tuple Nothing _) = list
85 |
86 | addAttrFnToTick :: forall model. Tuple NativeSelection Attr -> D3 model Unit
87 | addAttrFnToTick (Tuple selection attr) = pure $ addAttrFnToTickJS selection attr
88 |
89 | interpretTickMap :: forall model. NativeSelection -> TickMap model -> D3 model Unit
90 | interpretTickMap simulation tickMap = do
91 | (Context model scope) <- get
92 | -- [(label,attrs)] -> [(nativeselection, attr)] so as enable build up of list on JS side
93 | let
94 | attrs :: Array (Tuple NativeSelection Attr)
95 | attrs = concatMap (\(Tuple x ys) -> (Tuple x) <$> ys) (getNativeSelection scope tickMap)
96 | -- TODO pending better solution will pass Attr (purescript type) over FFI and decode there
97 | traverse_ addAttrFnToTick attrs
98 | pure $ attachTickFnToSimulationJS simulation
99 |
100 | interpretDrag :: forall model. DragBehavior -> D3 model Unit
101 | interpretDrag (DefaultDrag selectionName simulationName) = do
102 | (Context model scope) <- get
103 | let selection = lookup selectionName scope
104 | let simulation = lookup simulationName scope
105 | pure $ case selection, simulation of
106 | (Just sel), (Just sim) -> attachDefaultDragBehaviorJS sel sim
107 | _, _ -> unit
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/examples/NewSyntax/Tree.purs:
--------------------------------------------------------------------------------
1 | module NewSyntax.Tree (
2 | Model, TreeConfig -- TODO move to Base
3 | , Tree(..), TreeJson -- NB no constructor
4 | , chart
5 | , readModelFromFileContents -- read the tree structure
6 | , makeModel -- post process tree structure such that it can be used to render using chart
7 | ) where
8 |
9 | import D3.Base
10 |
11 | import Affjax (Error)
12 | import D3.Base.Attributes (LineJoin(..), strokeLineJoin, toString, transform)
13 | import D3.Layout.Trees (radialLink)
14 | import Data.Either (Either(..))
15 | import Data.Tuple (Tuple(..))
16 | import Debug.Trace (spy)
17 | import Math (pi)
18 | import Prelude (negate, show, ($), (*), (-), (/), (<), (<>), (==), (>=))
19 | import Unsafe.Coerce (unsafeCoerce)
20 |
21 | type ModelData = { name :: String }
22 | -- the Model given to D3
23 | type MyModel = Model ModelData
24 | -- nodes of tree AFTER D3 has processed them, contains original ModelData as "data" field
25 | type MyTreeNode = D3TreeNode ModelData
26 |
27 | makeModel :: Number -> TreeJson -> Model String
28 | makeModel width json = Model { json, d3Tree, config }
29 | where
30 | config = radialTreeConfig width
31 | hierarchicalData = d3Hierarchy json
32 | d3Tree = d3InitTree config hierarchicalData
33 |
34 | -- Projection functions to get subModels out of the Model for sub-selections
35 | linkData :: Projection (Model String)
36 | linkData (Model m) = d3HierarchyLinks m.d3Tree
37 |
38 | descendentsData :: Projection (Model String)
39 | descendentsData (Model m) = d3HierarchyDescendants m.d3Tree
40 |
41 | -- we give the chart our Model type but behind the scenes it is mutated by D3 and additionally
42 | -- which projection of the "Model" is active in each Join varies so we can't have both strong
43 | -- static type representations AND lightweight syntax with JS compatible lambdas
44 | d3TreeNode :: Datum -> MyTreeNode
45 | d3TreeNode = unsafeCoerce
46 |
47 | chart :: Tuple Number Number -> Selection (Model String)
48 | chart (Tuple width height) =
49 | let
50 | origin = {
51 | x: -width / 2.0
52 | , y: -height / 2.0
53 | }
54 | -- three little transform functions to build up the transforms on nodes and labels
55 | rotate x = show $ (x * 180.0 / pi - 90.0)
56 | rotateCommon d = "rotate(" <> rotate (d3TreeNode d).x <> ")"
57 | rotateText2 d = "rotate(" <> if (d3TreeNode d).x >= pi
58 | then "180" <> ")"
59 | else "0" <> ")"
60 | -- same translation for both text and node
61 | translate d = "translate(" <> show (d3TreeNode d).y <> ",0)"
62 |
63 | labelOffset :: Datum -> Number
64 | labelOffset d =
65 | if ((d3TreeNode d).x < pi) == hasChildren d
66 | then 6.0
67 | else (-6.0)
68 |
69 | textOffset :: Datum -> String
70 | textOffset d =
71 | if ((d3TreeNode d).x < pi) == hasChildren d
72 | then "start"
73 | else "end"
74 |
75 | in
76 | selectInDOM "div#tree" [] $ [
77 | svg [ viewBox origin.x origin.y width height ] [
78 | group_
79 | [ joinUsing linkData $
80 | Enter Path $
81 | Attrs [ strokeWidth 1.5 Px
82 | , strokeColor "#555"
83 | , strokeOpacity 0.4
84 | , fill "none"
85 | , radialLink (\d -> (d3TreeNode d).x) (\d -> (d3TreeNode d).y)
86 | ]
87 | ]
88 |
89 | , group_
90 | [ joinUsing descendentsData $
91 | Enter Circle $
92 | Attrs [ radius 2.5 Px
93 | , computeFill (\d -> if hasChildren d then "#555" else "#999")
94 | , transform [ rotateCommon, translate ]
95 | ]
96 | ]
97 |
98 | , group [ fontFamily "sans-serif"
99 | , fontSize 10.0 Pt
100 | , strokeLineJoin Round
101 | , strokeWidth 3.0 Px ]
102 | [ joinUsing descendentsData $
103 | Enter Text $
104 | Attrs [ transform [ rotateCommon, translate, rotateText2]
105 | , dy 0.31 Em
106 | , computeX Px labelOffset -- TODO this expression is clumsy
107 | , computeTextAnchor textOffset
108 | , computeText (\d -> (d3TreeNode d).data.name)
109 | -- TODO add clone step later, perhaps branch of Selection?
110 | ]
111 | ]
112 | ]
113 | ]
114 |
115 | -- | TODO All this stuff belongs eventually in the d3 base
116 | data Tree a = Node a (Array (Tree a))
117 | type TreeConfig :: forall k. k -> Type
118 | type TreeConfig a = {
119 | size :: Array Number
120 | , separation :: Datum -> Datum -> Int
121 | }
122 |
123 | radialTreeConfig :: forall a. Number -> TreeConfig a
124 | radialTreeConfig width =
125 | { size: [2.0 * pi, width / 2.0]
126 | , separation: radialSeparationJS
127 | }
128 |
129 | data Model :: forall k. k -> Type
130 | data Model a = Model {
131 | json :: TreeJson
132 | , d3Tree :: D3Tree
133 | , config :: TreeConfig a
134 | }
135 |
136 | type D3TreeNode a = {
137 | "data" :: a -- guaranteed coercible to the `a` of the `Model a`
138 | , x :: Number
139 | , y :: Number
140 | , value :: String
141 | , depth :: Number
142 | , height :: Number
143 | -- these next too are guaranteed coercible to the same type, ie D3TreeNode
144 | -- BUT ONLY IF the D3Tree is a successful conversion using d3Hierarchy
145 | -- TODO code out exceptions
146 | , parent :: RecursiveD3TreeNode -- this won't be present in the root node
147 | , children :: Array RecursiveD3TreeNode -- this won't be present in leaf nodes
148 | }
149 |
150 | -- do the decode on the Purescript side unless files are ginormous, this is just for prototyping
151 | -- this is an opaque type behind which hides the data type of the Purescript tree that was converted
152 | foreign import data RecursiveD3TreeNode :: Type
153 | -- this is the Purescript Tree after processing in JS to remove empty child fields from leaves etc
154 | -- need to ensure that this structure is encapsulated in libraries (ie by moving this code)
155 | foreign import data D3Tree :: Type
156 | foreign import data D3Hierarchical :: Type
157 | foreign import data TreeJson :: Type
158 | foreign import radialSeparationJS :: Datum -> Datum -> Int
159 | foreign import readJSONJS :: String -> TreeJson -- TODO no error handling at all here RN
160 | foreign import d3Hierarchy :: TreeJson -> D3Hierarchical
161 | foreign import d3InitTree :: forall a. TreeConfig a -> D3Hierarchical -> D3Tree
162 | foreign import hasChildren :: Datum -> Boolean
163 |
164 | foreign import d3HierarchyLinks :: D3Tree -> SubModel
165 | foreign import d3HierarchyDescendants :: D3Tree -> SubModel
166 |
167 |
168 |
169 | -- TODO read file elsewhere and pass it in
170 | readModelFromFileContents :: forall r. Tuple Number Number -> Either Error { body ∷ String | r } -> Either Error (Model String)
171 | readModelFromFileContents (Tuple width _) (Right { body } ) = Right $ makeModel width (readJSONJS body)
172 | readModelFromFileContents _ (Left error) = Left error
173 |
174 |
--------------------------------------------------------------------------------
/src/D3/Base/Attributes.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Attributes where
2 |
3 | import D3.Base.Foreign (Datum)
4 | import Data.Array (intercalate)
5 | import Prelude (class Show, flap, show, ($), (<>))
6 |
7 | -- internal definitions of Attrs, this is what the interpreter will work with
8 | -- these definitions correspond exactly to what D3 allows polymorphically for attribute setters
9 | -- the idea is to NOT make this available outside this module eventually, preferring to use
10 | -- stronger typing and more expressive functions that wrap them, as below
11 | -- HOWEVER it might be that there are just too many to be practical, time will tell
12 | data Attr =
13 | -- first the direct, static attributes
14 | StaticString String String
15 | | StaticNumber String Number
16 | | StaticArrayNumber String (Array Number)
17 | -- then the ones that are function of Datum only
18 | | StringAttr String (Datum -> String)
19 | | NumberAttr String (Datum -> Number)
20 | | ArrayNumberAttr String (Datum -> Array Number)
21 | -- lastly attribute functions that take Datum and the index
22 | | StringAttrI String (Datum -> Number -> String)
23 | | NumberAttrI String (Datum -> Number -> Number)
24 | | ArrayNumberAttrI String (Datum -> Number -> Array Number)
25 | -- Text in D3 is not an attribute in D3 but syntactically and semantically it really is
26 | -- so in our DSL we will just make it one and hide that fact
27 | | TextAttr (Datum -> String)
28 | -- Transition is also not an attribute in D3 but in this DSL it is
29 | -- TODO there may be real difficulties with this with the attributes that are inherited from Selection
30 | -- that's not clear right now
31 | -- | Transition TransitionAttributes
32 |
33 | -- many things that are attached to Transition in D3 will simply revert to Selection
34 | data TransitionAttributes = Duration Int | Filter | Remove -- nowhere close to exhaustive
35 |
36 | data NumberUnit = Em | Px | Rem | Percent | Pt | NoUnit
37 |
38 | -- prettier definitions for attributes
39 | -- TODO we're not doing anything to check / constrain the semantics of the units here, but it would be good to do so (type-level machinery?)
40 | -- TODO do we need more units like "1fr" etc for grids etc?
41 |
42 | toString :: Number -> NumberUnit -> String
43 | toString n =
44 | case _ of
45 | Em -> show n <> "em"
46 | Px -> show n <> "px"
47 | Rem -> show n <> "rem"
48 | Percent -> show n <> "%"
49 | Pt -> show n <> "pt"
50 | NoUnit -> show n
51 |
52 | staticNumberAttrWithUnits :: String -> Number -> NumberUnit -> Attr
53 | staticNumberAttrWithUnits label n u = StaticString label (toString n u)
54 |
55 | datumToStringWithUnit :: NumberUnit -> (Datum -> Number) -> (Datum -> String)
56 | datumToStringWithUnit u f = (\d -> toString (f d) u )
57 |
58 | datumAndIndexToStringWithUnit :: NumberUnit -> (Datum -> Number -> Number) -> (Datum -> Number -> String)
59 | datumAndIndexToStringWithUnit u f = (\d i -> toString (f d i) u )
60 |
61 | -- | static versions of attribute setters, where all selected elements will be given the same value for this attribute
62 | strokeColor :: String -> Attr
63 | strokeColor = StaticString "stroke"
64 |
65 | strokeOpacity :: Number -> Attr
66 | strokeOpacity = StaticNumber "stroke-opacity"
67 |
68 | fill :: String -> Attr
69 | fill = StaticString "fill"
70 |
71 | viewBox :: Number -> Number -> Number -> Number -> Attr
72 | viewBox xo yo width height = StaticArrayNumber "viewBox" [ xo, yo, width, height ]
73 |
74 | fontFamily :: String -> Attr
75 | fontFamily = StaticString "font-family"
76 |
77 | textAnchor :: String -> Attr
78 | textAnchor = StaticString "text-anchor"
79 |
80 | strokeWidth :: Number -> NumberUnit -> Attr
81 | strokeWidth n u = staticNumberAttrWithUnits "stroke-width" n u
82 |
83 | radius :: Number -> NumberUnit -> Attr
84 | radius n u = staticNumberAttrWithUnits "r" n u
85 |
86 | fontSize :: Number -> NumberUnit -> Attr
87 | fontSize n u = staticNumberAttrWithUnits "font-size" n u
88 |
89 | width :: Number -> NumberUnit -> Attr
90 | width n u = staticNumberAttrWithUnits "width" n u
91 |
92 | height :: Number -> NumberUnit -> Attr
93 | height n u = staticNumberAttrWithUnits "height" n u
94 |
95 | x :: Number -> NumberUnit -> Attr
96 | x n u = staticNumberAttrWithUnits "x" n u
97 |
98 | y :: Number -> NumberUnit -> Attr
99 | y n u = staticNumberAttrWithUnits "y" n u
100 |
101 | dx :: Number -> NumberUnit -> Attr
102 | dx n u = staticNumberAttrWithUnits "dx" n u
103 |
104 | dy :: Number -> NumberUnit -> Attr
105 | dy n u = staticNumberAttrWithUnits "dy" n u
106 |
107 | -- | computed versions of attribute setters, which need a function because the attribute is determined by the datum
108 | computeStrokeColor :: (Datum -> String) -> Attr
109 | computeStrokeColor = StringAttr "stroke"
110 |
111 | computeFill :: (Datum -> String) -> Attr
112 | computeFill = StringAttr "fill"
113 |
114 | computeText :: (Datum -> String) -> Attr
115 | computeText = TextAttr
116 |
117 | computeTextAnchor :: (Datum -> String) -> Attr
118 | computeTextAnchor = StringAttr "text-anchor"
119 |
120 | computeStrokeWidth :: NumberUnit -> (Datum -> Number) -> Attr
121 | computeStrokeWidth u f = StringAttr "stroke-width" (datumToStringWithUnit u f)
122 |
123 | computeStrokeOpacity :: (Datum -> Number) -> Attr
124 | computeStrokeOpacity = NumberAttr "stroke-opacity"
125 |
126 | computeRadius :: NumberUnit -> (Datum -> Number) -> Attr
127 | computeRadius u f = StringAttr "r" (datumToStringWithUnit u f)
128 |
129 | computeX :: NumberUnit -> (Datum -> Number) -> Attr
130 | computeX u f = StringAttr "x" (datumToStringWithUnit u f)
131 |
132 | computeY :: NumberUnit -> (Datum -> Number) -> Attr
133 | computeY u f = StringAttr "y" (datumToStringWithUnit u f)
134 |
135 | computeDX :: NumberUnit -> (Datum -> Number) -> Attr
136 | computeDX u f = StringAttr "dx" (datumToStringWithUnit u f)
137 |
138 | computeDY :: NumberUnit -> (Datum -> Number) -> Attr
139 | computeDY u f = StringAttr "dy" (datumToStringWithUnit u f)
140 |
141 | -- TODO refactor all these functions that differ only in attr "string" when finished
142 | computeXusingIndex :: NumberUnit -> (Datum -> Number -> Number) -> Attr
143 | computeXusingIndex u f = StringAttrI "x" (datumAndIndexToStringWithUnit u f)
144 |
145 | computeYusingIndex :: NumberUnit -> (Datum -> Number -> Number) -> Attr
146 | computeYusingIndex u f = StringAttrI "y" (datumAndIndexToStringWithUnit u f)
147 |
148 | data LineJoin = Arcs | Bevel | Miter | MiterClip | Round
149 | instance showLineJoin :: Show LineJoin where
150 | show Arcs = "arcs"
151 | show Bevel = "bevel"
152 | show Miter = "miter"
153 | show MiterClip = "miter-clip"
154 | show Round = "round"
155 |
156 | strokeLineJoin :: LineJoin -> Attr
157 | strokeLineJoin linejoin = StaticString "stroke-linejoin" $ show linejoin
158 |
159 | type TransformFn = Datum -> String
160 |
161 | transform :: Array TransformFn -> Attr
162 | transform fs = StringAttr "transform" (\d -> showTransform d)
163 | where
164 | showTransform d = intercalate " " $ flap fs d
165 |
166 | -- | Show instance etc
167 |
168 | bracket :: Array String -> String
169 | bracket ss = "(" <>
170 | (intercalate "," ss)
171 | <> ")"
172 |
173 | prefix :: String -> String
174 | prefix s = "\n.attr" <> s
175 |
176 | enQuote :: String -> String
177 | enQuote string = "\"" <> string <> "\""
178 |
179 | instance showAttribute :: Show Attr where
180 | show (StaticString a v) = prefix $ bracket $ [ enQuote a, enQuote v]
181 | show (StaticNumber a v) = prefix $ bracket $ [ enQuote a, enQuote $ show v ]
182 | show (StaticArrayNumber a v) = prefix $ bracket $ [ enQuote a, enQuote $ show v ]
183 | show (TextAttr fn) = prefix $ bracket $ [ enQuote "Text", "<\\d -> result>" ]
184 | show (StringAttr a fn) = prefix $ bracket $ [ enQuote a, "<\\d -> result>" ]
185 | show (NumberAttr a fn) = prefix $ bracket $ [ enQuote a, "<\\d -> result>" ]
186 | show (ArrayNumberAttr a fn) = prefix $ bracket $ [ enQuote a, "<\\d -> result>" ]
187 | show (StringAttrI a fn) = prefix $ bracket $ [ enQuote a, "<\\d i -> result>" ]
188 | show (NumberAttrI a fn) = prefix $ bracket $ [ enQuote a, "<\\d i -> result>" ]
189 | show (ArrayNumberAttrI a fn) = prefix $ bracket $ [ enQuote a, "<\\d i -> result>" ]
190 | -- show (Transition attrs) = "no proper show instance for Transition yet"
191 |
--------------------------------------------------------------------------------
/src/D3/Base/Selection.purs:
--------------------------------------------------------------------------------
1 | module D3.Base.Selection where
2 |
3 | import D3.Base.Attributes (Attr)
4 | import D3.Base.Foreign (SubModel)
5 | import Prelude (class Show, ($), (<>))
6 |
7 | import Data.Maybe (Maybe(..))
8 | import Data.Array (singleton)
9 |
10 | type Label = String
11 | type Selector = String
12 | data Element = Svg | Group | Div | Line | Circle | Path | Text | Table | Td | Tr
13 |
14 | type Projection model = model -> SubModel
15 |
16 | -- | Types to represent Selection and Insertion
17 | -- | you can append a list of many (different) elements
18 | -- | or an entire selection of only one type of element bound to some data
19 | -- | Selection will i guess be an indexed monad? in order to move back and forth
20 | -- | between Selection/Transition
21 | data Selection model =
22 | InitialSelect {
23 | selector :: String
24 | , attributes :: Array Attr
25 | , children :: Array (Selection model)
26 | }
27 |
28 | | Append {
29 | element :: Element
30 | , children :: Array (Selection model)
31 | , attributes :: Array Attr
32 | }
33 |
34 | | Join { -- enter with option update and/or exit enter will append the element, exit will terminate with remove
35 | element :: Element
36 | , selections :: { enter :: Selection model
37 | , update :: Maybe (Selection model) -- would have to be an Update
38 | , exit :: Maybe (Selection model) } -- would have to be an Exit
39 | , projection :: Maybe (Projection model)
40 | }
41 |
42 | | Update {
43 | children :: Array (Selection model)
44 | , attributes :: Array Attr
45 | }
46 |
47 | | Exit {
48 | children :: Array (Selection model)
49 | , attributes :: Array Attr
50 | }
51 | -- TODO can Transition, Call, Clone and other things be part of Selection?
52 |
53 | -- placeholder for a selection that isn't determined til runtime
54 | -- and which must be looked up
55 | | RunTimeSelection String (Selection model)
56 | | NullSelection -- used for optional Join functions
57 |
58 | selectInDOM :: forall model. Selector -> Array Attr -> Array (Selection model) -> Selection model
59 | selectInDOM selector attributes children =
60 | InitialSelect { selector, attributes, children }
61 |
62 | append :: forall model. Element -> Array Attr -> Array (Selection model) -> Selection model
63 | append element attributes children =
64 | Append { element, attributes, children }
65 |
66 | append_ :: forall model. Element -> Array Attr -> Selection model
67 | append_ element attributes =
68 | Append { element, attributes, children: [] }
69 |
70 | modifySelection :: forall model. String -> Selection model -> Selection model
71 | modifySelection name b = RunTimeSelection name b
72 |
73 |
74 | data JoinParameters model =
75 | AttrsAndChildren (Array Attr) (Array (Selection model))
76 | | AttrsAndChild (Array Attr) (Selection model)
77 | | Attrs (Array Attr)
78 | | Children (Array (Selection model))
79 | | Child (Selection model)
80 |
81 | data OnJoin model =
82 | Enter Element (JoinParameters model)
83 | | EnterUpdate Element (JoinParameters model) (JoinParameters model)
84 | | EnterExit Element (JoinParameters model) (JoinParameters model)
85 | | EnterUpdateExit Element (JoinParameters model) (JoinParameters model) (JoinParameters model)
86 |
87 | -- underlying function for all the enter variations
88 | join_ :: forall model. Maybe (Projection model) -> OnJoin model -> Selection model
89 | join_ projection =
90 | case _ of
91 | (Enter element p) ->
92 | Join { element
93 | , projection
94 | , selections: { enter: makeEnterSelection element p
95 | , update: Nothing
96 | , exit: Nothing }
97 | }
98 |
99 | (EnterUpdate element pe pu) ->
100 | Join { element
101 | , projection
102 | , selections: { enter: makeEnterSelection element pe
103 | , update: Just (makeUpdateSelection pu)
104 | , exit: Nothing }
105 | }
106 |
107 | (EnterExit element pe px) ->
108 | Join { element
109 | , projection
110 | , selections: { enter: makeEnterSelection element pe
111 | , update: Nothing
112 | , exit: Just (makeExitSelection px) }
113 | }
114 |
115 | (EnterUpdateExit element pe pu px) ->
116 | Join { element
117 | , projection
118 | , selections: { enter: makeEnterSelection element pe
119 | , update: Just (makeUpdateSelection pu)
120 | , exit: Just (makeExitSelection px) }
121 | }
122 |
123 |
124 | join :: forall model. OnJoin model -> Selection model
125 | join onJoin = join_ Nothing onJoin
126 |
127 | joinUsing :: forall model. Projection model -> OnJoin model -> Selection model
128 | joinUsing projection onJoin = join_ (Just projection) onJoin
129 |
130 | makeEnterSelection :: forall model. Element -> JoinParameters model -> Selection model
131 | makeEnterSelection element =
132 | case _ of
133 | (AttrsAndChildren attributes children) -> Append { element, attributes, children }
134 | (AttrsAndChild attributes child) -> Append { element, attributes, children: [child] }
135 | (Children children) -> Append { element, attributes: [], children }
136 | (Child child) -> Append { element, attributes: [], children: [child] }
137 | (Attrs attributes) -> Append { element, attributes, children: [] }
138 |
139 | makeUpdateSelection :: forall model. JoinParameters model -> Selection model
140 | makeUpdateSelection =
141 | case _ of
142 | (AttrsAndChildren attributes children) -> Update { attributes, children }
143 | (AttrsAndChild attributes child) -> Update { attributes, children: [child] }
144 | (Children children) -> Update { attributes: [], children }
145 | (Child child) -> Update { attributes: [], children: [child] }
146 | (Attrs attributes) -> Update { attributes, children: [] }
147 |
148 | makeExitSelection :: forall model. JoinParameters model -> Selection model
149 | makeExitSelection =
150 | case _ of
151 | (AttrsAndChildren attributes children) -> Exit { attributes, children }
152 | (AttrsAndChild attributes child) -> Exit { attributes, children: [child] }
153 | (Children children) -> Exit { attributes: [], children }
154 | (Child child) -> Exit { attributes: [], children: [child] }
155 | (Attrs attributes) -> Exit { attributes, children: [] }
156 |
157 |
158 |
159 |
160 | -- TODO rewrite the show instance once Selection ADT settles down
161 | -- | Show instance etc
162 |
163 | -- instance showSelection :: Show (Selection a) where
164 | -- show (InitialSelect r) = "d3.selectAll(\"" <> r.selector <> "\")" <> " name: " <> show r.label <> " " <> show r.attributes <> show r.children
165 | -- show (Append r) =
166 | -- let prefix = case r.label of
167 | -- (Just name) -> "const " <> name <> " = "
168 | -- Nothing -> "\n"
169 | -- in prefix <> ".append(\"" <> show r.element <> "\")" <> show r.attributes <> show r.children
170 | -- show (Join r) = "Join" <>
171 | -- show r.enterUpdateExit.enter <>
172 | -- show r.enterUpdateExit.update <>
173 | -- show r.enterUpdateExit.exit
174 | -- show (Transition r) =
175 | -- let prefix = case r.label of
176 | -- (Just name) -> "const " <> name <> " = "
177 | -- Nothing -> "\n"
178 | -- in prefix <> ".transition(\"" <> show r.duration <> "\")" <> show r.attributes
179 | -- show NullSelection = ""
180 | -- show (RunTimeSelection name selection) =
181 |
182 | instance showElement :: Show Element where
183 | show Svg = "svg"
184 | show Group = "g"
185 | show Div = "div"
186 | show Line = "line"
187 | show Circle = "circle"
188 | show Path = "path"
189 | show Text = "text"
190 | show Table = "table"
191 | show Td = "td"
192 | show Tr = "tr"
193 |
--------------------------------------------------------------------------------
/dist/flare-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flare",
3 | "children": [
4 | {
5 | "name": "analytics",
6 | "children": [
7 | {
8 | "name": "cluster",
9 | "children": [
10 | {"name": "AgglomerativeCluster", "value": 3938},
11 | {"name": "CommunityStructure", "value": 3812},
12 | {"name": "HierarchicalCluster", "value": 6714},
13 | {"name": "MergeEdge", "value": 743}
14 | ]
15 | },
16 | {
17 | "name": "graph",
18 | "children": [
19 | {"name": "BetweennessCentrality", "value": 3534},
20 | {"name": "LinkDistance", "value": 5731},
21 | {"name": "MaxFlowMinCut", "value": 7840},
22 | {"name": "ShortestPaths", "value": 5914},
23 | {"name": "SpanningTree", "value": 3416}
24 | ]
25 | },
26 | {
27 | "name": "optimization",
28 | "children": [
29 | {"name": "AspectRatioBanker", "value": 7074}
30 | ]
31 | }
32 | ]
33 | },
34 | {
35 | "name": "animate",
36 | "children": [
37 | {"name": "Easing", "value": 17010},
38 | {"name": "FunctionSequence", "value": 5842},
39 | {
40 | "name": "interpolate",
41 | "children": [
42 | {"name": "ArrayInterpolator", "value": 1983},
43 | {"name": "ColorInterpolator", "value": 2047},
44 | {"name": "DateInterpolator", "value": 1375},
45 | {"name": "Interpolator", "value": 8746},
46 | {"name": "MatrixInterpolator", "value": 2202},
47 | {"name": "NumberInterpolator", "value": 1382},
48 | {"name": "ObjectInterpolator", "value": 1629},
49 | {"name": "PointInterpolator", "value": 1675},
50 | {"name": "RectangleInterpolator", "value": 2042}
51 | ]
52 | },
53 | {"name": "ISchedulable", "value": 1041},
54 | {"name": "Parallel", "value": 5176},
55 | {"name": "Pause", "value": 449},
56 | {"name": "Scheduler", "value": 5593},
57 | {"name": "Sequence", "value": 5534},
58 | {"name": "Transition", "value": 9201},
59 | {"name": "Transitioner", "value": 19975},
60 | {"name": "TransitionEvent", "value": 1116},
61 | {"name": "Tween", "value": 6006}
62 | ]
63 | },
64 | {
65 | "name": "data",
66 | "children": [
67 | {
68 | "name": "converters",
69 | "children": [
70 | {"name": "Converters", "value": 721},
71 | {"name": "DelimitedTextConverter", "value": 4294},
72 | {"name": "GraphMLConverter", "value": 9800},
73 | {"name": "IDataConverter", "value": 1314},
74 | {"name": "JSONConverter", "value": 2220}
75 | ]
76 | },
77 | {"name": "DataField", "value": 1759},
78 | {"name": "DataSchema", "value": 2165},
79 | {"name": "DataSet", "value": 586},
80 | {"name": "DataSource", "value": 3331},
81 | {"name": "DataTable", "value": 772},
82 | {"name": "DataUtil", "value": 3322}
83 | ]
84 | },
85 | {
86 | "name": "display",
87 | "children": [
88 | {"name": "DirtySprite", "value": 8833},
89 | {"name": "LineSprite", "value": 1732},
90 | {"name": "RectSprite", "value": 3623},
91 | {"name": "TextSprite", "value": 10066}
92 | ]
93 | },
94 | {
95 | "name": "flex",
96 | "children": [
97 | {"name": "FlareVis", "value": 4116}
98 | ]
99 | },
100 | {
101 | "name": "physics",
102 | "children": [
103 | {"name": "DragForce", "value": 1082},
104 | {"name": "GravityForce", "value": 1336},
105 | {"name": "IForce", "value": 319},
106 | {"name": "NBodyForce", "value": 10498},
107 | {"name": "Particle", "value": 2822},
108 | {"name": "Simulation", "value": 9983},
109 | {"name": "Spring", "value": 2213},
110 | {"name": "SpringForce", "value": 1681}
111 | ]
112 | },
113 | {
114 | "name": "query",
115 | "children": [
116 | {"name": "AggregateExpression", "value": 1616},
117 | {"name": "And", "value": 1027},
118 | {"name": "Arithmetic", "value": 3891},
119 | {"name": "Average", "value": 891},
120 | {"name": "BinaryExpression", "value": 2893},
121 | {"name": "Comparison", "value": 5103},
122 | {"name": "CompositeExpression", "value": 3677},
123 | {"name": "Count", "value": 781},
124 | {"name": "DateUtil", "value": 4141},
125 | {"name": "Distinct", "value": 933},
126 | {"name": "Expression", "value": 5130},
127 | {"name": "ExpressionIterator", "value": 3617},
128 | {"name": "Fn", "value": 3240},
129 | {"name": "If", "value": 2732},
130 | {"name": "IsA", "value": 2039},
131 | {"name": "Literal", "value": 1214},
132 | {"name": "Match", "value": 3748},
133 | {"name": "Maximum", "value": 843},
134 | {
135 | "name": "methods",
136 | "children": [
137 | {"name": "add", "value": 593},
138 | {"name": "and", "value": 330},
139 | {"name": "average", "value": 287},
140 | {"name": "count", "value": 277},
141 | {"name": "distinct", "value": 292},
142 | {"name": "div", "value": 595},
143 | {"name": "eq", "value": 594},
144 | {"name": "fn", "value": 460},
145 | {"name": "gt", "value": 603},
146 | {"name": "gte", "value": 625},
147 | {"name": "iff", "value": 748},
148 | {"name": "isa", "value": 461},
149 | {"name": "lt", "value": 597},
150 | {"name": "lte", "value": 619},
151 | {"name": "max", "value": 283},
152 | {"name": "min", "value": 283},
153 | {"name": "mod", "value": 591},
154 | {"name": "mul", "value": 603},
155 | {"name": "neq", "value": 599},
156 | {"name": "not", "value": 386},
157 | {"name": "or", "value": 323},
158 | {"name": "orderby", "value": 307},
159 | {"name": "range", "value": 772},
160 | {"name": "select", "value": 296},
161 | {"name": "stddev", "value": 363},
162 | {"name": "sub", "value": 600},
163 | {"name": "sum", "value": 280},
164 | {"name": "update", "value": 307},
165 | {"name": "variance", "value": 335},
166 | {"name": "where", "value": 299},
167 | {"name": "xor", "value": 354},
168 | {"name": "_", "value": 264}
169 | ]
170 | },
171 | {"name": "Minimum", "value": 843},
172 | {"name": "Not", "value": 1554},
173 | {"name": "Or", "value": 970},
174 | {"name": "Query", "value": 13896},
175 | {"name": "Range", "value": 1594},
176 | {"name": "StringUtil", "value": 4130},
177 | {"name": "Sum", "value": 791},
178 | {"name": "Variable", "value": 1124},
179 | {"name": "Variance", "value": 1876},
180 | {"name": "Xor", "value": 1101}
181 | ]
182 | },
183 | {
184 | "name": "scale",
185 | "children": [
186 | {"name": "IScaleMap", "value": 2105},
187 | {"name": "LinearScale", "value": 1316},
188 | {"name": "LogScale", "value": 3151},
189 | {"name": "OrdinalScale", "value": 3770},
190 | {"name": "QuantileScale", "value": 2435},
191 | {"name": "QuantitativeScale", "value": 4839},
192 | {"name": "RootScale", "value": 1756},
193 | {"name": "Scale", "value": 4268},
194 | {"name": "ScaleType", "value": 1821},
195 | {"name": "TimeScale", "value": 5833}
196 | ]
197 | },
198 | {
199 | "name": "util",
200 | "children": [
201 | {"name": "Arrays", "value": 8258},
202 | {"name": "Colors", "value": 10001},
203 | {"name": "Dates", "value": 8217},
204 | {"name": "Displays", "value": 12555},
205 | {"name": "Filter", "value": 2324},
206 | {"name": "Geometry", "value": 10993},
207 | {
208 | "name": "heap",
209 | "children": [
210 | {"name": "FibonacciHeap", "value": 9354},
211 | {"name": "HeapNode", "value": 1233}
212 | ]
213 | },
214 | {"name": "IEvaluable", "value": 335},
215 | {"name": "IPredicate", "value": 383},
216 | {"name": "IValueProxy", "value": 874},
217 | {
218 | "name": "math",
219 | "children": [
220 | {"name": "DenseMatrix", "value": 3165},
221 | {"name": "IMatrix", "value": 2815},
222 | {"name": "SparseMatrix", "value": 3366}
223 | ]
224 | },
225 | {"name": "Maths", "value": 17705},
226 | {"name": "Orientation", "value": 1486},
227 | {
228 | "name": "palette",
229 | "children": [
230 | {"name": "ColorPalette", "value": 6367},
231 | {"name": "Palette", "value": 1229},
232 | {"name": "ShapePalette", "value": 2059},
233 | {"name": "SizePalette", "value": 2291}
234 | ]
235 | },
236 | {"name": "Property", "value": 5559},
237 | {"name": "Shapes", "value": 19118},
238 | {"name": "Sort", "value": 6887},
239 | {"name": "Stats", "value": 6557},
240 | {"name": "Strings", "value": 22026}
241 | ]
242 | },
243 | {
244 | "name": "vis",
245 | "children": [
246 | {
247 | "name": "axis",
248 | "children": [
249 | {"name": "Axes", "value": 1302},
250 | {"name": "Axis", "value": 24593},
251 | {"name": "AxisGridLine", "value": 652},
252 | {"name": "AxisLabel", "value": 636},
253 | {"name": "CartesianAxes", "value": 6703}
254 | ]
255 | },
256 | {
257 | "name": "controls",
258 | "children": [
259 | {"name": "AnchorControl", "value": 2138},
260 | {"name": "ClickControl", "value": 3824},
261 | {"name": "Control", "value": 1353},
262 | {"name": "ControlList", "value": 4665},
263 | {"name": "DragControl", "value": 2649},
264 | {"name": "ExpandControl", "value": 2832},
265 | {"name": "HoverControl", "value": 4896},
266 | {"name": "IControl", "value": 763},
267 | {"name": "PanZoomControl", "value": 5222},
268 | {"name": "SelectionControl", "value": 7862},
269 | {"name": "TooltipControl", "value": 8435}
270 | ]
271 | },
272 | {
273 | "name": "data",
274 | "children": [
275 | {"name": "Data", "value": 20544},
276 | {"name": "DataList", "value": 19788},
277 | {"name": "DataSprite", "value": 10349},
278 | {"name": "EdgeSprite", "value": 3301},
279 | {"name": "NodeSprite", "value": 19382},
280 | {
281 | "name": "render",
282 | "children": [
283 | {"name": "ArrowType", "value": 698},
284 | {"name": "EdgeRenderer", "value": 5569},
285 | {"name": "IRenderer", "value": 353},
286 | {"name": "ShapeRenderer", "value": 2247}
287 | ]
288 | },
289 | {"name": "ScaleBinding", "value": 11275},
290 | {"name": "Tree", "value": 7147},
291 | {"name": "TreeBuilder", "value": 9930}
292 | ]
293 | },
294 | {
295 | "name": "events",
296 | "children": [
297 | {"name": "DataEvent", "value": 2313},
298 | {"name": "SelectionEvent", "value": 1880},
299 | {"name": "TooltipEvent", "value": 1701},
300 | {"name": "VisualizationEvent", "value": 1117}
301 | ]
302 | },
303 | {
304 | "name": "legend",
305 | "children": [
306 | {"name": "Legend", "value": 20859},
307 | {"name": "LegendItem", "value": 4614},
308 | {"name": "LegendRange", "value": 10530}
309 | ]
310 | },
311 | {
312 | "name": "operator",
313 | "children": [
314 | {
315 | "name": "distortion",
316 | "children": [
317 | {"name": "BifocalDistortion", "value": 4461},
318 | {"name": "Distortion", "value": 6314},
319 | {"name": "FisheyeDistortion", "value": 3444}
320 | ]
321 | },
322 | {
323 | "name": "encoder",
324 | "children": [
325 | {"name": "ColorEncoder", "value": 3179},
326 | {"name": "Encoder", "value": 4060},
327 | {"name": "PropertyEncoder", "value": 4138},
328 | {"name": "ShapeEncoder", "value": 1690},
329 | {"name": "SizeEncoder", "value": 1830}
330 | ]
331 | },
332 | {
333 | "name": "filter",
334 | "children": [
335 | {"name": "FisheyeTreeFilter", "value": 5219},
336 | {"name": "GraphDistanceFilter", "value": 3165},
337 | {"name": "VisibilityFilter", "value": 3509}
338 | ]
339 | },
340 | {"name": "IOperator", "value": 1286},
341 | {
342 | "name": "label",
343 | "children": [
344 | {"name": "Labeler", "value": 9956},
345 | {"name": "RadialLabeler", "value": 3899},
346 | {"name": "StackedAreaLabeler", "value": 3202}
347 | ]
348 | },
349 | {
350 | "name": "layout",
351 | "children": [
352 | {"name": "AxisLayout", "value": 6725},
353 | {"name": "BundledEdgeRouter", "value": 3727},
354 | {"name": "CircleLayout", "value": 9317},
355 | {"name": "CirclePackingLayout", "value": 12003},
356 | {"name": "DendrogramLayout", "value": 4853},
357 | {"name": "ForceDirectedLayout", "value": 8411},
358 | {"name": "IcicleTreeLayout", "value": 4864},
359 | {"name": "IndentedTreeLayout", "value": 3174},
360 | {"name": "Layout", "value": 7881},
361 | {"name": "NodeLinkTreeLayout", "value": 12870},
362 | {"name": "PieLayout", "value": 2728},
363 | {"name": "RadialTreeLayout", "value": 12348},
364 | {"name": "RandomLayout", "value": 870},
365 | {"name": "StackedAreaLayout", "value": 9121},
366 | {"name": "TreeMapLayout", "value": 9191}
367 | ]
368 | },
369 | {"name": "Operator", "value": 2490},
370 | {"name": "OperatorList", "value": 5248},
371 | {"name": "OperatorSequence", "value": 4190},
372 | {"name": "OperatorSwitch", "value": 2581},
373 | {"name": "SortOperator", "value": 2023}
374 | ]
375 | },
376 | {"name": "Visualization", "value": 16540}
377 | ]
378 | }
379 | ]
380 | }
381 |
--------------------------------------------------------------------------------
/dist/miserables.json:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": [
3 | {"id": "Myriel", "group": 1},
4 | {"id": "Napoleon", "group": 1},
5 | {"id": "Mlle.Baptistine", "group": 1},
6 | {"id": "Mme.Magloire", "group": 1},
7 | {"id": "CountessdeLo", "group": 1},
8 | {"id": "Geborand", "group": 1},
9 | {"id": "Champtercier", "group": 1},
10 | {"id": "Cravatte", "group": 1},
11 | {"id": "Count", "group": 1},
12 | {"id": "OldMan", "group": 1},
13 | {"id": "Labarre", "group": 2},
14 | {"id": "Valjean", "group": 2},
15 | {"id": "Marguerite", "group": 3},
16 | {"id": "Mme.deR", "group": 2},
17 | {"id": "Isabeau", "group": 2},
18 | {"id": "Gervais", "group": 2},
19 | {"id": "Tholomyes", "group": 3},
20 | {"id": "Listolier", "group": 3},
21 | {"id": "Fameuil", "group": 3},
22 | {"id": "Blacheville", "group": 3},
23 | {"id": "Favourite", "group": 3},
24 | {"id": "Dahlia", "group": 3},
25 | {"id": "Zephine", "group": 3},
26 | {"id": "Fantine", "group": 3},
27 | {"id": "Mme.Thenardier", "group": 4},
28 | {"id": "Thenardier", "group": 4},
29 | {"id": "Cosette", "group": 5},
30 | {"id": "Javert", "group": 4},
31 | {"id": "Fauchelevent", "group": 0},
32 | {"id": "Bamatabois", "group": 2},
33 | {"id": "Perpetue", "group": 3},
34 | {"id": "Simplice", "group": 2},
35 | {"id": "Scaufflaire", "group": 2},
36 | {"id": "Woman1", "group": 2},
37 | {"id": "Judge", "group": 2},
38 | {"id": "Champmathieu", "group": 2},
39 | {"id": "Brevet", "group": 2},
40 | {"id": "Chenildieu", "group": 2},
41 | {"id": "Cochepaille", "group": 2},
42 | {"id": "Pontmercy", "group": 4},
43 | {"id": "Boulatruelle", "group": 6},
44 | {"id": "Eponine", "group": 4},
45 | {"id": "Anzelma", "group": 4},
46 | {"id": "Woman2", "group": 5},
47 | {"id": "MotherInnocent", "group": 0},
48 | {"id": "Gribier", "group": 0},
49 | {"id": "Jondrette", "group": 7},
50 | {"id": "Mme.Burgon", "group": 7},
51 | {"id": "Gavroche", "group": 8},
52 | {"id": "Gillenormand", "group": 5},
53 | {"id": "Magnon", "group": 5},
54 | {"id": "Mlle.Gillenormand", "group": 5},
55 | {"id": "Mme.Pontmercy", "group": 5},
56 | {"id": "Mlle.Vaubois", "group": 5},
57 | {"id": "Lt.Gillenormand", "group": 5},
58 | {"id": "Marius", "group": 8},
59 | {"id": "BaronessT", "group": 5},
60 | {"id": "Mabeuf", "group": 8},
61 | {"id": "Enjolras", "group": 8},
62 | {"id": "Combeferre", "group": 8},
63 | {"id": "Prouvaire", "group": 8},
64 | {"id": "Feuilly", "group": 8},
65 | {"id": "Courfeyrac", "group": 8},
66 | {"id": "Bahorel", "group": 8},
67 | {"id": "Bossuet", "group": 8},
68 | {"id": "Joly", "group": 8},
69 | {"id": "Grantaire", "group": 8},
70 | {"id": "MotherPlutarch", "group": 9},
71 | {"id": "Gueulemer", "group": 4},
72 | {"id": "Babet", "group": 4},
73 | {"id": "Claquesous", "group": 4},
74 | {"id": "Montparnasse", "group": 4},
75 | {"id": "Toussaint", "group": 5},
76 | {"id": "Child1", "group": 10},
77 | {"id": "Child2", "group": 10},
78 | {"id": "Brujon", "group": 4},
79 | {"id": "Mme.Hucheloup", "group": 8}
80 | ],
81 | "links": [
82 | {"source": "Napoleon", "target": "Myriel", "value": 1},
83 | {"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
84 | {"source": "Mme.Magloire", "target": "Myriel", "value": 10},
85 | {"source": "Mme.Magloire", "target": "Mlle.Baptistine", "value": 6},
86 | {"source": "CountessdeLo", "target": "Myriel", "value": 1},
87 | {"source": "Geborand", "target": "Myriel", "value": 1},
88 | {"source": "Champtercier", "target": "Myriel", "value": 1},
89 | {"source": "Cravatte", "target": "Myriel", "value": 1},
90 | {"source": "Count", "target": "Myriel", "value": 2},
91 | {"source": "OldMan", "target": "Myriel", "value": 1},
92 | {"source": "Valjean", "target": "Labarre", "value": 1},
93 | {"source": "Valjean", "target": "Mme.Magloire", "value": 3},
94 | {"source": "Valjean", "target": "Mlle.Baptistine", "value": 3},
95 | {"source": "Valjean", "target": "Myriel", "value": 5},
96 | {"source": "Marguerite", "target": "Valjean", "value": 1},
97 | {"source": "Mme.deR", "target": "Valjean", "value": 1},
98 | {"source": "Isabeau", "target": "Valjean", "value": 1},
99 | {"source": "Gervais", "target": "Valjean", "value": 1},
100 | {"source": "Listolier", "target": "Tholomyes", "value": 4},
101 | {"source": "Fameuil", "target": "Tholomyes", "value": 4},
102 | {"source": "Fameuil", "target": "Listolier", "value": 4},
103 | {"source": "Blacheville", "target": "Tholomyes", "value": 4},
104 | {"source": "Blacheville", "target": "Listolier", "value": 4},
105 | {"source": "Blacheville", "target": "Fameuil", "value": 4},
106 | {"source": "Favourite", "target": "Tholomyes", "value": 3},
107 | {"source": "Favourite", "target": "Listolier", "value": 3},
108 | {"source": "Favourite", "target": "Fameuil", "value": 3},
109 | {"source": "Favourite", "target": "Blacheville", "value": 4},
110 | {"source": "Dahlia", "target": "Tholomyes", "value": 3},
111 | {"source": "Dahlia", "target": "Listolier", "value": 3},
112 | {"source": "Dahlia", "target": "Fameuil", "value": 3},
113 | {"source": "Dahlia", "target": "Blacheville", "value": 3},
114 | {"source": "Dahlia", "target": "Favourite", "value": 5},
115 | {"source": "Zephine", "target": "Tholomyes", "value": 3},
116 | {"source": "Zephine", "target": "Listolier", "value": 3},
117 | {"source": "Zephine", "target": "Fameuil", "value": 3},
118 | {"source": "Zephine", "target": "Blacheville", "value": 3},
119 | {"source": "Zephine", "target": "Favourite", "value": 4},
120 | {"source": "Zephine", "target": "Dahlia", "value": 4},
121 | {"source": "Fantine", "target": "Tholomyes", "value": 3},
122 | {"source": "Fantine", "target": "Listolier", "value": 3},
123 | {"source": "Fantine", "target": "Fameuil", "value": 3},
124 | {"source": "Fantine", "target": "Blacheville", "value": 3},
125 | {"source": "Fantine", "target": "Favourite", "value": 4},
126 | {"source": "Fantine", "target": "Dahlia", "value": 4},
127 | {"source": "Fantine", "target": "Zephine", "value": 4},
128 | {"source": "Fantine", "target": "Marguerite", "value": 2},
129 | {"source": "Fantine", "target": "Valjean", "value": 9},
130 | {"source": "Mme.Thenardier", "target": "Fantine", "value": 2},
131 | {"source": "Mme.Thenardier", "target": "Valjean", "value": 7},
132 | {"source": "Thenardier", "target": "Mme.Thenardier", "value": 13},
133 | {"source": "Thenardier", "target": "Fantine", "value": 1},
134 | {"source": "Thenardier", "target": "Valjean", "value": 12},
135 | {"source": "Cosette", "target": "Mme.Thenardier", "value": 4},
136 | {"source": "Cosette", "target": "Valjean", "value": 31},
137 | {"source": "Cosette", "target": "Tholomyes", "value": 1},
138 | {"source": "Cosette", "target": "Thenardier", "value": 1},
139 | {"source": "Javert", "target": "Valjean", "value": 17},
140 | {"source": "Javert", "target": "Fantine", "value": 5},
141 | {"source": "Javert", "target": "Thenardier", "value": 5},
142 | {"source": "Javert", "target": "Mme.Thenardier", "value": 1},
143 | {"source": "Javert", "target": "Cosette", "value": 1},
144 | {"source": "Fauchelevent", "target": "Valjean", "value": 8},
145 | {"source": "Fauchelevent", "target": "Javert", "value": 1},
146 | {"source": "Bamatabois", "target": "Fantine", "value": 1},
147 | {"source": "Bamatabois", "target": "Javert", "value": 1},
148 | {"source": "Bamatabois", "target": "Valjean", "value": 2},
149 | {"source": "Perpetue", "target": "Fantine", "value": 1},
150 | {"source": "Simplice", "target": "Perpetue", "value": 2},
151 | {"source": "Simplice", "target": "Valjean", "value": 3},
152 | {"source": "Simplice", "target": "Fantine", "value": 2},
153 | {"source": "Simplice", "target": "Javert", "value": 1},
154 | {"source": "Scaufflaire", "target": "Valjean", "value": 1},
155 | {"source": "Woman1", "target": "Valjean", "value": 2},
156 | {"source": "Woman1", "target": "Javert", "value": 1},
157 | {"source": "Judge", "target": "Valjean", "value": 3},
158 | {"source": "Judge", "target": "Bamatabois", "value": 2},
159 | {"source": "Champmathieu", "target": "Valjean", "value": 3},
160 | {"source": "Champmathieu", "target": "Judge", "value": 3},
161 | {"source": "Champmathieu", "target": "Bamatabois", "value": 2},
162 | {"source": "Brevet", "target": "Judge", "value": 2},
163 | {"source": "Brevet", "target": "Champmathieu", "value": 2},
164 | {"source": "Brevet", "target": "Valjean", "value": 2},
165 | {"source": "Brevet", "target": "Bamatabois", "value": 1},
166 | {"source": "Chenildieu", "target": "Judge", "value": 2},
167 | {"source": "Chenildieu", "target": "Champmathieu", "value": 2},
168 | {"source": "Chenildieu", "target": "Brevet", "value": 2},
169 | {"source": "Chenildieu", "target": "Valjean", "value": 2},
170 | {"source": "Chenildieu", "target": "Bamatabois", "value": 1},
171 | {"source": "Cochepaille", "target": "Judge", "value": 2},
172 | {"source": "Cochepaille", "target": "Champmathieu", "value": 2},
173 | {"source": "Cochepaille", "target": "Brevet", "value": 2},
174 | {"source": "Cochepaille", "target": "Chenildieu", "value": 2},
175 | {"source": "Cochepaille", "target": "Valjean", "value": 2},
176 | {"source": "Cochepaille", "target": "Bamatabois", "value": 1},
177 | {"source": "Pontmercy", "target": "Thenardier", "value": 1},
178 | {"source": "Boulatruelle", "target": "Thenardier", "value": 1},
179 | {"source": "Eponine", "target": "Mme.Thenardier", "value": 2},
180 | {"source": "Eponine", "target": "Thenardier", "value": 3},
181 | {"source": "Anzelma", "target": "Eponine", "value": 2},
182 | {"source": "Anzelma", "target": "Thenardier", "value": 2},
183 | {"source": "Anzelma", "target": "Mme.Thenardier", "value": 1},
184 | {"source": "Woman2", "target": "Valjean", "value": 3},
185 | {"source": "Woman2", "target": "Cosette", "value": 1},
186 | {"source": "Woman2", "target": "Javert", "value": 1},
187 | {"source": "MotherInnocent", "target": "Fauchelevent", "value": 3},
188 | {"source": "MotherInnocent", "target": "Valjean", "value": 1},
189 | {"source": "Gribier", "target": "Fauchelevent", "value": 2},
190 | {"source": "Mme.Burgon", "target": "Jondrette", "value": 1},
191 | {"source": "Gavroche", "target": "Mme.Burgon", "value": 2},
192 | {"source": "Gavroche", "target": "Thenardier", "value": 1},
193 | {"source": "Gavroche", "target": "Javert", "value": 1},
194 | {"source": "Gavroche", "target": "Valjean", "value": 1},
195 | {"source": "Gillenormand", "target": "Cosette", "value": 3},
196 | {"source": "Gillenormand", "target": "Valjean", "value": 2},
197 | {"source": "Magnon", "target": "Gillenormand", "value": 1},
198 | {"source": "Magnon", "target": "Mme.Thenardier", "value": 1},
199 | {"source": "Mlle.Gillenormand", "target": "Gillenormand", "value": 9},
200 | {"source": "Mlle.Gillenormand", "target": "Cosette", "value": 2},
201 | {"source": "Mlle.Gillenormand", "target": "Valjean", "value": 2},
202 | {"source": "Mme.Pontmercy", "target": "Mlle.Gillenormand", "value": 1},
203 | {"source": "Mme.Pontmercy", "target": "Pontmercy", "value": 1},
204 | {"source": "Mlle.Vaubois", "target": "Mlle.Gillenormand", "value": 1},
205 | {"source": "Lt.Gillenormand", "target": "Mlle.Gillenormand", "value": 2},
206 | {"source": "Lt.Gillenormand", "target": "Gillenormand", "value": 1},
207 | {"source": "Lt.Gillenormand", "target": "Cosette", "value": 1},
208 | {"source": "Marius", "target": "Mlle.Gillenormand", "value": 6},
209 | {"source": "Marius", "target": "Gillenormand", "value": 12},
210 | {"source": "Marius", "target": "Pontmercy", "value": 1},
211 | {"source": "Marius", "target": "Lt.Gillenormand", "value": 1},
212 | {"source": "Marius", "target": "Cosette", "value": 21},
213 | {"source": "Marius", "target": "Valjean", "value": 19},
214 | {"source": "Marius", "target": "Tholomyes", "value": 1},
215 | {"source": "Marius", "target": "Thenardier", "value": 2},
216 | {"source": "Marius", "target": "Eponine", "value": 5},
217 | {"source": "Marius", "target": "Gavroche", "value": 4},
218 | {"source": "BaronessT", "target": "Gillenormand", "value": 1},
219 | {"source": "BaronessT", "target": "Marius", "value": 1},
220 | {"source": "Mabeuf", "target": "Marius", "value": 1},
221 | {"source": "Mabeuf", "target": "Eponine", "value": 1},
222 | {"source": "Mabeuf", "target": "Gavroche", "value": 1},
223 | {"source": "Enjolras", "target": "Marius", "value": 7},
224 | {"source": "Enjolras", "target": "Gavroche", "value": 7},
225 | {"source": "Enjolras", "target": "Javert", "value": 6},
226 | {"source": "Enjolras", "target": "Mabeuf", "value": 1},
227 | {"source": "Enjolras", "target": "Valjean", "value": 4},
228 | {"source": "Combeferre", "target": "Enjolras", "value": 15},
229 | {"source": "Combeferre", "target": "Marius", "value": 5},
230 | {"source": "Combeferre", "target": "Gavroche", "value": 6},
231 | {"source": "Combeferre", "target": "Mabeuf", "value": 2},
232 | {"source": "Prouvaire", "target": "Gavroche", "value": 1},
233 | {"source": "Prouvaire", "target": "Enjolras", "value": 4},
234 | {"source": "Prouvaire", "target": "Combeferre", "value": 2},
235 | {"source": "Feuilly", "target": "Gavroche", "value": 2},
236 | {"source": "Feuilly", "target": "Enjolras", "value": 6},
237 | {"source": "Feuilly", "target": "Prouvaire", "value": 2},
238 | {"source": "Feuilly", "target": "Combeferre", "value": 5},
239 | {"source": "Feuilly", "target": "Mabeuf", "value": 1},
240 | {"source": "Feuilly", "target": "Marius", "value": 1},
241 | {"source": "Courfeyrac", "target": "Marius", "value": 9},
242 | {"source": "Courfeyrac", "target": "Enjolras", "value": 17},
243 | {"source": "Courfeyrac", "target": "Combeferre", "value": 13},
244 | {"source": "Courfeyrac", "target": "Gavroche", "value": 7},
245 | {"source": "Courfeyrac", "target": "Mabeuf", "value": 2},
246 | {"source": "Courfeyrac", "target": "Eponine", "value": 1},
247 | {"source": "Courfeyrac", "target": "Feuilly", "value": 6},
248 | {"source": "Courfeyrac", "target": "Prouvaire", "value": 3},
249 | {"source": "Bahorel", "target": "Combeferre", "value": 5},
250 | {"source": "Bahorel", "target": "Gavroche", "value": 5},
251 | {"source": "Bahorel", "target": "Courfeyrac", "value": 6},
252 | {"source": "Bahorel", "target": "Mabeuf", "value": 2},
253 | {"source": "Bahorel", "target": "Enjolras", "value": 4},
254 | {"source": "Bahorel", "target": "Feuilly", "value": 3},
255 | {"source": "Bahorel", "target": "Prouvaire", "value": 2},
256 | {"source": "Bahorel", "target": "Marius", "value": 1},
257 | {"source": "Bossuet", "target": "Marius", "value": 5},
258 | {"source": "Bossuet", "target": "Courfeyrac", "value": 12},
259 | {"source": "Bossuet", "target": "Gavroche", "value": 5},
260 | {"source": "Bossuet", "target": "Bahorel", "value": 4},
261 | {"source": "Bossuet", "target": "Enjolras", "value": 10},
262 | {"source": "Bossuet", "target": "Feuilly", "value": 6},
263 | {"source": "Bossuet", "target": "Prouvaire", "value": 2},
264 | {"source": "Bossuet", "target": "Combeferre", "value": 9},
265 | {"source": "Bossuet", "target": "Mabeuf", "value": 1},
266 | {"source": "Bossuet", "target": "Valjean", "value": 1},
267 | {"source": "Joly", "target": "Bahorel", "value": 5},
268 | {"source": "Joly", "target": "Bossuet", "value": 7},
269 | {"source": "Joly", "target": "Gavroche", "value": 3},
270 | {"source": "Joly", "target": "Courfeyrac", "value": 5},
271 | {"source": "Joly", "target": "Enjolras", "value": 5},
272 | {"source": "Joly", "target": "Feuilly", "value": 5},
273 | {"source": "Joly", "target": "Prouvaire", "value": 2},
274 | {"source": "Joly", "target": "Combeferre", "value": 5},
275 | {"source": "Joly", "target": "Mabeuf", "value": 1},
276 | {"source": "Joly", "target": "Marius", "value": 2},
277 | {"source": "Grantaire", "target": "Bossuet", "value": 3},
278 | {"source": "Grantaire", "target": "Enjolras", "value": 3},
279 | {"source": "Grantaire", "target": "Combeferre", "value": 1},
280 | {"source": "Grantaire", "target": "Courfeyrac", "value": 2},
281 | {"source": "Grantaire", "target": "Joly", "value": 2},
282 | {"source": "Grantaire", "target": "Gavroche", "value": 1},
283 | {"source": "Grantaire", "target": "Bahorel", "value": 1},
284 | {"source": "Grantaire", "target": "Feuilly", "value": 1},
285 | {"source": "Grantaire", "target": "Prouvaire", "value": 1},
286 | {"source": "MotherPlutarch", "target": "Mabeuf", "value": 3},
287 | {"source": "Gueulemer", "target": "Thenardier", "value": 5},
288 | {"source": "Gueulemer", "target": "Valjean", "value": 1},
289 | {"source": "Gueulemer", "target": "Mme.Thenardier", "value": 1},
290 | {"source": "Gueulemer", "target": "Javert", "value": 1},
291 | {"source": "Gueulemer", "target": "Gavroche", "value": 1},
292 | {"source": "Gueulemer", "target": "Eponine", "value": 1},
293 | {"source": "Babet", "target": "Thenardier", "value": 6},
294 | {"source": "Babet", "target": "Gueulemer", "value": 6},
295 | {"source": "Babet", "target": "Valjean", "value": 1},
296 | {"source": "Babet", "target": "Mme.Thenardier", "value": 1},
297 | {"source": "Babet", "target": "Javert", "value": 2},
298 | {"source": "Babet", "target": "Gavroche", "value": 1},
299 | {"source": "Babet", "target": "Eponine", "value": 1},
300 | {"source": "Claquesous", "target": "Thenardier", "value": 4},
301 | {"source": "Claquesous", "target": "Babet", "value": 4},
302 | {"source": "Claquesous", "target": "Gueulemer", "value": 4},
303 | {"source": "Claquesous", "target": "Valjean", "value": 1},
304 | {"source": "Claquesous", "target": "Mme.Thenardier", "value": 1},
305 | {"source": "Claquesous", "target": "Javert", "value": 1},
306 | {"source": "Claquesous", "target": "Eponine", "value": 1},
307 | {"source": "Claquesous", "target": "Enjolras", "value": 1},
308 | {"source": "Montparnasse", "target": "Javert", "value": 1},
309 | {"source": "Montparnasse", "target": "Babet", "value": 2},
310 | {"source": "Montparnasse", "target": "Gueulemer", "value": 2},
311 | {"source": "Montparnasse", "target": "Claquesous", "value": 2},
312 | {"source": "Montparnasse", "target": "Valjean", "value": 1},
313 | {"source": "Montparnasse", "target": "Gavroche", "value": 1},
314 | {"source": "Montparnasse", "target": "Eponine", "value": 1},
315 | {"source": "Montparnasse", "target": "Thenardier", "value": 1},
316 | {"source": "Toussaint", "target": "Cosette", "value": 2},
317 | {"source": "Toussaint", "target": "Javert", "value": 1},
318 | {"source": "Toussaint", "target": "Valjean", "value": 1},
319 | {"source": "Child1", "target": "Gavroche", "value": 2},
320 | {"source": "Child2", "target": "Gavroche", "value": 2},
321 | {"source": "Child2", "target": "Child1", "value": 3},
322 | {"source": "Brujon", "target": "Babet", "value": 3},
323 | {"source": "Brujon", "target": "Gueulemer", "value": 3},
324 | {"source": "Brujon", "target": "Thenardier", "value": 3},
325 | {"source": "Brujon", "target": "Gavroche", "value": 1},
326 | {"source": "Brujon", "target": "Eponine", "value": 1},
327 | {"source": "Brujon", "target": "Claquesous", "value": 1},
328 | {"source": "Brujon", "target": "Montparnasse", "value": 1},
329 | {"source": "Mme.Hucheloup", "target": "Bossuet", "value": 1},
330 | {"source": "Mme.Hucheloup", "target": "Joly", "value": 1},
331 | {"source": "Mme.Hucheloup", "target": "Grantaire", "value": 1},
332 | {"source": "Mme.Hucheloup", "target": "Bahorel", "value": 1},
333 | {"source": "Mme.Hucheloup", "target": "Courfeyrac", "value": 1},
334 | {"source": "Mme.Hucheloup", "target": "Gavroche", "value": 1},
335 | {"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1}
336 | ]
337 | }
338 |
--------------------------------------------------------------------------------