D3 is a low-level data visualisation library by Observable .
D3 stands for “Data-Driven Documents”.
Getting started
Documentation
Create svg element and add that to the appropriate div
-element
const width = 640 ;
const height = 400 ;
const svg = d3. create ( "svg" )
. attr ( "width" , width)
. attr ( "height" , height);
element. append (svg. node () !! );
The double-bang operator is important when using ⌨️ TypeScript , otherwise, it will result in
Argument of type 'SVGSVGElement | null' is not assignable to parameter of type 'string | Node'. Type 'null' is not assignable to type 'string | Node'.
Interactivity
Graphs
Force Simulation
When implementing the example of a disjointed force-directed graph in ⌨️ TypeScript , d3.forceSimulation
needs nodes of type SimulationNodeDatum
. This requires implementing a specific type:
interface CustomNode extends d3 . SimulationNodeDatum {
id : string ;
}
export function setupPlot ( element : HTMLDivElement ) {
( ... )
const nodes : CustomNode [] = [{
id: "test-1"
}, {
id: "test-2"
}]
const simulation = d3. forceSimulation (nodes)
( ... )
for links any object with type { source: string; target: string; }
is sufficient.
By default, nodes will be centred around (0, 0)
, corresponding to the view box’s top left corner. To show the nodes, the view box must be set to have (0, 0)
at the centre using .attr("viewBox", [-width/2, -height/2, width, height]);
const svg = d3. create ( "svg" )
. attr ( "width" , width)
. attr ( "height" , height)
. attr ( "viewBox" , [ - width / 2 , - height / 2 , width, height]);
The rendered links and nodes must be updated on each simulation tick.
The final result
import * as d3 from "d3"
interface CustomNode extends d3 . SimulationNodeDatum {
id : string ;
}
export function setupPlot ( element : HTMLDivElement ) {
const width = 640 ;
const height = 400 ;
const nodes : CustomNode [] = [{
id: "test-1"
}, {
id: "test-2"
}]
const links : d3 . SimulationLinkDatum < CustomNode >[] = [{
source: "test-1" ,
target: "test-2"
}]
const simulation = d3. forceSimulation (nodes)
. force ( "link" , d3. forceLink (links). id ( d => (d as CustomNode ).id))
. force ( "charge" , d3. forceManyBody ())
. force ( "x" , d3. forceX ())
. force ( "y" , d3. forceY ());
const svg = d3. create ( "svg" )
. attr ( "width" , width)
. attr ( "height" , height)
. attr ( "viewBox" , [ - width / 2 , - height / 2 , width, height]);
// Add a line for each link, and a circle for each node.
const link = svg. append ( "g" )
. attr ( "stroke" , "#999" )
. attr ( "stroke-opacity" , 0.6 )
. selectAll ( "line" )
. data (links)
. join ( "line" )
. attr ( "stroke-width" , 2 );
const node = svg. append ( "g" )
. attr ( "stroke" , "#fff" )
. attr ( "stroke-width" , 1.5 )
. selectAll ( "circle" )
. data (nodes)
. join ( "circle" )
. attr ( "r" , 5 )
simulation. on ( "tick" , () => {
link
. attr ( "x1" , d => (d.source as CustomNode ).x !! )
. attr ( "y1" , d => (d.source as CustomNode ).y !! )
. attr ( "x2" , d => (d.target as CustomNode ).x !! )
. attr ( "y2" , d => (d.target as CustomNode ).y !! );
node
. attr ( "cx" , d => d.x !! )
. attr ( "cy" , d => d.y !! );
})
element. append (svg. node () !! );
}