@opendata-ai/openchart-vanilla 6.5.1 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +45 -2
- package/dist/index.js +759 -49
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/__tests__/sankey.test.ts +133 -0
- package/src/animation.ts +2 -1
- package/src/index.ts +3 -0
- package/src/mount.ts +19 -6
- package/src/sankey-mount.ts +532 -0
- package/src/sankey-renderer.ts +602 -0
- package/src/svg-renderer.ts +1 -32
package/dist/index.js
CHANGED
|
@@ -2788,7 +2788,7 @@ function createGraph(container, spec, options) {
|
|
|
2788
2788
|
}
|
|
2789
2789
|
chromeEl = document.createElement("div");
|
|
2790
2790
|
chromeEl.className = "oc-graph-chrome";
|
|
2791
|
-
|
|
2791
|
+
renderChrome3();
|
|
2792
2792
|
wrapper.appendChild(chromeEl);
|
|
2793
2793
|
canvas = document.createElement("canvas");
|
|
2794
2794
|
canvas.className = "oc-graph-canvas";
|
|
@@ -2800,7 +2800,7 @@ function createGraph(container, spec, options) {
|
|
|
2800
2800
|
if (options?.legend !== false) {
|
|
2801
2801
|
legendEl = document.createElement("div");
|
|
2802
2802
|
legendEl.className = "oc-graph-legend";
|
|
2803
|
-
|
|
2803
|
+
renderLegend3();
|
|
2804
2804
|
wrapper.appendChild(legendEl);
|
|
2805
2805
|
}
|
|
2806
2806
|
container.appendChild(wrapper);
|
|
@@ -2809,7 +2809,7 @@ function createGraph(container, spec, options) {
|
|
|
2809
2809
|
renderer = new GraphCanvasRenderer(canvas);
|
|
2810
2810
|
renderer.resize(width, canvasHeight);
|
|
2811
2811
|
}
|
|
2812
|
-
function
|
|
2812
|
+
function renderChrome3() {
|
|
2813
2813
|
if (!chromeEl) return;
|
|
2814
2814
|
let html = "";
|
|
2815
2815
|
if (compilation.chrome.title) {
|
|
@@ -2825,7 +2825,7 @@ function createGraph(container, spec, options) {
|
|
|
2825
2825
|
chromeEl.style.display = "";
|
|
2826
2826
|
}
|
|
2827
2827
|
}
|
|
2828
|
-
function
|
|
2828
|
+
function renderLegend3() {
|
|
2829
2829
|
if (!legendEl) return;
|
|
2830
2830
|
const entries = compilation.legend.entries;
|
|
2831
2831
|
if (entries.length === 0) {
|
|
@@ -3123,8 +3123,8 @@ function createGraph(container, spec, options) {
|
|
|
3123
3123
|
compilation = compile();
|
|
3124
3124
|
adjacencyMap = buildAdjacencyMap(compilation.edges);
|
|
3125
3125
|
buildDataMaps();
|
|
3126
|
-
|
|
3127
|
-
|
|
3126
|
+
renderChrome3();
|
|
3127
|
+
renderLegend3();
|
|
3128
3128
|
initSimulation();
|
|
3129
3129
|
initInteraction();
|
|
3130
3130
|
hoveredNodeId = null;
|
|
@@ -3158,8 +3158,8 @@ function createGraph(container, spec, options) {
|
|
|
3158
3158
|
};
|
|
3159
3159
|
});
|
|
3160
3160
|
spatialIndex.rebuild(positionedNodes);
|
|
3161
|
-
|
|
3162
|
-
|
|
3161
|
+
renderChrome3();
|
|
3162
|
+
renderLegend3();
|
|
3163
3163
|
needsRender = true;
|
|
3164
3164
|
scheduleRender();
|
|
3165
3165
|
}
|
|
@@ -3264,7 +3264,7 @@ function cancelAnimations(svg) {
|
|
|
3264
3264
|
svg.classList.remove("oc-animate");
|
|
3265
3265
|
}
|
|
3266
3266
|
}
|
|
3267
|
-
function setupAnimationCleanup(svg) {
|
|
3267
|
+
function setupAnimationCleanup(svg, onComplete) {
|
|
3268
3268
|
const style = svg.style;
|
|
3269
3269
|
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 600;
|
|
3270
3270
|
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
@@ -3274,6 +3274,7 @@ function setupAnimationCleanup(svg) {
|
|
|
3274
3274
|
const totalTime = totalStagger + duration + annotationDelay + 500;
|
|
3275
3275
|
const timer2 = setTimeout(() => {
|
|
3276
3276
|
svg.classList.remove("oc-animate");
|
|
3277
|
+
onComplete?.();
|
|
3277
3278
|
}, totalTime);
|
|
3278
3279
|
return () => {
|
|
3279
3280
|
clearTimeout(timer2);
|
|
@@ -3955,32 +3956,7 @@ function renderAnnotation(parent, annotation, index2) {
|
|
|
3955
3956
|
if (annotation.label?.visible) {
|
|
3956
3957
|
if (annotation.label.connector) {
|
|
3957
3958
|
const c2 = annotation.label.connector;
|
|
3958
|
-
if (c2.style === "
|
|
3959
|
-
const pointsDown = c2.to.y > c2.from.y;
|
|
3960
|
-
const caretSize = 4;
|
|
3961
|
-
const labelLines = annotation.label.text.split("\n");
|
|
3962
|
-
const labelFontSize = annotation.label.style.fontSize ?? 12;
|
|
3963
|
-
const labelLineHeight = labelFontSize * (annotation.label.style.lineHeight ?? 1.3);
|
|
3964
|
-
const textBottom = annotation.label.y + (labelLines.length - 1) * labelLineHeight + labelFontSize * 0.25;
|
|
3965
|
-
const textTop = annotation.label.y - labelFontSize;
|
|
3966
|
-
const gapEdge = pointsDown ? textBottom : textTop;
|
|
3967
|
-
const midY = (gapEdge + c2.to.y) / 2;
|
|
3968
|
-
const tipX = c2.to.x;
|
|
3969
|
-
const tipY = pointsDown ? midY + caretSize / 2 : midY - caretSize / 2;
|
|
3970
|
-
const baseY = pointsDown ? tipY - caretSize : tipY + caretSize;
|
|
3971
|
-
const path = createSVGElement("path");
|
|
3972
|
-
path.setAttribute("class", "oc-annotation-connector");
|
|
3973
|
-
setAttrs(path, {
|
|
3974
|
-
d: `M${tipX - caretSize},${baseY} L${tipX},${tipY} L${tipX + caretSize},${baseY}`,
|
|
3975
|
-
fill: "none",
|
|
3976
|
-
stroke: c2.stroke,
|
|
3977
|
-
"stroke-width": 1.5,
|
|
3978
|
-
"stroke-opacity": 0.4,
|
|
3979
|
-
"stroke-linecap": "round",
|
|
3980
|
-
"stroke-linejoin": "round"
|
|
3981
|
-
});
|
|
3982
|
-
g.appendChild(path);
|
|
3983
|
-
} else if (c2.style === "curve") {
|
|
3959
|
+
if (c2.style === "curve") {
|
|
3984
3960
|
renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
|
|
3985
3961
|
} else {
|
|
3986
3962
|
const connector = createSVGElement("line");
|
|
@@ -4910,7 +4886,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
|
|
|
4910
4886
|
};
|
|
4911
4887
|
}
|
|
4912
4888
|
function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
4913
|
-
const
|
|
4889
|
+
const SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
4914
4890
|
const cleanups = [];
|
|
4915
4891
|
const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
|
|
4916
4892
|
for (const el of annotationGroups) {
|
|
@@ -4949,7 +4925,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
4949
4925
|
const createdHandles = [];
|
|
4950
4926
|
for (const ep of endpoints) {
|
|
4951
4927
|
if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
|
|
4952
|
-
const handleEl = document.createElementNS(
|
|
4928
|
+
const handleEl = document.createElementNS(SVG_NS3, "circle");
|
|
4953
4929
|
handleEl.setAttribute("class", "oc-connector-handle");
|
|
4954
4930
|
handleEl.setAttribute("data-endpoint", ep.name);
|
|
4955
4931
|
handleEl.setAttribute("cx", String(ep.cx));
|
|
@@ -5561,6 +5537,7 @@ function createChart(container, spec, options) {
|
|
|
5561
5537
|
let resizeTimer = null;
|
|
5562
5538
|
let isFirstRender = true;
|
|
5563
5539
|
let cleanupAnimations = null;
|
|
5540
|
+
let pendingResize = false;
|
|
5564
5541
|
let selectedElement = options?.selectedElement ?? null;
|
|
5565
5542
|
let overlayElement = null;
|
|
5566
5543
|
let isTextEditingActive = false;
|
|
@@ -5927,7 +5904,13 @@ function createChart(container, spec, options) {
|
|
|
5927
5904
|
container.classList.remove("oc-dark");
|
|
5928
5905
|
}
|
|
5929
5906
|
if (shouldAnimate && svgElement) {
|
|
5930
|
-
cleanupAnimations = setupAnimationCleanup(svgElement)
|
|
5907
|
+
cleanupAnimations = setupAnimationCleanup(svgElement, () => {
|
|
5908
|
+
cleanupAnimations = null;
|
|
5909
|
+
if (pendingResize) {
|
|
5910
|
+
pendingResize = false;
|
|
5911
|
+
resize();
|
|
5912
|
+
}
|
|
5913
|
+
});
|
|
5931
5914
|
}
|
|
5932
5915
|
if (isFirstRender) {
|
|
5933
5916
|
isFirstRender = false;
|
|
@@ -5943,7 +5926,10 @@ function createChart(container, spec, options) {
|
|
|
5943
5926
|
}
|
|
5944
5927
|
function resize() {
|
|
5945
5928
|
if (destroyed) return;
|
|
5946
|
-
if (cleanupAnimations)
|
|
5929
|
+
if (cleanupAnimations) {
|
|
5930
|
+
pendingResize = true;
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
5947
5933
|
render();
|
|
5948
5934
|
}
|
|
5949
5935
|
function doExport(format, exportOptions) {
|
|
@@ -5973,6 +5959,7 @@ function createChart(container, spec, options) {
|
|
|
5973
5959
|
if (cleanupAnimations) {
|
|
5974
5960
|
cleanupAnimations();
|
|
5975
5961
|
cleanupAnimations = null;
|
|
5962
|
+
pendingResize = false;
|
|
5976
5963
|
}
|
|
5977
5964
|
cancelAnimations(svgElement);
|
|
5978
5965
|
if (resizeTimer !== null) {
|
|
@@ -6396,6 +6383,728 @@ function renderCell(cell) {
|
|
|
6396
6383
|
}
|
|
6397
6384
|
}
|
|
6398
6385
|
|
|
6386
|
+
// src/sankey-mount.ts
|
|
6387
|
+
import { compileSankey } from "@opendata-ai/openchart-engine";
|
|
6388
|
+
|
|
6389
|
+
// src/sankey-renderer.ts
|
|
6390
|
+
import { BRAND_MIN_WIDTH as BRAND_MIN_WIDTH3, estimateTextWidth as estimateTextWidth2 } from "@opendata-ai/openchart-core";
|
|
6391
|
+
import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
|
|
6392
|
+
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
6393
|
+
var XLINK_NS2 = "http://www.w3.org/1999/xlink";
|
|
6394
|
+
var BRAND_URL2 = "https://opendata.ai";
|
|
6395
|
+
var EASE_VAR_MAP2 = {
|
|
6396
|
+
smooth: "var(--oc-ease-smooth)",
|
|
6397
|
+
snappy: "var(--oc-ease-snappy)"
|
|
6398
|
+
};
|
|
6399
|
+
function createSVGElement2(tag) {
|
|
6400
|
+
return document.createElementNS(SVG_NS2, tag);
|
|
6401
|
+
}
|
|
6402
|
+
function setAttrs2(el, attrs) {
|
|
6403
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
6404
|
+
el.setAttribute(key, String(value));
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
function applyTextStyle2(el, style) {
|
|
6408
|
+
setAttrs2(el, {
|
|
6409
|
+
"font-family": style.fontFamily,
|
|
6410
|
+
"font-size": style.fontSize,
|
|
6411
|
+
"font-weight": style.fontWeight
|
|
6412
|
+
});
|
|
6413
|
+
el.style.setProperty("fill", style.fill);
|
|
6414
|
+
if (style.textAnchor) {
|
|
6415
|
+
el.setAttribute("text-anchor", style.textAnchor);
|
|
6416
|
+
}
|
|
6417
|
+
if (style.dominantBaseline) {
|
|
6418
|
+
el.setAttribute("dominant-baseline", style.dominantBaseline);
|
|
6419
|
+
}
|
|
6420
|
+
if (style.fontVariant) {
|
|
6421
|
+
el.setAttribute("font-variant", style.fontVariant);
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
function stampAnimationAttrs2(el, mark, fallbackIndex, animation) {
|
|
6425
|
+
if (!animation?.enabled) return;
|
|
6426
|
+
const idx = mark.animationIndex ?? fallbackIndex;
|
|
6427
|
+
el.setAttribute("data-animation-index", String(idx));
|
|
6428
|
+
el.style.setProperty("--oc-mark-index", String(idx));
|
|
6429
|
+
}
|
|
6430
|
+
function wrapText2(text, fontSize, fontWeight, maxWidth) {
|
|
6431
|
+
if (maxWidth <= 0) return [text];
|
|
6432
|
+
const AVG_CHAR_WIDTH = 0.55;
|
|
6433
|
+
const WEIGHT_FACTORS = {
|
|
6434
|
+
100: 0.9,
|
|
6435
|
+
200: 0.92,
|
|
6436
|
+
300: 0.95,
|
|
6437
|
+
400: 1,
|
|
6438
|
+
500: 1.02,
|
|
6439
|
+
600: 1.05,
|
|
6440
|
+
700: 1.08,
|
|
6441
|
+
800: 1.1,
|
|
6442
|
+
900: 1.12
|
|
6443
|
+
};
|
|
6444
|
+
const weightFactor = WEIGHT_FACTORS[fontWeight] ?? 1;
|
|
6445
|
+
const charWidth = fontSize * AVG_CHAR_WIDTH * weightFactor;
|
|
6446
|
+
const maxChars = Math.floor(maxWidth / charWidth);
|
|
6447
|
+
if (text.length <= maxChars) return [text];
|
|
6448
|
+
const words = text.split(" ");
|
|
6449
|
+
const lines = [];
|
|
6450
|
+
let current = "";
|
|
6451
|
+
for (const word of words) {
|
|
6452
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
6453
|
+
if (candidate.length > maxChars && current) {
|
|
6454
|
+
lines.push(current);
|
|
6455
|
+
current = word;
|
|
6456
|
+
} else {
|
|
6457
|
+
current = candidate;
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
if (current) lines.push(current);
|
|
6461
|
+
return lines;
|
|
6462
|
+
}
|
|
6463
|
+
function renderChromeElement2(parent, element, className, chromeKey) {
|
|
6464
|
+
const text = createSVGElement2("text");
|
|
6465
|
+
setAttrs2(text, { x: element.x, y: element.y });
|
|
6466
|
+
applyTextStyle2(text, element.style);
|
|
6467
|
+
text.setAttribute("class", className);
|
|
6468
|
+
text.setAttribute("data-chrome-key", chromeKey);
|
|
6469
|
+
const lines = wrapText2(
|
|
6470
|
+
element.text,
|
|
6471
|
+
element.style.fontSize,
|
|
6472
|
+
element.style.fontWeight,
|
|
6473
|
+
element.maxWidth
|
|
6474
|
+
);
|
|
6475
|
+
if (lines.length === 1) {
|
|
6476
|
+
text.textContent = element.text;
|
|
6477
|
+
} else {
|
|
6478
|
+
const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
|
|
6479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6480
|
+
const tspan = createSVGElement2("tspan");
|
|
6481
|
+
setAttrs2(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
|
|
6482
|
+
tspan.textContent = lines[i];
|
|
6483
|
+
text.appendChild(tspan);
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
parent.appendChild(text);
|
|
6487
|
+
}
|
|
6488
|
+
function renderChrome2(parent, layout) {
|
|
6489
|
+
const g = createSVGElement2("g");
|
|
6490
|
+
g.setAttribute("class", "oc-chrome");
|
|
6491
|
+
const { chrome } = layout;
|
|
6492
|
+
if (chrome.title) {
|
|
6493
|
+
renderChromeElement2(g, chrome.title, "oc-title", "title");
|
|
6494
|
+
}
|
|
6495
|
+
if (chrome.subtitle) {
|
|
6496
|
+
renderChromeElement2(g, chrome.subtitle, "oc-subtitle", "subtitle");
|
|
6497
|
+
}
|
|
6498
|
+
const bottomOffset = layout.area.y + layout.area.height;
|
|
6499
|
+
if (chrome.source) {
|
|
6500
|
+
renderChromeElement2(
|
|
6501
|
+
g,
|
|
6502
|
+
{ ...chrome.source, y: bottomOffset + chrome.source.y },
|
|
6503
|
+
"oc-source",
|
|
6504
|
+
"source"
|
|
6505
|
+
);
|
|
6506
|
+
}
|
|
6507
|
+
if (chrome.byline) {
|
|
6508
|
+
renderChromeElement2(
|
|
6509
|
+
g,
|
|
6510
|
+
{ ...chrome.byline, y: bottomOffset + chrome.byline.y },
|
|
6511
|
+
"oc-byline",
|
|
6512
|
+
"byline"
|
|
6513
|
+
);
|
|
6514
|
+
}
|
|
6515
|
+
if (chrome.footer) {
|
|
6516
|
+
renderChromeElement2(
|
|
6517
|
+
g,
|
|
6518
|
+
{ ...chrome.footer, y: bottomOffset + chrome.footer.y },
|
|
6519
|
+
"oc-footer",
|
|
6520
|
+
"footer"
|
|
6521
|
+
);
|
|
6522
|
+
}
|
|
6523
|
+
parent.appendChild(g);
|
|
6524
|
+
}
|
|
6525
|
+
function renderBrand2(parent, layout) {
|
|
6526
|
+
if (layout.dimensions.width < BRAND_MIN_WIDTH3) return;
|
|
6527
|
+
const { width } = layout.dimensions;
|
|
6528
|
+
const padding = layout.theme.spacing.padding;
|
|
6529
|
+
const rightEdge = width - padding;
|
|
6530
|
+
const fill = layout.theme.colors.axis;
|
|
6531
|
+
const bottomOffset = layout.area.y + layout.area.height;
|
|
6532
|
+
const { chrome } = layout;
|
|
6533
|
+
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
6534
|
+
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
|
|
6535
|
+
const a2 = createSVGElement2("a");
|
|
6536
|
+
a2.setAttribute("href", BRAND_URL2);
|
|
6537
|
+
a2.setAttributeNS(XLINK_NS2, "xlink:href", BRAND_URL2);
|
|
6538
|
+
a2.setAttribute("target", "_blank");
|
|
6539
|
+
a2.setAttribute("rel", "noopener");
|
|
6540
|
+
a2.setAttribute("class", "oc-chrome-ref");
|
|
6541
|
+
const text = createSVGElement2("text");
|
|
6542
|
+
setAttrs2(text, {
|
|
6543
|
+
x: rightEdge,
|
|
6544
|
+
y: chromeY,
|
|
6545
|
+
"dominant-baseline": "hanging",
|
|
6546
|
+
"text-anchor": "end",
|
|
6547
|
+
"font-family": layout.theme.fonts.family,
|
|
6548
|
+
"font-size": 20,
|
|
6549
|
+
"fill-opacity": 0.55
|
|
6550
|
+
});
|
|
6551
|
+
text.style.setProperty("fill", fill);
|
|
6552
|
+
const openSpan = createSVGElement2("tspan");
|
|
6553
|
+
setAttrs2(openSpan, { "font-weight": 500 });
|
|
6554
|
+
openSpan.textContent = "Open";
|
|
6555
|
+
const dataSpan = createSVGElement2("tspan");
|
|
6556
|
+
setAttrs2(dataSpan, { "font-weight": 600 });
|
|
6557
|
+
dataSpan.textContent = "Data";
|
|
6558
|
+
text.appendChild(openSpan);
|
|
6559
|
+
text.appendChild(dataSpan);
|
|
6560
|
+
a2.appendChild(text);
|
|
6561
|
+
parent.appendChild(a2);
|
|
6562
|
+
}
|
|
6563
|
+
function renderLegend2(parent, legend) {
|
|
6564
|
+
if (legend.entries.length === 0) return;
|
|
6565
|
+
const g = createSVGElement2("g");
|
|
6566
|
+
g.setAttribute("class", "oc-legend");
|
|
6567
|
+
g.setAttribute("role", "list");
|
|
6568
|
+
g.setAttribute("aria-label", "Chart legend");
|
|
6569
|
+
const isHorizontal = legend.position === "top" || legend.position === "bottom";
|
|
6570
|
+
let offsetX = legend.bounds.x;
|
|
6571
|
+
let offsetY = legend.bounds.y;
|
|
6572
|
+
for (let i = 0; i < legend.entries.length; i++) {
|
|
6573
|
+
const entry = legend.entries[i];
|
|
6574
|
+
if (isHorizontal && i > 0) {
|
|
6575
|
+
const labelWidth = estimateTextWidth2(
|
|
6576
|
+
entry.label,
|
|
6577
|
+
legend.labelStyle.fontSize,
|
|
6578
|
+
legend.labelStyle.fontWeight
|
|
6579
|
+
);
|
|
6580
|
+
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
6581
|
+
if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {
|
|
6582
|
+
offsetX = legend.bounds.x;
|
|
6583
|
+
offsetY += legend.swatchSize + 6;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
const entryG = createSVGElement2("g");
|
|
6587
|
+
entryG.setAttribute("class", "oc-legend-entry");
|
|
6588
|
+
entryG.setAttribute("role", "listitem");
|
|
6589
|
+
entryG.setAttribute("data-legend-index", String(i));
|
|
6590
|
+
entryG.setAttribute("data-legend-label", entry.label);
|
|
6591
|
+
if (entry.overflow) {
|
|
6592
|
+
entryG.setAttribute("data-legend-overflow", "true");
|
|
6593
|
+
entryG.setAttribute("aria-label", entry.label);
|
|
6594
|
+
entryG.setAttribute("opacity", "0.5");
|
|
6595
|
+
} else {
|
|
6596
|
+
entryG.setAttribute(
|
|
6597
|
+
"aria-label",
|
|
6598
|
+
`${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
|
|
6599
|
+
);
|
|
6600
|
+
entryG.setAttribute("style", "cursor: pointer");
|
|
6601
|
+
if (entry.active === false) {
|
|
6602
|
+
entryG.setAttribute("opacity", "0.3");
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
const rect = createSVGElement2("rect");
|
|
6606
|
+
setAttrs2(rect, {
|
|
6607
|
+
x: offsetX,
|
|
6608
|
+
y: offsetY,
|
|
6609
|
+
width: legend.swatchSize,
|
|
6610
|
+
height: legend.swatchSize,
|
|
6611
|
+
fill: entry.color,
|
|
6612
|
+
rx: 2
|
|
6613
|
+
});
|
|
6614
|
+
entryG.appendChild(rect);
|
|
6615
|
+
const label = createSVGElement2("text");
|
|
6616
|
+
setAttrs2(label, {
|
|
6617
|
+
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
6618
|
+
y: offsetY + legend.swatchSize / 2,
|
|
6619
|
+
"dominant-baseline": "central"
|
|
6620
|
+
});
|
|
6621
|
+
applyTextStyle2(label, legend.labelStyle);
|
|
6622
|
+
label.textContent = entry.label;
|
|
6623
|
+
entryG.appendChild(label);
|
|
6624
|
+
g.appendChild(entryG);
|
|
6625
|
+
if (isHorizontal) {
|
|
6626
|
+
const labelWidth = estimateTextWidth2(
|
|
6627
|
+
entry.label,
|
|
6628
|
+
legend.labelStyle.fontSize,
|
|
6629
|
+
legend.labelStyle.fontWeight
|
|
6630
|
+
);
|
|
6631
|
+
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
6632
|
+
offsetX += entryWidth;
|
|
6633
|
+
} else {
|
|
6634
|
+
offsetY += legend.swatchSize + legend.entryGap;
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
parent.appendChild(g);
|
|
6638
|
+
}
|
|
6639
|
+
function buildNodePositionMap(nodes) {
|
|
6640
|
+
const map = /* @__PURE__ */ new Map();
|
|
6641
|
+
for (const node of nodes) {
|
|
6642
|
+
map.set(node.nodeId, { x: node.x, width: node.width });
|
|
6643
|
+
}
|
|
6644
|
+
return map;
|
|
6645
|
+
}
|
|
6646
|
+
function renderGradientDefs(defs, links, nodePositions) {
|
|
6647
|
+
for (let i = 0; i < links.length; i++) {
|
|
6648
|
+
const link = links[i];
|
|
6649
|
+
if (link.sourceColor === link.targetColor) continue;
|
|
6650
|
+
const gradId = `oc-sg-${link.sourceId}-${link.targetId}-${i}`;
|
|
6651
|
+
const gradient = createSVGElement2("linearGradient");
|
|
6652
|
+
gradient.setAttribute("id", gradId);
|
|
6653
|
+
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
6654
|
+
const sourcePos = nodePositions.get(link.sourceId);
|
|
6655
|
+
const targetPos = nodePositions.get(link.targetId);
|
|
6656
|
+
const x1 = sourcePos ? sourcePos.x + sourcePos.width : 0;
|
|
6657
|
+
const x22 = targetPos ? targetPos.x : 0;
|
|
6658
|
+
gradient.setAttribute("x1", String(x1));
|
|
6659
|
+
gradient.setAttribute("x2", String(x22));
|
|
6660
|
+
const stop0 = createSVGElement2("stop");
|
|
6661
|
+
setAttrs2(stop0, { offset: "0%", "stop-color": link.sourceColor });
|
|
6662
|
+
gradient.appendChild(stop0);
|
|
6663
|
+
const stop1 = createSVGElement2("stop");
|
|
6664
|
+
setAttrs2(stop1, { offset: "100%", "stop-color": link.targetColor });
|
|
6665
|
+
gradient.appendChild(stop1);
|
|
6666
|
+
defs.appendChild(gradient);
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
function renderLinks(parent, links, _nodePositions, animation) {
|
|
6670
|
+
const g = createSVGElement2("g");
|
|
6671
|
+
g.setAttribute("class", "oc-sankey-links");
|
|
6672
|
+
for (let i = 0; i < links.length; i++) {
|
|
6673
|
+
const link = links[i];
|
|
6674
|
+
const linkG = createSVGElement2("g");
|
|
6675
|
+
linkG.setAttribute("class", "oc-sankey-link");
|
|
6676
|
+
linkG.setAttribute("data-mark-id", `link-${link.sourceId}-${link.targetId}`);
|
|
6677
|
+
linkG.setAttribute("data-source", link.sourceId);
|
|
6678
|
+
linkG.setAttribute("data-target", link.targetId);
|
|
6679
|
+
if (link.aria?.label) {
|
|
6680
|
+
linkG.setAttribute("aria-label", link.aria.label);
|
|
6681
|
+
}
|
|
6682
|
+
stampAnimationAttrs2(linkG, link, i, animation);
|
|
6683
|
+
const path = createSVGElement2("path");
|
|
6684
|
+
path.setAttribute("d", link.path);
|
|
6685
|
+
path.setAttribute("stroke", "none");
|
|
6686
|
+
path.setAttribute("fill-opacity", String(link.fillOpacity));
|
|
6687
|
+
if (link.sourceColor !== link.targetColor) {
|
|
6688
|
+
const gradId = `oc-sg-${link.sourceId}-${link.targetId}-${i}`;
|
|
6689
|
+
path.setAttribute("fill", `url(#${gradId})`);
|
|
6690
|
+
} else {
|
|
6691
|
+
path.setAttribute("fill", link.sourceColor);
|
|
6692
|
+
}
|
|
6693
|
+
linkG.appendChild(path);
|
|
6694
|
+
g.appendChild(linkG);
|
|
6695
|
+
}
|
|
6696
|
+
parent.appendChild(g);
|
|
6697
|
+
}
|
|
6698
|
+
function renderNodes(parent, nodes, animation) {
|
|
6699
|
+
const g = createSVGElement2("g");
|
|
6700
|
+
g.setAttribute("class", "oc-sankey-nodes");
|
|
6701
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6702
|
+
const node = nodes[i];
|
|
6703
|
+
const nodeG = createSVGElement2("g");
|
|
6704
|
+
nodeG.setAttribute("class", "oc-sankey-node");
|
|
6705
|
+
nodeG.setAttribute("data-mark-id", `node-${node.nodeId}`);
|
|
6706
|
+
nodeG.setAttribute("data-node-id", node.nodeId);
|
|
6707
|
+
if (node.aria?.label) {
|
|
6708
|
+
nodeG.setAttribute("aria-label", node.aria.label);
|
|
6709
|
+
}
|
|
6710
|
+
stampAnimationAttrs2(nodeG, node, i, animation);
|
|
6711
|
+
const rect = createSVGElement2("rect");
|
|
6712
|
+
setAttrs2(rect, {
|
|
6713
|
+
x: node.x,
|
|
6714
|
+
y: node.y,
|
|
6715
|
+
width: node.width,
|
|
6716
|
+
height: Math.max(node.height, 1),
|
|
6717
|
+
// Ensure at least 1px visibility
|
|
6718
|
+
fill: node.fill,
|
|
6719
|
+
rx: node.cornerRadius
|
|
6720
|
+
});
|
|
6721
|
+
if (node.stroke) {
|
|
6722
|
+
rect.setAttribute("stroke", node.stroke);
|
|
6723
|
+
}
|
|
6724
|
+
if (node.strokeWidth) {
|
|
6725
|
+
rect.setAttribute("stroke-width", String(node.strokeWidth));
|
|
6726
|
+
}
|
|
6727
|
+
nodeG.appendChild(rect);
|
|
6728
|
+
g.appendChild(nodeG);
|
|
6729
|
+
}
|
|
6730
|
+
parent.appendChild(g);
|
|
6731
|
+
}
|
|
6732
|
+
function renderLabels(parent, nodes) {
|
|
6733
|
+
const g = createSVGElement2("g");
|
|
6734
|
+
g.setAttribute("class", "oc-sankey-labels");
|
|
6735
|
+
for (const node of nodes) {
|
|
6736
|
+
const { label } = node;
|
|
6737
|
+
if (!label.visible) continue;
|
|
6738
|
+
const text = createSVGElement2("text");
|
|
6739
|
+
setAttrs2(text, { x: label.x, y: label.y });
|
|
6740
|
+
applyTextStyle2(text, label.style);
|
|
6741
|
+
text.textContent = label.text;
|
|
6742
|
+
g.appendChild(text);
|
|
6743
|
+
}
|
|
6744
|
+
parent.appendChild(g);
|
|
6745
|
+
}
|
|
6746
|
+
function renderSankeySVG(layout, animation) {
|
|
6747
|
+
const { width, height } = layout.dimensions;
|
|
6748
|
+
const svg = createSVGElement2("svg");
|
|
6749
|
+
setAttrs2(svg, {
|
|
6750
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
6751
|
+
xmlns: SVG_NS2,
|
|
6752
|
+
overflow: "visible"
|
|
6753
|
+
});
|
|
6754
|
+
svg.style.height = `${height}px`;
|
|
6755
|
+
svg.setAttribute("role", layout.a11y.role);
|
|
6756
|
+
svg.setAttribute("aria-label", layout.a11y.altText);
|
|
6757
|
+
const animate = animation?.enabled;
|
|
6758
|
+
const classes = animate ? "oc-chart oc-sankey oc-animate" : "oc-chart oc-sankey";
|
|
6759
|
+
svg.setAttribute("class", classes);
|
|
6760
|
+
if (animation?.enabled) {
|
|
6761
|
+
const totalMarks = layout.nodes.length + layout.links.length;
|
|
6762
|
+
const stagger = clampStaggerDelay2(animation.staggerDelay, totalMarks);
|
|
6763
|
+
svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
|
|
6764
|
+
svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
6765
|
+
svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
|
|
6766
|
+
const easeVar = EASE_VAR_MAP2[animation.ease] || EASE_VAR_MAP2.smooth;
|
|
6767
|
+
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
6768
|
+
}
|
|
6769
|
+
const bg = createSVGElement2("rect");
|
|
6770
|
+
bg.setAttribute("class", "oc-background");
|
|
6771
|
+
setAttrs2(bg, {
|
|
6772
|
+
x: 0,
|
|
6773
|
+
y: 0,
|
|
6774
|
+
width,
|
|
6775
|
+
height,
|
|
6776
|
+
fill: layout.theme.colors.background
|
|
6777
|
+
});
|
|
6778
|
+
svg.appendChild(bg);
|
|
6779
|
+
const nodePositions = buildNodePositionMap(layout.nodes);
|
|
6780
|
+
const defs = createSVGElement2("defs");
|
|
6781
|
+
renderGradientDefs(defs, layout.links, nodePositions);
|
|
6782
|
+
svg.appendChild(defs);
|
|
6783
|
+
renderLinks(svg, layout.links, nodePositions, animation);
|
|
6784
|
+
renderNodes(svg, layout.nodes, animation);
|
|
6785
|
+
renderLabels(svg, layout.nodes);
|
|
6786
|
+
renderLegend2(svg, layout.legend);
|
|
6787
|
+
renderChrome2(svg, layout);
|
|
6788
|
+
renderBrand2(svg, layout);
|
|
6789
|
+
return svg;
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6792
|
+
// src/sankey-mount.ts
|
|
6793
|
+
function resolveDarkMode3(mode) {
|
|
6794
|
+
if (mode === "force") return true;
|
|
6795
|
+
if (mode === "off" || mode === void 0) return false;
|
|
6796
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
6797
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
6798
|
+
}
|
|
6799
|
+
return false;
|
|
6800
|
+
}
|
|
6801
|
+
var HIGHLIGHT_OPACITY = 0.7;
|
|
6802
|
+
var DIM_OPACITY = 0.15;
|
|
6803
|
+
function createSankey(container, spec, options) {
|
|
6804
|
+
let currentSpec = spec;
|
|
6805
|
+
let currentLayout;
|
|
6806
|
+
let destroyed = false;
|
|
6807
|
+
let svgElement = null;
|
|
6808
|
+
let tooltipManager = null;
|
|
6809
|
+
let cleanupTooltipEvents = null;
|
|
6810
|
+
let disconnectResize = null;
|
|
6811
|
+
let isFirstRender = true;
|
|
6812
|
+
let animationCleanup = null;
|
|
6813
|
+
let pendingResize = false;
|
|
6814
|
+
function getContainerDimensions() {
|
|
6815
|
+
const rect = container.getBoundingClientRect();
|
|
6816
|
+
return {
|
|
6817
|
+
width: Math.max(rect.width || 600, 100),
|
|
6818
|
+
height: Math.max(rect.height || 400, 100)
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
function compile() {
|
|
6822
|
+
const { width, height } = getContainerDimensions();
|
|
6823
|
+
const darkMode = resolveDarkMode3(options?.darkMode);
|
|
6824
|
+
const compileOpts = {
|
|
6825
|
+
width,
|
|
6826
|
+
height,
|
|
6827
|
+
theme: options?.theme,
|
|
6828
|
+
darkMode
|
|
6829
|
+
};
|
|
6830
|
+
return compileSankey(currentSpec, compileOpts);
|
|
6831
|
+
}
|
|
6832
|
+
function wireTooltipAndInteraction(svg, layout) {
|
|
6833
|
+
const cleanups = [];
|
|
6834
|
+
const nodeElements = svg.querySelectorAll(".oc-sankey-node");
|
|
6835
|
+
for (const el of nodeElements) {
|
|
6836
|
+
const markId = el.getAttribute("data-mark-id");
|
|
6837
|
+
if (!markId) continue;
|
|
6838
|
+
const content = layout.tooltipDescriptors.get(markId);
|
|
6839
|
+
const nodeId = el.getAttribute("data-node-id");
|
|
6840
|
+
const nodeData = nodeId ? layout.nodes.find((n) => n.nodeId === nodeId)?.data ?? {} : {};
|
|
6841
|
+
const handleMouseEnter = (e) => {
|
|
6842
|
+
const mouseEvent = e;
|
|
6843
|
+
if (content && tooltipManager) {
|
|
6844
|
+
const svgRect = svg.getBoundingClientRect();
|
|
6845
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
6846
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
6847
|
+
tooltipManager.show(content, x3, y3);
|
|
6848
|
+
}
|
|
6849
|
+
options?.onNodeHover?.(nodeData);
|
|
6850
|
+
if (nodeId) highlightConnectedLinks(svg, nodeId, layout);
|
|
6851
|
+
};
|
|
6852
|
+
const handleMouseMove = (e) => {
|
|
6853
|
+
if (content && tooltipManager) {
|
|
6854
|
+
const mouseEvent = e;
|
|
6855
|
+
const svgRect = svg.getBoundingClientRect();
|
|
6856
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
6857
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
6858
|
+
tooltipManager.show(content, x3, y3);
|
|
6859
|
+
}
|
|
6860
|
+
};
|
|
6861
|
+
const handleMouseLeave = () => {
|
|
6862
|
+
tooltipManager?.hide();
|
|
6863
|
+
options?.onNodeHover?.(null);
|
|
6864
|
+
resetLinkOpacity(svg, layout);
|
|
6865
|
+
};
|
|
6866
|
+
const handleClick = () => {
|
|
6867
|
+
options?.onNodeClick?.(nodeData);
|
|
6868
|
+
};
|
|
6869
|
+
el.addEventListener("mouseenter", handleMouseEnter);
|
|
6870
|
+
el.addEventListener("mousemove", handleMouseMove);
|
|
6871
|
+
el.addEventListener("mouseleave", handleMouseLeave);
|
|
6872
|
+
el.addEventListener("click", handleClick);
|
|
6873
|
+
cleanups.push(() => {
|
|
6874
|
+
el.removeEventListener("mouseenter", handleMouseEnter);
|
|
6875
|
+
el.removeEventListener("mousemove", handleMouseMove);
|
|
6876
|
+
el.removeEventListener("mouseleave", handleMouseLeave);
|
|
6877
|
+
el.removeEventListener("click", handleClick);
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6880
|
+
const linkElements = svg.querySelectorAll(".oc-sankey-link");
|
|
6881
|
+
for (const el of linkElements) {
|
|
6882
|
+
const markId = el.getAttribute("data-mark-id");
|
|
6883
|
+
if (!markId) continue;
|
|
6884
|
+
const content = layout.tooltipDescriptors.get(markId);
|
|
6885
|
+
const sourceId = el.getAttribute("data-source");
|
|
6886
|
+
const targetId = el.getAttribute("data-target");
|
|
6887
|
+
const linkData = findLinkData(layout, sourceId, targetId);
|
|
6888
|
+
const handleMouseEnter = (e) => {
|
|
6889
|
+
const mouseEvent = e;
|
|
6890
|
+
if (content && tooltipManager) {
|
|
6891
|
+
const svgRect = svg.getBoundingClientRect();
|
|
6892
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
6893
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
6894
|
+
tooltipManager.show(content, x3, y3);
|
|
6895
|
+
}
|
|
6896
|
+
options?.onLinkHover?.(linkData);
|
|
6897
|
+
};
|
|
6898
|
+
const handleMouseMove = (e) => {
|
|
6899
|
+
if (content && tooltipManager) {
|
|
6900
|
+
const mouseEvent = e;
|
|
6901
|
+
const svgRect = svg.getBoundingClientRect();
|
|
6902
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
6903
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
6904
|
+
tooltipManager.show(content, x3, y3);
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
const handleMouseLeave = () => {
|
|
6908
|
+
tooltipManager?.hide();
|
|
6909
|
+
options?.onLinkHover?.(null);
|
|
6910
|
+
};
|
|
6911
|
+
const handleClick = () => {
|
|
6912
|
+
options?.onLinkClick?.(linkData);
|
|
6913
|
+
};
|
|
6914
|
+
el.addEventListener("mouseenter", handleMouseEnter);
|
|
6915
|
+
el.addEventListener("mousemove", handleMouseMove);
|
|
6916
|
+
el.addEventListener("mouseleave", handleMouseLeave);
|
|
6917
|
+
el.addEventListener("click", handleClick);
|
|
6918
|
+
cleanups.push(() => {
|
|
6919
|
+
el.removeEventListener("mouseenter", handleMouseEnter);
|
|
6920
|
+
el.removeEventListener("mousemove", handleMouseMove);
|
|
6921
|
+
el.removeEventListener("mouseleave", handleMouseLeave);
|
|
6922
|
+
el.removeEventListener("click", handleClick);
|
|
6923
|
+
});
|
|
6924
|
+
}
|
|
6925
|
+
return () => {
|
|
6926
|
+
for (const cleanup of cleanups) {
|
|
6927
|
+
cleanup();
|
|
6928
|
+
}
|
|
6929
|
+
};
|
|
6930
|
+
}
|
|
6931
|
+
function findLinkData(layout, sourceId, targetId) {
|
|
6932
|
+
if (!sourceId || !targetId) return {};
|
|
6933
|
+
const link = layout.links.find((l) => l.sourceId === sourceId && l.targetId === targetId);
|
|
6934
|
+
return link?.data ?? {};
|
|
6935
|
+
}
|
|
6936
|
+
function highlightConnectedLinks(svg, nodeId, _layout) {
|
|
6937
|
+
const linkElements = svg.querySelectorAll(".oc-sankey-link");
|
|
6938
|
+
for (const el of linkElements) {
|
|
6939
|
+
const source = el.getAttribute("data-source");
|
|
6940
|
+
const target = el.getAttribute("data-target");
|
|
6941
|
+
const path = el.querySelector("path");
|
|
6942
|
+
if (!path) continue;
|
|
6943
|
+
const isConnected = source === nodeId || target === nodeId;
|
|
6944
|
+
path.setAttribute("fill-opacity", String(isConnected ? HIGHLIGHT_OPACITY : DIM_OPACITY));
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
function resetLinkOpacity(svg, layout) {
|
|
6948
|
+
const linkElements = svg.querySelectorAll(".oc-sankey-link");
|
|
6949
|
+
for (const el of linkElements) {
|
|
6950
|
+
const path = el.querySelector("path");
|
|
6951
|
+
if (!path) continue;
|
|
6952
|
+
const source = el.getAttribute("data-source");
|
|
6953
|
+
const target = el.getAttribute("data-target");
|
|
6954
|
+
const link = layout.links.find((l) => l.sourceId === source && l.targetId === target);
|
|
6955
|
+
path.setAttribute("fill-opacity", String(link?.fillOpacity ?? 0.35));
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
function render() {
|
|
6959
|
+
if (destroyed) return;
|
|
6960
|
+
if (animationCleanup) {
|
|
6961
|
+
animationCleanup();
|
|
6962
|
+
animationCleanup = null;
|
|
6963
|
+
}
|
|
6964
|
+
if (svgElement) {
|
|
6965
|
+
cancelAnimations(svgElement);
|
|
6966
|
+
}
|
|
6967
|
+
if (cleanupTooltipEvents) {
|
|
6968
|
+
cleanupTooltipEvents();
|
|
6969
|
+
cleanupTooltipEvents = null;
|
|
6970
|
+
}
|
|
6971
|
+
if (svgElement?.parentNode) {
|
|
6972
|
+
svgElement.parentNode.removeChild(svgElement);
|
|
6973
|
+
}
|
|
6974
|
+
currentLayout = compile();
|
|
6975
|
+
const shouldAnimate = isFirstRender && currentLayout.animation?.enabled;
|
|
6976
|
+
isFirstRender = false;
|
|
6977
|
+
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
6978
|
+
svgElement = renderSankeySVG(currentLayout, animation);
|
|
6979
|
+
container.appendChild(svgElement);
|
|
6980
|
+
const isDark = resolveDarkMode3(options?.darkMode);
|
|
6981
|
+
if (isDark) {
|
|
6982
|
+
container.classList.add("oc-dark");
|
|
6983
|
+
} else {
|
|
6984
|
+
container.classList.remove("oc-dark");
|
|
6985
|
+
}
|
|
6986
|
+
if (options?.tooltip !== false && svgElement) {
|
|
6987
|
+
if (!tooltipManager) {
|
|
6988
|
+
tooltipManager = createTooltipManager(container);
|
|
6989
|
+
}
|
|
6990
|
+
cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);
|
|
6991
|
+
}
|
|
6992
|
+
if (shouldAnimate && svgElement) {
|
|
6993
|
+
animationCleanup = setupAnimationCleanup(svgElement, () => {
|
|
6994
|
+
animationCleanup = null;
|
|
6995
|
+
if (pendingResize) {
|
|
6996
|
+
pendingResize = false;
|
|
6997
|
+
resize();
|
|
6998
|
+
}
|
|
6999
|
+
});
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
function update(newSpec) {
|
|
7003
|
+
if (destroyed) return;
|
|
7004
|
+
currentSpec = newSpec;
|
|
7005
|
+
isFirstRender = true;
|
|
7006
|
+
render();
|
|
7007
|
+
}
|
|
7008
|
+
function resize() {
|
|
7009
|
+
if (destroyed) return;
|
|
7010
|
+
if (animationCleanup) {
|
|
7011
|
+
pendingResize = true;
|
|
7012
|
+
return;
|
|
7013
|
+
}
|
|
7014
|
+
render();
|
|
7015
|
+
}
|
|
7016
|
+
function doExport(format, exportOptions) {
|
|
7017
|
+
if (!svgElement) {
|
|
7018
|
+
throw new Error("Sankey is not rendered yet");
|
|
7019
|
+
}
|
|
7020
|
+
switch (format) {
|
|
7021
|
+
case "svg":
|
|
7022
|
+
return exportSVG(svgElement);
|
|
7023
|
+
case "svg-with-fonts":
|
|
7024
|
+
return exportSVGWithFonts(svgElement, exportOptions);
|
|
7025
|
+
case "png":
|
|
7026
|
+
return exportPNG(svgElement, exportOptions);
|
|
7027
|
+
case "jpg":
|
|
7028
|
+
return exportJPG(svgElement, exportOptions);
|
|
7029
|
+
default:
|
|
7030
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
function destroy() {
|
|
7034
|
+
if (destroyed) return;
|
|
7035
|
+
destroyed = true;
|
|
7036
|
+
if (animationCleanup) {
|
|
7037
|
+
animationCleanup();
|
|
7038
|
+
animationCleanup = null;
|
|
7039
|
+
pendingResize = false;
|
|
7040
|
+
}
|
|
7041
|
+
if (svgElement) {
|
|
7042
|
+
cancelAnimations(svgElement);
|
|
7043
|
+
}
|
|
7044
|
+
if (disconnectResize) {
|
|
7045
|
+
disconnectResize();
|
|
7046
|
+
disconnectResize = null;
|
|
7047
|
+
}
|
|
7048
|
+
if (cleanupTooltipEvents) {
|
|
7049
|
+
cleanupTooltipEvents();
|
|
7050
|
+
cleanupTooltipEvents = null;
|
|
7051
|
+
}
|
|
7052
|
+
if (tooltipManager) {
|
|
7053
|
+
tooltipManager.destroy();
|
|
7054
|
+
tooltipManager = null;
|
|
7055
|
+
}
|
|
7056
|
+
if (svgElement?.parentNode) {
|
|
7057
|
+
svgElement.parentNode.removeChild(svgElement);
|
|
7058
|
+
}
|
|
7059
|
+
svgElement = null;
|
|
7060
|
+
container.classList.remove("oc-dark");
|
|
7061
|
+
}
|
|
7062
|
+
try {
|
|
7063
|
+
currentLayout = compile();
|
|
7064
|
+
const shouldAnimate = currentLayout.animation?.enabled;
|
|
7065
|
+
isFirstRender = false;
|
|
7066
|
+
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
7067
|
+
svgElement = renderSankeySVG(currentLayout, animation);
|
|
7068
|
+
container.appendChild(svgElement);
|
|
7069
|
+
const isDark = resolveDarkMode3(options?.darkMode);
|
|
7070
|
+
if (isDark) {
|
|
7071
|
+
container.classList.add("oc-dark");
|
|
7072
|
+
} else {
|
|
7073
|
+
container.classList.remove("oc-dark");
|
|
7074
|
+
}
|
|
7075
|
+
if (options?.tooltip !== false && svgElement) {
|
|
7076
|
+
tooltipManager = createTooltipManager(container);
|
|
7077
|
+
cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);
|
|
7078
|
+
}
|
|
7079
|
+
if (shouldAnimate && svgElement) {
|
|
7080
|
+
animationCleanup = setupAnimationCleanup(svgElement, () => {
|
|
7081
|
+
animationCleanup = null;
|
|
7082
|
+
if (pendingResize) {
|
|
7083
|
+
pendingResize = false;
|
|
7084
|
+
resize();
|
|
7085
|
+
}
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
} catch (err) {
|
|
7089
|
+
console.error("[viz] Sankey mount failed:", err);
|
|
7090
|
+
throw err;
|
|
7091
|
+
}
|
|
7092
|
+
if (options?.responsive !== false) {
|
|
7093
|
+
disconnectResize = observeResize(container, () => {
|
|
7094
|
+
resize();
|
|
7095
|
+
});
|
|
7096
|
+
}
|
|
7097
|
+
return {
|
|
7098
|
+
update,
|
|
7099
|
+
resize,
|
|
7100
|
+
export: doExport,
|
|
7101
|
+
destroy,
|
|
7102
|
+
get layout() {
|
|
7103
|
+
return currentLayout;
|
|
7104
|
+
}
|
|
7105
|
+
};
|
|
7106
|
+
}
|
|
7107
|
+
|
|
6399
7108
|
// src/table-keyboard.ts
|
|
6400
7109
|
function attachKeyboardNav(options) {
|
|
6401
7110
|
const { wrapper, onSort, onClearSearch, onAnnounce } = options;
|
|
@@ -6589,12 +7298,12 @@ import { compileTable } from "@opendata-ai/openchart-engine";
|
|
|
6589
7298
|
|
|
6590
7299
|
// src/table-renderer.ts
|
|
6591
7300
|
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE3 } from "@opendata-ai/openchart-core";
|
|
6592
|
-
import { clampStaggerDelay as
|
|
6593
|
-
var
|
|
7301
|
+
import { clampStaggerDelay as clampStaggerDelay3 } from "@opendata-ai/openchart-engine";
|
|
7302
|
+
var EASE_VAR_MAP3 = {
|
|
6594
7303
|
smooth: "var(--oc-ease-smooth)",
|
|
6595
7304
|
snappy: "var(--oc-ease-snappy)"
|
|
6596
7305
|
};
|
|
6597
|
-
var
|
|
7306
|
+
var BRAND_URL3 = "https://tryopendata.ai";
|
|
6598
7307
|
function renderChromeBlock(layout, position) {
|
|
6599
7308
|
const chrome = layout.chrome;
|
|
6600
7309
|
if (position === "header") {
|
|
@@ -6839,7 +7548,7 @@ function renderTable(layout, container, opts) {
|
|
|
6839
7548
|
brand.className = "oc-table-ref";
|
|
6840
7549
|
brand.style.cssText = "text-align: right; padding: 4px 8px;";
|
|
6841
7550
|
const brandLink = document.createElement("a");
|
|
6842
|
-
brandLink.href =
|
|
7551
|
+
brandLink.href = BRAND_URL3;
|
|
6843
7552
|
brandLink.target = "_blank";
|
|
6844
7553
|
brandLink.rel = "noopener";
|
|
6845
7554
|
brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE3}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : "sans-serif"};`;
|
|
@@ -6849,11 +7558,11 @@ function renderTable(layout, container, opts) {
|
|
|
6849
7558
|
if (opts?.animate && layout.animation?.enabled) {
|
|
6850
7559
|
const anim = layout.animation;
|
|
6851
7560
|
const rowCount = layout.rows.length;
|
|
6852
|
-
const stagger =
|
|
7561
|
+
const stagger = clampStaggerDelay3(anim.staggerDelay, rowCount);
|
|
6853
7562
|
const s = wrapper.style;
|
|
6854
7563
|
s.setProperty("--oc-animation-duration", `${anim.duration}ms`);
|
|
6855
7564
|
s.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
6856
|
-
s.setProperty("--oc-animation-ease",
|
|
7565
|
+
s.setProperty("--oc-animation-ease", EASE_VAR_MAP3[anim.ease] || EASE_VAR_MAP3.smooth);
|
|
6857
7566
|
wrapper.classList.add("oc-animate");
|
|
6858
7567
|
}
|
|
6859
7568
|
container.appendChild(wrapper);
|
|
@@ -6861,7 +7570,7 @@ function renderTable(layout, container, opts) {
|
|
|
6861
7570
|
}
|
|
6862
7571
|
|
|
6863
7572
|
// src/table-mount.ts
|
|
6864
|
-
function
|
|
7573
|
+
function resolveDarkMode4(mode) {
|
|
6865
7574
|
if (mode === "force") return true;
|
|
6866
7575
|
if (mode === "off" || mode === void 0) return false;
|
|
6867
7576
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -6929,7 +7638,7 @@ function createTable(container, spec, options) {
|
|
|
6929
7638
|
}
|
|
6930
7639
|
function compile() {
|
|
6931
7640
|
const state = getState();
|
|
6932
|
-
const darkMode =
|
|
7641
|
+
const darkMode = resolveDarkMode4(options?.darkMode);
|
|
6933
7642
|
const { width } = getContainerDimensions();
|
|
6934
7643
|
const compileOpts = {
|
|
6935
7644
|
width,
|
|
@@ -6990,7 +7699,7 @@ function createTable(container, spec, options) {
|
|
|
6990
7699
|
if (isFirstRender) {
|
|
6991
7700
|
isFirstRender = false;
|
|
6992
7701
|
}
|
|
6993
|
-
const isDark =
|
|
7702
|
+
const isDark = resolveDarkMode4(options?.darkMode);
|
|
6994
7703
|
if (isDark) {
|
|
6995
7704
|
container.classList.add("oc-dark");
|
|
6996
7705
|
} else {
|
|
@@ -7145,7 +7854,7 @@ function createTable(container, spec, options) {
|
|
|
7145
7854
|
throw new Error(`Unsupported export format: ${format}`);
|
|
7146
7855
|
}
|
|
7147
7856
|
const state = getState();
|
|
7148
|
-
const darkMode =
|
|
7857
|
+
const darkMode = resolveDarkMode4(options?.darkMode);
|
|
7149
7858
|
const { width } = getContainerDimensions();
|
|
7150
7859
|
const fullLayout = compileTable(currentSpec, {
|
|
7151
7860
|
width,
|
|
@@ -7226,6 +7935,7 @@ export {
|
|
|
7226
7935
|
attachKeyboardNav,
|
|
7227
7936
|
createChart,
|
|
7228
7937
|
createGraph,
|
|
7938
|
+
createSankey,
|
|
7229
7939
|
createSimulationWorker,
|
|
7230
7940
|
createTable,
|
|
7231
7941
|
createTextEditOverlay,
|