@opendata-ai/openchart-vanilla 6.5.1 → 6.5.2
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.js +16 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/animation.ts +2 -1
- package/src/mount.ts +19 -6
- package/src/svg-renderer.ts +1 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-vanilla",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.2",
|
|
4
4
|
"description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@floating-ui/dom": "^1.7.6",
|
|
53
|
-
"@opendata-ai/openchart-core": "6.5.
|
|
54
|
-
"@opendata-ai/openchart-engine": "6.5.
|
|
53
|
+
"@opendata-ai/openchart-core": "6.5.2",
|
|
54
|
+
"@opendata-ai/openchart-engine": "6.5.2",
|
|
55
55
|
"d3-force": "^3.0.0",
|
|
56
56
|
"d3-quadtree": "^3.0.1"
|
|
57
57
|
},
|
package/src/animation.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function cancelAnimations(svg: SVGElement | null): void {
|
|
|
23
23
|
* rather than animationend events, because animationend fires per-element and the first
|
|
24
24
|
* element to finish would prematurely kill staggered animations still in progress.
|
|
25
25
|
*/
|
|
26
|
-
export function setupAnimationCleanup(svg: SVGElement): () => void {
|
|
26
|
+
export function setupAnimationCleanup(svg: SVGElement, onComplete?: () => void): () => void {
|
|
27
27
|
// Read the animation timing from the CSS custom properties set by the renderer
|
|
28
28
|
const style = svg.style;
|
|
29
29
|
const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 600;
|
|
@@ -39,6 +39,7 @@ export function setupAnimationCleanup(svg: SVGElement): () => void {
|
|
|
39
39
|
|
|
40
40
|
const timer = setTimeout(() => {
|
|
41
41
|
svg.classList.remove('oc-animate');
|
|
42
|
+
onComplete?.();
|
|
42
43
|
}, totalTime);
|
|
43
44
|
|
|
44
45
|
return () => {
|
package/src/mount.ts
CHANGED
|
@@ -1801,6 +1801,7 @@ export function createChart(
|
|
|
1801
1801
|
// Animation state
|
|
1802
1802
|
let isFirstRender = true;
|
|
1803
1803
|
let cleanupAnimations: (() => void) | null = null;
|
|
1804
|
+
let pendingResize = false;
|
|
1804
1805
|
|
|
1805
1806
|
// Selection and text editing state
|
|
1806
1807
|
let selectedElement: ElementRef | null = options?.selectedElement ?? null;
|
|
@@ -2307,8 +2308,17 @@ export function createChart(
|
|
|
2307
2308
|
}
|
|
2308
2309
|
|
|
2309
2310
|
// Set up animation cleanup on first render only.
|
|
2311
|
+
// onComplete fires when animations finish naturally (not on cancellation/destroy).
|
|
2312
|
+
// It nulls out cleanupAnimations so resizes work after the animation window,
|
|
2313
|
+
// and replays any resize that was skipped mid-animation.
|
|
2310
2314
|
if (shouldAnimate && svgElement) {
|
|
2311
|
-
cleanupAnimations = setupAnimationCleanup(svgElement)
|
|
2315
|
+
cleanupAnimations = setupAnimationCleanup(svgElement, () => {
|
|
2316
|
+
cleanupAnimations = null;
|
|
2317
|
+
if (pendingResize) {
|
|
2318
|
+
pendingResize = false;
|
|
2319
|
+
resize();
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2312
2322
|
}
|
|
2313
2323
|
if (isFirstRender) {
|
|
2314
2324
|
isFirstRender = false;
|
|
@@ -2328,10 +2338,12 @@ export function createChart(
|
|
|
2328
2338
|
if (destroyed) return;
|
|
2329
2339
|
// Skip resize during entrance animation. The resize observer fires
|
|
2330
2340
|
// immediately when the container first enters DOM layout, and re-rendering
|
|
2331
|
-
// would destroy the animated SVG.
|
|
2332
|
-
//
|
|
2333
|
-
|
|
2334
|
-
|
|
2341
|
+
// would destroy the animated SVG. Resizes during this window are queued
|
|
2342
|
+
// and replayed once the animation completes via the onComplete callback.
|
|
2343
|
+
if (cleanupAnimations) {
|
|
2344
|
+
pendingResize = true;
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2335
2347
|
render();
|
|
2336
2348
|
}
|
|
2337
2349
|
|
|
@@ -2370,10 +2382,11 @@ export function createChart(
|
|
|
2370
2382
|
if (destroyed) return;
|
|
2371
2383
|
destroyed = true;
|
|
2372
2384
|
|
|
2373
|
-
// Cancel entrance animations
|
|
2385
|
+
// Cancel entrance animations (cancellation does not fire onComplete)
|
|
2374
2386
|
if (cleanupAnimations) {
|
|
2375
2387
|
cleanupAnimations();
|
|
2376
2388
|
cleanupAnimations = null;
|
|
2389
|
+
pendingResize = false;
|
|
2377
2390
|
}
|
|
2378
2391
|
cancelAnimations(svgElement);
|
|
2379
2392
|
|
package/src/svg-renderer.ts
CHANGED
|
@@ -901,38 +901,7 @@ function renderAnnotation(parent: SVGElement, annotation: ResolvedAnnotation, in
|
|
|
901
901
|
// Render connector first (behind the label text)
|
|
902
902
|
if (annotation.label.connector) {
|
|
903
903
|
const c = annotation.label.connector;
|
|
904
|
-
if (c.style === '
|
|
905
|
-
// Small directional chevron centered in the gap between the label
|
|
906
|
-
// text and the data mark, pointing toward the data.
|
|
907
|
-
const pointsDown = c.to.y > c.from.y;
|
|
908
|
-
const caretSize = 4;
|
|
909
|
-
// c.from.y is near the text baseline, not the visual bottom.
|
|
910
|
-
// Estimate the text bottom from the label's line count and font size.
|
|
911
|
-
const labelLines = annotation.label.text.split('\n');
|
|
912
|
-
const labelFontSize = annotation.label.style.fontSize ?? 12;
|
|
913
|
-
const labelLineHeight = labelFontSize * (annotation.label.style.lineHeight ?? 1.3);
|
|
914
|
-
const textBottom =
|
|
915
|
-
annotation.label.y + (labelLines.length - 1) * labelLineHeight + labelFontSize * 0.25;
|
|
916
|
-
const textTop = annotation.label.y - labelFontSize;
|
|
917
|
-
// Center caret in the gap between text edge and data point
|
|
918
|
-
const gapEdge = pointsDown ? textBottom : textTop;
|
|
919
|
-
const midY = (gapEdge + c.to.y) / 2;
|
|
920
|
-
const tipX = c.to.x;
|
|
921
|
-
const tipY = pointsDown ? midY + caretSize / 2 : midY - caretSize / 2;
|
|
922
|
-
const baseY = pointsDown ? tipY - caretSize : tipY + caretSize;
|
|
923
|
-
const path = createSVGElement('path');
|
|
924
|
-
path.setAttribute('class', 'oc-annotation-connector');
|
|
925
|
-
setAttrs(path, {
|
|
926
|
-
d: `M${tipX - caretSize},${baseY} L${tipX},${tipY} L${tipX + caretSize},${baseY}`,
|
|
927
|
-
fill: 'none',
|
|
928
|
-
stroke: c.stroke,
|
|
929
|
-
'stroke-width': 1.5,
|
|
930
|
-
'stroke-opacity': 0.4,
|
|
931
|
-
'stroke-linecap': 'round',
|
|
932
|
-
'stroke-linejoin': 'round',
|
|
933
|
-
});
|
|
934
|
-
g.appendChild(path);
|
|
935
|
-
} else if (c.style === 'curve') {
|
|
904
|
+
if (c.style === 'curve') {
|
|
936
905
|
renderCurvedArrow(g, c.from, c.to, c.stroke);
|
|
937
906
|
} else {
|
|
938
907
|
const connector = createSVGElement('line');
|