Declarative and reusable D3. Replace select, append, data and more with one function.
Declarative and reusable D3. Replace
select,
append,
data,
join,
enter,
exit,
transitionand more with one function.
Warning, highly experimental at this stage. API will change.
Instead of imperative code:
import * as d3 from 'd3';// HTML file has an element const svg = d3.select('svg'); svg .append('rect') .attr('fill', 'pink') .attr('x', 0) .attr('width', 100) .attr('height', 100);
Write declarative style like this:
import render from 'd3-render';const data = [{ append: 'rect', fill: 'pink', x: 0, width: 100, height: 100 }];
// HTML file has an element render('svg', data);
$ npm install d3-render d3-selection d3-transition
D3 Render needs
d3-selection(>=1.4) and
d3-transition(>=1) as peerDependencies.
Render
One function to rule them all. To use, add this to your JavaScript or TypeScript file:
import render from 'd3-render';render(selector, data);
// Render also returns the full D3 selection for advanced use cases const selection = render(selector, data);
rendertakes two arguments:
selector
A D3 selector string, HTML node or D3 selection to specify the root element where
renderwill run. Works like a bit like d3.select. Most common usage is with an id or class.
// Selects first element render('svg', data);// Select by id render('#root', data);
// Select first element with this class name render('.data-viz', data);
// Select by DOM node const node = document.querySelector('.data-viz'); render(node, data);
// Select by D3 selection const selection = d3.select('svg'); render(selection, data);
// Or called by D3 d3.select('svg').call(render, data);
data
An array of objects describing elements that D3 will append or update. For example:
const data = [ { append: 'circle', r: 50, cx: 50, cy: 50, fill: 'purple', }, { append: 'rect', width: 100, height: 100, x: 100, y: 0, fill: 'blue', }, ];render('#root', data);
renderuses this data to append two elements to
#root, with the following result:
The D3 selection API is called for you, hiding imperative code like
selection.append()or
selection.attr().
datacan be hierarchical in structure with the special
childrenkey.
Say we want to wrap the circle and rectangle above within a group element:
const data = [ { append: 'g', children: [ { append: 'circle', ... }, { append: 'rect', ... }, ], }, ];
renderhandles D3's nested appends for you and produces:
The
childrenkey can be applied to any element on any level, so you can deeply nest to your hearts content.
Below is a list of important element keys:
| Element Key | Description | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
append* | Any SVG or HTML element to append. eg.
rect,
circle,
path,
g,
div,
imgand more. Runs D3's
selection.append()behind the scenes. | |
key| Unique identifier used to match elements on the same nesting level. Useful for transitions. | |
class| Class name of appended element | |
id| Id of appended element | |
x,
y,
width,
height,
cx,
cy,
r,
d,
fillOpacityetc | Any valid attribute and value for the appended SVG element. Same as using
selection.attr(), but camel case the key, eg.
fillOpacityinstead of
fill-opacity. Can optionally use
{ enter, exit }for animation (but must have a
duration). For example, to expand or contract height from
0to
100pxwhen element enters or exits, use:
height: { enter: 100, exit: 0 }| |
text| Text string to display in element. Only works for
textelements. eg.
{ append: text, text: 'Greetings'}| |
html| String that is evaluated into html via
element.innerHTML. Useful for inlined text formatting eg.
{ html: 'Important normal'}. Replaces any
children. | |
style| An object with style property keys and values. Keys are camel cased. eg.
style: { fillOpacity: 0.5 }. Runs
selection.style()in the background. | |
duration| Number in milliseconds. Activates a D3 transition, setting the time it takes for the element to enter, update or exit. Calls
selection.transition().duration(myDuration). | |
delay| Number in milliseconds. Delays the start of the transition. | |
ease| Sets the easing function for D3 transition. Use any D3 easing function here. eg.
{ append: 'rect', ease: d3.easeQuadInOut }| |
children| Array of element objects, which will be nested under the current element. | |
onClick,
onMouseOver,
onMouseOut, supports any
on*event | A function for element event. Function can be used like this:
{ onClick: (event, data, index) => {} }| |
call| A function with a D3 selection as the first argument. Useful for creating an axis or for advanced functionality, eg.
{ call: xAxis }. Essentially runs
selection.call(). |
* Required
To make updates to rendered elements, just run
renderagain, but with a different
datavalue. Add a duration value for a smooth transition.
// Initial data const data = [ { append: 'ellipse', fill: 'red', rx: 100, ry: 50, duration: 1000 }, ];// Initial render on render('#root', data);
// After two seconds, change ellipse to blue setTimeout(() => { // Set some updated data const newData = [ { append: 'ellipse', fill: 'blue', rx: 100, ry: 50, duration: 1000 }, ];
// Call render again render('#root', newData); }, 2000);
Behind the scenes,
renderdoes a lot of heavy lifting for you. It binds your
data, appends the ellipse and then rebinds the
newDatato trigger an update and transistion. This is the equivalent vanilla D3 code:
const data = [{ fill: 'red' }]; const svg = d3.select('#root');function update(data) { svg .selectAll('ellipse') .data(data) .join( enter => enter .append('ellipse') .attr('rx', 100) .attr('ry', 50) .attr('fill', d => d.fill), update => update.call(update => update .transition() .duration(1000) .attr('fill', d => d.fill) ) ); }
update(data);
// After two seconds, turn ellipse blue setTimeout(() => { update([{ fill: 'blue' }]); }, 2000);
We are using d3-selection 1.4's
joinfunction, which is much easier to remember than the old general update pattern. This article from Mike Bostock explains
joinextremely well and is the underlying inspiration for our
renderfunction.
The
renderfunction not only tracks updated elements, but also new or removed elements since the last
datachange.
Here is a simple example below:
// Build an svg with nothing in it render('#root', []); // Renders:// Two seconds later, re-render with a new rect element setTimeout(() => { render('#root', [{ append: 'rect', width: 100, height: 100, fill: 'pink' }]); // Renders: Howdy there!
// Two seconds after text element appears, remove it setTimeout(() => { render('#root', []); // Renders: }, 2000); }, 2000);
D3's data binding works much the same way. In fact, we are also data binding in the background, but we've also added some boilerplate to make enter/exit transitions easy to do.
Let's enable enter/exit transitions by adding two lines of code:
render('#root', []);setTimeout(() => { render('#root', [ { append: 'rect', // Was width: 0 width: { enter: 100, exit: 0 }, height: 100, fill: 'pink', // Length of transition in milliseconds duration: 1000, }, ]);
setTimeout(() => { render('#root', []); }, 2000); }, 2000);
The
widthhas been changed to an object with an
entervalue, and
exitvalue. When the
rectenters, the width is
0, it then takes 1 second (from
duration) to animate to
100px.
When the
rectis removed from
data, the exit transition kicks in, animating the width from
100pxto
0in 1 second. The
rectelement is then removed from the DOM.
The
{ enter, exit }animation object is a powerful pattern that can be applied to any attribute in the element.
An event handler for the element can be defined along with the rest of the attributes.
render('#root', [ { append: 'circle', r: 50, cx: 50, cy: 50, // Circle can call function when it is clicked or tapped onClick: (event, datum, index) => {}, // Or when the mouse is over onMouseOver: (event, datum, index) => {}, }, ]);
Any
on*event can be used eg.
onDrag,
onScroll,
onWheeletc. D3 Render maps the declared event function to
selection.on().
An element can be styled inline with the
stylekey and a
styleobject value. Style properties must be in camel case.
render('#root', [ { append: 'rect', width: 50, height: 50, style: { fillOpacity: 0.5, }, }, ]);
D3 Render is actually inspired by React's declarative mental model, so it is no suprise that integration between the two is quite simple:
// App.jsimport React from 'react'; import render from 'd3-render';
const App = () => { const svg = React.useRef(); const [data, setData] = React.useState([ { append: 'rect', width: 100, height: 100, fill: 'green', duration: 1000, // Add some interactivity to the element onClick: () => { setData([{ ...data[0], fill: 'yellow' }]); }, }, ]);
React.useEffect(() => { if (svg && svg.current) { // Pass svg node to D3 render, along with data. // render runs whenever data changes render(svg.current, data); } }, [data]);
return ; };
updateexample
htmlexample
childrenexample
keyexample
d3.select('svg').call(render, data)
delay: function(d, i) { this.getTotalLength() }
d3-selectionand
d3-transition
renderand
renderSelectioninto one function
styleattribute key
selection.prototype = renderfor
d3.render()to work
selection.html()
updateto
{ enter, exit }transition object
startin transition object
{ enter, exit }for
ease,
delayand
duration
This project was bootstrapped with TSDX.
Below is a list of commands you will probably find useful.
npm startor
yarn start
Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for you convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab.
Your library will be rebuilt if you make edits.
npm run buildor
yarn build
Bundles the package to the
distfolder. The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
npm testor
yarn test
Runs the test watcher (Jest) in an interactive mode. By default, runs tests related to files changed since the last commit.