@bgchen already outlined how you can adapt the code while keeping the interactivity. If we only want to port the static chart, we can simplify the steps a bit.
Boilerplate
First, our template:
<!DOCTYPE html>
<script src="https://cdn.jsdelivr.net/npm/d3-require@1"></script>
<script>
(async()=>{
// code goes here
})()
</script>
This gives us require
, although we need to access it through window.d3.require()
.
Next, the license. This part is especially important if you plan to republish the code or any derivative version of it:
/*
Copyright 2018–2020 Observable, Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
We may also want to reference the notebook version:
/* Ported from https://observablehq.com/@d3/sankey-diagram@278 */
Basics
With that out of the way, it’s time to port the actual code. Like Bryan already mentioned, the order matters. Because Observable resolves cell dependencies automatically, you’ll often find the final results at the top. A good approach is therefore to port all cells starting from the bottom:
const d3 = await window.d3.require("d3@6", "d3-sankey@0.12");
const width = 954;
const height = 600;
Next, the viewof
s. Since we’re removing the interactivity, we might as well just port their default values:
const align = "justify";
const edgeColor = "path";
The next rule of thumb: Whenever you see a cell with curly braces and a return statement:
someName = {
return 1 + 2;
}
wrap it in an immediately invoked function expression:
const someName = (()=>{
return 1 + 2;
})();
That way you don’t have to restructure the inner code. Let’s see that applied to color
:
const color = (()=>{
const color = d3.scaleOrdinal(d3.schemeCategory10);
return d => color(d.category === undefined ? d.name : d.category);
})();
FileAttachment
In data
we meet our first major obstacle, FileAttachment
. To keep things simple, we download the attached CSV file and inline its contents by adding another <script>
tag:
<!-- Data: Department of Energy & Climate Change via Tom Counsell -->
<script id="data" type="text/csv">source,target,value
Agricultural 'waste',Bio-conversion,124.729
Bio-conversion,Liquid,0.597
...
Wave,Electricity grid,19.013
Wind,Electricity grid,289.366
</script>
Note that the opening tag must immediately be followed by the data. No line break, no whitespace.
With the data inlined, we remove the reference to FileAttachment
and just grab the data through standard DOM methods. Our data
variable becomes:
const data = (()=>{
const links = d3.csvParse(document.getElementById('data').textContent, 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"};
})();
The format
and sankey
cells are left as an exercise to the reader.
DOM.uid
Finally, there is the chart
cell. We can move its code over almost 1:1, with one exception, a call to DOM.uid:
.attr("id", d => (d.uid = DOM.uid("link")).id)
This short statement produces a lot of magic, so we need to make multiple adjustments. The first change sets the ID, as before:
.attr("id", (d, i) => (d.uid = `link-${i}`))
When a DOM.uid() value gets converted to a string, it “magically” turns into url(#the-uid)
. We don’t have that luxury anymore, so our next step is to look for the line:
: edgeColor === "path" ? d.uid
and change it to
: edgeColor === "path" ? `url(#${d.uid})`
Wrapping up
We conclude our exercise by appending the chart to the DOM:
document.body.appendChild(chart);
Your final code should look similar to this gist (see it rendered here).