Search code examples
javascriptwordpressd3.jsobservablehq

D3 example from Observable on my wordpress site


Thank you very much for the help in advance.

I am trying to add this Sankey Diagram on my wordpress site.

https://observablehq.com/@d3/sankey-diagram

I am having a hard time. I downloaded the JS code, pasted it on my wordpress site, but it gives me a lot of errors

Uncaught SyntaxError: Unexpected token 'export'

Is the first error from the browser.

This is the code I downloaded below:

<script src="https://d3js.org/d3.v5.js"></script>
<script>
// https://observablehq.com/@d3/sankey-diagram@273
export default function define(runtime, observer) {
  const main = runtime.module();
  const fileAttachments = new Map([["energy.csv",new URL("./files/d6774e9422bd72369f195a30d3a6b33ff9d41676cff4d89c93511e1a458efb3cfd16cbb7ce3fecdd8dd2466121e10c9bfe57fd73c7520bf358d352a92b898614",import.meta.url)]]);
  main.builtin("FileAttachment", runtime.fileAttachments(name => fileAttachments.get(name)));
  main.variable(observer()).define(["md"], function(md){return(
md`# Sankey Diagram

This [Sankey diagram](https://github.com/d3/d3-sankey) visualizes the flow of energy: *supplies* are on the left, and *demands* are on the right. Links show how varying amounts of energy are converted or transmitted before being consumed or lost. Data: [Department of Energy & Climate Change](http://www.decc.gov.uk/en/content/cms/tackling/2050/calculator_on/calculator_on.aspx) via [Tom Counsell](https://tamc.github.io/Sankey/)
`
)});
  main.variable(observer("viewof edgeColor")).define("viewof edgeColor", ["html","URLSearchParams"], function(html,URLSearchParams){return(
Object.assign(html`<select>
  <option value=input>Color by input
  <option value=output>Color by output
  <option value=path selected>Color by input-output
  <option value=none>No color
</select>`, {
  value: new URLSearchParams(html`<a href>`.search).get("color") || "path"
})
)});
  main.variable(observer("edgeColor")).define("edgeColor", ["Generators", "viewof edgeColor"], (G, _) => G.input(_));
  main.variable(observer("viewof align")).define("viewof align", ["html","URLSearchParams"], function(html,URLSearchParams){return(
Object.assign(html`<select>
  <option value=left>Left-aligned
  <option value=right>Right-aligned
  <option value=center>Centered
  <option value=justify selected>Justified
</select>`, {
  value: new URLSearchParams(html`<a href>`.search).get("align") || "justify"
})
)});
  main.variable(observer("align")).define("align", ["Generators", "viewof align"], (G, _) => G.input(_));
  main.variable(observer("chart")).define("chart", ["d3","width","height","sankey","data","color","format","edgeColor","DOM"], function(d3,width,height,sankey,data,color,format,edgeColor,DOM)
{
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height]);

  const {nodes, links} = sankey(data);

  svg.append("g")
      .attr("stroke", "#000")
    .selectAll("rect")
    .data(nodes)
    .join("rect")
      .attr("x", d => d.x0)
      .attr("y", d => d.y0)
      .attr("height", d => d.y1 - d.y0)
      .attr("width", d => d.x1 - d.x0)
      .attr("fill", color)
    .append("title")
      .text(d => `${d.name}\n${format(d.value)}`);

  const link = svg.append("g")
      .attr("fill", "none")
      .attr("stroke-opacity", 0.5)
    .selectAll("g")
    .data(links)
    .join("g")
      .style("mix-blend-mode", "multiply");

  if (edgeColor === "path") {
    const gradient = link.append("linearGradient")
        .attr("id", d => (d.uid = DOM.uid("link")).id)
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("x1", d => d.source.x1)
        .attr("x2", d => d.target.x0);

    gradient.append("stop")
        .attr("offset", "0%")
        .attr("stop-color", d => color(d.source));

    gradient.append("stop")
        .attr("offset", "100%")
        .attr("stop-color", d => color(d.target));
  }

  link.append("path")
      .attr("d", d3.sankeyLinkHorizontal())
      .attr("stroke", d => edgeColor === "none" ? "#aaa"
          : edgeColor === "path" ? d.uid 
          : edgeColor === "input" ? color(d.source) 
          : color(d.target))
      .attr("stroke-width", d => Math.max(1, d.width));

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

  svg.append("g")
      .attr("font-family", "sans-serif")
      .attr("font-size", 10)
    .selectAll("text")
    .data(nodes)
    .join("text")
      .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
      .attr("y", d => (d.y1 + d.y0) / 2)
      .attr("dy", "0.35em")
      .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
      .text(d => d.name);

  return svg.node();
}
);
  main.variable(observer("sankey")).define("sankey", ["d3","align","width","height"], function(d3,align,width,height)
{
  const sankey = d3.sankey()
      .nodeId(d => d.name)
      .nodeAlign(d3[`sankey${align[0].toUpperCase()}${align.slice(1)}`])
      .nodeWidth(15)
      .nodePadding(10)
      .extent([[1, 5], [width - 1, height - 5]]);
  return ({nodes, links}) => sankey({
    nodes: nodes.map(d => Object.assign({}, d)),
    links: links.map(d => Object.assign({}, d))
  });
}
);
  main.variable(observer("format")).define("format", ["d3","data"], function(d3,data)
{
  const format = d3.format(",.0f");
  return data.units ? d => `${format(d)} ${data.units}` : format;
}
);
  main.variable(observer("color")).define("color", ["d3"], function(d3)
{
  const color = d3.scaleOrdinal(d3.schemeCategory10);
  return d => color(d.category === undefined ? d.name : d.category);
}
);
  main.variable(observer("data")).define("data", ["d3","FileAttachment"], async function(d3,FileAttachment)
{
  const links = d3.csvParse(await FileAttachment("energy.csv").text(), d3.autoType);
  const nodes = Array.from(new Set(links.flatMap(l => [l.source, l.target])), name => ({name, category: name.replace(/ .*/, "")}));
  return {nodes, links, units: "TWh"};
}
);
  main.variable(observer("width")).define("width", function(){return(
954
)});
  main.variable(observer("height")).define("height", function(){return(
600
)});
  main.variable(observer("d3")).define("d3", ["require"], function(require){return(
require("d3@5", "d3-sankey@0.12")
)});
  return main;
}
</script>

The code downloaded is slightly different from the code on the webpage. I tried both versions of the code.

I believe there is something I am not understanding about D3, something about it being a module? I am not sure of the proper way to upload this on my wordpress site.

Here is the URL to the site to make things easier: http://danielb66.sg-host.com/2001-2/


Solution

  • Here is an example with a couple of differences as here we're using the runtime and file attachment from an external server.

    In your example you have to replace the following lines

    import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
    

    replace https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js with ./runtime.js downloaded runtime.js

    replace the file attachment URL with path relative to downloaded files

    const fileAttachments = new Map([["energy.csv",new URL("https://static.observableusercontent.com/files/d6774e9422bd72369f195a30d3a6b33ff9d41676cff4d89c93511e1a458efb3cfd16cbb7ce3fecdd8dd2466121e10c9bfe57fd73c7520bf358d352a92b898614",import.meta.url)]]);
    

    and lastly after loading the runtime and the notebook you have to initialize Runtime and mount project on HTML node element. In this example document.body, but you could pass any div element selector i.e. document.querySelector(".container")

    const runtime = new Runtime();
    const main = runtime.module(define, Inspector.into(document.body));
    

    <body>
    
      <script type="module">
        import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js"; export default function define(runtime, observer) { const main = runtime.module(); const fileAttachments = new Map([["energy.csv",new URL("https://static.observableusercontent.com/files/d6774e9422bd72369f195a30d3a6b33ff9d41676cff4d89c93511e1a458efb3cfd16cbb7ce3fecdd8dd2466121e10c9bfe57fd73c7520bf358d352a92b898614",import.meta.url)]]);
        main.builtin("FileAttachment", runtime.fileAttachments(name => fileAttachments.get(name))); main.variable(observer()).define(["md"], function(md){return( md`# Sankey Diagram This [Sankey diagram](https://github.com/d3/d3-sankey) visualizes the flow
        of energy: *supplies* are on the left, and *demands* are on the right. Links show how varying amounts of energy are converted or transmitted before being consumed or lost. Data: [Department of Energy & Climate Change](http://www.decc.gov.uk/en/content/cms/tackling/2050/calculator_on/calculator_on.aspx)
        via [Tom Counsell](https://tamc.github.io/Sankey/) ` )}); main.variable(observer("viewof edgeColor")).define("viewof edgeColor", ["html","URLSearchParams"], function(html,URLSearchParams){return( Object.assign(html`
        <select>
          <option value=input>Color by input
            <option value=output>Color by output
              <option value=path selected>Color by input-output
                <option value=none>No color
        </select>`, { value: new URLSearchParams(html`<a href>`.search).get("color") || "path"
    })
    )});
      main.variable(observer("edgeColor")).define("edgeColor", ["Generators", "viewof edgeColor"], (G, _) => G.input(_));
      main.variable(observer("viewof align")).define("viewof align", ["html","URLSearchParams"], function(html,URLSearchParams){return(
    Object.assign(html`<select>
      <option value=left>Left-aligned
      <option value=right>Right-aligned
      <option value=center>Centered
      <option value=justify selected>Justified
    </select>`, {
      value: new URLSearchParams(html`<a href>`.search).get("align") || "justify"
    })
    )});
      main.variable(observer("align")).define("align", ["Generators", "viewof align"], (G, _) => G.input(_));
      main.variable(observer("chart")).define("chart", ["d3","width","height","sankey","data","color","format","edgeColor","DOM"], function(d3,width,height,sankey,data,color,format,edgeColor,DOM)
    {
      const svg = d3.create("svg")
          .attr("viewBox", [0, 0, width, height]);
    
      const {nodes, links} = sankey(data);
    
      svg.append("g")
          .attr("stroke", "#000")
        .selectAll("rect")
        .data(nodes)
        .join("rect")
          .attr("x", d => d.x0)
          .attr("y", d => d.y0)
          .attr("height", d => d.y1 - d.y0)
          .attr("width", d => d.x1 - d.x0)
          .attr("fill", color)
        .append("title")
          .text(d => `${d.name}\n${format(d.value)}`);
    
      const link = svg.append("g")
          .attr("fill", "none")
          .attr("stroke-opacity", 0.5)
        .selectAll("g")
        .data(links)
        .join("g")
          .style("mix-blend-mode", "multiply");
    
      if (edgeColor === "path") {
        const gradient = link.append("linearGradient")
            .attr("id", d => (d.uid = DOM.uid("link")).id)
            .attr("gradientUnits", "userSpaceOnUse")
            .attr("x1", d => d.source.x1)
            .attr("x2", d => d.target.x0);
    
        gradient.append("stop")
            .attr("offset", "0%")
            .attr("stop-color", d => color(d.source));
    
        gradient.append("stop")
            .attr("offset", "100%")
            .attr("stop-color", d => color(d.target));
      }
    
      link.append("path")
          .attr("d", d3.sankeyLinkHorizontal())
          .attr("stroke", d => edgeColor === "none" ? "#aaa"
              : edgeColor === "path" ? d.uid 
              : edgeColor === "input" ? color(d.source) 
              : color(d.target))
          .attr("stroke-width", d => Math.max(1, d.width));
    
      link.append("title")
          .text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)}`);
    
      svg.append("g")
          .attr("font-family", "sans-serif")
          .attr("font-size", 10)
        .selectAll("text")
        .data(nodes)
        .join("text")
          .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
          .attr("y", d => (d.y1 + d.y0) / 2)
          .attr("dy", "0.35em")
          .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
          .text(d => d.name);
    
      return svg.node();
    }
    );
      main.variable(observer("sankey")).define("sankey", ["d3","align","width","height"], function(d3,align,width,height)
    {
      const sankey = d3.sankey()
          .nodeId(d => d.name)
          .nodeAlign(d3[`sankey${align[0].toUpperCase()}${align.slice(1)}`])
          .nodeWidth(15)
          .nodePadding(10)
          .extent([[1, 5], [width - 1, height - 5]]);
      return ({nodes, links}) => sankey({
        nodes: nodes.map(d => Object.assign({}, d)),
        links: links.map(d => Object.assign({}, d))
      });
    }
    );
      main.variable(observer("format")).define("format", ["d3","data"], function(d3,data)
    {
      const format = d3.format(",.0f");
      return data.units ? d => `${format(d)} ${data.units}` : format;
    }
    );
      main.variable(observer("color")).define("color", ["d3"], function(d3)
    {
      const color = d3.scaleOrdinal(d3.schemeCategory10);
      return d => color(d.category === undefined ? d.name : d.category);
    }
    );
      main.variable(observer("data")).define("data", ["d3","FileAttachment"], async function(d3,FileAttachment)
    {
      const links = d3.csvParse(await FileAttachment("energy.csv").text(), d3.autoType);
      const nodes = Array.from(new Set(links.flatMap(l => [l.source, l.target])), name => ({name, category: name.replace(/ .*/, "")}));
      return {nodes, links, units: "TWh"};
    }
    );
      main.variable(observer("width")).define("width", function(){return(
    954
    )});
      main.variable(observer("height")).define("height", function(){return(
    600
    )});
      main.variable(observer("d3")).define("d3", ["require"], function(require){return(
    require("d3@5", "d3-sankey@0.12")
    )});
      return main;
    }
    
    const runtime = new Runtime();
    const main = runtime.module(define, Inspector.into(document.body));
      
      
      </script>
    </body>