Server-side D3 Visualization
d3-node is a utility library designed to facilitate server-side rendering of D3.js visualizations within a Node.js environment. It enables developers to generate static SVG or HTML strings, or raster images (PNG) via the optional `node-canvas` library, entirely on the backend. This capability is crucial for use cases like pre-rendering charts and maps for improved initial page load performance, offloading data processing from client browsers, and creating static image outputs for reports or social media sharing. The current stable version is 4.0.1. The project demonstrates active maintenance with consistent updates, including enhancements like explicit SVG attribute parameters and robust canvas support. Its key differentiators include the ability to leverage the entire D3 ecosystem and npm packages, produce portable SVG with embedded stylesheets, and simplify the adaptation of existing D3 examples for server-side generation.
Common errors
-
TypeError: d3n.createCanvas is not a function
cause The `canvasModule` option was not provided to the `D3Node` constructor, or the `canvas` package is not installed.fixInstall `canvas` (`npm install canvas`) and initialize `D3Node` with `new D3Node({ canvasModule: require('canvas') })`. -
ReferenceError: d3 is not defined
cause Attempting to use the global `d3` object (e.g., `d3.scaleLinear()`) without properly accessing it from the `D3Node` instance or importing it directly.fixAccess D3 methods via the D3Node instance: `const d3Local = d3n.d3; d3Local.scaleLinear()` or explicitly `import * as d3 from 'd3';` if using specific D3 modules outside of the D3Node context. -
Error: Node was not found
cause This error often occurs in D3.js when trying to append elements to a non-existent or invalid DOM element. In `d3-node`, it might mean the `selector` or `container` options are misconfigured, or D3 is trying to operate on an element that hasn't been created or selected correctly within the virtual DOM.fixEnsure the `selector` matches an element in the `container` HTML string, and that you are appending to a valid D3 selection object obtained from `d3n.document.querySelector()` or `d3n.createSVG()`.
Warnings
- gotcha d3-node is explicitly tested on Node.js v16 and up. Using older Node.js versions might lead to unexpected behavior or compatibility issues with underlying D3.js or other dependencies.
- breaking When generating raster images (e.g., PNG), the `canvas` package must be installed separately as a peer dependency and passed to the D3Node constructor via the `canvasModule` option. It is not bundled directly with `d3-node`.
- gotcha D3.js itself underwent significant breaking changes between v3 and v4 (and subsequent versions), including a modularized structure and changes to API calls (e.g., `d3.scale.linear()` became `d3.scaleLinear()`). If adapting older D3 code, you must port it to a modern D3.js API, which `d3-node` supports via its internal D3 instance.
Install
-
npm install d3-node -
yarn add d3-node -
pnpm add d3-node
Imports
- D3Node
const D3Node = require('d3-node')import { D3Node } from 'd3-node' - D3 selection object
const d3 = d3n.d3
- SVG string output
d3n.svgString()
Quickstart
import { D3Node } from 'd3-node';
import * as d3 from 'd3'; // Import D3 for specific utilities like scaleLinear
import fs from 'fs';
const options = {
selector: '#chart',
container: '<div id="container"><div id="chart"></div></div>',
styles: '.bar { fill: steelblue; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; }'
};
const d3n = new D3Node(options);
const d3Local = d3n.d3; // Get the D3 instance associated with D3Node
const width = 400;
const height = 200;
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const svg = d3n.createSVG(width, height);
const data = [10, 20, 40, 60, 80];
const xScale = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.1)
.domain(data.map((d, i) => i));
const yScale = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, d3.max(data)]);
svg.append('g')
.attr('fill', 'steelblue')
.selectAll('rect')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', (d, i) => xScale(i))
.attr('y', d => yScale(d))
.attr('height', d => yScale(0) - yScale(d))
.attr('width', xScale.bandwidth());
// Add X axis
svg.append('g')
.attr('class', 'axis x-axis')
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale).tickFormat(i => `Item ${i + 1}`));
// Add Y axis
svg.append('g')
.attr('class', 'axis y-axis')
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));
const svgOutput = d3n.svgString();
fs.writeFileSync('output.svg', svgOutput);
console.log('SVG written to output.svg');
// For canvas output (requires 'canvas' package):
// import Canvas from 'canvas';
// const d3nCanvas = new D3Node({ canvasModule: Canvas });
// const canvas = d3nCanvas.createCanvas(width, height);
// const context = canvas.getContext('2d');
// // ... draw on context with D3-Canvas specific methods ...
// canvas.createPNGStream().pipe(fs.createWriteStream('output.png'));