UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

555 lines (507 loc) 24.8 kB
<!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>