Spaces:
Running
Running
Commit
·
b0b2cd8
1
Parent(s):
7db744a
prueba
Browse files
script.js
CHANGED
|
@@ -1,4 +1,282 @@
|
|
| 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
// Sankey diagram data structure with example data
|
| 3 |
let sankeyData = {
|
| 4 |
nodes: [
|
|
@@ -56,17 +334,14 @@ function initDiagram() {
|
|
| 56 |
|
| 57 |
const sankey = d3.sankey()
|
| 58 |
.nodeWidth(parseInt(document.getElementById('node-width').value))
|
| 59 |
-
.nodePadding(
|
| 60 |
-
.
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
nodes: sankeyData.nodes.map(d => Object.assign({}, d)),
|
| 65 |
-
links: sankeyData.links.map(d => Object.assign({}, d))
|
| 66 |
-
});
|
| 67 |
|
| 68 |
-
const nodes =
|
| 69 |
-
const links =
|
| 70 |
|
| 71 |
// Clear previous diagram
|
| 72 |
svg.attr("width", width)
|
|
@@ -80,13 +355,13 @@ function initDiagram() {
|
|
| 80 |
.selectAll("path")
|
| 81 |
.data(links)
|
| 82 |
.join("path")
|
| 83 |
-
.attr("d",
|
| 84 |
.attr("stroke", d => {
|
| 85 |
const srcColor = sankeyData.nodes[d.source.index]?.color || "#93c5fd";
|
| 86 |
const rgb = hexToRgb(srcColor);
|
| 87 |
return `rgba(${rgb.r},${rgb.g},${rgb.b},${linkAlpha})`;
|
| 88 |
})
|
| 89 |
-
.attr("stroke-width", d => Math.max(1, d.
|
| 90 |
.style("mix-blend-mode", "multiply")
|
| 91 |
.on("mouseover", function() {
|
| 92 |
d3.select(this).attr("stroke-opacity", 0.8);
|
|
@@ -100,17 +375,17 @@ function initDiagram() {
|
|
| 100 |
.selectAll("g")
|
| 101 |
.data(nodes)
|
| 102 |
.join("g")
|
| 103 |
-
.attr("transform", d => `translate(${d.
|
| 104 |
|
| 105 |
// Apply stored positions if they exist
|
| 106 |
node.attr("transform", d => {
|
| 107 |
const pos = nodePositions[d.index];
|
| 108 |
-
return pos ? `translate(${pos.x},${pos.y})` : `translate(${d.
|
| 109 |
});
|
| 110 |
|
| 111 |
node.append("rect")
|
| 112 |
-
.attr("height", d => d.
|
| 113 |
-
.attr("width", d => d.
|
| 114 |
.attr("fill", d => d.color || "#4f46e5")
|
| 115 |
.attr("stroke", "#fff")
|
| 116 |
.attr("stroke-width", 1)
|
|
@@ -119,22 +394,21 @@ function initDiagram() {
|
|
| 119 |
|
| 120 |
// Add drag behavior
|
| 121 |
node.call(d3.drag()
|
| 122 |
-
.subject(d => ({ x: d.
|
| 123 |
.on("start", function(event) {
|
| 124 |
d3.select(this).raise();
|
| 125 |
})
|
| 126 |
.on("drag", function(event, d) {
|
| 127 |
-
//
|
| 128 |
-
d.
|
| 129 |
-
d.
|
| 130 |
-
|
| 131 |
-
d.y1 = event.y + (d.y1 - d.y0);
|
| 132 |
-
|
| 133 |
-
nodePositions[d.index] = { x: d.x0, y: d.y0 };
|
| 134 |
-
d3.select(this).attr("transform", `translate(${d.x0},${d.y0})`);
|
| 135 |
|
| 136 |
// Update links
|
| 137 |
-
link.attr("d",
|
|
|
|
|
|
|
|
|
|
| 138 |
})
|
| 139 |
);
|
| 140 |
|
|
|
|
| 1 |
|
| 2 |
+
// Define the d3.sankey layout function
|
| 3 |
+
d3.sankey = function() {
|
| 4 |
+
var sankey = {},
|
| 5 |
+
nodeWidth = 24,
|
| 6 |
+
nodePadding = 8,
|
| 7 |
+
size = [1, 1],
|
| 8 |
+
nodes = [],
|
| 9 |
+
links = [];
|
| 10 |
+
|
| 11 |
+
sankey.nodeWidth = function(_) {
|
| 12 |
+
if (!arguments.length) return nodeWidth;
|
| 13 |
+
nodeWidth = +_;
|
| 14 |
+
return sankey;
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
sankey.nodePadding = function(_) {
|
| 18 |
+
if (!arguments.length) return nodePadding;
|
| 19 |
+
nodePadding = +_;
|
| 20 |
+
return sankey;
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
sankey.nodes = function(_) {
|
| 24 |
+
if (!arguments.length) return nodes;
|
| 25 |
+
nodes = _;
|
| 26 |
+
return sankey;
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
sankey.links = function(_) {
|
| 30 |
+
if (!arguments.length) return links;
|
| 31 |
+
links = _;
|
| 32 |
+
return sankey;
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
sankey.size = function(_) {
|
| 36 |
+
if (!arguments.length) return size;
|
| 37 |
+
size = _;
|
| 38 |
+
return sankey;
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
sankey.layout = function(iterations) {
|
| 42 |
+
if (!nodes.length) return sankey;
|
| 43 |
+
computeNodeLinks();
|
| 44 |
+
computeNodeValues();
|
| 45 |
+
computeNodeBreadths();
|
| 46 |
+
computeNodeDepths(iterations || 32);
|
| 47 |
+
computeLinkDepths();
|
| 48 |
+
return sankey;
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
sankey.relayout = function() {
|
| 52 |
+
computeLinkDepths();
|
| 53 |
+
return sankey;
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
sankey.link = function() {
|
| 57 |
+
var curvature = .5;
|
| 58 |
+
|
| 59 |
+
function link(d) {
|
| 60 |
+
var x0 = d.source.x + d.source.dx,
|
| 61 |
+
x1 = d.target.x,
|
| 62 |
+
xi = d3.interpolateNumber(x0, x1),
|
| 63 |
+
x2 = xi(curvature),
|
| 64 |
+
x3 = xi(1 - curvature),
|
| 65 |
+
y0 = d.source.y + d.sy + d.dy / 2,
|
| 66 |
+
y1 = d.target.y + d.ty + d.dy / 2;
|
| 67 |
+
return "M" + x0 + "," + y0
|
| 68 |
+
+ "C" + x2 + "," + y0
|
| 69 |
+
+ " " + x3 + "," + y1
|
| 70 |
+
+ " " + x1 + "," + y1;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
link.curvature = function(_) {
|
| 74 |
+
if (!arguments.length) return curvature;
|
| 75 |
+
curvature = +_;
|
| 76 |
+
return link;
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
return link;
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
// Populate the sourceLinks and targetLinks for each node.
|
| 83 |
+
function computeNodeLinks() {
|
| 84 |
+
nodes.forEach(function(node) {
|
| 85 |
+
node.sourceLinks = [];
|
| 86 |
+
node.targetLinks = [];
|
| 87 |
+
});
|
| 88 |
+
links.forEach(function(link) {
|
| 89 |
+
var source = link.source,
|
| 90 |
+
target = link.target;
|
| 91 |
+
if (typeof source === "number") source = link.source = nodes[link.source];
|
| 92 |
+
if (typeof target === "number") target = link.target = nodes[link.target];
|
| 93 |
+
source.sourceLinks.push(link);
|
| 94 |
+
target.targetLinks.push(link);
|
| 95 |
+
});
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Compute the value (size) of each node by summing the associated links.
|
| 99 |
+
function computeNodeValues() {
|
| 100 |
+
nodes.forEach(function(node) {
|
| 101 |
+
node.value = Math.max(
|
| 102 |
+
d3.sum(node.sourceLinks, value),
|
| 103 |
+
d3.sum(node.targetLinks, value)
|
| 104 |
+
);
|
| 105 |
+
});
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
function computeNodeBreadths() {
|
| 109 |
+
var remainingNodes = nodes,
|
| 110 |
+
nextNodes,
|
| 111 |
+
x = 0;
|
| 112 |
+
|
| 113 |
+
while (remainingNodes.length) {
|
| 114 |
+
nextNodes = [];
|
| 115 |
+
remainingNodes.forEach(function(node) {
|
| 116 |
+
node.x = x;
|
| 117 |
+
node.dx = nodeWidth;
|
| 118 |
+
node.sourceLinks.forEach(function(link) {
|
| 119 |
+
if (nextNodes.indexOf(link.target) < 0) {
|
| 120 |
+
nextNodes.push(link.target);
|
| 121 |
+
}
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
remainingNodes = nextNodes;
|
| 125 |
+
++x;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
moveSinksRight(x);
|
| 129 |
+
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
function moveSinksRight(x) {
|
| 133 |
+
nodes.forEach(function(node) {
|
| 134 |
+
if (!node.sourceLinks.length) {
|
| 135 |
+
node.x = x - 1;
|
| 136 |
+
}
|
| 137 |
+
});
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
function scaleNodeBreadths(kx) {
|
| 141 |
+
nodes.forEach(function(node) {
|
| 142 |
+
node.x *= kx;
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
function computeNodeDepths(iterations) {
|
| 147 |
+
// Group nodes by x-coordinate
|
| 148 |
+
var nodesByBreadth = d3.group(nodes, d => d.x);
|
| 149 |
+
// Convert Map to Array and ensure it's sorted
|
| 150 |
+
nodesByBreadth = Array.from(nodesByBreadth.values());
|
| 151 |
+
|
| 152 |
+
initializeNodeDepth();
|
| 153 |
+
resolveCollisions();
|
| 154 |
+
for (var alpha = 1; iterations > 0; --iterations) {
|
| 155 |
+
relaxRightToLeft(alpha *= .99);
|
| 156 |
+
resolveCollisions();
|
| 157 |
+
relaxLeftToRight(alpha);
|
| 158 |
+
resolveCollisions();
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
function initializeNodeDepth() {
|
| 162 |
+
// Calculate vertical scaling factor
|
| 163 |
+
var ky = d3.min(nodesByBreadth, function(nodes) {
|
| 164 |
+
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
|
| 165 |
+
});
|
| 166 |
+
|
| 167 |
+
nodesByBreadth.forEach(function(nodes) {
|
| 168 |
+
nodes.forEach(function(node, i) {
|
| 169 |
+
node.y = i;
|
| 170 |
+
node.dy = node.value * ky;
|
| 171 |
+
});
|
| 172 |
+
});
|
| 173 |
+
|
| 174 |
+
links.forEach(function(link) {
|
| 175 |
+
link.dy = link.value * ky;
|
| 176 |
+
});
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
function relaxLeftToRight(alpha) {
|
| 180 |
+
nodesByBreadth.forEach(function(nodes) {
|
| 181 |
+
nodes.forEach(function(node) {
|
| 182 |
+
if (node.targetLinks.length) {
|
| 183 |
+
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
|
| 184 |
+
node.y += (y - center(node)) * alpha;
|
| 185 |
+
}
|
| 186 |
+
});
|
| 187 |
+
});
|
| 188 |
+
|
| 189 |
+
function weightedSource(link) {
|
| 190 |
+
return center(link.source) * link.value;
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
function relaxRightToLeft(alpha) {
|
| 195 |
+
nodesByBreadth.slice().reverse().forEach(function(nodes) {
|
| 196 |
+
nodes.forEach(function(node) {
|
| 197 |
+
if (node.sourceLinks.length) {
|
| 198 |
+
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
|
| 199 |
+
node.y += (y - center(node)) * alpha;
|
| 200 |
+
}
|
| 201 |
+
});
|
| 202 |
+
});
|
| 203 |
+
|
| 204 |
+
function weightedTarget(link) {
|
| 205 |
+
return center(link.target) * link.value;
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function resolveCollisions() {
|
| 210 |
+
nodesByBreadth.forEach(function(nodes) {
|
| 211 |
+
var node,
|
| 212 |
+
dy,
|
| 213 |
+
y0 = 0,
|
| 214 |
+
n = nodes.length,
|
| 215 |
+
i;
|
| 216 |
+
|
| 217 |
+
nodes.sort(ascendingDepth);
|
| 218 |
+
for (i = 0; i < n; ++i) {
|
| 219 |
+
node = nodes[i];
|
| 220 |
+
dy = y0 - node.y;
|
| 221 |
+
if (dy > 0) node.y += dy;
|
| 222 |
+
y0 = node.y + node.dy + nodePadding;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
dy = y0 - nodePadding - size[1];
|
| 226 |
+
if (dy > 0) {
|
| 227 |
+
y0 = node.y -= dy;
|
| 228 |
+
for (i = n - 2; i >= 0; --i) {
|
| 229 |
+
node = nodes[i];
|
| 230 |
+
dy = node.y + node.dy + nodePadding - y0;
|
| 231 |
+
if (dy > 0) node.y -= dy;
|
| 232 |
+
y0 = node.y;
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
});
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function ascendingDepth(a, b) {
|
| 239 |
+
return a.y - b.y;
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
function computeLinkDepths() {
|
| 244 |
+
nodes.forEach(function(node) {
|
| 245 |
+
node.sourceLinks.sort(ascendingTargetDepth);
|
| 246 |
+
node.targetLinks.sort(ascendingSourceDepth);
|
| 247 |
+
});
|
| 248 |
+
nodes.forEach(function(node) {
|
| 249 |
+
var sy = 0, ty = 0;
|
| 250 |
+
node.sourceLinks.forEach(function(link) {
|
| 251 |
+
link.sy = sy;
|
| 252 |
+
sy += link.dy;
|
| 253 |
+
});
|
| 254 |
+
node.targetLinks.forEach(function(link) {
|
| 255 |
+
link.ty = ty;
|
| 256 |
+
ty += link.dy;
|
| 257 |
+
});
|
| 258 |
+
});
|
| 259 |
+
|
| 260 |
+
function ascendingSourceDepth(a, b) {
|
| 261 |
+
return a.source.y - b.source.y;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
function ascendingTargetDepth(a, b) {
|
| 265 |
+
return a.target.y - b.target.y;
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
function value(link) {
|
| 270 |
+
return link.value;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
function center(node) {
|
| 274 |
+
return node.y + node.dy / 2;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
return sankey;
|
| 278 |
+
};
|
| 279 |
+
|
| 280 |
// Sankey diagram data structure with example data
|
| 281 |
let sankeyData = {
|
| 282 |
nodes: [
|
|
|
|
| 334 |
|
| 335 |
const sankey = d3.sankey()
|
| 336 |
.nodeWidth(parseInt(document.getElementById('node-width').value))
|
| 337 |
+
.nodePadding(8)
|
| 338 |
+
.size([width - textPadding, height - textPadding])
|
| 339 |
+
.nodes(sankeyData.nodes.map(d => Object.assign({}, d)))
|
| 340 |
+
.links(sankeyData.links.map(d => Object.assign({}, d)))
|
| 341 |
+
.layout(32); // Increase iterations for better layout
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
+
const nodes = sankey.nodes();
|
| 344 |
+
const links = sankey.links();
|
| 345 |
|
| 346 |
// Clear previous diagram
|
| 347 |
svg.attr("width", width)
|
|
|
|
| 355 |
.selectAll("path")
|
| 356 |
.data(links)
|
| 357 |
.join("path")
|
| 358 |
+
.attr("d", sankey.link())
|
| 359 |
.attr("stroke", d => {
|
| 360 |
const srcColor = sankeyData.nodes[d.source.index]?.color || "#93c5fd";
|
| 361 |
const rgb = hexToRgb(srcColor);
|
| 362 |
return `rgba(${rgb.r},${rgb.g},${rgb.b},${linkAlpha})`;
|
| 363 |
})
|
| 364 |
+
.attr("stroke-width", d => Math.max(1, d.dy))
|
| 365 |
.style("mix-blend-mode", "multiply")
|
| 366 |
.on("mouseover", function() {
|
| 367 |
d3.select(this).attr("stroke-opacity", 0.8);
|
|
|
|
| 375 |
.selectAll("g")
|
| 376 |
.data(nodes)
|
| 377 |
.join("g")
|
| 378 |
+
.attr("transform", d => `translate(${d.x},${d.y})`);
|
| 379 |
|
| 380 |
// Apply stored positions if they exist
|
| 381 |
node.attr("transform", d => {
|
| 382 |
const pos = nodePositions[d.index];
|
| 383 |
+
return pos ? `translate(${pos.x},${pos.y})` : `translate(${d.x},${d.y})`;
|
| 384 |
});
|
| 385 |
|
| 386 |
node.append("rect")
|
| 387 |
+
.attr("height", d => d.dy)
|
| 388 |
+
.attr("width", d => d.dx)
|
| 389 |
.attr("fill", d => d.color || "#4f46e5")
|
| 390 |
.attr("stroke", "#fff")
|
| 391 |
.attr("stroke-width", 1)
|
|
|
|
| 394 |
|
| 395 |
// Add drag behavior
|
| 396 |
node.call(d3.drag()
|
| 397 |
+
.subject(d => ({ x: d.x, y: d.y }))
|
| 398 |
.on("start", function(event) {
|
| 399 |
d3.select(this).raise();
|
| 400 |
})
|
| 401 |
.on("drag", function(event, d) {
|
| 402 |
+
// Keep x position fixed, only allow vertical movement
|
| 403 |
+
d.y = event.y;
|
| 404 |
+
nodePositions[d.index] = { x: d.x, y: d.y };
|
| 405 |
+
d3.select(this).attr("transform", `translate(${d.x},${d.y})`);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
// Update links
|
| 408 |
+
link.attr("d", sankey.link());
|
| 409 |
+
|
| 410 |
+
// After dragging, relayout to maintain proper link positions
|
| 411 |
+
sankey.relayout();
|
| 412 |
})
|
| 413 |
);
|
| 414 |
|