Working with D3.js and Immutable.js
Asked Answered
Y

4

9

In my application I use D3.js for some visualizations.

Now D3 works with mutable native JavaScript data structures.

So some data marshalling would be necessary for this to work with Immutable.js.

I also use Reflux with React, so in my Store I manage an Immutable Map. Since this Map becomes a new thing on every change I cannot just pass it to D3 Force Layout because that works with mutable data so each time it recalculates everything from scratch.

I end up with managing both immutable and mutable data structures but this feels very wrong.

I found an article, Practical Time Series Visualization using D3 + OM, which seems to touch on the subject and suggests to use Cursors. The problem is it uses Clojure(Script) when I'm using just JavaScript.

I understand this is very abstract without code examples but any suggestion on the subject of working/syncing both immutable and mutable data will be appreciated!

Yellowbird answered 8/4, 2015 at 12:52 Comment(3)
Sounds like a bad choice of tools. Why insist on both?Kohlrabi
Did you ever find a solution to this?Alow
Not really. I did find a hacky workaround which I eventually dropped. It was by passing by reference a property of the immutable object to a d3 function which constantly mutated that property ) There is nothing really immutable about immutable.js Objects. You can mutate them via plain ol javascrip. Also immutable.js doesnt use Object.freeze(obj) in their source. Only time they return a new value is when you use immutable.js API methods on them. As you can see this way is a little of both, sometimes confusing.Yellowbird
W
4

I see no reason why d3.js should not work with immutable.js. In my opinion the key is to understand how d3.js deals with data, especially how d3.js' data joins work.

Data Joins - how data is bound to DOM elements in d3.js

Every time nodes are selected d3.selectAll('div') and joined with .data([1, 3, 5, 9]) d3.js compares if already existing div elements are bound to the data elements. This is done by evaluating if a selected div DOM node has a __data__ property associated. The data property is set and maintained by d3.js. By default the index within the joined array is what goes into __data__. But you can also define a key function to override this behavior.

Further reading

How selections work in which Mike Bostock explains how d3.js calculates the enter, update and exit selections through the data binding mechanism described above.

Waggery answered 22/9, 2015 at 7:17 Comment(0)
Y
1

In my opinion it is important to first understand the problem.

I assume you are dealing with some D3 functionality that mutates the data passed in, like d3-force and data is your applications state, like if you are using Immutable.js with React and Redux.

The problem is that D3 has no way to know how to handle Immutable.js data. Even if you find a way to get D3 to do that it won't be a good idea because it is not efficient in the case of d3-force when on every tick it will create a new immutable.

So what I suggest is to store your state in a plain JavaScript array or object for D3 to interact with.

For example, suppose you have a graph and when you add a node you dispatch some action(ADD_NODE) to your store and you might have a handler that updates your immutable state like reducers in Redux. What you want is to create another reducer that will take that node and return plain [...nodes]. The same for other CRUD actions. This way your data stays in sync. For example if a user clicks any of your nodes you just can get original data by index and all D3 mutations a saved.

Here is a example if you are using Redux. Hope it helps with gathering ideas.

const graphNodesReducer = (state = [], action) => {
     if (action.type in nodeTypes) {
         return [...state, action.payload];
         // or [...state, action.payload.toJS()] if node is sent as immutable
         // or [...state, action.payload.get('label')] you can choose what to set
     }
     return state;
}
You answered 25/11, 2016 at 2:31 Comment(0)
S
0

There are now native JavaScript React Cursors.

So you can repeat what the 'Practical Time Series Visualization using D3 + OM' blog post suggests.

Spiry answered 11/3, 2017 at 20:56 Comment(0)
V
0

D3 is a bunch of number-crunching functions plus some DOM selection and data-binding utilities. In React, we ignore the latter, which means D3 is mostly mathy stuff which is perfectly composable with immutable data structures and functional code.

However, d3-force is an exception, and what this question is really about. d3-force is a stateful physics simulation, and thus doesn't compose well with immutable data or functional code. In other words, the question is really how to make messy, impure, real-world, stateful things work with the functional paradigm. Fortunately, there are patterns and precedents for such things.

Namely, just treat such things as external APIs, which the functional part of your app talks to over pub/sub. This is nothing new; React/Redux for example has tools and patterns for talking to external APIs. Just instead of calling http, websockets, or localStorage, we can call an API which creates, manages, subscribes to, and destroys physics simulations.

The upshot is to use the same tools for talking to the simulation API as for talking to others; for example Redux Saga.

Verbenaceous answered 10/7, 2018 at 19:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.