function SankeyChart({
nodes,
links
}, {
format = ",",
nodeId = d => d.id,
nodeGroup,
nodeGroups,
nodeLabel,
nodeTitle = d => `${d.id}\n${format(d.value)}`,
nodeAlign = d3Sankey.sankeyLeft,
nodeWidth = _nodeWidth,
nodePadding = _padding,
nodeLabelPadding = 5,
nodeStroke = "currentColor",
nodeStrokeWidth,
nodeStrokeOpacity,
nodeStrokeLinejoin,
linkSource = ({source}) => source,
linkTarget = ({target}) => target,
linkValue = ({value}) => value,
linkPath = straightLine,
linkTitle = d => `${d.source.id} → ${d.target.id}\n${format(d.value)}`,
linkColor = 'layered',
linkStrokeOpacity = linkOpacity/100,
colors = colorScale,
width = _width,
height = _height,
marginTop = 5,
marginRight = 1,
marginBottom = 5,
marginLeft = 1,
} = {}) {
const LS = d3.map(links, linkSource).map(intern);
const LT = d3.map(links, linkTarget).map(intern);
const LV = d3.map(links, linkValue);
if (nodes === undefined) nodes = Array.from(d3.union(LS, LT), id => ({id}));
const N = d3.map(nodes, nodeId).map(intern);
const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern);
// Replace the input nodes and links with mutable objects for the simulation.
nodes = d3.map(nodes, (_, i) => ({id: N[i]}));
console.log(nodes)
links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i], value: LV[i]}));
// Compute default domains.
if (G && nodeGroups === undefined) nodeGroups = G;
// Construct the scales.
const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);
// Compute the Sankey layout.
const sankey = d3Sankey
.sankey()
.nodeId(({index: i}) => N[i])
.nodeAlign(nodeAlign)
.nodeWidth(nodeWidth)
.nodePadding(nodePadding)
.nodeSort(null)
.iterations(_itterations)
.linkSort(linkSort)
.extent([[marginLeft, marginTop], [width - marginRight, height - marginBottom]])
({nodes, links});
// Compute titles and labels using layout nodes, so as to access aggregate values.
if (typeof format !== "function") format = d3.format(format);
const Tl = nodeLabel === undefined ? N : nodeLabel == null ? null : d3.map(nodes, nodeLabel);
const Tt = nodeTitle == null ? null : d3.map(nodes, nodeTitle);
const Lt = linkTitle == null ? null : d3.map(links, linkTitle);
console.log(nodes);
//const Vl = nodeValue === undefined ? N : nodeValue == null ? null : d3.map(nodes, nodeValue);
// A unique identifier for clip paths (to avoid conflicts).
const uid = `O-${Math.random().toString(16).slice(2)}`;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
const link = svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(links)
.join("g")
.attr("stroke-opacity", d => {
if (d.value <= linkThreshold) return 0;
return linkStrokeOpacity;
})
var totalNodes = 16;
link.append("path")
.attr("d", linkPath)
.attr("stroke", function(d) {
if (linkColor === "layered") {
// Use the color of the target node for the last two nodes
if (d.target.index >= totalNodes - 2) {
return color(G[d.target.index]);
} else {
// Use the color of the source node for other nodes
return color(G[d.source.index]);
}
} else if (linkColor === "source-target") {
return `url(#${uid}-link-${d.index})`;
} else if (linkColor === "source") {
return color(G[d.source.index]);
} else if (linkColor === "target") {
return color(G[d.target.index]);
} else {
// Handle other cases or default behavior here
return linkColor;
}
})
.attr("stroke-width", ({width}) => Math.max(1, width))
.attr('stroke-linejoin', 'bevel')
.call(Lt ? path => path.append("title").text(({index: i}) => Lt[i]) : () => {});
const node = svg.append("g")
.attr("stroke", nodeStroke)
.attr("stroke-width", nodeStrokeWidth)
.attr("stroke-opacity", 1)
.attr("stroke-linejoin", nodeStrokeLinejoin)
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("x", d => d.x0)
.attr('y', d => {
if (d.id === 'Grid') return 0;
return d.y0;
})
.attr("height", d => d.y1 - d.y0,30)
.attr("width", d => d.x1 - d.x0);
if (G) node.attr("fill", ({index: i}) => color(G[i]));
if (Tt) node.append("title").text(({index: i}) => Tt[i]);
const Labels = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 + 34 + (d.layer * 2))
.attr("y", d => {
if (d.value <= 4) {
if (d.id === 'Solar') return d.y1 + 8;
return d.y0 - 8;
}
if (d.id === 'Grid') return (d.y1 - d.y0) / 2;
return (d.y1 + d.y0) / 2;
})
.attr('fill', d => {
if (['Nuclear', 'Hydro', 'Wind', 'Geothermal', 'Coal', 'Petroleum', 'Energy Services'].includes(d.id)) {
if (d.value > 4) return 'white';
}
return 'black';
})
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(({ index: i }) => Tl[i]);
console.log(_mode)
if (_mode === "Verbose") {
Labels.append("tspan")
.attr("x", d => d.x0 + 34 + (d.layer * 2))
.attr("y", d => {
if (d.value <= 4) {
if (d.id === 'Solar') return d.y1 + 18;
return d.y0 + 2;
}
if (d.id === 'Grid') return ((d.y1 - d.y0) / 2) + 10;
return ((d.y1 + d.y0) / 2) + 10;
})
.attr('fill', d => {
if (['Nuclear', 'Hydro', 'Wind', 'Geothermal', 'Coal', 'Petroleum', 'Energy Services'].includes(d.id)) {
if (d.value > 4) return 'white';
}
return 'black';
})
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(d => String(d.value.toFixed(2)));
}
const cloudData = [
{ x: 10, y: 40 },
{ x: 20, y: 32 },
{ x: 30, y: 40 },
{ x: 40, y: 32 },
{ x: 50, y: 40 },
{ x: 60, y: 32 },
{ x: 70, y: 40 },
{ x: 60, y: 48 },
{ x: 50, y: 56 },
{ x: 40, y: 48 },
{ x: 30, y: 56 },
{ x: 20, y: 48 },
{ x: 10, y: 56 },
];
svg.append("g")
.selectAll("circle")
.data(cloudData)
.enter()
.append("circle")
.attr("cx", d => d.x+(width*4/5))
.attr("cy", d => d.y)
.attr("r", 10)
.attr("fill", "grey")
.attr("stroke", "grey");
svg.append("text")
.attr("x", width*9/10)
.attr("y", 60)
.attr("font-size", 50)
.attr("fill","green")
.text("$");
function intern(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
}
return Object.assign(svg.node(), {scales: {color}});
}