Observable Framework 1.9.0 GitHub️ 2.0k


D3 (or D3.js) is “a free, open-source JavaScript library for visualizing data. Its low-level approach built on web standards offers unparalleled flexibility in authoring dynamic, data-driven graphics.” D3 is available by default as d3 in Markdown, but you can import it explicitly like so:

import * as d3 from "npm:d3";

For example, here is an interactive force-directed graph showing the character co-occurence in Les Misérables; data is from the Stanford Graph Base. Color represents arbitrary clusters in the data. Drag nodes below to better understand connections.

const width = 640;
const height = 640;
const color = d3.scaleOrdinal(d3.schemeObservable10);

// Copy the data to protect against mutation by d3.forceSimulation.
const links = data.links.map((d) => Object.create(d));
const nodes = data.nodes.map((d) => Object.create(d));

const simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id((d) => d.id))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked);

const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [0, 0, width, height])
    .attr("style", "max-width: 100%; height: auto;");

const link = svg.append("g")
    .attr("stroke", "var(--theme-foreground-faint)")
    .attr("stroke-opacity", 0.6)
    .attr("stroke-width", (d) => Math.sqrt(d.value));

const node = svg.append("g")
    .attr("stroke", "var(--theme-background)")
    .attr("stroke-width", 1.5)
    .attr("r", 5)
    .attr("fill", (d) => color(d.group))

    .text((d) => d.id);

function ticked() {
      .attr("x1", (d) => d.source.x)
      .attr("y1", (d) => d.source.y)
      .attr("x2", (d) => d.target.x)
      .attr("y2", (d) => d.target.y);

      .attr("cx", (d) => d.x)
      .attr("cy", (d) => d.y);


The drag interaction is implemented by this helper function:

function drag(simulation) {

  function dragstarted(event) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;

  function dragged(event) {
    event.subject.fx = event.x;
    event.subject.fy = event.y;

  function dragended(event) {
    if (!event.active) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;

  return d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

The data is loaded as a JSON file:

const data = FileAttachment("miserables.json").json();

We recommend using Observable Plot if you want to create simple charts from your data; but for more complex or bespoke needs, including interactivity and animation, you will most probably want to use D3.

Check out D3’s extensive documentation for more examples.