How to run a D3 example

Viewed 2107

For example https://observablehq.com/@d3/zoomable-treemap

If you paste the fragments of script into a <script> tag in a HTML file the (obviously) it doesn't work.

How are you supposed to run these things?

And what the heck is italic f?

Why are no statements terminated with a semicolon and why are things declared without var?

Surely this cannot possibly work?

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<h1>H1</h1?

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>


<script type="text/javascript">
chart = {
  const x = d3.scaleLinear().rangeRound([0, width]);
  const y = d3.scaleLinear().rangeRound([0, height]);

  const svg = d3.create("svg")
      .attr("viewBox", [0.5, -30.5, width, height + 30])
      .style("font", "10px sans-serif");

  let group = svg.append("g")
      .call(render, treemap(data));

  function render(group, root) {
    const node = group
      .selectAll("g")
      .data(root.children.concat(root))
      .join("g");

    node.filter(d => d === root ? d.parent : d.children)
        .attr("cursor", "pointer")
        .on("click", d => d === root ? zoomout(root) : zoomin(d));

    node.append("title")
        .text(d => `${name(d)}\n${format(d.value)}`);

    node.append("rect")
        .attr("id", d => (d.leafUid = DOM.uid("leaf")).id)
        .attr("fill", d => d === root ? "#fff" : d.children ? "#ccc" : "#ddd")
        .attr("stroke", "#fff");

    node.append("clipPath")
        .attr("id", d => (d.clipUid = DOM.uid("clip")).id)
      .append("use")
        .attr("xlink:href", d => d.leafUid.href);

    node.append("text")
        .attr("clip-path", d => d.clipUid)
        .attr("font-weight", d => d === root ? "bold" : null)
      .selectAll("tspan")
      .data(d => (d === root ? name(d) : d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(format(d.value)))
      .join("tspan")
        .attr("x", 3)
        .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
        .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
        .attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
        .text(d => d);

    group.call(position, root);
  }

  function position(group, root) {
    group.selectAll("g")
        .attr("transform", d => d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`)
      .select("rect")
        .attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
        .attr("height", d => d === root ? 30 : y(d.y1) - y(d.y0));
  }

  // When zooming in, draw the new nodes on top, and fade them in.
  function zoomin(d) {
    const group0 = group.attr("pointer-events", "none");
    const group1 = group = svg.append("g").call(render, d);

    x.domain([d.x0, d.x1]);
    y.domain([d.y0, d.y1]);

    svg.transition()
        .duration(750)
        .call(t => group0.transition(t).remove()
          .call(position, d.parent))
        .call(t => group1.transition(t)
          .attrTween("opacity", () => d3.interpolate(0, 1))
          .call(position, d));
  }

  // When zooming out, draw the old nodes on top, and fade them out.
  function zoomout(d) {
    const group0 = group.attr("pointer-events", "none");
    const group1 = group = svg.insert("g", "*").call(render, d.parent);

    x.domain([d.parent.x0, d.parent.x1]);
    y.domain([d.parent.y0, d.parent.y1]);

    svg.transition()
        .duration(750)
        .call(t => group0.transition(t).remove()
          .attrTween("opacity", () => d3.interpolate(1, 0))
          .call(position, d))
        .call(t => group1.transition(t)
          .call(position, d.parent));
  }

  return svg.node();
}

data = FileAttachment("flare-2.json").json()

treemap = data => d3.treemap()
    .tile(tile)
  (d3.hierarchy(data)
    .sum(d => d.value)
    .sort((a, b) => b.value - a.value))

function tile(node, x0, y0, x1, y1) {
  d3.treemapBinary(node, 0, 0, width, height);
  for (const child of node.children) {
    child.x0 = x0 + child.x0 / width * (x1 - x0);
    child.x1 = x0 + child.x1 / width * (x1 - x0);
    child.y0 = y0 + child.y0 / height * (y1 - y0);
    child.y1 = y0 + child.y1 / height * (y1 - y0);
  }
}

name = d => d.ancestors().reverse().map(d => d.data.name).join("/")
width = 954
height = 924
format = d3.format(",d")
d3 = require("d3@5")
</script>
</body>
</html>
2 Answers

If you're using Observable as a prototype tool you first have to know that they have a different flavor of Javascript. You can't just copy and paste the cells to a pure html/js file, but it's indeed very easy to use their ecosystem

Here are some links from their documentation

If you're prototyping and wants to quick embed your code

Another resource on how to convert notebooks to standalone

Here is another similar questions with answer

D3 example from Observable on my wordpress site

Hopefully that works for you

Here is how you can port your code from ObservableHQ to vanilla.js. You should be able to run this example with little to no modification.

<script src="https://d3js.org/d3.v6.min.js"></script>
<script type="text/javascript">
'use strict';
 var data = {};
 const  width = 954;
 const height = 924;
 const format = d3.format(",d");
 

 d3.json("/api/graph/flare.json").then(function (dt) {
    data = dt;        
    chart();

})
    .catch(function (error) {
        console.log(error);
});

function chart(){
  treemap = dt => d3.treemap()
  .tile(tile)
  (d3.hierarchy(dt)
  .sum(d => d.value)
  .sort((a, b) => b.value - a.value))
  
  const name = d => d.ancestors().reverse().map(d => d.data.name).join("/")

  const x = d3.scaleLinear().rangeRound([0, width]);
  const y = d3.scaleLinear().rangeRound([0, height]);

  const svg = d3.create("svg")
  .attr("viewBox", [0.5, -30.5, width, height + 30])
  .style("font", "10px sans-serif");

  let group = svg.append("g")
  .call(render, treemap(data));
}

 function render(group, root) {
   const node = group
   .selectAll("g")
   .data(root.children.concat(root))
   .join("g");

   node.filter(d => d === root ? d.parent : d.children)
    .attr("cursor", "pointer")
    .on("click", d => d === root ? zoomout(root) : zoomin(d));

  node.append("title")
    .text(d => `${name(d)}\n${format(d.value)}`);

  node.append("rect")
    .attr("id", d => (d.leafUid = d3.select("leaf")).id)
    .attr("fill", d => d === root ? "#fff" : d.children ? "#ccc" : "#ddd")
    .attr("stroke", "#fff");

  node.append("clipPath")
    .attr("id", d => (d.clipUid = d3.select("clip")).id)
    .append("use")
    .attr("xlink:href", d => d.leafUid.href);

  node.append("text")
    .attr("clip-path", d => d.clipUid)
    .attr("font-weight", d => d === root ? "bold" : null)
    .selectAll("tspan")
    .data(d => (d === root ? name(d) : d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(format(d.value)))
  .join("tspan")
    .attr("x", 3)
    .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
    .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
    .attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
    .text(d => d);

 group.call(position, root);
}

function position(group, root) {
    group.selectAll("g")
    .attr("transform", d => d === root ? `translate(0,-30)` :    `translate(${x(d.x0)},${y(d.y0)})`)
    .select("rect")
    .attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
    .attr("height", d => d === root ? 30 : y(d.y1) - y(d.y0));
 }


function zoomin(d) {
  const group0 = group.attr("pointer-events", "none");
  const group1 = group = svg.append("g").call(render, d);

  x.domain([d.x0, d.x1]);
  y.domain([d.y0, d.y1]);

  svg.transition()
    .duration(750)
    .call(t => group0.transition(t).remove()
      .call(position, d.parent))
    .call(t => group1.transition(t)
      .attrTween("opacity", () => d3.interpolate(0, 1))
      .call(position, d));
 }

function zoomout(d) {
  const group0 = group.attr("pointer-events", "none");
  const group1 = group = svg.insert("g", "*").call(render, d.parent);

  x.domain([d.parent.x0, d.parent.x1]);
  y.domain([d.parent.y0, d.parent.y1]);

  svg.transition()
      .duration(750)
      .call(t => group0.transition(t).remove()
      .attrTween("opacity", () => d3.interpolate(1, 0))
      .call(position, d))
      .call(t => group1.transition(t)
      .call(position, d.parent));
 }

  return svg.node();
}  



function tile(node, x0, y0, x1, y1) {
   d3.treemapBinary(node, 0, 0, width, height);
    for (const child of node.children) {
    child.x0 = x0 + child.x0 / width * (x1 - x0);
    child.x1 = x0 + child.x1 / width * (x1 - x0);
    child.y0 = y0 + child.y0 / height * (y1 - y0);
    child.y1 = y0 + child.y1 / height * (y1 - y0);
  }
 }

 
Related