create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
555 lines (507 loc) • 24.8 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="A Sankey diagram with the thickness of links indicating the flow quantity." />
<meta itemprop="description" content="A Sankey diagram with the thickness of links indicating the flow quantity." />
<meta property="og:description" content="A Sankey diagram with the thickness of links indicating the flow quantity." />
<meta name="twitter:description" content="A Sankey diagram with the thickness of links indicating the flow quantity." />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Sankey Diagram Visualizing Proportional Width Flows by Volume" />
<meta property="og:title" content="Sankey Diagram Visualizing Proportional Width Flows by Volume" />
<meta name="twitter:title" content="Sankey Diagram Visualizing Proportional Width Flows by Volume" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/sankey.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/sankey.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/sankey.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/sankey.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/sankey.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Sankey Diagram Visualizing Proportional Width Flows by Volume | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<link rel="stylesheet" href="../assets/css/prism.css"/>
<script src="../assets/js/prism.js"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
class SankeyLayout extends go.LayeredDigraphLayout {
constructor(init) {
super();
if (init) Object.assign(this, init);
}
// determine the desired height of each node/vertex,
// based on the thicknesses of the connected links;
// actually modify the height of each node's SHAPE
makeNetwork(coll) {
const net = super.makeNetwork(coll);
this.diagram.nodes.each(node => {
// figure out how tall the node's bar should be
const height = this.getAutoHeightForNode(node);
const shape = node.findObject('SHAPE');
if (shape) shape.height = height;
const text = node.findObject('TEXT');
const ltext = node.findObject('LTEXT');
const font =
'bold ' +
Math.max(12, Math.round(height / 8)) +
'pt Segoe UI, Roboto, Helvetica Neue, Noto Sans, sans-serif';
if (text) text.font = font;
if (ltext) ltext.font = font;
// and update the vertex's dimensions accordingly
const v = net.findVertex(node);
if (v !== null) {
node.ensureBounds();
const r = node.actualBounds;
v.width = r.width;
v.height = r.height;
v.focusY = v.height / 2;
}
});
return net;
}
getAutoHeightForNode(node) {
let heightIn = 0;
let it = node.findLinksInto();
while (it.next()) {
heightIn += it.value.computeThickness();
}
let heightOut = 0;
it = node.findLinksOutOf();
while (it.next()) {
heightOut += it.value.computeThickness();
}
let h = Math.max(heightIn, heightOut);
if (h < 10) h = 10;
return h;
}
// treat dummy vertexes as having the thickness of the link that they are in
nodeMinColumnSpace(v, topleft) {
if (v.node === null) {
if (v.edgesCount >= 1) {
let max = 1;
const it = v.edges;
while (it.next()) {
const edge = it.value;
if (edge.link != null) {
const t = edge.link.computeThickness();
if (t > max) max = t;
break;
}
}
return Math.max(2, Math.ceil(max / this.columnSpacing));
}
return 2;
}
return super.nodeMinColumnSpace(v, topleft);
}
// treat dummy vertexes as being thicker, so that the Bezier curves are gentler
nodeMinLayerSpace(v, topleft) {
if (v.node === null) return 100;
return super.nodeMinLayerSpace(v, topleft);
}
assignLayers() {
super.assignLayers();
const maxlayer = this.maxLayer;
// now make sure every vertex with no outputs is maxlayer
for (const it = this.network.vertexes.iterator; it.next(); ) {
const v = it.value;
const node = v.node;
if (v.destinationVertexes.count == 0) {
v.layer = 0;
}
if (v.sourceVertexes.count == 0) {
v.layer = maxlayer;
}
}
// from now on, the LayeredDigraphLayout will think that the Node is bigger than it really is
// (other than the ones that are the widest or tallest in their respective layer).
}
commitLayout() {
super.commitLayout();
for (const it = this.network.edges.iterator; it.next(); ) {
const link = it.value.link;
if (link && link.curve === go.Curve.Bezier) {
// depend on Link.adjusting === go.LinkAdjusting.End to fix up the end points of the links
// without losing the intermediate points of the route as determined by LayeredDigraphLayout
link.invalidateRoute();
}
}
}
}
// end of SankeyLayout
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
initialAutoScale: go.AutoScale.Uniform,
'animationManager.isEnabled': false,
layout: new SankeyLayout({
setsPortSpots: false, // to allow the "Side" spots on the nodes to take effect
layerSpacing: 200, // lots of space between layers, for nicer thick links
columnSpacing: 1
})
});
// this function provides a common style for the TextBlocks
function textStyle(tb) {
tb.font = 'bold 12pt Segoe UI, sans-serif';
tb.stroke = 'black';
tb.margin = 5;
}
// define the Node template
myDiagram.nodeTemplate =
new go.Node(go.Panel.Horizontal, {
locationObjectName: 'SHAPE',
locationSpot: go.Spot.Left,
portSpreading: go.PortSpreading.Packed // rather than the default go.PortSpreading.Evenly
})
.add(
new go.TextBlock({ name: 'LTEXT' })
.apply(textStyle)
.bind('text', 'ltext'),
new go.Shape({
name: 'SHAPE',
fill: '#2E8DEF', // default fill color
strokeWidth: 0,
portId: '',
fromSpot: go.Spot.RightSide,
toSpot: go.Spot.LeftSide,
height: 10,
width: 20
})
.bind('fill', 'color'),
new go.TextBlock({ name: 'TEXT' })
.apply(textStyle)
.bind('text')
);
function getAutoLinkColor(data) {
const nodedata = myDiagram.model.findNodeDataForKey(data.from);
const hex = nodedata.color;
return go.Brush.lightenBy(hex, 0.2);
}
// define the Link template
const linkSelectionAdornmentTemplate =
new go.Adornment('Link')
.add(
new go.Shape({
isPanelMain: true,
fill: null,
stroke: 'rgba(0, 0, 255, 0.3)',
strokeWidth: 0
}) // use selection object's strokeWidth
);
myDiagram.linkTemplate =
new go.Link({
curve: go.Curve.Bezier,
selectionAdornmentTemplate: linkSelectionAdornmentTemplate,
layerName: 'Background',
fromEndSegmentLength: 150,
toEndSegmentLength: 150,
adjusting: go.LinkAdjusting.End
})
.add(
new go.Shape({ strokeWidth: 4, stroke: 'rgba(173, 173, 173, 0.25)', opacity: 0.65 })
.bind('stroke', '', getAutoLinkColor)
.bind('strokeWidth', 'width')
);
// read in the JSON-format data from the "mySavedModel" element
load();
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').textContent);
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div
id="myDiagramDiv"
style="border: solid 1px black; width: 99%; height: 850px; background-color: #f2e9da"></div>
<p>
A Sankey diagram is a type of flow diagram where the Link thickness is proportional to the flow
quantity.
</p>
<p>This sample customizes LayeredDigraphLayout.</p>
<div>
<div>
<button onclick="load()">Load</button>
</div>
<pre class="lang-js" style="max-height: 250px"><code id="mySavedModel">
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"key":"Coal reserves", "text":"Coal reserves", "color":"#562038"},
{"key":"Coal imports", "text":"Coal imports", "color":"#562038"},
{"key":"Oil reserves", "text":"Oil\nreserves", "color":"#562038"},
{"key":"Oil imports", "text":"Oil imports", "color":"#562038"},
{"key":"Gas reserves", "text":"Gas reserves", "color":"#268251"},
{"key":"Gas imports", "text":"Gas imports", "color":"#268251"},
{"key":"UK land based bioenergy", "text":"UK land based bioenergy", "color":"#f6bcd5"},
{"key":"Marine algae", "text":"Marine algae", "color":"#681313"},
{"key":"Agricultural 'waste'", "text":"Agricultural 'waste'", "color":"#254d57"},
{"key":"Other waste", "text":"Other waste", "color":"#c9b7d8"},
{"key":"Biomass imports", "text":"Biomass imports", "color":"#fea19f"},
{"key":"Biofuel imports", "text":"Biofuel imports", "color":"#d93c3c"},
{"key":"Coal", "text":"Coal", "color":"#562038"},
{"key":"Oil", "text":"Oil", "color":"#562038"},
{"key":"Natural gas", "text":"Natural\ngas", "color":"#254d57"},
{"key":"Solar", "text":"Solar", "color":"#c9a59d"},
{"key":"Solar PV", "text":"Solar PV", "color":"#c9a59d"},
{"key":"Bio-conversion", "text":"Bio-conversion", "color":"#b5cbe9"},
{"key":"Solid", "text":"Solid", "color":"#9c8000"},
{"key":"Liquid", "text":"Liquid", "color":"#e44d42"},
{"key":"Gas", "text":"Gas", "color":"#268251"},
{"key":"Nuclear", "text":"Nuclear", "color":"#c260ad"},
{"key":"Thermal generation", "text":"Thermal\ngeneration", "color":"#254d57"},
{"key":"CHP", "text":"CHP", "color":"yellow"},
{"key":"Electricity imports", "text":"Electricity imports", "color":"yellow"},
{"key":"Wind", "text":"Wind", "color":"#cbcbcb"},
{"key":"Tidal", "text":"Tidal", "color":"#6f3a5f"},
{"key":"Wave", "text":"Wave", "color":"#8b8b8b"},
{"key":"Geothermal", "text":"Geothermal", "color":"#556171"},
{"key":"Hydro", "text":"Hydro", "color":"#7c3e06"},
{"key":"Electricity grid", "text":"Electricity grid", "color":"#c260ad"},
{"key":"H2 conversion", "text":"H2 conversion", "color":"#868686"},
{"key":"Solar Thermal", "text":"Solar Thermal", "color":"#c9a59d"},
{"key":"H2", "text":"H2", "color":"#868686"},
{"key":"Pumped heat", "text":"Pumped heat", "color":"#96665c"},
{"key":"District heating", "text":"District heating", "color":"#c9b7d8"},
{"key":"Losses", "ltext":"Losses", "color":"#254d57"},
{"key":"Over generation / exports", "ltext":"Over generation / exports", "color":"#f6bcd5"},
{"key":"Heating and cooling - homes", "ltext":"Heating and cooling - homes", "color":"#c7a39b"},
{"key":"Road transport", "ltext":"Road transport", "color":"#cbcbcb"},
{"key":"Heating and cooling - commercial", "ltext":"Heating and cooling - commercial", "color":"#c9a59d"},
{"key":"Industry", "ltext":"Industry", "color":"#96665c"},
{"key":"Lighting & appliances - homes", "ltext":"Lighting & appliances - homes", "color":"#2dc3d2"},
{"key":"Lighting & appliances - commercial", "ltext":"Lighting & appliances - commercial", "color":"#2dc3d2"},
{"key":"Agriculture", "ltext":"Agriculture", "color":"#5c5c10"},
{"key":"Rail transport", "ltext":"Rail transport", "color":"#6b6b45"},
{"key":"Domestic aviation", "ltext":"Domestic aviation", "color":"#40a840"},
{"key":"National navigation", "ltext":"National navigation", "color":"#268251"},
{"key":"International aviation", "ltext":"International aviation", "color":"#fec184"},
{"key":"International shipping", "ltext":"International shipping", "color":"#fec184"},
{"key":"Geosequestration", "ltext":"Geosequestration", "color":"#fec184"}
], "linkDataArray": [
{"from":"Coal reserves", "to":"Coal", "width":31},
{"from":"Coal imports", "to":"Coal", "width":86},
{"from":"Oil reserves", "to":"Oil", "width":244},
{"from":"Oil imports", "to":"Oil", "width":1},
{"from":"Gas reserves", "to":"Natural gas", "width":182},
{"from":"Gas imports", "to":"Natural gas", "width":61},
{"from":"UK land based bioenergy", "to":"Bio-conversion", "width":1},
{"from":"Marine algae", "to":"Bio-conversion", "width":1},
{"from":"Agricultural 'waste'", "to":"Bio-conversion", "width":1},
{"from":"Other waste", "to":"Bio-conversion", "width":8},
{"from":"Other waste", "to":"Solid", "width":1},
{"from":"Biomass imports", "to":"Solid", "width":1},
{"from":"Biofuel imports", "to":"Liquid", "width":1},
{"from":"Coal", "to":"Solid", "width":117},
{"from":"Oil", "to":"Liquid", "width":244},
{"from":"Natural gas", "to":"Gas", "width":244},
{"from":"Solar", "to":"Solar PV", "width":1},
{"from":"Solar PV", "to":"Electricity grid", "width":1},
{"from":"Solar", "to":"Solar Thermal", "width":1},
{"from":"Bio-conversion", "to":"Solid", "width":3},
{"from":"Bio-conversion", "to":"Liquid", "width":1},
{"from":"Bio-conversion", "to":"Gas", "width":5},
{"from":"Bio-conversion", "to":"Losses", "width":1},
{"from":"Solid", "to":"Over generation / exports", "width":1},
{"from":"Liquid", "to":"Over generation / exports", "width":18},
{"from":"Gas", "to":"Over generation / exports", "width":1},
{"from":"Solid", "to":"Thermal generation", "width":106},
{"from":"Liquid", "to":"Thermal generation", "width":2},
{"from":"Gas", "to":"Thermal generation", "width":87},
{"from":"Nuclear", "to":"Thermal generation", "width":41},
{"from":"Thermal generation", "to":"District heating", "width":2},
{"from":"Thermal generation", "to":"Electricity grid", "width":92},
{"from":"Thermal generation", "to":"Losses", "width":142},
{"from":"Solid", "to":"CHP", "width":1},
{"from":"Liquid", "to":"CHP", "width":1},
{"from":"Gas", "to":"CHP", "width":1},
{"from":"CHP", "to":"Electricity grid", "width":1},
{"from":"CHP", "to":"Losses", "width":1},
{"from":"Electricity imports", "to":"Electricity grid", "width":1},
{"from":"Wind", "to":"Electricity grid", "width":1},
{"from":"Tidal", "to":"Electricity grid", "width":1},
{"from":"Wave", "to":"Electricity grid", "width":1},
{"from":"Geothermal", "to":"Electricity grid", "width":1},
{"from":"Hydro", "to":"Electricity grid", "width":1},
{"from":"Electricity grid", "to":"H2 conversion", "width":1},
{"from":"Electricity grid", "to":"Over generation / exports", "width":1},
{"from":"Electricity grid", "to":"Losses", "width":6},
{"from":"Gas", "to":"H2 conversion", "width":1},
{"from":"H2 conversion", "to":"H2", "width":1},
{"from":"H2 conversion", "to":"Losses", "width":1},
{"from":"Solar Thermal", "to":"Heating and cooling - homes", "width":1},
{"from":"H2", "to":"Road transport", "width":1},
{"from":"Pumped heat", "to":"Heating and cooling - homes", "width":1},
{"from":"Pumped heat", "to":"Heating and cooling - commercial", "width":1},
{"from":"CHP", "to":"Heating and cooling - homes", "width":1},
{"from":"CHP", "to":"Heating and cooling - commercial", "width":1},
{"from":"District heating", "to":"Heating and cooling - homes", "width":1},
{"from":"District heating", "to":"Heating and cooling - commercial", "width":1},
{"from":"District heating", "to":"Industry", "width":2},
{"from":"Electricity grid", "to":"Heating and cooling - homes", "width":7},
{"from":"Solid", "to":"Heating and cooling - homes", "width":3},
{"from":"Liquid", "to":"Heating and cooling - homes", "width":3},
{"from":"Gas", "to":"Heating and cooling - homes", "width":81},
{"from":"Electricity grid", "to":"Heating and cooling - commercial", "width":7},
{"from":"Solid", "to":"Heating and cooling - commercial", "width":1},
{"from":"Liquid", "to":"Heating and cooling - commercial", "width":2},
{"from":"Gas", "to":"Heating and cooling - commercial", "width":19},
{"from":"Electricity grid", "to":"Lighting & appliances - homes", "width":21},
{"from":"Gas", "to":"Lighting & appliances - homes", "width":2},
{"from":"Electricity grid", "to":"Lighting & appliances - commercial", "width":18},
{"from":"Gas", "to":"Lighting & appliances - commercial", "width":2},
{"from":"Electricity grid", "to":"Industry", "width":30},
{"from":"Solid", "to":"Industry", "width":13},
{"from":"Liquid", "to":"Industry", "width":34},
{"from":"Gas", "to":"Industry", "width":54},
{"from":"Electricity grid", "to":"Agriculture", "width":1},
{"from":"Solid", "to":"Agriculture", "width":1},
{"from":"Liquid", "to":"Agriculture", "width":1},
{"from":"Gas", "to":"Agriculture", "width":1},
{"from":"Electricity grid", "to":"Road transport", "width":1},
{"from":"Liquid", "to":"Road transport", "width":122},
{"from":"Electricity grid", "to":"Rail transport", "width":2},
{"from":"Liquid", "to":"Rail transport", "width":1},
{"from":"Liquid", "to":"Domestic aviation", "width":2},
{"from":"Liquid", "to":"National navigation", "width":4},
{"from":"Liquid", "to":"International aviation", "width":38},
{"from":"Liquid", "to":"International shipping", "width":13},
{"from":"Electricity grid", "to":"Geosequestration", "width":1},
{"from":"Gas", "to":"Losses", "width":2}
]}
</code></pre>
</div>
This sample demonstrates one way of generating a Sankey or flow diagram. The data was derived from
<a href="https://tamc.github.io/Sankey/">https://tamc.github.io/Sankey/</a>.
</div>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Links</h4>
<p>
The <a href="../api/symbols/Link.html" target="api">Link</a> class is used to implement a visual relationship between nodes.
Links are normally created by the presence of link data objects in the <a href="../api/symbols/GraphLinksModel.html#linkDataArray" target="api">GraphLinksModel.linkDataArray</a>
or by a parent key reference as the value of the <a href="../api/symbols/TreeModel.html#nodeParentKeyProperty" target="api">TreeModel.nodeParentKeyProperty</a> of a node data object
in a <a href="../api/symbols/TreeModel.html" target="api">TreeModel</a>.
More information can be found in the <a href="../intro/links.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#links">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Layered Digraph Layout</h4>
<p>
This predefined layout is used for placing Nodes of a general directed graph in layers (rows or columns). This is more general than <a href="../api/symbols/TreeLayout.html">TreeLayout</a>,
as it does not require that the graph be tree-structured.
More information can be found in the <a href="../intro/layouts.html#LayeredDigraphLayout">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#layered-digraph">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Custom Layouts</h4>
<p>
GoJS allows for the creation of custom layouts to meet specific needs.
</p>
<p>
There are also many layouts that are extensions -- not predefined in the <code>go.js</code> or <code>go-debug.js</code> library,
but available as source code in one of the three extension directories, with some documentation and corresponding samples.
More information can be found in the <a href="../intro/layouts.html#CustomLayouts">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#customlayout">Related samples</a>
</p>
<hr>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>