@opendata-ai/openchart-vanilla 8.7.0 → 8.9.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/{chunk-5O624XON.js → chunk-6TD7GWR4.js} +1141 -293
- package/dist/chunk-6TD7GWR4.js.map +1 -0
- package/dist/{chunk-DFKLAB67.js → chunk-MGFXAROR.js} +20 -8
- package/dist/chunk-MGFXAROR.js.map +1 -0
- package/dist/{chunk-I73B3DJI.js → chunk-R5TZHNTG.js} +23 -5
- package/dist/chunk-R5TZHNTG.js.map +1 -0
- package/dist/{chunk-SXTHPXG6.js → chunk-TP3GCWCO.js} +78 -7
- package/dist/chunk-TP3GCWCO.js.map +1 -0
- package/dist/{chunk-5IMVUHVC.js → chunk-YBVDBY6P.js} +17 -13
- package/dist/chunk-YBVDBY6P.js.map +1 -0
- package/dist/{chunk-EZQDCLZN.js → chunk-YHZAH6EN.js} +3 -2
- package/dist/{chunk-EZQDCLZN.js.map → chunk-YHZAH6EN.js.map} +1 -1
- package/dist/export-gif.d.ts +2 -2
- package/dist/export-gif.js +2 -2
- package/dist/graph-3d/index.js +1 -1
- package/dist/index.d.ts +34 -5
- package/dist/index.js +633 -212
- package/dist/index.js.map +1 -1
- package/dist/{mount-WiY9BIr9.d.ts → mount-FmUdKlCq.d.ts} +11 -1
- package/dist/static.js +2 -2
- package/dist/story/index.d.ts +2 -2
- package/dist/story/index.js +3 -3
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-5IMVUHVC.js.map +0 -1
- package/dist/chunk-5O624XON.js.map +0 -1
- package/dist/chunk-DFKLAB67.js.map +0 -1
- package/dist/chunk-I73B3DJI.js.map +0 -1
- package/dist/chunk-SXTHPXG6.js.map +0 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
exportPNG,
|
|
5
5
|
exportSVG,
|
|
6
6
|
exportSVGWithFonts
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-R5TZHNTG.js";
|
|
8
8
|
import {
|
|
9
9
|
buildGradientDefs,
|
|
10
10
|
buildPatternDefs,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
resolveMarkFill,
|
|
18
18
|
resolvePatternFill,
|
|
19
19
|
stampAnimationVars
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-TP3GCWCO.js";
|
|
21
21
|
import {
|
|
22
22
|
SVG_NS,
|
|
23
23
|
createSVGElement,
|
|
@@ -453,6 +453,140 @@ function materializeCanvasModeSVG(layout, opts) {
|
|
|
453
453
|
return materializeRasterMarkSVG(layout);
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
// src/transition/keys.ts
|
|
457
|
+
function annotationKey(a) {
|
|
458
|
+
if (a.id) return `id:${a.id}`;
|
|
459
|
+
return `${a.type}:${a.label?.text ?? ""}`;
|
|
460
|
+
}
|
|
461
|
+
function keyAnnotations(annotations) {
|
|
462
|
+
const seen = /* @__PURE__ */ new Map();
|
|
463
|
+
return annotations.map((a) => {
|
|
464
|
+
const base = annotationKey(a);
|
|
465
|
+
const n = seen.get(base) ?? 0;
|
|
466
|
+
seen.set(base, n + 1);
|
|
467
|
+
return n === 0 ? base : `${base}#${n}`;
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/transition/gate.ts
|
|
472
|
+
var TRANSITIONABLE_MARKS = /* @__PURE__ */ new Set([
|
|
473
|
+
"bar",
|
|
474
|
+
"line",
|
|
475
|
+
"area",
|
|
476
|
+
"point",
|
|
477
|
+
"beeswarm",
|
|
478
|
+
"arc",
|
|
479
|
+
"histogram",
|
|
480
|
+
"density"
|
|
481
|
+
]);
|
|
482
|
+
var DEFAULT_UPDATE_MAX_MARKS = 500;
|
|
483
|
+
var CANVAS_DEFAULT_UPDATE_MAX_MARKS = 2e4;
|
|
484
|
+
function canTransitionSpecShape(prevSpec, nextSpec) {
|
|
485
|
+
const prev = prevSpec;
|
|
486
|
+
const next = nextSpec;
|
|
487
|
+
if (!prev || !next || !("mark" in prev) || !("mark" in next)) return false;
|
|
488
|
+
const prevMark = typeof prev.mark === "string" ? prev.mark : prev.mark?.type;
|
|
489
|
+
const nextMark = typeof next.mark === "string" ? next.mark : next.mark?.type;
|
|
490
|
+
if (prevMark !== nextMark) return false;
|
|
491
|
+
if (!TRANSITIONABLE_MARKS.has(prevMark)) return false;
|
|
492
|
+
const prevEnc = prev.encoding;
|
|
493
|
+
const nextEnc = next.encoding;
|
|
494
|
+
if (!prevEnc || !nextEnc) return false;
|
|
495
|
+
if (JSON.stringify(prev.transform) !== JSON.stringify(next.transform)) return false;
|
|
496
|
+
for (const channel of ["x", "y", "theta", "radius"]) {
|
|
497
|
+
for (const property of ["aggregate", "bin", "stack", "timeUnit"]) {
|
|
498
|
+
if (JSON.stringify(prevEnc[channel]?.[property]) !== JSON.stringify(nextEnc[channel]?.[property]))
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
const field = prevEnc[channel]?.field;
|
|
502
|
+
if (field) {
|
|
503
|
+
const a = prev.fields?.[field];
|
|
504
|
+
const b = next.fields?.[field];
|
|
505
|
+
if (["unit", "measure", "representation"].some((key) => a?.[key] !== b?.[key])) return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return !(prevEnc.x?.field !== nextEnc.x?.field || prevEnc.x?.type !== nextEnc.x?.type || prevEnc.x?.scale?.type !== nextEnc.x?.scale?.type || prevEnc.y?.field !== nextEnc.y?.field || prevEnc.y?.type !== nextEnc.y?.type || prevEnc.y?.scale?.type !== nextEnc.y?.scale?.type || prevEnc.theta?.field !== nextEnc.theta?.field || prevEnc.theta?.type !== nextEnc.theta?.type || prevEnc.color?.field !== nextEnc.color?.field || prevEnc.key?.field !== nextEnc.key?.field || prevEnc.facet?.field !== nextEnc.facet?.field || prevEnc.row?.field !== nextEnc.row?.field || prevEnc.column?.field !== nextEnc.column?.field);
|
|
509
|
+
}
|
|
510
|
+
function canTransition(args) {
|
|
511
|
+
const { prevLayout, nextLayout, prevSpec, nextSpec, isFirstRender, entranceInFlight } = args;
|
|
512
|
+
if (!prevLayout || !prevSpec || isFirstRender) return false;
|
|
513
|
+
if (!nextLayout.animation?.update) return false;
|
|
514
|
+
if (!canTransitionSpecShape(prevSpec, nextSpec)) return false;
|
|
515
|
+
if (entranceInFlight) return false;
|
|
516
|
+
const cap = nextLayout.animation.update.maxMarks ?? (nextLayout.markRenderMode === "canvas" ? CANVAS_DEFAULT_UPDATE_MAX_MARKS : DEFAULT_UPDATE_MAX_MARKS);
|
|
517
|
+
if (Math.max(prevLayout.marks.length, nextLayout.marks.length) > cap) return false;
|
|
518
|
+
if (!hasVisibleChange(prevLayout, nextLayout)) return false;
|
|
519
|
+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
520
|
+
try {
|
|
521
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
function hasVisibleChange(prevLayout, nextLayout) {
|
|
528
|
+
return hasGeometryChanged(prevLayout, nextLayout) || hasColorChanged(prevLayout, nextLayout) || hasAnnotationChanged(prevLayout, nextLayout);
|
|
529
|
+
}
|
|
530
|
+
function hasColorChanged(prevLayout, nextLayout) {
|
|
531
|
+
const prevMarks = prevLayout.marks;
|
|
532
|
+
const nextMarks = nextLayout.marks;
|
|
533
|
+
if (prevMarks.length !== nextMarks.length) return true;
|
|
534
|
+
for (let i = 0; i < prevMarks.length; i++) {
|
|
535
|
+
const p = prevMarks[i];
|
|
536
|
+
const n = nextMarks[i];
|
|
537
|
+
if (p.fill !== n.fill || p.stroke !== n.stroke) return true;
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
function hasAnnotationChanged(prevLayout, nextLayout) {
|
|
542
|
+
const prev = prevLayout.annotations ?? [];
|
|
543
|
+
const next = nextLayout.annotations ?? [];
|
|
544
|
+
if (prev.length !== next.length) return true;
|
|
545
|
+
const prevKeys = keyAnnotations(prev);
|
|
546
|
+
const nextKeys = keyAnnotations(next);
|
|
547
|
+
for (let i = 0; i < prev.length; i++) {
|
|
548
|
+
if (prevKeys[i] !== nextKeys[i]) return true;
|
|
549
|
+
const p = prev[i];
|
|
550
|
+
const n = next[i];
|
|
551
|
+
if (p.label?.x !== n.label?.x || p.label?.y !== n.label?.y) return true;
|
|
552
|
+
if (p.opacity !== n.opacity) return true;
|
|
553
|
+
}
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
function hasGeometryChanged(prevLayout, nextLayout) {
|
|
557
|
+
const prevMarks = prevLayout.marks;
|
|
558
|
+
const nextMarks = nextLayout.marks;
|
|
559
|
+
if (prevMarks.length !== nextMarks.length) return true;
|
|
560
|
+
for (let i = 0; i < prevMarks.length; i++) {
|
|
561
|
+
const p = prevMarks[i];
|
|
562
|
+
const n = nextMarks[i];
|
|
563
|
+
if (p.type !== n.type || p.key !== n.key) return true;
|
|
564
|
+
if (p.type === "rect" && n.type === "rect") {
|
|
565
|
+
if (p.x !== n.x || p.y !== n.y || p.width !== n.width || p.height !== n.height) return true;
|
|
566
|
+
} else if (p.type === "line" && n.type === "line") {
|
|
567
|
+
if (p.path !== n.path || p.points.length !== n.points.length) return true;
|
|
568
|
+
for (let j = 0; j < p.points.length; j++) {
|
|
569
|
+
if (p.points[j].x !== n.points[j].x || p.points[j].y !== n.points[j].y) return true;
|
|
570
|
+
}
|
|
571
|
+
} else if (p.type === "area" && n.type === "area") {
|
|
572
|
+
if (p.path !== n.path) return true;
|
|
573
|
+
} else if (p.type === "arc" && n.type === "arc") {
|
|
574
|
+
if (p.startAngle !== n.startAngle || p.endAngle !== n.endAngle || p.innerRadius !== n.innerRadius || p.outerRadius !== n.outerRadius || p.center.x !== n.center.x || p.center.y !== n.center.y) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
} else if (p.type === "point" && n.type === "point") {
|
|
578
|
+
if (p.cx !== n.cx || p.cy !== n.cy || p.r !== n.r) return true;
|
|
579
|
+
} else if (p.type === "rule" && n.type === "rule") {
|
|
580
|
+
if (p.x1 !== n.x1 || p.y1 !== n.y1 || p.x2 !== n.x2 || p.y2 !== n.y2) return true;
|
|
581
|
+
} else if (p.type === "tick" && n.type === "tick") {
|
|
582
|
+
if (p.x !== n.x || p.y !== n.y) return true;
|
|
583
|
+
} else if (p.type === "textMark" && n.type === "textMark") {
|
|
584
|
+
if (p.x !== n.x || p.y !== n.y) return true;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
456
590
|
// src/text-edit-overlay.ts
|
|
457
591
|
function getScale(svg) {
|
|
458
592
|
const viewBox = svg.viewBox?.baseVal;
|
|
@@ -705,138 +839,91 @@ function createTextEditOverlayAtPosition(config) {
|
|
|
705
839
|
return { destroy };
|
|
706
840
|
}
|
|
707
841
|
|
|
708
|
-
// src/transition/
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
"
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const prevEnc = prev.encoding;
|
|
745
|
-
const nextEnc = next.encoding;
|
|
746
|
-
if (!prevEnc || !nextEnc) return false;
|
|
747
|
-
return !(prevEnc.x?.field !== nextEnc.x?.field || prevEnc.x?.type !== nextEnc.x?.type || prevEnc.y?.field !== nextEnc.y?.field || prevEnc.y?.type !== nextEnc.y?.type || prevEnc.theta?.field !== nextEnc.theta?.field || prevEnc.theta?.type !== nextEnc.theta?.type || prevEnc.color?.field !== nextEnc.color?.field || prevEnc.key?.field !== nextEnc.key?.field || prevEnc.facet?.field !== nextEnc.facet?.field || prevEnc.row?.field !== nextEnc.row?.field || prevEnc.column?.field !== nextEnc.column?.field);
|
|
748
|
-
}
|
|
749
|
-
function canTransition(args) {
|
|
750
|
-
const { prevLayout, nextLayout, prevSpec, nextSpec, isFirstRender, entranceInFlight } = args;
|
|
751
|
-
if (!prevLayout || !prevSpec || isFirstRender) return false;
|
|
752
|
-
if (!nextLayout.animation?.update) return false;
|
|
753
|
-
if (!canTransitionSpecShape(prevSpec, nextSpec)) return false;
|
|
754
|
-
if (entranceInFlight) return false;
|
|
755
|
-
if (prevLayout.dimensions.width !== nextLayout.dimensions.width || prevLayout.dimensions.height !== nextLayout.dimensions.height) {
|
|
756
|
-
return false;
|
|
757
|
-
}
|
|
758
|
-
const cap = nextLayout.animation.update.maxMarks ?? (nextLayout.markRenderMode === "canvas" ? CANVAS_DEFAULT_UPDATE_MAX_MARKS : DEFAULT_UPDATE_MAX_MARKS);
|
|
759
|
-
if (Math.max(prevLayout.marks.length, nextLayout.marks.length) > cap) return false;
|
|
760
|
-
if (!hasVisibleChange(prevLayout, nextLayout)) return false;
|
|
761
|
-
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
762
|
-
try {
|
|
763
|
-
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
764
|
-
} catch {
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return true;
|
|
768
|
-
}
|
|
769
|
-
function hasVisibleChange(prevLayout, nextLayout) {
|
|
770
|
-
return hasGeometryChanged(prevLayout, nextLayout) || hasColorChanged(prevLayout, nextLayout) || hasAnnotationChanged(prevLayout, nextLayout);
|
|
771
|
-
}
|
|
772
|
-
function hasColorChanged(prevLayout, nextLayout) {
|
|
773
|
-
const prevMarks = prevLayout.marks;
|
|
774
|
-
const nextMarks = nextLayout.marks;
|
|
775
|
-
if (prevMarks.length !== nextMarks.length) return true;
|
|
776
|
-
for (let i = 0; i < prevMarks.length; i++) {
|
|
777
|
-
const p = prevMarks[i];
|
|
778
|
-
const n = nextMarks[i];
|
|
779
|
-
if (p.fill !== n.fill || p.stroke !== n.stroke) return true;
|
|
780
|
-
}
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
function hasAnnotationChanged(prevLayout, nextLayout) {
|
|
784
|
-
const prev = prevLayout.annotations ?? [];
|
|
785
|
-
const next = nextLayout.annotations ?? [];
|
|
786
|
-
if (prev.length !== next.length) return true;
|
|
787
|
-
const prevKeys = keyAnnotations(prev);
|
|
788
|
-
const nextKeys = keyAnnotations(next);
|
|
789
|
-
for (let i = 0; i < prev.length; i++) {
|
|
790
|
-
if (prevKeys[i] !== nextKeys[i]) return true;
|
|
791
|
-
const p = prev[i];
|
|
792
|
-
const n = next[i];
|
|
793
|
-
if (p.label?.x !== n.label?.x || p.label?.y !== n.label?.y) return true;
|
|
794
|
-
if (p.opacity !== n.opacity) return true;
|
|
795
|
-
}
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
function hasGeometryChanged(prevLayout, nextLayout) {
|
|
799
|
-
const prevMarks = prevLayout.marks;
|
|
800
|
-
const nextMarks = nextLayout.marks;
|
|
801
|
-
if (prevMarks.length !== nextMarks.length) return true;
|
|
802
|
-
for (let i = 0; i < prevMarks.length; i++) {
|
|
803
|
-
const p = prevMarks[i];
|
|
804
|
-
const n = nextMarks[i];
|
|
805
|
-
if (p.type !== n.type || p.key !== n.key) return true;
|
|
806
|
-
if (p.type === "rect" && n.type === "rect") {
|
|
807
|
-
if (p.x !== n.x || p.y !== n.y || p.width !== n.width || p.height !== n.height) return true;
|
|
808
|
-
} else if (p.type === "line" && n.type === "line") {
|
|
809
|
-
if (p.path !== n.path || p.points.length !== n.points.length) return true;
|
|
810
|
-
for (let j = 0; j < p.points.length; j++) {
|
|
811
|
-
if (p.points[j].x !== n.points[j].x || p.points[j].y !== n.points[j].y) return true;
|
|
842
|
+
// src/transition/labels.ts
|
|
843
|
+
function buildLabelMotion(svg, previous, next, snapshot) {
|
|
844
|
+
const attributes = [];
|
|
845
|
+
const add = (element, name, delta, key) => {
|
|
846
|
+
const raw = element.getAttribute(name);
|
|
847
|
+
if (raw === null) return;
|
|
848
|
+
const to = Number(raw);
|
|
849
|
+
if (!Number.isFinite(to)) return;
|
|
850
|
+
const captured = snapshot?.get(key);
|
|
851
|
+
attributes.push({
|
|
852
|
+
element,
|
|
853
|
+
name,
|
|
854
|
+
from: captured?.type === "attribute" ? captured.value : to + delta,
|
|
855
|
+
to,
|
|
856
|
+
key
|
|
857
|
+
});
|
|
858
|
+
};
|
|
859
|
+
for (const group of svg.querySelectorAll("[data-endpoint-key]")) {
|
|
860
|
+
const key = group.getAttribute("data-endpoint-key");
|
|
861
|
+
const leading = group.getAttribute("data-endpoint-side") === "leading";
|
|
862
|
+
const before = (leading ? previous.endpointLabels?.leading : previous.endpointLabels?.entries)?.find((entry) => entry.seriesKey === key);
|
|
863
|
+
const after = (leading ? next.endpointLabels?.leading : next.endpointLabels?.entries)?.find(
|
|
864
|
+
(entry) => entry.seriesKey === key
|
|
865
|
+
);
|
|
866
|
+
if (!before || !after) continue;
|
|
867
|
+
const beforeBounds = leading ? previous.endpointLabels?.leadingBounds : previous.endpointLabels?.bounds;
|
|
868
|
+
const afterBounds = leading ? next.endpointLabels?.leadingBounds : next.endpointLabels?.bounds;
|
|
869
|
+
const dx = (beforeBounds?.x ?? 0) - (afterBounds?.x ?? 0);
|
|
870
|
+
const dy = before.labelY - after.labelY;
|
|
871
|
+
Array.from(group.querySelectorAll("text, tspan, line, circle")).forEach((element, index) => {
|
|
872
|
+
const marker = element.classList.contains("oc-endpoint-marker");
|
|
873
|
+
const leader = element.classList.contains("oc-endpoint-leader");
|
|
874
|
+
const prefix = `endpoint:${key}:${leading}:${index}`;
|
|
875
|
+
for (const name of ["x", "x1", "x2", "cx"]) {
|
|
876
|
+
const offset = marker ? (before.marker?.x ?? 0) - (after.marker?.x ?? 0) : leader && name === "x2" ? previous.area.x + (leading ? 0 : previous.area.width) - next.area.x - (leading ? 0 : next.area.width) : dx;
|
|
877
|
+
add(element, name, offset, `${prefix}:${name}`);
|
|
812
878
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if (p.startAngle !== n.startAngle || p.endAngle !== n.endAngle || p.innerRadius !== n.innerRadius || p.outerRadius !== n.outerRadius || p.center.x !== n.center.x || p.center.y !== n.center.y) {
|
|
817
|
-
return true;
|
|
879
|
+
for (const name of ["y", "y1", "y2", "cy"]) {
|
|
880
|
+
const offset = marker || leader && name === "y2" ? before.dataY - after.dataY : dy;
|
|
881
|
+
add(element, name, offset, `${prefix}:${name}`);
|
|
818
882
|
}
|
|
819
|
-
}
|
|
820
|
-
if (p.cx !== n.cx || p.cy !== n.cy || p.r !== n.r) return true;
|
|
821
|
-
} else if (p.type === "rule" && n.type === "rule") {
|
|
822
|
-
if (p.x1 !== n.x1 || p.y1 !== n.y1 || p.x2 !== n.x2 || p.y2 !== n.y2) return true;
|
|
823
|
-
} else if (p.type === "tick" && n.type === "tick") {
|
|
824
|
-
if (p.x !== n.x || p.y !== n.y) return true;
|
|
825
|
-
} else if (p.type === "textMark" && n.type === "textMark") {
|
|
826
|
-
if (p.x !== n.x || p.y !== n.y) return true;
|
|
827
|
-
}
|
|
883
|
+
});
|
|
828
884
|
}
|
|
829
|
-
|
|
885
|
+
const beforeMarks = new Map(
|
|
886
|
+
previous.marks.filter((mark) => mark.key).map((mark) => [mark.key, mark])
|
|
887
|
+
);
|
|
888
|
+
const afterMarks = new Map(next.marks.filter((mark) => mark.key).map((mark) => [mark.key, mark]));
|
|
889
|
+
for (const label of svg.querySelectorAll("[data-label-key]")) {
|
|
890
|
+
const key = label.getAttribute("data-label-key");
|
|
891
|
+
const before = beforeMarks.get(key);
|
|
892
|
+
const after = afterMarks.get(key);
|
|
893
|
+
if (!before || !after || !("label" in before) || !("label" in after) || !before.label || !after.label)
|
|
894
|
+
continue;
|
|
895
|
+
const dx = before.type === "arc" && after.type === "arc" ? before.center.x - after.center.x : 0;
|
|
896
|
+
const dy = before.type === "arc" && after.type === "arc" ? before.center.y - after.center.y : 0;
|
|
897
|
+
add(label, "x", before.label.x - after.label.x - dx, `label:${key}:x`);
|
|
898
|
+
add(label, "y", before.label.y - after.label.y - dy, `label:${key}:y`);
|
|
899
|
+
}
|
|
900
|
+
let progress = 0;
|
|
901
|
+
return {
|
|
902
|
+
apply(value) {
|
|
903
|
+
progress = value;
|
|
904
|
+
for (const item of attributes)
|
|
905
|
+
item.element.setAttribute(item.name, String(item.from + (item.to - item.from) * value));
|
|
906
|
+
},
|
|
907
|
+
snapshot(target) {
|
|
908
|
+
for (const item of attributes)
|
|
909
|
+
target.set(item.key, {
|
|
910
|
+
type: "attribute",
|
|
911
|
+
value: item.from + (item.to - item.from) * progress
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
};
|
|
830
915
|
}
|
|
831
916
|
|
|
832
917
|
// src/transition/driver.ts
|
|
833
|
-
import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
918
|
+
import { isGradientDef, sampleMotion as sampleMotion3 } from "@opendata-ai/openchart-core";
|
|
834
919
|
import { buildAreaPath as buildAreaPath2, buildLinePath as buildLinePath2, EXIT_DEFAULTS } from "@opendata-ai/openchart-engine";
|
|
835
920
|
|
|
836
921
|
// src/transition/apply.ts
|
|
922
|
+
import { sampleMotion as sampleMotion2 } from "@opendata-ai/openchart-core";
|
|
837
923
|
import { buildArcPath } from "@opendata-ai/openchart-engine";
|
|
838
924
|
|
|
839
925
|
// src/transition/canvas-tweens.ts
|
|
926
|
+
import { sampleMotion } from "@opendata-ai/openchart-core";
|
|
840
927
|
import { serializeKeyValue as serializeKeyValue2 } from "@opendata-ai/openchart-engine";
|
|
841
928
|
|
|
842
929
|
// src/transition/chrome-tweens.ts
|
|
@@ -1183,7 +1270,7 @@ function buildCanvasPointsTween(prevLayout, nextLayout, layer, tweens, fromSnaps
|
|
|
1183
1270
|
}
|
|
1184
1271
|
function applyCanvasPointsState(tw, elapsed, update, exit, enterDelay, enterDuration) {
|
|
1185
1272
|
const soa = tw.layer.state.marks;
|
|
1186
|
-
const easedUpdate =
|
|
1273
|
+
const easedUpdate = sampleMotion(update, elapsed).position;
|
|
1187
1274
|
for (let s = 0; s < tw.soaIndex.length; s++) {
|
|
1188
1275
|
const i = tw.soaIndex[s];
|
|
1189
1276
|
soa.x[i] = tw.fromCx[s] + (tw.toCx[s] - tw.fromCx[s]) * easedUpdate;
|
|
@@ -1199,7 +1286,7 @@ function applyCanvasPointsState(tw, elapsed, update, exit, enterDelay, enterDura
|
|
|
1199
1286
|
}
|
|
1200
1287
|
}
|
|
1201
1288
|
if (tw.exitState) {
|
|
1202
|
-
const easedExit =
|
|
1289
|
+
const easedExit = sampleMotion(exit, elapsed).position;
|
|
1203
1290
|
tw.exitState.alpha = 1 - easedExit;
|
|
1204
1291
|
tw.layer.state.exiting = tw.exitState;
|
|
1205
1292
|
}
|
|
@@ -1266,7 +1353,7 @@ function buildCanvasGridlinesTween(prevLayout, nextLayout, layer, tweens) {
|
|
|
1266
1353
|
}
|
|
1267
1354
|
function applyCanvasGridlinesState(tw, elapsed, update, enterDelay, enterDuration) {
|
|
1268
1355
|
const live = tw.layer.state.gridlines;
|
|
1269
|
-
const easedUpdate =
|
|
1356
|
+
const easedUpdate = sampleMotion(update, elapsed).position;
|
|
1270
1357
|
const tEnter = elapsed < enterDelay ? 0 : Math.min((elapsed - enterDelay) / enterDuration, 1);
|
|
1271
1358
|
const easedEnter = cubicOut(tEnter);
|
|
1272
1359
|
for (let i = 0; i < tw.index.length; i++) {
|
|
@@ -1614,19 +1701,9 @@ function applyTweenState(tw, elapsed, update, exit, enterDelay, enterDuration) {
|
|
|
1614
1701
|
applyCanvasGridlinesState(tw, elapsed, update, enterDelay, enterDuration);
|
|
1615
1702
|
return;
|
|
1616
1703
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
} else if (tw.kind === "enter") {
|
|
1621
|
-
if (elapsed < enterDelay) {
|
|
1622
|
-
tLocal = 0;
|
|
1623
|
-
} else {
|
|
1624
|
-
tLocal = Math.min((elapsed - enterDelay) / enterDuration, 1);
|
|
1625
|
-
}
|
|
1626
|
-
} else {
|
|
1627
|
-
tLocal = Math.min(elapsed / update.duration, 1);
|
|
1628
|
-
}
|
|
1629
|
-
const eased = cubicOut(tLocal);
|
|
1704
|
+
const phase = tw.kind === "exit" ? exit : tw.kind === "enter" ? { duration: enterDuration } : update;
|
|
1705
|
+
const elapsedMs = tw.kind === "enter" ? Math.max(0, elapsed - enterDelay) : elapsed;
|
|
1706
|
+
const eased = sampleMotion2(phase, elapsedMs).position;
|
|
1630
1707
|
switch (tw.tweenType) {
|
|
1631
1708
|
case "rect": {
|
|
1632
1709
|
const geom = lerpGeom(tw.from, tw.to, eased);
|
|
@@ -1640,11 +1717,13 @@ function applyTweenState(tw, elapsed, update, exit, enterDelay, enterDuration) {
|
|
|
1640
1717
|
break;
|
|
1641
1718
|
}
|
|
1642
1719
|
case "line": {
|
|
1720
|
+
if (tw.kind !== "update") break;
|
|
1643
1721
|
tw.ptsBuffer = interpolatePoints(tw.fromPts, tw.toPts, eased, tw.ptsBuffer);
|
|
1644
1722
|
applyLinePath(tw.el, tw.ptsBuffer, tw.interpolate, tw.pathEl);
|
|
1645
1723
|
break;
|
|
1646
1724
|
}
|
|
1647
1725
|
case "area": {
|
|
1726
|
+
if (tw.kind !== "update") break;
|
|
1648
1727
|
tw.topBuffer = interpolatePoints(tw.fromTop, tw.toTop, eased, tw.topBuffer);
|
|
1649
1728
|
tw.bottomBuffer = interpolatePoints(tw.fromBottom, tw.toBottom, eased, tw.bottomBuffer);
|
|
1650
1729
|
applyAreaPaths(
|
|
@@ -1750,9 +1829,11 @@ function snapTweenToFinal(tw) {
|
|
|
1750
1829
|
}
|
|
1751
1830
|
break;
|
|
1752
1831
|
case "line":
|
|
1832
|
+
if (tw.kind !== "update") break;
|
|
1753
1833
|
applyLinePath(tw.el, tw.toPts, tw.interpolate, tw.pathEl);
|
|
1754
1834
|
break;
|
|
1755
1835
|
case "area":
|
|
1836
|
+
if (tw.kind !== "update") break;
|
|
1756
1837
|
applyAreaPaths(
|
|
1757
1838
|
tw.el,
|
|
1758
1839
|
tw.toTop,
|
|
@@ -1976,7 +2057,7 @@ function buildLineTweens(prevLayout, nextLayout, marksContainer, dom, tweens, gh
|
|
|
1976
2057
|
const el = dom.any(key);
|
|
1977
2058
|
if (!el) continue;
|
|
1978
2059
|
const snap = fromSnapshot?.get(key);
|
|
1979
|
-
if (snap && snap.type === "line") {
|
|
2060
|
+
if (snap && snap.type === "line" && !snap.points) {
|
|
1980
2061
|
const ghost = el.cloneNode(true);
|
|
1981
2062
|
ghost.classList.add("oc-ghost");
|
|
1982
2063
|
ghost.setAttribute("aria-hidden", "true");
|
|
@@ -2014,11 +2095,12 @@ function buildLineTweens(prevLayout, nextLayout, marksContainer, dom, tweens, gh
|
|
|
2014
2095
|
});
|
|
2015
2096
|
continue;
|
|
2016
2097
|
}
|
|
2017
|
-
const prevPKs = prev.pointKeys ?? prev.points.map((_, i) => `${i}`);
|
|
2098
|
+
const prevPKs = snap?.type === "line" && snap.pointKeys ? snap.pointKeys : prev.pointKeys ?? prev.points.map((_, i) => `${i}`);
|
|
2018
2099
|
const nextPKs = next.pointKeys ?? next.points.map((_, i) => `${i}`);
|
|
2019
2100
|
const prevKeySet = new Set(prevPKs);
|
|
2020
2101
|
const hasSurvivors = nextPKs.some((k) => prevKeySet.has(k));
|
|
2021
|
-
|
|
2102
|
+
const hasGaps = (prev.path?.match(/M/g)?.length ?? 0) > 1 || (next.path?.match(/M/g)?.length ?? 0) > 1;
|
|
2103
|
+
if (!hasSurvivors || hasGaps) {
|
|
2022
2104
|
const ghost = renderSingleMark(prev, 0);
|
|
2023
2105
|
if (ghost) {
|
|
2024
2106
|
ghost.classList.add("oc-ghost");
|
|
@@ -2054,13 +2136,19 @@ function buildLineTweens(prevLayout, nextLayout, marksContainer, dom, tweens, gh
|
|
|
2054
2136
|
pathEl: resolveLinePathEl(el)
|
|
2055
2137
|
});
|
|
2056
2138
|
} else {
|
|
2057
|
-
const [fromPts, toPts] = normalizePointArrays(
|
|
2139
|
+
const [fromPts, toPts] = normalizePointArrays(
|
|
2140
|
+
snap?.type === "line" && snap.points ? snap.points : prev.points,
|
|
2141
|
+
next.points,
|
|
2142
|
+
prevPKs,
|
|
2143
|
+
nextPKs
|
|
2144
|
+
);
|
|
2058
2145
|
tweens.push({
|
|
2059
2146
|
tweenType: "line",
|
|
2060
2147
|
kind: "update",
|
|
2061
2148
|
el,
|
|
2062
2149
|
fromPts,
|
|
2063
2150
|
toPts,
|
|
2151
|
+
pointKeys: mergeKeyOrder(prevPKs, nextPKs),
|
|
2064
2152
|
finalPoints: next.points,
|
|
2065
2153
|
interpolate: next.interpolate,
|
|
2066
2154
|
pathEl: resolveLinePathEl(el)
|
|
@@ -2179,7 +2267,8 @@ function buildAreaTweens(prevLayout, nextLayout, marksContainer, dom, tweens, gh
|
|
|
2179
2267
|
const nextPKs = next.pointKeys ?? next.topPoints.map((_, i) => `${i}`);
|
|
2180
2268
|
const prevKeySet = new Set(prevPKs);
|
|
2181
2269
|
const hasSurvivors = nextPKs.some((k) => prevKeySet.has(k));
|
|
2182
|
-
|
|
2270
|
+
const hasGaps = (prev.path?.match(/M/g)?.length ?? 0) > 1 || (next.path?.match(/M/g)?.length ?? 0) > 1;
|
|
2271
|
+
if (!hasSurvivors || hasGaps) {
|
|
2183
2272
|
const ghost = renderSingleMark(prev, 0);
|
|
2184
2273
|
if (ghost) {
|
|
2185
2274
|
ghost.classList.add("oc-ghost");
|
|
@@ -2237,6 +2326,7 @@ function buildAreaTweens(prevLayout, nextLayout, marksContainer, dom, tweens, gh
|
|
|
2237
2326
|
);
|
|
2238
2327
|
tweens.push({
|
|
2239
2328
|
tweenType: "area",
|
|
2329
|
+
pointKeys: mergeKeyOrder(prevPKs, nextPKs),
|
|
2240
2330
|
kind: "update",
|
|
2241
2331
|
el,
|
|
2242
2332
|
fromTop,
|
|
@@ -2865,16 +2955,13 @@ function runTransition(args) {
|
|
|
2865
2955
|
}
|
|
2866
2956
|
buildColorTweens(prevLayout, nextLayout, dom, tweens);
|
|
2867
2957
|
buildAnnotationTweens(svg, prevLayout, nextLayout, tweens);
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2958
|
+
const labelMotion = buildLabelMotion(svg, prevLayout, nextLayout, fromSnapshot);
|
|
2959
|
+
labelMotion.apply(0);
|
|
2870
2960
|
for (const tw of tweens) {
|
|
2871
2961
|
applyTweenState(tw, 0, update, exit, enterDelay, enterDuration);
|
|
2872
2962
|
}
|
|
2873
2963
|
canvas?.repaint();
|
|
2874
2964
|
canvas?.setInteractionSuspended(true);
|
|
2875
|
-
for (const el of secondaryEls) {
|
|
2876
|
-
el.style.opacity = "0";
|
|
2877
|
-
}
|
|
2878
2965
|
let rafId = null;
|
|
2879
2966
|
let running = true;
|
|
2880
2967
|
let pulsedDot = null;
|
|
@@ -2891,7 +2978,7 @@ function runTransition(args) {
|
|
|
2891
2978
|
for (const tw of tweens) {
|
|
2892
2979
|
applyTweenState(tw, elapsed, update, exit, enterDelay, enterDuration);
|
|
2893
2980
|
}
|
|
2894
|
-
|
|
2981
|
+
labelMotion.apply(sampleMotion3(update, elapsed).position);
|
|
2895
2982
|
canvas?.repaint();
|
|
2896
2983
|
if (tGlobal >= 1) {
|
|
2897
2984
|
finish();
|
|
@@ -2914,9 +3001,6 @@ function runTransition(args) {
|
|
|
2914
3001
|
removeGhosts();
|
|
2915
3002
|
settleCanvas();
|
|
2916
3003
|
pulsedDot = pulseSparklineTerminator(svg, marksContainer);
|
|
2917
|
-
for (let i = 0; i < secondaryEls.length; i++) {
|
|
2918
|
-
secondaryEls[i].style.opacity = secondaryOriginalOpacity[i];
|
|
2919
|
-
}
|
|
2920
3004
|
onComplete();
|
|
2921
3005
|
}
|
|
2922
3006
|
function snapToFinal() {
|
|
@@ -2935,16 +3019,16 @@ function runTransition(args) {
|
|
|
2935
3019
|
}
|
|
2936
3020
|
}
|
|
2937
3021
|
function removeGhosts() {
|
|
3022
|
+
labelMotion.apply(1);
|
|
2938
3023
|
for (const ghost of ghosts) {
|
|
2939
3024
|
ghost.parentNode?.removeChild(ghost);
|
|
2940
3025
|
}
|
|
2941
3026
|
}
|
|
2942
3027
|
function captureSnapshot() {
|
|
2943
3028
|
const snap = /* @__PURE__ */ new Map();
|
|
2944
|
-
const tUpdate = Math.min(lastElapsed / update.duration, 1);
|
|
2945
|
-
const easedUpdate = cubicOut(tUpdate);
|
|
2946
3029
|
for (const tw of tweens) {
|
|
2947
|
-
|
|
3030
|
+
const easedUpdate = tw.kind === "enter" ? sampleMotion3({ duration: enterDuration }, Math.max(0, lastElapsed - enterDelay)).position : sampleMotion3(update, lastElapsed).position;
|
|
3031
|
+
if (tw.kind === "exit") continue;
|
|
2948
3032
|
if (tw.tweenType === "canvasPoints") {
|
|
2949
3033
|
for (let s = 0; s < tw.soaIndex.length; s++) {
|
|
2950
3034
|
snap.set(tw.soaKeys[s], {
|
|
@@ -2995,19 +3079,26 @@ function runTransition(args) {
|
|
|
2995
3079
|
}
|
|
2996
3080
|
case "line": {
|
|
2997
3081
|
const pts = interpolatePoints(tw.fromPts, tw.toPts, easedUpdate);
|
|
2998
|
-
const d = buildLinePath2(pts, tw.interpolate);
|
|
2999
|
-
snap.set(key, { type: "line", d });
|
|
3082
|
+
const d = tw.kind === "update" ? buildLinePath2(pts, tw.interpolate) : tw.pathEl?.getAttribute("d") ?? "";
|
|
3083
|
+
snap.set(key, { type: "line", d, points: pts, pointKeys: tw.pointKeys });
|
|
3000
3084
|
break;
|
|
3001
3085
|
}
|
|
3002
3086
|
case "area": {
|
|
3003
3087
|
const top = interpolatePoints(tw.fromTop, tw.toTop, easedUpdate);
|
|
3004
3088
|
const bottom = interpolatePoints(tw.fromBottom, tw.toBottom, easedUpdate);
|
|
3005
|
-
const d = buildAreaPath2(top, bottom, tw.interpolate);
|
|
3089
|
+
const d = tw.kind === "update" ? buildAreaPath2(top, bottom, tw.interpolate) : tw.fillPathEl?.getAttribute("d") ?? "";
|
|
3006
3090
|
let topD;
|
|
3007
3091
|
if (tw.hasStroke) {
|
|
3008
|
-
topD = buildLinePath2(top, tw.interpolate);
|
|
3092
|
+
topD = tw.kind === "update" ? buildLinePath2(top, tw.interpolate) : tw.topPathEl?.getAttribute("d") ?? "";
|
|
3009
3093
|
}
|
|
3010
|
-
snap.set(key, {
|
|
3094
|
+
snap.set(key, {
|
|
3095
|
+
type: "area",
|
|
3096
|
+
d,
|
|
3097
|
+
topD,
|
|
3098
|
+
points: top,
|
|
3099
|
+
bottomPoints: bottom,
|
|
3100
|
+
pointKeys: tw.pointKeys
|
|
3101
|
+
});
|
|
3011
3102
|
break;
|
|
3012
3103
|
}
|
|
3013
3104
|
case "arc": {
|
|
@@ -3021,6 +3112,7 @@ function runTransition(args) {
|
|
|
3021
3112
|
}
|
|
3022
3113
|
}
|
|
3023
3114
|
}
|
|
3115
|
+
labelMotion.snapshot(snap);
|
|
3024
3116
|
return snap;
|
|
3025
3117
|
}
|
|
3026
3118
|
if (!manual) {
|
|
@@ -3041,9 +3133,6 @@ function runTransition(args) {
|
|
|
3041
3133
|
snapToFinal();
|
|
3042
3134
|
removeGhosts();
|
|
3043
3135
|
settleCanvas();
|
|
3044
|
-
for (let i = 0; i < secondaryEls.length; i++) {
|
|
3045
|
-
secondaryEls[i].style.opacity = secondaryOriginalOpacity[i];
|
|
3046
|
-
}
|
|
3047
3136
|
},
|
|
3048
3137
|
get running() {
|
|
3049
3138
|
return running;
|
|
@@ -3056,7 +3145,7 @@ function runTransition(args) {
|
|
|
3056
3145
|
for (const tw of tweens) {
|
|
3057
3146
|
applyTweenState(tw, elapsedMs, update, exit, enterDelay, enterDuration);
|
|
3058
3147
|
}
|
|
3059
|
-
|
|
3148
|
+
labelMotion.apply(sampleMotion3(update, elapsedMs).position);
|
|
3060
3149
|
if (elapsedMs >= totalMs) {
|
|
3061
3150
|
snapToFinal();
|
|
3062
3151
|
removeGhosts();
|
|
@@ -3100,27 +3189,6 @@ function pulseSparklineTerminator(svg, marksContainer) {
|
|
|
3100
3189
|
);
|
|
3101
3190
|
return dot;
|
|
3102
3191
|
}
|
|
3103
|
-
function collectSecondaryElements(svg) {
|
|
3104
|
-
const els = [];
|
|
3105
|
-
const epLabels = svg.querySelector(".oc-endpoint-labels");
|
|
3106
|
-
if (epLabels) els.push(epLabels);
|
|
3107
|
-
const markLabels = svg.querySelector(".oc-mark-labels");
|
|
3108
|
-
if (markLabels) els.push(markLabels);
|
|
3109
|
-
return els;
|
|
3110
|
-
}
|
|
3111
|
-
function applySecondaryOpacity(els, elapsed, enterDelay, enterDuration) {
|
|
3112
|
-
if (els.length === 0) return;
|
|
3113
|
-
let t;
|
|
3114
|
-
if (elapsed < enterDelay) {
|
|
3115
|
-
t = 0;
|
|
3116
|
-
} else {
|
|
3117
|
-
t = Math.min((elapsed - enterDelay) / enterDuration, 1);
|
|
3118
|
-
}
|
|
3119
|
-
const opacity = String(cubicOut(t));
|
|
3120
|
-
for (const el of els) {
|
|
3121
|
-
el.style.opacity = opacity;
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
3192
|
|
|
3125
3193
|
// src/dom-helpers.ts
|
|
3126
3194
|
function applySrOnlyStyles(el) {
|
|
@@ -3170,10 +3238,12 @@ function createYouDrawIt(options) {
|
|
|
3170
3238
|
return pattern;
|
|
3171
3239
|
}
|
|
3172
3240
|
function findTargetLinePath(svg, seriesKey) {
|
|
3173
|
-
if (seriesKey) {
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
|
|
3241
|
+
if (seriesKey !== void 0) {
|
|
3242
|
+
const group = [...svg.querySelectorAll(".oc-mark-line")].find(
|
|
3243
|
+
(element) => element.getAttribute("data-series") === seriesKey
|
|
3244
|
+
);
|
|
3245
|
+
const path = group?.querySelector("path");
|
|
3246
|
+
if (path) return path;
|
|
3177
3247
|
}
|
|
3178
3248
|
return svg.querySelector(".oc-mark-line path");
|
|
3179
3249
|
}
|
|
@@ -3584,6 +3654,78 @@ function createYouDrawIt(options) {
|
|
|
3584
3654
|
};
|
|
3585
3655
|
}
|
|
3586
3656
|
|
|
3657
|
+
// src/transition/reconcile.ts
|
|
3658
|
+
function identity(element, index) {
|
|
3659
|
+
for (const attr of ["data-key", "data-label-key", "data-endpoint-key", "data-tick-value"]) {
|
|
3660
|
+
const value = element.getAttribute(attr);
|
|
3661
|
+
if (value !== null)
|
|
3662
|
+
return `${element.tagName}:${attr}:${value}:${element.getAttribute("data-endpoint-side") ?? ""}`;
|
|
3663
|
+
}
|
|
3664
|
+
return `${element.tagName}:${element.getAttribute("class") ?? ""}:${index}`;
|
|
3665
|
+
}
|
|
3666
|
+
function reconcileSVG(current, next) {
|
|
3667
|
+
for (const attribute of Array.from(current.attributes)) {
|
|
3668
|
+
if (!next.hasAttribute(attribute.name)) current.removeAttribute(attribute.name);
|
|
3669
|
+
}
|
|
3670
|
+
for (const attribute of Array.from(next.attributes)) {
|
|
3671
|
+
if (current.getAttribute(attribute.name) !== attribute.value) {
|
|
3672
|
+
current.setAttribute(attribute.name, attribute.value);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
const candidates = new Map(
|
|
3676
|
+
Array.from(current.children, (child, index) => [identity(child, index), child])
|
|
3677
|
+
);
|
|
3678
|
+
const survivors = /* @__PURE__ */ new Set();
|
|
3679
|
+
let elementIndex = 0;
|
|
3680
|
+
Array.from(next.childNodes).forEach((child, index) => {
|
|
3681
|
+
const match = child.nodeType === 1 ? candidates.get(identity(child, elementIndex++)) : void 0;
|
|
3682
|
+
const node = match ?? child;
|
|
3683
|
+
if (match) reconcileSVG(match, child);
|
|
3684
|
+
survivors.add(node);
|
|
3685
|
+
const at = current.childNodes[index];
|
|
3686
|
+
if (at !== node) current.insertBefore(node, at ?? null);
|
|
3687
|
+
});
|
|
3688
|
+
for (const child of Array.from(current.childNodes)) {
|
|
3689
|
+
if (!survivors.has(child)) current.removeChild(child);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
// src/transition/scene.ts
|
|
3694
|
+
function sampleLayout(layout, snapshot) {
|
|
3695
|
+
if (!snapshot?.size) return layout;
|
|
3696
|
+
const marks = layout.marks.map((mark) => {
|
|
3697
|
+
const geometry = mark.key ? snapshot.get(mark.key) : void 0;
|
|
3698
|
+
if (!geometry) return mark;
|
|
3699
|
+
if ((mark.type === "line" && geometry.type === "line" || mark.type === "area" && geometry.type === "area") && geometry.points) {
|
|
3700
|
+
const positions = new Map(
|
|
3701
|
+
(geometry.pointKeys ?? mark.pointKeys ?? []).map((key, index) => [
|
|
3702
|
+
key,
|
|
3703
|
+
geometry.points[index]
|
|
3704
|
+
])
|
|
3705
|
+
);
|
|
3706
|
+
return {
|
|
3707
|
+
...mark,
|
|
3708
|
+
...mark.type === "line" ? { points: geometry.points } : {
|
|
3709
|
+
topPoints: geometry.points,
|
|
3710
|
+
bottomPoints: geometry.type === "area" ? geometry.bottomPoints ?? mark.bottomPoints : mark.bottomPoints
|
|
3711
|
+
},
|
|
3712
|
+
path: geometry.d,
|
|
3713
|
+
dataPoints: mark.dataPoints?.map((point, index) => ({
|
|
3714
|
+
...point,
|
|
3715
|
+
...positions.get(mark.pointKeys?.[index] ?? "") ?? point
|
|
3716
|
+
}))
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
if (mark.type === "arc" && geometry.type === "arc")
|
|
3720
|
+
return { ...mark, ...geometry, center: { x: geometry.cx, y: geometry.cy } };
|
|
3721
|
+
if (mark.type === "rect" && geometry.type === "rect") return { ...mark, ...geometry };
|
|
3722
|
+
if (mark.type === "point" && geometry.type === "point")
|
|
3723
|
+
return { ...mark, ...geometry, r: geometry.r ?? mark.r };
|
|
3724
|
+
return mark;
|
|
3725
|
+
});
|
|
3726
|
+
return { ...layout, marks };
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3587
3729
|
// src/mount.ts
|
|
3588
3730
|
import { cssTokenDefault, isGraphSpec, isLayerSpec } from "@opendata-ai/openchart-core";
|
|
3589
3731
|
import {
|
|
@@ -3619,7 +3761,7 @@ function setupAnimationCleanup(svg, onComplete, totalMs) {
|
|
|
3619
3761
|
cancelAnimations(svg);
|
|
3620
3762
|
};
|
|
3621
3763
|
}
|
|
3622
|
-
function setupTableAnimationCleanup(wrapper) {
|
|
3764
|
+
function setupTableAnimationCleanup(wrapper, onComplete) {
|
|
3623
3765
|
const style = wrapper.style;
|
|
3624
3766
|
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 500;
|
|
3625
3767
|
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
@@ -3628,6 +3770,7 @@ function setupTableAnimationCleanup(wrapper) {
|
|
|
3628
3770
|
const totalTime = totalStagger + duration + 300;
|
|
3629
3771
|
const timer = setTimeout(() => {
|
|
3630
3772
|
wrapper.classList.remove("oc-animate");
|
|
3773
|
+
onComplete?.();
|
|
3631
3774
|
}, totalTime);
|
|
3632
3775
|
return () => {
|
|
3633
3776
|
clearTimeout(timer);
|
|
@@ -3642,13 +3785,13 @@ function buildMarkDataMap(layout) {
|
|
|
3642
3785
|
const mark = layout.marks[i];
|
|
3643
3786
|
switch (mark.type) {
|
|
3644
3787
|
case "line":
|
|
3645
|
-
map.set(`line-${
|
|
3788
|
+
map.set(`line-${i}`, {
|
|
3646
3789
|
datum: mark.data[0] ?? {},
|
|
3647
3790
|
series: mark.seriesKey
|
|
3648
3791
|
});
|
|
3649
3792
|
break;
|
|
3650
3793
|
case "area":
|
|
3651
|
-
map.set(`area-${
|
|
3794
|
+
map.set(`area-${i}`, {
|
|
3652
3795
|
datum: mark.data[0] ?? {},
|
|
3653
3796
|
series: mark.seriesKey
|
|
3654
3797
|
});
|
|
@@ -3754,7 +3897,9 @@ function collectSeriesGroups(layout) {
|
|
|
3754
3897
|
for (let i = 0; i < layout.marks.length; i++) {
|
|
3755
3898
|
const mark = layout.marks[i];
|
|
3756
3899
|
if ((mark.type === "line" || mark.type === "area") && mark.dataPoints?.length) {
|
|
3757
|
-
const
|
|
3900
|
+
const scope = mark.key?.match(/^layer:\d+:/)?.[0] ?? "";
|
|
3901
|
+
const seriesKey = mark.seriesKey ?? "__default__";
|
|
3902
|
+
const key = `${scope}${seriesKey}`;
|
|
3758
3903
|
const existingType = markTypeByKey.get(key);
|
|
3759
3904
|
if (existingType === "line" && mark.type === "area") continue;
|
|
3760
3905
|
const color = mark.type === "line" ? mark.stroke : getRepresentativeColor(mark.fill);
|
|
@@ -3762,7 +3907,7 @@ function collectSeriesGroups(layout) {
|
|
|
3762
3907
|
for (const dp of mark.dataPoints) {
|
|
3763
3908
|
pointsByX.set(snapKey(dp.x), { ...dp });
|
|
3764
3909
|
}
|
|
3765
|
-
byKey.set(key, { seriesKey:
|
|
3910
|
+
byKey.set(key, { key, seriesKey, markId: `${mark.type}-${i}`, color, pointsByX });
|
|
3766
3911
|
markTypeByKey.set(key, mark.type);
|
|
3767
3912
|
}
|
|
3768
3913
|
}
|
|
@@ -3796,6 +3941,9 @@ function buildSliceTooltip(hits, emphasisKey) {
|
|
|
3796
3941
|
const title = hits[0].point.tooltip?.title;
|
|
3797
3942
|
const fields = [];
|
|
3798
3943
|
const isMulti = hits.length > 1;
|
|
3944
|
+
const seriesCounts = /* @__PURE__ */ new Map();
|
|
3945
|
+
for (const { group } of hits)
|
|
3946
|
+
seriesCounts.set(group.seriesKey, (seriesCounts.get(group.seriesKey) ?? 0) + 1);
|
|
3799
3947
|
let total;
|
|
3800
3948
|
for (const { group, point } of hits) {
|
|
3801
3949
|
const tip = point.tooltip;
|
|
@@ -3808,10 +3956,10 @@ function buildSliceTooltip(hits, emphasisKey) {
|
|
|
3808
3956
|
) ?? candidates[candidates.length - 1] ?? null;
|
|
3809
3957
|
if (!yField) continue;
|
|
3810
3958
|
fields.push({
|
|
3811
|
-
label: group.seriesKey,
|
|
3959
|
+
label: group.seriesKey === "__default__" ? yField.label : (seriesCounts.get(group.seriesKey) ?? 0) > 1 ? `${group.seriesKey} \xB7 ${yField.label}` : group.seriesKey,
|
|
3812
3960
|
value: yField.value,
|
|
3813
3961
|
color: group.color,
|
|
3814
|
-
...emphasisKey === group.
|
|
3962
|
+
...emphasisKey === group.key ? { emphasis: true } : {}
|
|
3815
3963
|
});
|
|
3816
3964
|
} else {
|
|
3817
3965
|
for (const f of tip.fields) {
|
|
@@ -3884,8 +4032,10 @@ function wireVoronoiTooltipEvents(svg, layout, tooltipManager, emphasis) {
|
|
|
3884
4032
|
crosshair.style.display = "";
|
|
3885
4033
|
}
|
|
3886
4034
|
currentEmphasis = emphasisKey;
|
|
3887
|
-
|
|
3888
|
-
|
|
4035
|
+
const emphasizedGroup = groups.find((group) => group.key === emphasisKey);
|
|
4036
|
+
if (emphasizedGroup) {
|
|
4037
|
+
emphasis?.setMark(svg.querySelector(`[data-mark-id="${emphasizedGroup.markId}"]`));
|
|
4038
|
+
} else emphasis?.clear();
|
|
3889
4039
|
const tooltip = buildSliceTooltip(hits, emphasisKey);
|
|
3890
4040
|
if (!tooltip) return false;
|
|
3891
4041
|
const containerAnchorX = snappedX * viewBoxToContainer;
|
|
@@ -3898,7 +4048,7 @@ function wireVoronoiTooltipEvents(svg, layout, tooltipManager, emphasis) {
|
|
|
3898
4048
|
function keysAt(snappedX) {
|
|
3899
4049
|
const keys = [];
|
|
3900
4050
|
for (const group of groups) {
|
|
3901
|
-
if (group.pointsByX.has(snappedX)) keys.push(group.
|
|
4051
|
+
if (group.pointsByX.has(snappedX)) keys.push(group.key);
|
|
3902
4052
|
}
|
|
3903
4053
|
return keys;
|
|
3904
4054
|
}
|
|
@@ -3914,7 +4064,7 @@ function wireVoronoiTooltipEvents(svg, layout, tooltipManager, emphasis) {
|
|
|
3914
4064
|
const dy = Math.abs(point.y - svgY);
|
|
3915
4065
|
if (dy < nearestDy) {
|
|
3916
4066
|
nearestDy = dy;
|
|
3917
|
-
nearestKey = group.
|
|
4067
|
+
nearestKey = group.key;
|
|
3918
4068
|
}
|
|
3919
4069
|
}
|
|
3920
4070
|
const emphasisKey = nearestDy <= EMPHASIS_PROXIMITY_PX ? nearestKey : null;
|
|
@@ -3959,13 +4109,12 @@ function wireVoronoiTooltipEvents(svg, layout, tooltipManager, emphasis) {
|
|
|
3959
4109
|
if (te.touches.length === 0) return;
|
|
3960
4110
|
const t = te.touches[0];
|
|
3961
4111
|
const { svgX, svgY, viewBoxToContainer } = toSvgCoords(t.clientX, t.clientY);
|
|
3962
|
-
if (te.cancelable) te.preventDefault();
|
|
3963
4112
|
positionAt(svgX, svgY, viewBoxToContainer);
|
|
3964
4113
|
};
|
|
3965
4114
|
overlay.addEventListener("mousemove", handleMouseMove);
|
|
3966
4115
|
overlay.addEventListener("mouseleave", hideOnPointerOut);
|
|
3967
|
-
overlay.addEventListener("touchstart", handleTouch, { passive:
|
|
3968
|
-
overlay.addEventListener("touchmove", handleTouch, { passive:
|
|
4116
|
+
overlay.addEventListener("touchstart", handleTouch, { passive: true });
|
|
4117
|
+
overlay.addEventListener("touchmove", handleTouch, { passive: true });
|
|
3969
4118
|
overlay.addEventListener("touchend", hideOnPointerOut);
|
|
3970
4119
|
return {
|
|
3971
4120
|
cleanup() {
|
|
@@ -5412,6 +5561,7 @@ function wireCanvasInteractions({
|
|
|
5412
5561
|
}
|
|
5413
5562
|
|
|
5414
5563
|
// src/scatter-canvas/entrance.ts
|
|
5564
|
+
import { sampleMotion as sampleMotion4 } from "@opendata-ai/openchart-core";
|
|
5415
5565
|
var MAX_TOTAL_STAGGER_MS = 300;
|
|
5416
5566
|
var POINT_DURATION_FRACTION = 0.4;
|
|
5417
5567
|
function clampStagger(delay, elementCount) {
|
|
@@ -5430,7 +5580,7 @@ function entranceAlphaAt(t, markIndex, enter, markCount, ease) {
|
|
|
5430
5580
|
const local = (t - stagger * markIndex) / pointDuration;
|
|
5431
5581
|
if (local <= 0) return 0;
|
|
5432
5582
|
if (local >= 1) return 1;
|
|
5433
|
-
return ease(local);
|
|
5583
|
+
return enter.spring ? sampleMotion4(enter, local * enter.duration).position : ease(local);
|
|
5434
5584
|
}
|
|
5435
5585
|
function applyEntranceAt(state, t, enter, ease) {
|
|
5436
5586
|
const alpha = state.enterAlpha;
|
|
@@ -5914,8 +6064,8 @@ function createChart(container, spec, options) {
|
|
|
5914
6064
|
let isAutoHeight = null;
|
|
5915
6065
|
let lastRenderedWidth = null;
|
|
5916
6066
|
let lastRenderedSvgHeight = null;
|
|
6067
|
+
let lastHostHeight = FALLBACK_HEIGHT;
|
|
5917
6068
|
let transitionHandle = null;
|
|
5918
|
-
let transitionSnapshot = null;
|
|
5919
6069
|
let fontsReloadPending = false;
|
|
5920
6070
|
let selectedElement = options?.selectedElement ?? null;
|
|
5921
6071
|
let overlayElement = null;
|
|
@@ -5929,13 +6079,13 @@ function createChart(container, spec, options) {
|
|
|
5929
6079
|
let youDrawIt = null;
|
|
5930
6080
|
let ydiEditSuppressWarned = false;
|
|
5931
6081
|
container.classList.add("oc-root");
|
|
5932
|
-
function resolveEffectiveFont() {
|
|
5933
|
-
return options?.theme?.fonts?.family ??
|
|
6082
|
+
function resolveEffectiveFont(candidate = currentSpec) {
|
|
6083
|
+
return options?.theme?.fonts?.family ?? candidate.theme?.fonts?.family ?? resolveFontFamily(container);
|
|
5934
6084
|
}
|
|
5935
6085
|
let fontFamily = resolveEffectiveFont();
|
|
5936
6086
|
let measureText = createMeasureText(fontFamily);
|
|
5937
6087
|
let renderGen = 0;
|
|
5938
|
-
function compileAt(width, height) {
|
|
6088
|
+
function compileAt(width, height, candidate, measure, runtime) {
|
|
5939
6089
|
const darkMode = resolveDarkMode(options?.darkMode);
|
|
5940
6090
|
const compileOpts = {
|
|
5941
6091
|
width,
|
|
@@ -5944,12 +6094,11 @@ function createChart(container, spec, options) {
|
|
|
5944
6094
|
darkMode,
|
|
5945
6095
|
renderer: options?.renderer,
|
|
5946
6096
|
watermark: options?.watermark,
|
|
5947
|
-
measureText
|
|
6097
|
+
measureText: measure
|
|
5948
6098
|
};
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
return compileChart(withRuntimeHidden(currentSpec), compileOpts);
|
|
6099
|
+
const effective = runtime ? withRuntimeHidden(candidate) : candidate;
|
|
6100
|
+
if (isLayerSpec(effective)) return compileLayer(effective, compileOpts);
|
|
6101
|
+
return compileChart(effective, compileOpts);
|
|
5953
6102
|
}
|
|
5954
6103
|
function verticalOverheads(layout, width) {
|
|
5955
6104
|
let topLegendBlock = 0;
|
|
@@ -5968,19 +6117,19 @@ function createChart(container, spec, options) {
|
|
|
5968
6117
|
}
|
|
5969
6118
|
return layout.chrome.topHeight + layout.chrome.bottomHeight + topLegendBlock + (layout.metrics?.height ?? 0) + facetGrowth;
|
|
5970
6119
|
}
|
|
5971
|
-
function compile() {
|
|
5972
|
-
const { width, height } = getContainerDimensions();
|
|
5973
|
-
const growsWithChrome = isAutoHeight === true && !isLayerSpec(
|
|
6120
|
+
function compile(candidate = currentSpec, measure = measureText, runtime = true) {
|
|
6121
|
+
const { width, height } = getContainerDimensions(candidate);
|
|
6122
|
+
const growsWithChrome = isAutoHeight === true && !isLayerSpec(candidate) && !isGraphSpec(candidate) && candidate.display !== "sparkline";
|
|
5974
6123
|
if (!growsWithChrome) {
|
|
5975
|
-
return compileAt(width, height);
|
|
6124
|
+
return compileAt(width, height, candidate, measure, runtime);
|
|
5976
6125
|
}
|
|
5977
|
-
let layout = compileAt(width, height);
|
|
6126
|
+
let layout = compileAt(width, height, candidate, measure, runtime);
|
|
5978
6127
|
const overheads = verticalOverheads(layout, width);
|
|
5979
6128
|
if (overheads > 0) {
|
|
5980
|
-
layout = compileAt(width, height + overheads);
|
|
6129
|
+
layout = compileAt(width, height + overheads, candidate, measure, runtime);
|
|
5981
6130
|
const overheads2 = verticalOverheads(layout, width);
|
|
5982
6131
|
if (overheads2 !== overheads) {
|
|
5983
|
-
layout = compileAt(width, height + overheads2);
|
|
6132
|
+
layout = compileAt(width, height + overheads2, candidate, measure, runtime);
|
|
5984
6133
|
}
|
|
5985
6134
|
}
|
|
5986
6135
|
return layout;
|
|
@@ -6062,9 +6211,9 @@ function createChart(container, spec, options) {
|
|
|
6062
6211
|
if (!("field" in colorEnc) || !colorEnc.field) return void 0;
|
|
6063
6212
|
return colorEnc.field;
|
|
6064
6213
|
}
|
|
6065
|
-
function getContainerDimensions() {
|
|
6214
|
+
function getContainerDimensions(candidate = currentSpec) {
|
|
6066
6215
|
const rect = container.getBoundingClientRect();
|
|
6067
|
-
const isSparkline = "display" in
|
|
6216
|
+
const isSparkline = "display" in candidate && candidate.display === "sparkline";
|
|
6068
6217
|
if (isSparkline) {
|
|
6069
6218
|
let width = rect.width;
|
|
6070
6219
|
let height = rect.height;
|
|
@@ -6081,12 +6230,14 @@ function createChart(container, spec, options) {
|
|
|
6081
6230
|
if (isAutoHeight === null && (rect.width > 0 || rect.height > 0)) {
|
|
6082
6231
|
isAutoHeight = rect.height === 0 && rect.width > 0;
|
|
6083
6232
|
}
|
|
6233
|
+
const ownHeight = lastRenderedSvgHeight !== null && Math.abs(rect.height - lastRenderedSvgHeight) < 0.5;
|
|
6234
|
+
if (!ownHeight && rect.height > 0) lastHostHeight = rect.height;
|
|
6084
6235
|
return {
|
|
6085
6236
|
width: Math.max(rect.width || 600, 100),
|
|
6086
6237
|
// Latched-auto mounts always compile against the fixed budget: after
|
|
6087
6238
|
// the first render the container's measured height is just our own
|
|
6088
6239
|
// SVG, so reading it back would re-pin whatever we rendered last.
|
|
6089
|
-
height: isAutoHeight === true ? FALLBACK_HEIGHT : Math.max(rect.height || FALLBACK_HEIGHT, 100)
|
|
6240
|
+
height: isAutoHeight === true ? FALLBACK_HEIGHT : Math.max(ownHeight ? lastHostHeight : rect.height || FALLBACK_HEIGHT, 100)
|
|
6090
6241
|
};
|
|
6091
6242
|
}
|
|
6092
6243
|
function getSpecAnnotations() {
|
|
@@ -6314,11 +6465,13 @@ function createChart(container, spec, options) {
|
|
|
6314
6465
|
svg.removeEventListener("keydown", handleKeyDown);
|
|
6315
6466
|
};
|
|
6316
6467
|
}
|
|
6317
|
-
function render() {
|
|
6468
|
+
function render(retainScene = false, prepared) {
|
|
6469
|
+
const retainedSVG = retainScene ? svgElement : null;
|
|
6318
6470
|
if (isDragging) {
|
|
6319
6471
|
pendingRender = true;
|
|
6320
6472
|
return;
|
|
6321
6473
|
}
|
|
6474
|
+
const nextLayout = prepared ?? compile();
|
|
6322
6475
|
if (transitionHandle) {
|
|
6323
6476
|
transitionHandle.cancel();
|
|
6324
6477
|
transitionHandle = null;
|
|
@@ -6363,7 +6516,7 @@ function createChart(container, spec, options) {
|
|
|
6363
6516
|
isTextEditingActive = false;
|
|
6364
6517
|
}
|
|
6365
6518
|
overlayElement = null;
|
|
6366
|
-
if (svgElement?.parentNode) {
|
|
6519
|
+
if (!retainedSVG && svgElement?.parentNode) {
|
|
6367
6520
|
svgElement.parentNode.removeChild(svgElement);
|
|
6368
6521
|
}
|
|
6369
6522
|
if (tooltipManager) {
|
|
@@ -6373,7 +6526,7 @@ function createChart(container, spec, options) {
|
|
|
6373
6526
|
srTableWrapper.parentNode.removeChild(srTableWrapper);
|
|
6374
6527
|
srTableWrapper = null;
|
|
6375
6528
|
}
|
|
6376
|
-
currentLayout =
|
|
6529
|
+
currentLayout = nextLayout;
|
|
6377
6530
|
lastRenderedWidth = currentLayout.dimensions.width;
|
|
6378
6531
|
lastRenderedSvgHeight = currentLayout.dimensions.height;
|
|
6379
6532
|
const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;
|
|
@@ -6381,6 +6534,7 @@ function createChart(container, spec, options) {
|
|
|
6381
6534
|
const canvasMode = currentLayout.markRenderMode === "canvas";
|
|
6382
6535
|
if (canvasMode) {
|
|
6383
6536
|
canvasLayer = createScatterCanvasLayer(container, currentLayout);
|
|
6537
|
+
if (retainedSVG) container.insertBefore(canvasLayer.canvas, retainedSVG);
|
|
6384
6538
|
canvasLayer.repaint();
|
|
6385
6539
|
if (!warnedCanvasEditMode && (options?.editable || hasEditingCallbacks(options) || options?.onAnnotationEdit)) {
|
|
6386
6540
|
warnedCanvasEditMode = true;
|
|
@@ -6389,11 +6543,16 @@ function createChart(container, spec, options) {
|
|
|
6389
6543
|
);
|
|
6390
6544
|
}
|
|
6391
6545
|
}
|
|
6392
|
-
|
|
6546
|
+
const sceneHost = retainedSVG ? document.createElement("div") : container;
|
|
6547
|
+
svgElement = renderChartSVG(currentLayout, sceneHost, {
|
|
6393
6548
|
animate: shouldAnimate,
|
|
6394
6549
|
crosshair,
|
|
6395
6550
|
canvasMarks: canvasMode
|
|
6396
6551
|
});
|
|
6552
|
+
if (retainedSVG && svgElement) {
|
|
6553
|
+
reconcileSVG(retainedSVG, svgElement);
|
|
6554
|
+
svgElement = retainedSVG;
|
|
6555
|
+
}
|
|
6397
6556
|
tooltipManager = createTooltipManager(container);
|
|
6398
6557
|
if (canvasMode && canvasLayer) {
|
|
6399
6558
|
cleanupCanvasEvents = wireCanvasInteractions({
|
|
@@ -6589,30 +6748,32 @@ function createChart(container, spec, options) {
|
|
|
6589
6748
|
function beginManualUpdate(newSpec) {
|
|
6590
6749
|
return doUpdate(newSpec, void 0, true);
|
|
6591
6750
|
}
|
|
6592
|
-
function doUpdate(newSpec, updateOpts, manual) {
|
|
6751
|
+
function doUpdate(newSpec, updateOpts, manual, layoutOnly = false) {
|
|
6593
6752
|
if (destroyed) return null;
|
|
6594
6753
|
const prevSpec = currentSpec;
|
|
6595
6754
|
const prevLayout = currentLayout;
|
|
6596
6755
|
const entranceWasRunning = cleanupAnimations != null;
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6756
|
+
const nextFont = resolveEffectiveFont(newSpec);
|
|
6757
|
+
const nextMeasure = nextFont === fontFamily ? measureText : createMeasureText(nextFont);
|
|
6758
|
+
const nextLayout = compile(newSpec, nextMeasure, layoutOnly);
|
|
6759
|
+
const snapshot = transitionHandle?.running ? transitionHandle.snapshot() : void 0;
|
|
6600
6760
|
currentSpec = newSpec;
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6761
|
+
fontFamily = nextFont;
|
|
6762
|
+
measureText = nextMeasure;
|
|
6763
|
+
if (!layoutOnly) {
|
|
6764
|
+
runtimeHiddenSeries.clear();
|
|
6765
|
+
runtimeShownSeries.clear();
|
|
6766
|
+
}
|
|
6767
|
+
if (!layoutOnly) {
|
|
6768
|
+
searchBaseline = authoredHighlight(newSpec);
|
|
6769
|
+
seriesSearch?.setSelected([]);
|
|
6770
|
+
}
|
|
6610
6771
|
ydiEditSuppressWarned = false;
|
|
6611
6772
|
editSuppressWarned = false;
|
|
6612
6773
|
if (updateOpts && "selectedElement" in updateOpts) {
|
|
6613
6774
|
selectedElement = updateOpts.selectedElement ?? null;
|
|
6614
6775
|
}
|
|
6615
|
-
render();
|
|
6776
|
+
render(canTransitionSpecShape(prevSpec, newSpec), nextLayout);
|
|
6616
6777
|
if (svgElement && canTransition({
|
|
6617
6778
|
prevLayout,
|
|
6618
6779
|
nextLayout: currentLayout,
|
|
@@ -6621,14 +6782,12 @@ function createChart(container, spec, options) {
|
|
|
6621
6782
|
isFirstRender: false,
|
|
6622
6783
|
entranceInFlight: entranceWasRunning
|
|
6623
6784
|
})) {
|
|
6624
|
-
const snapshot = transitionSnapshot;
|
|
6625
|
-
transitionSnapshot = null;
|
|
6626
6785
|
const handle = runTransition({
|
|
6627
6786
|
svg: svgElement,
|
|
6628
6787
|
prevLayout,
|
|
6629
6788
|
nextLayout: currentLayout,
|
|
6630
6789
|
animation: currentLayout.animation,
|
|
6631
|
-
fromSnapshot: snapshot
|
|
6790
|
+
fromSnapshot: snapshot,
|
|
6632
6791
|
manual,
|
|
6633
6792
|
// render() above already rebuilt the layer for the NEXT layout, so this
|
|
6634
6793
|
// is the destination-mode layer the tween writes into.
|
|
@@ -6637,10 +6796,9 @@ function createChart(container, spec, options) {
|
|
|
6637
6796
|
transitionHandle = null;
|
|
6638
6797
|
}
|
|
6639
6798
|
});
|
|
6640
|
-
|
|
6799
|
+
transitionHandle = handle;
|
|
6641
6800
|
return handle;
|
|
6642
6801
|
}
|
|
6643
|
-
transitionSnapshot = null;
|
|
6644
6802
|
return null;
|
|
6645
6803
|
}
|
|
6646
6804
|
function resize() {
|
|
@@ -6657,7 +6815,7 @@ function createChart(container, spec, options) {
|
|
|
6657
6815
|
pendingResize = true;
|
|
6658
6816
|
return;
|
|
6659
6817
|
}
|
|
6660
|
-
|
|
6818
|
+
doUpdate(currentSpec, void 0, false, true);
|
|
6661
6819
|
}
|
|
6662
6820
|
function handleSearchChange(selected) {
|
|
6663
6821
|
const merged = [...searchBaseline];
|
|
@@ -6694,7 +6852,7 @@ function createChart(container, spec, options) {
|
|
|
6694
6852
|
}
|
|
6695
6853
|
const svg = canvasLayer && currentLayout.markRenderMode === "canvas" ? materializeCanvasModeSVG(currentLayout, {
|
|
6696
6854
|
forceVector: format === "png" || format === "jpg" || format === "gif"
|
|
6697
|
-
}) :
|
|
6855
|
+
}) : renderChartSVG(currentLayout, document.createElement("div"), { animate: false });
|
|
6698
6856
|
const theme = "theme" in currentLayout ? currentLayout.theme : void 0;
|
|
6699
6857
|
const withTheme = (o) => ({ theme, ...o ?? {} });
|
|
6700
6858
|
switch (format) {
|
|
@@ -6728,7 +6886,6 @@ function createChart(container, spec, options) {
|
|
|
6728
6886
|
transitionHandle.cancel();
|
|
6729
6887
|
transitionHandle = null;
|
|
6730
6888
|
}
|
|
6731
|
-
transitionSnapshot = null;
|
|
6732
6889
|
if (cleanupAnimations) {
|
|
6733
6890
|
cleanupAnimations();
|
|
6734
6891
|
cleanupAnimations = null;
|
|
@@ -6810,10 +6967,10 @@ function createChart(container, spec, options) {
|
|
|
6810
6967
|
container.dataset.ocFontsState = pending ? "pending" : "ready";
|
|
6811
6968
|
if (options?.responsive !== false) {
|
|
6812
6969
|
disconnectResize = observeResize(container, (width, height) => {
|
|
6970
|
+
const widthChanged = lastRenderedWidth === null || Math.abs(width - lastRenderedWidth) >= 0.5;
|
|
6971
|
+
const selfInducedHeight = lastRenderedSvgHeight !== null && Math.abs(height - lastRenderedSvgHeight) < 0.5;
|
|
6972
|
+
if (!widthChanged && selfInducedHeight) return;
|
|
6813
6973
|
if (isAutoHeight === true) {
|
|
6814
|
-
const widthChanged = width !== lastRenderedWidth;
|
|
6815
|
-
const selfInducedHeight = height === lastRenderedSvgHeight;
|
|
6816
|
-
if (!widthChanged && selfInducedHeight) return;
|
|
6817
6974
|
if (!selfInducedHeight) {
|
|
6818
6975
|
isAutoHeight = null;
|
|
6819
6976
|
}
|
|
@@ -6827,6 +6984,10 @@ function createChart(container, spec, options) {
|
|
|
6827
6984
|
update,
|
|
6828
6985
|
beginManualUpdate,
|
|
6829
6986
|
resize,
|
|
6987
|
+
sampleLayout: () => sampleLayout(
|
|
6988
|
+
currentLayout,
|
|
6989
|
+
transitionHandle?.running ? transitionHandle.snapshot() : void 0
|
|
6990
|
+
),
|
|
6830
6991
|
export: doExport,
|
|
6831
6992
|
destroy,
|
|
6832
6993
|
get layout() {
|
|
@@ -6976,6 +7137,84 @@ function scrubCamera(step, stepProgress, fitted, holdUntil, ease) {
|
|
|
6976
7137
|
// src/map-mount.ts
|
|
6977
7138
|
import { compileGeoMap } from "@opendata-ai/openchart-engine";
|
|
6978
7139
|
|
|
7140
|
+
// src/interactions/mark-navigation.ts
|
|
7141
|
+
function captureMarkFocus(svg) {
|
|
7142
|
+
const active = document.activeElement;
|
|
7143
|
+
if (!svg || !active || !svg.contains(active)) return null;
|
|
7144
|
+
return active.getAttribute("data-navigation-key") ?? "";
|
|
7145
|
+
}
|
|
7146
|
+
function restoreMarkFocus(svg, key) {
|
|
7147
|
+
if (key === null) return;
|
|
7148
|
+
const matches = [...svg.querySelectorAll("[data-navigation-key]")].filter(
|
|
7149
|
+
(element) => element.getAttribute("data-navigation-key") === key
|
|
7150
|
+
);
|
|
7151
|
+
const target = matches.length === 1 ? matches[0] : svg;
|
|
7152
|
+
if (target === svg && !svg.hasAttribute("tabindex")) svg.setAttribute("tabindex", "-1");
|
|
7153
|
+
target.focus({ preventScroll: true });
|
|
7154
|
+
}
|
|
7155
|
+
function wireMarkNavigation(svg, marks) {
|
|
7156
|
+
const cleanups = [];
|
|
7157
|
+
if (svg.getAttribute("role") === "img") svg.setAttribute("role", "group");
|
|
7158
|
+
marks.forEach((mark, index) => {
|
|
7159
|
+
const element = mark.element;
|
|
7160
|
+
element.setAttribute("tabindex", index === 0 ? "0" : "-1");
|
|
7161
|
+
element.setAttribute("data-navigation-key", mark.key);
|
|
7162
|
+
const focus = () => {
|
|
7163
|
+
for (const other of marks) other.element.setAttribute("tabindex", "-1");
|
|
7164
|
+
element.setAttribute("tabindex", "0");
|
|
7165
|
+
const bounds = element.getBoundingClientRect();
|
|
7166
|
+
mark.enter(
|
|
7167
|
+
new MouseEvent("focus", {
|
|
7168
|
+
clientX: bounds.left + bounds.width / 2,
|
|
7169
|
+
clientY: bounds.top + bounds.height / 2
|
|
7170
|
+
})
|
|
7171
|
+
);
|
|
7172
|
+
};
|
|
7173
|
+
const keydown = (event) => {
|
|
7174
|
+
let next;
|
|
7175
|
+
switch (event.key) {
|
|
7176
|
+
case "ArrowRight":
|
|
7177
|
+
case "ArrowDown":
|
|
7178
|
+
next = Math.min(index + 1, marks.length - 1);
|
|
7179
|
+
break;
|
|
7180
|
+
case "ArrowLeft":
|
|
7181
|
+
case "ArrowUp":
|
|
7182
|
+
next = Math.max(index - 1, 0);
|
|
7183
|
+
break;
|
|
7184
|
+
case "Home":
|
|
7185
|
+
next = 0;
|
|
7186
|
+
break;
|
|
7187
|
+
case "End":
|
|
7188
|
+
next = marks.length - 1;
|
|
7189
|
+
break;
|
|
7190
|
+
case "Enter":
|
|
7191
|
+
case " ":
|
|
7192
|
+
mark.activate();
|
|
7193
|
+
break;
|
|
7194
|
+
case "Escape":
|
|
7195
|
+
mark.leave();
|
|
7196
|
+
break;
|
|
7197
|
+
default:
|
|
7198
|
+
return;
|
|
7199
|
+
}
|
|
7200
|
+
event.preventDefault();
|
|
7201
|
+
event.stopPropagation();
|
|
7202
|
+
if (next !== void 0) marks[next].element.focus({ preventScroll: true });
|
|
7203
|
+
};
|
|
7204
|
+
element.addEventListener("focus", focus);
|
|
7205
|
+
element.addEventListener("blur", mark.leave);
|
|
7206
|
+
element.addEventListener("keydown", keydown);
|
|
7207
|
+
cleanups.push(() => {
|
|
7208
|
+
element.removeEventListener("focus", focus);
|
|
7209
|
+
element.removeEventListener("blur", mark.leave);
|
|
7210
|
+
element.removeEventListener("keydown", keydown);
|
|
7211
|
+
});
|
|
7212
|
+
});
|
|
7213
|
+
return () => {
|
|
7214
|
+
for (const cleanup of cleanups) cleanup();
|
|
7215
|
+
};
|
|
7216
|
+
}
|
|
7217
|
+
|
|
6979
7218
|
// src/map-camera.ts
|
|
6980
7219
|
var FOCUS_DIM_OPACITY = 0.25;
|
|
6981
7220
|
function focusTargetForFeatures(layout, ids, padding = 16) {
|
|
@@ -7003,6 +7242,42 @@ function cameraForTarget(layout, target) {
|
|
|
7003
7242
|
const cam = fitTarget(target, vb);
|
|
7004
7243
|
return { ...cam, k: Math.max(1, Math.min(40, cam.k)) };
|
|
7005
7244
|
}
|
|
7245
|
+
var DEFAULT_MAX_ZOOM = 12;
|
|
7246
|
+
function resolveMapZoom(zoom) {
|
|
7247
|
+
if (!zoom) return null;
|
|
7248
|
+
const cfg = zoom === true ? {} : zoom;
|
|
7249
|
+
const max = cfg.maxZoom;
|
|
7250
|
+
return {
|
|
7251
|
+
maxZoom: typeof max === "number" && Number.isFinite(max) ? Math.max(1, max) : DEFAULT_MAX_ZOOM,
|
|
7252
|
+
controls: cfg.controls !== false
|
|
7253
|
+
};
|
|
7254
|
+
}
|
|
7255
|
+
function clampMapCamera(camera, vb, maxZoom) {
|
|
7256
|
+
const k = Math.min(Math.max(camera.k, 1), Math.max(1, maxZoom));
|
|
7257
|
+
const halfW = vb.width / (2 * k);
|
|
7258
|
+
const halfH = vb.height / (2 * k);
|
|
7259
|
+
return {
|
|
7260
|
+
cx: Math.min(Math.max(camera.cx, halfW), vb.width - halfW),
|
|
7261
|
+
cy: Math.min(Math.max(camera.cy, halfH), vb.height - halfH),
|
|
7262
|
+
k
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
7265
|
+
function zoomCameraAt(camera, vb, k, pivotX, pivotY) {
|
|
7266
|
+
const ox = pivotX - vb.width / 2;
|
|
7267
|
+
const oy = pivotY - vb.height / 2;
|
|
7268
|
+
const px = camera.cx + ox / camera.k;
|
|
7269
|
+
const py = camera.cy + oy / camera.k;
|
|
7270
|
+
return { cx: px - ox / k, cy: py - oy / k, k };
|
|
7271
|
+
}
|
|
7272
|
+
function panCamera(camera, dx, dy) {
|
|
7273
|
+
return { cx: camera.cx - dx / camera.k, cy: camera.cy - dy / camera.k, k: camera.k };
|
|
7274
|
+
}
|
|
7275
|
+
function wheelZoomFactor(e) {
|
|
7276
|
+
const perUnit = e.deltaMode === 1 ? 0.05 : e.deltaMode === 2 ? 1 : 2e-3;
|
|
7277
|
+
let exp = -e.deltaY * perUnit;
|
|
7278
|
+
if (e.ctrlKey && e.deltaMode === 0 && Math.abs(e.deltaY) < 50) exp *= 5;
|
|
7279
|
+
return 2 ** Math.max(-1, Math.min(1, exp));
|
|
7280
|
+
}
|
|
7006
7281
|
function applyMapCamera(svg, camera, layout) {
|
|
7007
7282
|
const cameraGroup = svg.querySelector("[data-oc-map-camera]");
|
|
7008
7283
|
if (!cameraGroup) return;
|
|
@@ -7037,6 +7312,21 @@ function applyMapCamera(svg, camera, layout) {
|
|
|
7037
7312
|
}
|
|
7038
7313
|
}
|
|
7039
7314
|
}
|
|
7315
|
+
function ensureMapClip(svg, layout, clipId) {
|
|
7316
|
+
if (svg.querySelector(`#${clipId}`)) return;
|
|
7317
|
+
let defs = svg.querySelector("defs");
|
|
7318
|
+
if (!defs) {
|
|
7319
|
+
defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
7320
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
7321
|
+
}
|
|
7322
|
+
const clip = document.createElementNS("http://www.w3.org/2000/svg", "clipPath");
|
|
7323
|
+
clip.setAttribute("id", clipId);
|
|
7324
|
+
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
7325
|
+
rect.setAttribute("width", String(layout.area.width));
|
|
7326
|
+
rect.setAttribute("height", String(layout.area.height));
|
|
7327
|
+
clip.appendChild(rect);
|
|
7328
|
+
defs.appendChild(clip);
|
|
7329
|
+
}
|
|
7040
7330
|
|
|
7041
7331
|
// src/map-renderer.ts
|
|
7042
7332
|
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
@@ -7282,6 +7572,14 @@ function renderMapSVG(layout, opts) {
|
|
|
7282
7572
|
}
|
|
7283
7573
|
const classes = animate ? "oc-map oc-animate" : "oc-map";
|
|
7284
7574
|
svg.setAttribute("class", classes);
|
|
7575
|
+
const background = createSVGElement2("rect");
|
|
7576
|
+
setAttrs2(background, {
|
|
7577
|
+
width,
|
|
7578
|
+
height,
|
|
7579
|
+
fill: layout.theme.colors.background,
|
|
7580
|
+
"aria-hidden": "true"
|
|
7581
|
+
});
|
|
7582
|
+
svg.appendChild(background);
|
|
7285
7583
|
const bulk = features.length > BULK_ANIMATION_THRESHOLD;
|
|
7286
7584
|
let mapStaggerBudget = 0;
|
|
7287
7585
|
if (animate && animation?.enter) {
|
|
@@ -7294,6 +7592,7 @@ function renderMapSVG(layout, opts) {
|
|
|
7294
7592
|
const stagger = !bulk && n > 1 ? mapStaggerBudget / (n - 1) : 0;
|
|
7295
7593
|
stampAnimationVars(svg, {
|
|
7296
7594
|
duration: perFeature,
|
|
7595
|
+
spring: animation.enter.spring,
|
|
7297
7596
|
stagger,
|
|
7298
7597
|
annotationDelay: animation.annotationDelay,
|
|
7299
7598
|
ease: animation.enter.ease
|
|
@@ -7462,7 +7761,7 @@ function runMapFillTransition(svg, prev, opts) {
|
|
|
7462
7761
|
const key = p.getAttribute("data-key");
|
|
7463
7762
|
const to = p.getAttribute("fill");
|
|
7464
7763
|
const from = key ? snapshot.fills.get(key) : void 0;
|
|
7465
|
-
if (from && to && from !== to) {
|
|
7764
|
+
if (from && to && from !== to && !from.startsWith("url(") && !to.startsWith("url(")) {
|
|
7466
7765
|
tweens.push({ kind: "fill", el: p, to, interp: interpolateRgb2(from, to) });
|
|
7467
7766
|
p.setAttribute("fill", from);
|
|
7468
7767
|
}
|
|
@@ -7524,6 +7823,452 @@ function runMapFillTransition(svg, prev, opts) {
|
|
|
7524
7823
|
};
|
|
7525
7824
|
}
|
|
7526
7825
|
|
|
7826
|
+
// src/map-zoom.ts
|
|
7827
|
+
var ZOOM_STEP = 2;
|
|
7828
|
+
var KEY_PAN_FRACTION = 0.1;
|
|
7829
|
+
var DRAG_THRESHOLD = 3;
|
|
7830
|
+
var HINT_MS = 1500;
|
|
7831
|
+
var EPS = 1e-3;
|
|
7832
|
+
var clipIdCounter = 0;
|
|
7833
|
+
var ICONS = {
|
|
7834
|
+
in: '<path d="M8 3v10M3 8h10"/>',
|
|
7835
|
+
out: '<path d="M3 8h10"/>',
|
|
7836
|
+
reset: '<path d="M3 8a5 5 0 1 0 1.6-3.7"/><path d="M3 2.5v2.8h2.8"/>'
|
|
7837
|
+
};
|
|
7838
|
+
function isMacLike() {
|
|
7839
|
+
if (typeof navigator === "undefined") return false;
|
|
7840
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
|
|
7841
|
+
}
|
|
7842
|
+
function createMapZoomController(host) {
|
|
7843
|
+
const { container } = host;
|
|
7844
|
+
const clipId = `oc-map-zoom-clip-${++clipIdCounter}`;
|
|
7845
|
+
let overlay = null;
|
|
7846
|
+
let buttons = null;
|
|
7847
|
+
let hint = null;
|
|
7848
|
+
let hintTimer = null;
|
|
7849
|
+
let drag = null;
|
|
7850
|
+
let suppressClick = false;
|
|
7851
|
+
let pinch = null;
|
|
7852
|
+
let touchPan = null;
|
|
7853
|
+
let gestureStartK = null;
|
|
7854
|
+
function vb() {
|
|
7855
|
+
return host.getLayout().mapSize;
|
|
7856
|
+
}
|
|
7857
|
+
function clamp(camera) {
|
|
7858
|
+
const cfg = host.getConfig();
|
|
7859
|
+
return clampMapCamera(camera, vb(), cfg?.maxZoom ?? 1);
|
|
7860
|
+
}
|
|
7861
|
+
function clampK(k) {
|
|
7862
|
+
const cfg = host.getConfig();
|
|
7863
|
+
return Math.min(Math.max(k, 1), cfg?.maxZoom ?? 1);
|
|
7864
|
+
}
|
|
7865
|
+
function svgRect() {
|
|
7866
|
+
const svg = host.getSvg();
|
|
7867
|
+
let rect = svg?.getBoundingClientRect();
|
|
7868
|
+
if (!rect || !rect.width || !rect.height) rect = container.getBoundingClientRect();
|
|
7869
|
+
if (!rect.width || !rect.height) return null;
|
|
7870
|
+
return rect;
|
|
7871
|
+
}
|
|
7872
|
+
function toMapFrame(clientX, clientY) {
|
|
7873
|
+
const rect = svgRect();
|
|
7874
|
+
if (!rect) return null;
|
|
7875
|
+
const layout = host.getLayout();
|
|
7876
|
+
const x = (clientX - rect.left) * (layout.width / rect.width) - layout.area.x;
|
|
7877
|
+
const y = (clientY - rect.top) * (layout.height / rect.height) - layout.area.y;
|
|
7878
|
+
const inside = x >= 0 && y >= 0 && x <= layout.area.width && y <= layout.area.height;
|
|
7879
|
+
return { x, y, inside };
|
|
7880
|
+
}
|
|
7881
|
+
function unitsPerPx() {
|
|
7882
|
+
const rect = svgRect();
|
|
7883
|
+
return rect ? host.getLayout().width / rect.width : 1;
|
|
7884
|
+
}
|
|
7885
|
+
function isZoomed() {
|
|
7886
|
+
return host.getCamera().k > 1 + EPS;
|
|
7887
|
+
}
|
|
7888
|
+
function fromControls(e) {
|
|
7889
|
+
const t = e.target;
|
|
7890
|
+
return !!t?.closest?.(".oc-map-zoom");
|
|
7891
|
+
}
|
|
7892
|
+
function zoomAround(factor, x, y, animate) {
|
|
7893
|
+
const cam = host.getCamera();
|
|
7894
|
+
const next = clamp(zoomCameraAt(cam, vb(), clampK(cam.k * factor), x, y));
|
|
7895
|
+
if (animate) host.animateCamera(next);
|
|
7896
|
+
else host.setCamera(next);
|
|
7897
|
+
}
|
|
7898
|
+
function zoomAtCenter(factor) {
|
|
7899
|
+
const { width, height } = vb();
|
|
7900
|
+
zoomAround(factor, width / 2, height / 2, true);
|
|
7901
|
+
}
|
|
7902
|
+
function showHint() {
|
|
7903
|
+
if (!hint) return;
|
|
7904
|
+
hint.setAttribute("data-visible", "");
|
|
7905
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
7906
|
+
hintTimer = setTimeout(() => {
|
|
7907
|
+
hint?.removeAttribute("data-visible");
|
|
7908
|
+
hintTimer = null;
|
|
7909
|
+
}, HINT_MS);
|
|
7910
|
+
}
|
|
7911
|
+
function onWheel(e) {
|
|
7912
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
7913
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7914
|
+
if (!p?.inside) return;
|
|
7915
|
+
if (!e.ctrlKey && !e.metaKey) {
|
|
7916
|
+
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) showHint();
|
|
7917
|
+
return;
|
|
7918
|
+
}
|
|
7919
|
+
e.preventDefault();
|
|
7920
|
+
hint?.removeAttribute("data-visible");
|
|
7921
|
+
zoomAround(wheelZoomFactor(e), p.x, p.y, false);
|
|
7922
|
+
}
|
|
7923
|
+
function onGestureStart(e) {
|
|
7924
|
+
if (!host.getConfig()) return;
|
|
7925
|
+
e.preventDefault();
|
|
7926
|
+
gestureStartK = host.getCamera().k;
|
|
7927
|
+
}
|
|
7928
|
+
function onGestureChange(e) {
|
|
7929
|
+
if (!host.getConfig() || gestureStartK === null) return;
|
|
7930
|
+
e.preventDefault();
|
|
7931
|
+
if (pinch) return;
|
|
7932
|
+
const g = e;
|
|
7933
|
+
const p = toMapFrame(g.clientX, g.clientY);
|
|
7934
|
+
if (!p) return;
|
|
7935
|
+
const cam = host.getCamera();
|
|
7936
|
+
host.setCamera(clamp(zoomCameraAt(cam, vb(), clampK(gestureStartK * g.scale), p.x, p.y)));
|
|
7937
|
+
}
|
|
7938
|
+
function onGestureEnd(e) {
|
|
7939
|
+
if (gestureStartK === null) return;
|
|
7940
|
+
e.preventDefault();
|
|
7941
|
+
gestureStartK = null;
|
|
7942
|
+
}
|
|
7943
|
+
function onPointerDown(e) {
|
|
7944
|
+
if (e.pointerType === "touch" || e.button !== 0) return;
|
|
7945
|
+
if (!host.getConfig() || fromControls(e) || !isZoomed()) return;
|
|
7946
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7947
|
+
if (!p?.inside) return;
|
|
7948
|
+
drag = {
|
|
7949
|
+
id: e.pointerId,
|
|
7950
|
+
startX: e.clientX,
|
|
7951
|
+
startY: e.clientY,
|
|
7952
|
+
lastX: e.clientX,
|
|
7953
|
+
lastY: e.clientY,
|
|
7954
|
+
moved: false
|
|
7955
|
+
};
|
|
7956
|
+
}
|
|
7957
|
+
function onMouseDown(e) {
|
|
7958
|
+
if (e.button !== 0 || !host.getConfig() || fromControls(e)) return;
|
|
7959
|
+
if (!isZoomed() && e.detail < 2) return;
|
|
7960
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7961
|
+
if (!p?.inside) return;
|
|
7962
|
+
e.preventDefault();
|
|
7963
|
+
host.getSvg()?.focus({ preventScroll: true });
|
|
7964
|
+
}
|
|
7965
|
+
function onPointerMove(e) {
|
|
7966
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
7967
|
+
if (!drag.moved) {
|
|
7968
|
+
if (Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < DRAG_THRESHOLD) return;
|
|
7969
|
+
drag.moved = true;
|
|
7970
|
+
const svg = host.getSvg();
|
|
7971
|
+
try {
|
|
7972
|
+
svg?.setPointerCapture(e.pointerId);
|
|
7973
|
+
} catch {
|
|
7974
|
+
}
|
|
7975
|
+
svg?.classList.add("oc-map--panning");
|
|
7976
|
+
host.hideTooltip();
|
|
7977
|
+
}
|
|
7978
|
+
e.preventDefault();
|
|
7979
|
+
const s = unitsPerPx();
|
|
7980
|
+
const dx = (e.clientX - drag.lastX) * s;
|
|
7981
|
+
const dy = (e.clientY - drag.lastY) * s;
|
|
7982
|
+
drag.lastX = e.clientX;
|
|
7983
|
+
drag.lastY = e.clientY;
|
|
7984
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
7985
|
+
}
|
|
7986
|
+
function endDrag(e) {
|
|
7987
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
7988
|
+
const svg = host.getSvg();
|
|
7989
|
+
if (drag.moved) {
|
|
7990
|
+
suppressClick = true;
|
|
7991
|
+
setTimeout(() => {
|
|
7992
|
+
suppressClick = false;
|
|
7993
|
+
}, 0);
|
|
7994
|
+
}
|
|
7995
|
+
try {
|
|
7996
|
+
if (svg?.hasPointerCapture?.(e.pointerId)) svg.releasePointerCapture(e.pointerId);
|
|
7997
|
+
} catch {
|
|
7998
|
+
}
|
|
7999
|
+
svg?.classList.remove("oc-map--panning");
|
|
8000
|
+
drag = null;
|
|
8001
|
+
}
|
|
8002
|
+
function onClickCapture(e) {
|
|
8003
|
+
if (!suppressClick) return;
|
|
8004
|
+
suppressClick = false;
|
|
8005
|
+
e.stopPropagation();
|
|
8006
|
+
e.preventDefault();
|
|
8007
|
+
}
|
|
8008
|
+
function onDblClick(e) {
|
|
8009
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
8010
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
8011
|
+
if (!p?.inside) return;
|
|
8012
|
+
e.preventDefault();
|
|
8013
|
+
zoomAround(e.shiftKey ? 1 / ZOOM_STEP : ZOOM_STEP, p.x, p.y, true);
|
|
8014
|
+
}
|
|
8015
|
+
function touchPoint(t) {
|
|
8016
|
+
return toMapFrame(t.clientX, t.clientY);
|
|
8017
|
+
}
|
|
8018
|
+
function startPinch(e) {
|
|
8019
|
+
const a = touchPoint(e.touches[0]);
|
|
8020
|
+
const b = touchPoint(e.touches[1]);
|
|
8021
|
+
if (!a || !b) return;
|
|
8022
|
+
pinch = {
|
|
8023
|
+
dist: Math.hypot(b.x - a.x, b.y - a.y) || 1,
|
|
8024
|
+
x: (a.x + b.x) / 2,
|
|
8025
|
+
y: (a.y + b.y) / 2
|
|
8026
|
+
};
|
|
8027
|
+
touchPan = null;
|
|
8028
|
+
}
|
|
8029
|
+
function startTouchPan(t) {
|
|
8030
|
+
touchPan = isZoomed() ? { x: t.clientX, y: t.clientY, moved: false } : null;
|
|
8031
|
+
}
|
|
8032
|
+
function onTouchStart(e) {
|
|
8033
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
8034
|
+
if (e.touches.length >= 2) {
|
|
8035
|
+
const a = touchPoint(e.touches[0]);
|
|
8036
|
+
if (!a?.inside) return;
|
|
8037
|
+
e.preventDefault();
|
|
8038
|
+
host.hideTooltip();
|
|
8039
|
+
startPinch(e);
|
|
8040
|
+
} else if (e.touches.length === 1) {
|
|
8041
|
+
const p = touchPoint(e.touches[0]);
|
|
8042
|
+
if (!p?.inside) return;
|
|
8043
|
+
startTouchPan(e.touches[0]);
|
|
8044
|
+
}
|
|
8045
|
+
}
|
|
8046
|
+
function onTouchMove(e) {
|
|
8047
|
+
if (!host.getConfig()) return;
|
|
8048
|
+
if (pinch && e.touches.length >= 2) {
|
|
8049
|
+
e.preventDefault();
|
|
8050
|
+
const a = touchPoint(e.touches[0]);
|
|
8051
|
+
const b = touchPoint(e.touches[1]);
|
|
8052
|
+
if (!a || !b) return;
|
|
8053
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y) || 1;
|
|
8054
|
+
const x = (a.x + b.x) / 2;
|
|
8055
|
+
const y = (a.y + b.y) / 2;
|
|
8056
|
+
let cam = host.getCamera();
|
|
8057
|
+
cam = zoomCameraAt(cam, vb(), clampK(cam.k * (dist / pinch.dist)), x, y);
|
|
8058
|
+
cam = panCamera(cam, x - pinch.x, y - pinch.y);
|
|
8059
|
+
host.setCamera(clamp(cam));
|
|
8060
|
+
pinch = { dist, x, y };
|
|
8061
|
+
return;
|
|
8062
|
+
}
|
|
8063
|
+
if (touchPan && e.touches.length === 1) {
|
|
8064
|
+
const t = e.touches[0];
|
|
8065
|
+
if (!touchPan.moved) {
|
|
8066
|
+
if (Math.hypot(t.clientX - touchPan.x, t.clientY - touchPan.y) < DRAG_THRESHOLD) return;
|
|
8067
|
+
touchPan.moved = true;
|
|
8068
|
+
host.hideTooltip();
|
|
8069
|
+
}
|
|
8070
|
+
if (e.cancelable) e.preventDefault();
|
|
8071
|
+
const s = unitsPerPx();
|
|
8072
|
+
const dx = (t.clientX - touchPan.x) * s;
|
|
8073
|
+
const dy = (t.clientY - touchPan.y) * s;
|
|
8074
|
+
touchPan.x = t.clientX;
|
|
8075
|
+
touchPan.y = t.clientY;
|
|
8076
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
function onTouchEnd(e) {
|
|
8080
|
+
if (e.touches.length >= 2) {
|
|
8081
|
+
startPinch(e);
|
|
8082
|
+
return;
|
|
8083
|
+
}
|
|
8084
|
+
pinch = null;
|
|
8085
|
+
if (e.touches.length === 1) {
|
|
8086
|
+
startTouchPan(e.touches[0]);
|
|
8087
|
+
if (touchPan) touchPan.moved = true;
|
|
8088
|
+
} else {
|
|
8089
|
+
touchPan = null;
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
function onKeyDown(e) {
|
|
8093
|
+
if (!host.getConfig() || e.target !== host.getSvg()) return;
|
|
8094
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
8095
|
+
const { width, height } = vb();
|
|
8096
|
+
switch (e.key) {
|
|
8097
|
+
case "+":
|
|
8098
|
+
case "=":
|
|
8099
|
+
zoomAtCenter(ZOOM_STEP);
|
|
8100
|
+
break;
|
|
8101
|
+
case "-":
|
|
8102
|
+
case "_":
|
|
8103
|
+
zoomAtCenter(1 / ZOOM_STEP);
|
|
8104
|
+
break;
|
|
8105
|
+
case "0":
|
|
8106
|
+
host.reset();
|
|
8107
|
+
break;
|
|
8108
|
+
case "ArrowLeft":
|
|
8109
|
+
case "ArrowRight":
|
|
8110
|
+
case "ArrowUp":
|
|
8111
|
+
case "ArrowDown": {
|
|
8112
|
+
if (!isZoomed()) return;
|
|
8113
|
+
const dx = e.key === "ArrowLeft" ? width * KEY_PAN_FRACTION : e.key === "ArrowRight" ? -width * KEY_PAN_FRACTION : 0;
|
|
8114
|
+
const dy = e.key === "ArrowUp" ? height * KEY_PAN_FRACTION : e.key === "ArrowDown" ? -height * KEY_PAN_FRACTION : 0;
|
|
8115
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
8116
|
+
break;
|
|
8117
|
+
}
|
|
8118
|
+
default:
|
|
8119
|
+
return;
|
|
8120
|
+
}
|
|
8121
|
+
e.preventDefault();
|
|
8122
|
+
}
|
|
8123
|
+
function makeButton(kind, label) {
|
|
8124
|
+
const btn = document.createElement("button");
|
|
8125
|
+
btn.type = "button";
|
|
8126
|
+
btn.className = "oc-map-zoom-btn";
|
|
8127
|
+
btn.setAttribute("data-zoom", kind);
|
|
8128
|
+
btn.setAttribute("aria-label", label);
|
|
8129
|
+
btn.title = label;
|
|
8130
|
+
btn.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${ICONS[kind]}</svg>`;
|
|
8131
|
+
btn.addEventListener("click", (e) => {
|
|
8132
|
+
e.stopPropagation();
|
|
8133
|
+
if (kind === "in") zoomAtCenter(ZOOM_STEP);
|
|
8134
|
+
else if (kind === "out") zoomAtCenter(1 / ZOOM_STEP);
|
|
8135
|
+
else host.reset();
|
|
8136
|
+
});
|
|
8137
|
+
return btn;
|
|
8138
|
+
}
|
|
8139
|
+
function ensureOverlay(showControls) {
|
|
8140
|
+
if (!overlay) {
|
|
8141
|
+
overlay = document.createElement("div");
|
|
8142
|
+
overlay.className = "oc-map-zoom";
|
|
8143
|
+
hint = document.createElement("div");
|
|
8144
|
+
hint.className = "oc-map-zoom-hint";
|
|
8145
|
+
hint.setAttribute("aria-hidden", "true");
|
|
8146
|
+
hint.textContent = isMacLike() ? "Use \u2318 + scroll to zoom the map" : "Use Ctrl + scroll to zoom the map";
|
|
8147
|
+
overlay.appendChild(hint);
|
|
8148
|
+
if (getComputedStyle(container).position === "static") {
|
|
8149
|
+
container.style.position = "relative";
|
|
8150
|
+
}
|
|
8151
|
+
container.appendChild(overlay);
|
|
8152
|
+
}
|
|
8153
|
+
if (showControls && !buttons) {
|
|
8154
|
+
const group = document.createElement("div");
|
|
8155
|
+
group.className = "oc-map-zoom-controls";
|
|
8156
|
+
group.setAttribute("role", "group");
|
|
8157
|
+
group.setAttribute("aria-label", "Map zoom");
|
|
8158
|
+
buttons = {
|
|
8159
|
+
in: makeButton("in", "Zoom in"),
|
|
8160
|
+
out: makeButton("out", "Zoom out"),
|
|
8161
|
+
reset: makeButton("reset", "Reset zoom")
|
|
8162
|
+
};
|
|
8163
|
+
group.append(buttons.in, buttons.out, buttons.reset);
|
|
8164
|
+
overlay.appendChild(group);
|
|
8165
|
+
} else if (!showControls && buttons) {
|
|
8166
|
+
buttons.in.parentElement?.remove();
|
|
8167
|
+
buttons = null;
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
function removeOverlay() {
|
|
8171
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
8172
|
+
hintTimer = null;
|
|
8173
|
+
overlay?.remove();
|
|
8174
|
+
overlay = null;
|
|
8175
|
+
buttons = null;
|
|
8176
|
+
hint = null;
|
|
8177
|
+
}
|
|
8178
|
+
function placeOverlay(layout) {
|
|
8179
|
+
if (!overlay || !layout.width || !layout.height) return;
|
|
8180
|
+
const { area, width, height } = layout;
|
|
8181
|
+
overlay.style.left = `${area.x / width * 100}%`;
|
|
8182
|
+
overlay.style.top = `${area.y / height * 100}%`;
|
|
8183
|
+
overlay.style.width = `${area.width / width * 100}%`;
|
|
8184
|
+
overlay.style.height = `${area.height / height * 100}%`;
|
|
8185
|
+
}
|
|
8186
|
+
function onRender(restoreFocus) {
|
|
8187
|
+
const cfg = host.getConfig();
|
|
8188
|
+
const svg = host.getSvg();
|
|
8189
|
+
if (!cfg || !svg) {
|
|
8190
|
+
removeOverlay();
|
|
8191
|
+
return;
|
|
8192
|
+
}
|
|
8193
|
+
const layout = host.getLayout();
|
|
8194
|
+
svg.setAttribute("tabindex", "0");
|
|
8195
|
+
svg.setAttribute("aria-keyshortcuts", "+ - 0 ArrowUp ArrowDown ArrowLeft ArrowRight");
|
|
8196
|
+
ensureMapClip(svg, layout, clipId);
|
|
8197
|
+
ensureOverlay(cfg.controls);
|
|
8198
|
+
placeOverlay(layout);
|
|
8199
|
+
if (restoreFocus) svg.focus({ preventScroll: true });
|
|
8200
|
+
onCamera();
|
|
8201
|
+
}
|
|
8202
|
+
function onCamera() {
|
|
8203
|
+
const cfg = host.getConfig();
|
|
8204
|
+
const svg = host.getSvg();
|
|
8205
|
+
if (!cfg || !svg) return;
|
|
8206
|
+
const cam = host.getCamera();
|
|
8207
|
+
const zoomed = cam.k > 1 + EPS;
|
|
8208
|
+
const { width, height } = vb();
|
|
8209
|
+
const atFullView = !zoomed && Math.abs(cam.cx - width / 2) < EPS && Math.abs(cam.cy - height / 2) < EPS;
|
|
8210
|
+
const group = svg.querySelector(".oc-map-group");
|
|
8211
|
+
if (zoomed) group?.setAttribute("clip-path", `url(#${clipId})`);
|
|
8212
|
+
else group?.removeAttribute("clip-path");
|
|
8213
|
+
svg.style.touchAction = zoomed ? "none" : "pan-x pan-y";
|
|
8214
|
+
if (zoomed) svg.setAttribute("data-oc-zoomed", "");
|
|
8215
|
+
else svg.removeAttribute("data-oc-zoomed");
|
|
8216
|
+
if (buttons) {
|
|
8217
|
+
setDisabled(buttons.in, cam.k >= cfg.maxZoom - EPS, svg);
|
|
8218
|
+
setDisabled(buttons.out, !zoomed, svg);
|
|
8219
|
+
setDisabled(buttons.reset, atFullView, svg);
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
function setDisabled(btn, disabled, svg) {
|
|
8223
|
+
if (btn.disabled === disabled) return;
|
|
8224
|
+
const hadFocus = disabled && document.activeElement === btn;
|
|
8225
|
+
btn.disabled = disabled;
|
|
8226
|
+
if (hadFocus) svg.focus({ preventScroll: true });
|
|
8227
|
+
}
|
|
8228
|
+
const touchOpts = { passive: false };
|
|
8229
|
+
container.addEventListener("wheel", onWheel, { passive: false });
|
|
8230
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
8231
|
+
container.addEventListener("mousedown", onMouseDown);
|
|
8232
|
+
container.addEventListener("pointermove", onPointerMove);
|
|
8233
|
+
container.addEventListener("pointerup", endDrag);
|
|
8234
|
+
container.addEventListener("pointercancel", endDrag);
|
|
8235
|
+
container.addEventListener("click", onClickCapture, true);
|
|
8236
|
+
container.addEventListener("dblclick", onDblClick);
|
|
8237
|
+
container.addEventListener("touchstart", onTouchStart, touchOpts);
|
|
8238
|
+
container.addEventListener("touchmove", onTouchMove, touchOpts);
|
|
8239
|
+
container.addEventListener("touchend", onTouchEnd);
|
|
8240
|
+
container.addEventListener("touchcancel", onTouchEnd);
|
|
8241
|
+
container.addEventListener("gesturestart", onGestureStart);
|
|
8242
|
+
container.addEventListener("gesturechange", onGestureChange);
|
|
8243
|
+
container.addEventListener("gestureend", onGestureEnd);
|
|
8244
|
+
container.addEventListener("keydown", onKeyDown);
|
|
8245
|
+
return {
|
|
8246
|
+
onRender,
|
|
8247
|
+
onCamera,
|
|
8248
|
+
destroy() {
|
|
8249
|
+
container.removeEventListener("wheel", onWheel);
|
|
8250
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
8251
|
+
container.removeEventListener("mousedown", onMouseDown);
|
|
8252
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
8253
|
+
container.removeEventListener("pointerup", endDrag);
|
|
8254
|
+
container.removeEventListener("pointercancel", endDrag);
|
|
8255
|
+
container.removeEventListener("click", onClickCapture, true);
|
|
8256
|
+
container.removeEventListener("dblclick", onDblClick);
|
|
8257
|
+
container.removeEventListener("touchstart", onTouchStart);
|
|
8258
|
+
container.removeEventListener("touchmove", onTouchMove);
|
|
8259
|
+
container.removeEventListener("touchend", onTouchEnd);
|
|
8260
|
+
container.removeEventListener("touchcancel", onTouchEnd);
|
|
8261
|
+
container.removeEventListener("gesturestart", onGestureStart);
|
|
8262
|
+
container.removeEventListener("gesturechange", onGestureChange);
|
|
8263
|
+
container.removeEventListener("gestureend", onGestureEnd);
|
|
8264
|
+
container.removeEventListener("keydown", onKeyDown);
|
|
8265
|
+
const svg = host.getSvg();
|
|
8266
|
+
svg?.removeAttribute("tabindex");
|
|
8267
|
+
removeOverlay();
|
|
8268
|
+
}
|
|
8269
|
+
};
|
|
8270
|
+
}
|
|
8271
|
+
|
|
7527
8272
|
// src/map-mount.ts
|
|
7528
8273
|
function focusSignature(focus) {
|
|
7529
8274
|
if (!focus) return null;
|
|
@@ -7552,6 +8297,7 @@ function restoreFeatureOrder(el) {
|
|
|
7552
8297
|
parent.appendChild(el);
|
|
7553
8298
|
}
|
|
7554
8299
|
}
|
|
8300
|
+
var USER_ZOOM_MS = 250;
|
|
7555
8301
|
function prefersReducedMotion2() {
|
|
7556
8302
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
7557
8303
|
try {
|
|
@@ -7574,11 +8320,12 @@ function createGeoMap(container, spec, options) {
|
|
|
7574
8320
|
let currentCamera = { cx: 0, cy: 0, k: 1 };
|
|
7575
8321
|
let currentFocusIds = null;
|
|
7576
8322
|
let cameraTween = null;
|
|
8323
|
+
let zoomController = null;
|
|
7577
8324
|
let isFirstRender = true;
|
|
7578
8325
|
let fontsReloadPending = false;
|
|
7579
8326
|
container.classList.add("oc-map-root");
|
|
7580
|
-
function resolveEffectiveFont() {
|
|
7581
|
-
return options?.theme?.fonts?.family ??
|
|
8327
|
+
function resolveEffectiveFont(candidate = currentSpec) {
|
|
8328
|
+
return options?.theme?.fonts?.family ?? candidate.theme?.fonts?.family ?? resolveFontFamily(container);
|
|
7582
8329
|
}
|
|
7583
8330
|
let fontFamily = resolveEffectiveFont();
|
|
7584
8331
|
let measureText = createMeasureText(fontFamily);
|
|
@@ -7595,7 +8342,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7595
8342
|
}
|
|
7596
8343
|
return { width, height: Math.round(width * 0.625) };
|
|
7597
8344
|
}
|
|
7598
|
-
function compile() {
|
|
8345
|
+
function compile(candidate = currentSpec, measure = measureText) {
|
|
7599
8346
|
const { width, height } = getContainerDimensions();
|
|
7600
8347
|
const darkMode = resolveDarkMode(options?.darkMode);
|
|
7601
8348
|
const compileOpts = {
|
|
@@ -7604,12 +8351,14 @@ function createGeoMap(container, spec, options) {
|
|
|
7604
8351
|
theme: options?.theme,
|
|
7605
8352
|
darkMode,
|
|
7606
8353
|
watermark: options?.watermark,
|
|
7607
|
-
measureText
|
|
8354
|
+
measureText: measure
|
|
7608
8355
|
};
|
|
7609
|
-
return compileGeoMap(
|
|
8356
|
+
return compileGeoMap(candidate, compileOpts);
|
|
7610
8357
|
}
|
|
7611
8358
|
function wireTooltipAndInteraction(svg, layout) {
|
|
7612
8359
|
const cleanups = [];
|
|
8360
|
+
const navigation = [];
|
|
8361
|
+
let activeLeave = null;
|
|
7613
8362
|
const featuresById = /* @__PURE__ */ new Map();
|
|
7614
8363
|
for (const f of layout.features) {
|
|
7615
8364
|
const key = String(f.id);
|
|
@@ -7622,9 +8371,11 @@ function createGeoMap(container, spec, options) {
|
|
|
7622
8371
|
const content = layout.tooltipDescriptors.get(featureId);
|
|
7623
8372
|
const feature = featuresById.get(featureId);
|
|
7624
8373
|
const handleMouseEnter = (e) => {
|
|
8374
|
+
activeLeave?.();
|
|
8375
|
+
activeLeave = handleMouseLeave;
|
|
7625
8376
|
const mouseEvent = e;
|
|
7626
8377
|
el.style.setProperty("cursor", "pointer");
|
|
7627
|
-
raiseFeature(el, svg);
|
|
8378
|
+
if (document.activeElement !== el) raiseFeature(el, svg);
|
|
7628
8379
|
el.classList.add("oc-map-feature--hover");
|
|
7629
8380
|
if (content && tooltipManager && options?.tooltip !== false) {
|
|
7630
8381
|
const svgRect = svg.getBoundingClientRect();
|
|
@@ -7651,6 +8402,8 @@ function createGeoMap(container, spec, options) {
|
|
|
7651
8402
|
}
|
|
7652
8403
|
};
|
|
7653
8404
|
const handleMouseLeave = () => {
|
|
8405
|
+
if (activeLeave !== handleMouseLeave) return;
|
|
8406
|
+
activeLeave = null;
|
|
7654
8407
|
el.style.removeProperty("cursor");
|
|
7655
8408
|
el.classList.remove("oc-map-feature--hover");
|
|
7656
8409
|
restoreFeatureOrder(el);
|
|
@@ -7667,6 +8420,13 @@ function createGeoMap(container, spec, options) {
|
|
|
7667
8420
|
});
|
|
7668
8421
|
}
|
|
7669
8422
|
};
|
|
8423
|
+
navigation.push({
|
|
8424
|
+
element: el,
|
|
8425
|
+
key: `feature:${featureId}`,
|
|
8426
|
+
enter: handleMouseEnter,
|
|
8427
|
+
leave: handleMouseLeave,
|
|
8428
|
+
activate: handleClick
|
|
8429
|
+
});
|
|
7670
8430
|
el.addEventListener("mouseenter", handleMouseEnter);
|
|
7671
8431
|
el.addEventListener("mousemove", handleMouseMove);
|
|
7672
8432
|
el.addEventListener("mouseleave", handleMouseLeave);
|
|
@@ -7702,6 +8462,8 @@ function createGeoMap(container, spec, options) {
|
|
|
7702
8462
|
const content = layout.tooltipDescriptors.get(`point:${pointKey}`);
|
|
7703
8463
|
const pointMark = pointMarksByKey.get(pointKey);
|
|
7704
8464
|
const handlePointMouseEnter = (e) => {
|
|
8465
|
+
activeLeave?.();
|
|
8466
|
+
activeLeave = handlePointMouseLeave;
|
|
7705
8467
|
const mouseEvent = e;
|
|
7706
8468
|
el.style.setProperty("cursor", "pointer");
|
|
7707
8469
|
el.style.setProperty("filter", "brightness(1.15)");
|
|
@@ -7729,6 +8491,8 @@ function createGeoMap(container, spec, options) {
|
|
|
7729
8491
|
}
|
|
7730
8492
|
};
|
|
7731
8493
|
const handlePointMouseLeave = () => {
|
|
8494
|
+
if (activeLeave !== handlePointMouseLeave) return;
|
|
8495
|
+
activeLeave = null;
|
|
7732
8496
|
el.style.removeProperty("cursor");
|
|
7733
8497
|
el.style.removeProperty("filter");
|
|
7734
8498
|
tooltipManager?.hide();
|
|
@@ -7743,6 +8507,13 @@ function createGeoMap(container, spec, options) {
|
|
|
7743
8507
|
});
|
|
7744
8508
|
}
|
|
7745
8509
|
};
|
|
8510
|
+
navigation.push({
|
|
8511
|
+
element: el,
|
|
8512
|
+
key: `point:${pointKey}`,
|
|
8513
|
+
enter: handlePointMouseEnter,
|
|
8514
|
+
leave: handlePointMouseLeave,
|
|
8515
|
+
activate: handlePointClick
|
|
8516
|
+
});
|
|
7746
8517
|
el.addEventListener("mouseenter", handlePointMouseEnter);
|
|
7747
8518
|
el.addEventListener("mousemove", handlePointMouseMove);
|
|
7748
8519
|
el.addEventListener("mouseleave", handlePointMouseLeave);
|
|
@@ -7754,7 +8525,9 @@ function createGeoMap(container, spec, options) {
|
|
|
7754
8525
|
el.removeEventListener("click", handlePointClick);
|
|
7755
8526
|
});
|
|
7756
8527
|
}
|
|
8528
|
+
cleanups.push(wireMarkNavigation(svg, navigation));
|
|
7757
8529
|
return () => {
|
|
8530
|
+
activeLeave?.();
|
|
7758
8531
|
for (const cleanup of cleanups) {
|
|
7759
8532
|
cleanup();
|
|
7760
8533
|
}
|
|
@@ -7774,6 +8547,18 @@ function createGeoMap(container, spec, options) {
|
|
|
7774
8547
|
}
|
|
7775
8548
|
return cameraForTarget(currentLayout, null);
|
|
7776
8549
|
}
|
|
8550
|
+
function commitCamera(cam) {
|
|
8551
|
+
currentCamera = cam;
|
|
8552
|
+
if (svgElement) applyMapCamera(svgElement, cam, currentLayout);
|
|
8553
|
+
zoomController?.onCamera();
|
|
8554
|
+
options?.onCameraChange?.({ ...cam });
|
|
8555
|
+
}
|
|
8556
|
+
function cancelCameraTween() {
|
|
8557
|
+
if (cameraTween) {
|
|
8558
|
+
cameraTween.cancel();
|
|
8559
|
+
cameraTween = null;
|
|
8560
|
+
}
|
|
8561
|
+
}
|
|
7777
8562
|
function animateCamera(target, duration) {
|
|
7778
8563
|
if (cameraTween) cameraTween.cancel();
|
|
7779
8564
|
cameraTween = createTween2({
|
|
@@ -7782,20 +8567,64 @@ function createGeoMap(container, spec, options) {
|
|
|
7782
8567
|
duration,
|
|
7783
8568
|
ease: easingFns.easeInOutCubic,
|
|
7784
8569
|
onFrame(cam) {
|
|
7785
|
-
|
|
7786
|
-
if (svgElement) applyMapCamera(svgElement, cam, currentLayout);
|
|
8570
|
+
commitCamera(cam);
|
|
7787
8571
|
}
|
|
7788
8572
|
});
|
|
7789
8573
|
cameraTween.to(target);
|
|
7790
8574
|
}
|
|
7791
8575
|
function driveCamera(cam, duration) {
|
|
7792
8576
|
if (duration === 0 || prefersReducedMotion2()) {
|
|
7793
|
-
|
|
7794
|
-
|
|
8577
|
+
cancelCameraTween();
|
|
8578
|
+
commitCamera(cam);
|
|
7795
8579
|
} else {
|
|
7796
8580
|
animateCamera(cam, duration);
|
|
7797
8581
|
}
|
|
7798
8582
|
}
|
|
8583
|
+
function resetCamera(duration) {
|
|
8584
|
+
currentFocusIds = null;
|
|
8585
|
+
const cam = cameraForTarget(currentLayout, null);
|
|
8586
|
+
driveCamera(cam, duration);
|
|
8587
|
+
applyFocusDim(null);
|
|
8588
|
+
updateA11yLive(null);
|
|
8589
|
+
}
|
|
8590
|
+
function rescaleFreeCamera(prev) {
|
|
8591
|
+
const next = currentLayout.mapSize;
|
|
8592
|
+
if (!prev.width || !prev.height || prev.width === next.width && prev.height === next.height) {
|
|
8593
|
+
return;
|
|
8594
|
+
}
|
|
8595
|
+
const scaled = {
|
|
8596
|
+
cx: currentCamera.cx * next.width / prev.width,
|
|
8597
|
+
cy: currentCamera.cy * next.height / prev.height,
|
|
8598
|
+
k: currentCamera.k
|
|
8599
|
+
};
|
|
8600
|
+
const zoom = resolveMapZoom(currentSpec.geo?.zoom);
|
|
8601
|
+
currentCamera = zoom ? clampMapCamera(scaled, next, zoom.maxZoom) : scaled;
|
|
8602
|
+
}
|
|
8603
|
+
function syncZoomController(restoreFocus) {
|
|
8604
|
+
const zoom = resolveMapZoom(currentSpec.geo?.zoom);
|
|
8605
|
+
if (!zoom && !zoomController) return;
|
|
8606
|
+
if (!zoomController) {
|
|
8607
|
+
zoomController = createMapZoomController({
|
|
8608
|
+
container,
|
|
8609
|
+
getSvg: () => svgElement,
|
|
8610
|
+
getLayout: () => currentLayout,
|
|
8611
|
+
getConfig: () => resolveMapZoom(currentSpec.geo?.zoom),
|
|
8612
|
+
getCamera: () => ({ ...currentCamera }),
|
|
8613
|
+
setCamera(cam) {
|
|
8614
|
+
cancelCameraTween();
|
|
8615
|
+
currentFocusIds = null;
|
|
8616
|
+
commitCamera(cam);
|
|
8617
|
+
},
|
|
8618
|
+
animateCamera(cam) {
|
|
8619
|
+
currentFocusIds = null;
|
|
8620
|
+
driveCamera(cam, USER_ZOOM_MS);
|
|
8621
|
+
},
|
|
8622
|
+
reset: () => resetCamera(USER_ZOOM_MS),
|
|
8623
|
+
hideTooltip: () => tooltipManager?.hide()
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
8626
|
+
zoomController.onRender(restoreFocus);
|
|
8627
|
+
}
|
|
7799
8628
|
function updateA11yLive(ids) {
|
|
7800
8629
|
let liveEl = container.querySelector(".oc-map-live");
|
|
7801
8630
|
if (!liveEl) {
|
|
@@ -7814,9 +8643,9 @@ function createGeoMap(container, spec, options) {
|
|
|
7814
8643
|
});
|
|
7815
8644
|
liveEl.textContent = `Zoomed to ${names.join(", ")}`;
|
|
7816
8645
|
}
|
|
7817
|
-
function applyFocusDim(ids) {
|
|
7818
|
-
if (!
|
|
7819
|
-
const features =
|
|
8646
|
+
function applyFocusDim(ids, target = svgElement) {
|
|
8647
|
+
if (!target) return;
|
|
8648
|
+
const features = target.querySelectorAll(".oc-map-feature");
|
|
7820
8649
|
if (!ids || ids.length === 0) {
|
|
7821
8650
|
for (const el of features) {
|
|
7822
8651
|
el.style.removeProperty("opacity");
|
|
@@ -7833,6 +8662,8 @@ function createGeoMap(container, spec, options) {
|
|
|
7833
8662
|
}
|
|
7834
8663
|
}
|
|
7835
8664
|
function render(animate = false) {
|
|
8665
|
+
const focusedMark = captureMarkFocus(svgElement);
|
|
8666
|
+
tooltipManager?.hide();
|
|
7836
8667
|
if (animationCleanup) {
|
|
7837
8668
|
animationCleanup();
|
|
7838
8669
|
animationCleanup = null;
|
|
@@ -7842,6 +8673,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7842
8673
|
fillTransitionHandle = null;
|
|
7843
8674
|
}
|
|
7844
8675
|
const oldSvg = svgElement;
|
|
8676
|
+
const oldSvgHadFocus = !!oldSvg && document.activeElement === oldSvg;
|
|
7845
8677
|
const newSvg = renderMapSVG(currentLayout, { animate });
|
|
7846
8678
|
if (oldSvg) {
|
|
7847
8679
|
if (cleanupTooltipEvents) {
|
|
@@ -7862,6 +8694,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7862
8694
|
const isAnimating = animate && !!currentLayout.animation?.enter;
|
|
7863
8695
|
if (isAnimating) {
|
|
7864
8696
|
animationCleanup = setupAnimationCleanup(newSvg, () => {
|
|
8697
|
+
animationCleanup = null;
|
|
7865
8698
|
if (pendingResize && !destroyed) {
|
|
7866
8699
|
pendingResize = false;
|
|
7867
8700
|
resize();
|
|
@@ -7893,19 +8726,24 @@ function createGeoMap(container, spec, options) {
|
|
|
7893
8726
|
}
|
|
7894
8727
|
}
|
|
7895
8728
|
isFirstRender = false;
|
|
8729
|
+
syncZoomController(oldSvgHadFocus);
|
|
8730
|
+
if (svgElement) restoreMarkFocus(svgElement, focusedMark);
|
|
7896
8731
|
}
|
|
7897
8732
|
function update(newSpec) {
|
|
8733
|
+
if (destroyed) return;
|
|
8734
|
+
const nextFont = resolveEffectiveFont(newSpec);
|
|
8735
|
+
const nextMeasure = nextFont === fontFamily ? measureText : createMeasureText(nextFont);
|
|
8736
|
+
const nextLayout = compile(newSpec, nextMeasure);
|
|
7898
8737
|
currentSpec = newSpec;
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
fontFamily = nextFont;
|
|
7902
|
-
measureText = createMeasureText(fontFamily);
|
|
7903
|
-
}
|
|
8738
|
+
fontFamily = nextFont;
|
|
8739
|
+
measureText = nextMeasure;
|
|
7904
8740
|
const canAnimate = !animationCleanup && !prefersReducedMotion2();
|
|
7905
8741
|
const prevPaint = canAnimate ? captureMapSnapshot(svgElement) : null;
|
|
7906
8742
|
const prevFocusIds = currentFocusIds ? currentFocusIds.map(String).sort().join(",") : null;
|
|
7907
8743
|
const prevFocusSig = focusSignature(currentLayout.focus);
|
|
7908
|
-
|
|
8744
|
+
const prevMapSize = currentLayout.mapSize;
|
|
8745
|
+
currentLayout = nextLayout;
|
|
8746
|
+
if (!currentFocusIds) rescaleFreeCamera(prevMapSize);
|
|
7909
8747
|
render();
|
|
7910
8748
|
if (canAnimate && prevPaint && (prevPaint.fills.size > 0 || prevPaint.points.size > 0) && svgElement && currentLayout.animation?.update) {
|
|
7911
8749
|
fillTransitionHandle = runMapFillTransition(svgElement, prevPaint, {
|
|
@@ -7934,11 +8772,10 @@ function createGeoMap(container, spec, options) {
|
|
|
7934
8772
|
pendingResize = true;
|
|
7935
8773
|
return;
|
|
7936
8774
|
}
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
cameraTween = null;
|
|
7940
|
-
}
|
|
8775
|
+
cancelCameraTween();
|
|
8776
|
+
const prevMapSize = currentLayout.mapSize;
|
|
7941
8777
|
currentLayout = compile();
|
|
8778
|
+
if (!currentFocusIds) rescaleFreeCamera(prevMapSize);
|
|
7942
8779
|
render();
|
|
7943
8780
|
if (currentFocusIds && currentFocusIds.length > 0) {
|
|
7944
8781
|
currentCamera = resolveCamera();
|
|
@@ -7949,18 +8786,29 @@ function createGeoMap(container, spec, options) {
|
|
|
7949
8786
|
if (svgElement) applyMapCamera(svgElement, currentCamera, currentLayout);
|
|
7950
8787
|
}
|
|
7951
8788
|
applyFocusDim(currentFocusIds);
|
|
8789
|
+
zoomController?.onCamera();
|
|
7952
8790
|
}
|
|
7953
8791
|
function exportChart(format, options_) {
|
|
7954
8792
|
if (!svgElement) return "";
|
|
8793
|
+
const terminalSvg = renderMapSVG(currentLayout, { animate: false });
|
|
8794
|
+
applyMapCamera(terminalSvg, currentCamera, currentLayout);
|
|
8795
|
+
const clip = svgElement.querySelector(".oc-map-group")?.getAttribute("clip-path");
|
|
8796
|
+
if (clip) {
|
|
8797
|
+
const clipId = clip.slice(5, -1);
|
|
8798
|
+
ensureMapClip(terminalSvg, currentLayout, clipId);
|
|
8799
|
+
terminalSvg.querySelector(".oc-map-group")?.setAttribute("clip-path", clip);
|
|
8800
|
+
}
|
|
8801
|
+
applyFocusDim(currentFocusIds, terminalSvg);
|
|
8802
|
+
const resolvedOptions = { theme: currentLayout.theme, ...options_ };
|
|
7955
8803
|
switch (format) {
|
|
7956
8804
|
case "svg":
|
|
7957
|
-
return exportSVG(
|
|
8805
|
+
return exportSVG(terminalSvg);
|
|
7958
8806
|
case "svg-with-fonts":
|
|
7959
|
-
return exportSVGWithFonts(
|
|
8807
|
+
return exportSVGWithFonts(terminalSvg, resolvedOptions);
|
|
7960
8808
|
case "png":
|
|
7961
|
-
return exportPNG(
|
|
8809
|
+
return exportPNG(terminalSvg, resolvedOptions);
|
|
7962
8810
|
case "jpg":
|
|
7963
|
-
return exportJPG(
|
|
8811
|
+
return exportJPG(terminalSvg, resolvedOptions);
|
|
7964
8812
|
default:
|
|
7965
8813
|
return "";
|
|
7966
8814
|
}
|
|
@@ -7977,9 +8825,10 @@ function createGeoMap(container, spec, options) {
|
|
|
7977
8825
|
fillTransitionHandle.cancel();
|
|
7978
8826
|
fillTransitionHandle = null;
|
|
7979
8827
|
}
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
8828
|
+
cancelCameraTween();
|
|
8829
|
+
if (zoomController) {
|
|
8830
|
+
zoomController.destroy();
|
|
8831
|
+
zoomController = null;
|
|
7983
8832
|
}
|
|
7984
8833
|
if (cleanupTooltipEvents) {
|
|
7985
8834
|
cleanupTooltipEvents();
|
|
@@ -8062,19 +8911,15 @@ function createGeoMap(container, spec, options) {
|
|
|
8062
8911
|
applyFocusDim(null);
|
|
8063
8912
|
},
|
|
8064
8913
|
resetView(opts) {
|
|
8065
|
-
|
|
8066
|
-
const cam = cameraForTarget(currentLayout, null);
|
|
8067
|
-
driveCamera(cam, opts?.duration ?? storyMotion.camera);
|
|
8068
|
-
applyFocusDim(null);
|
|
8069
|
-
updateA11yLive(null);
|
|
8914
|
+
resetCamera(opts?.duration ?? storyMotion.camera);
|
|
8070
8915
|
},
|
|
8071
8916
|
getCamera() {
|
|
8072
8917
|
return { ...currentCamera };
|
|
8073
8918
|
},
|
|
8074
8919
|
setCamera(camera) {
|
|
8075
|
-
|
|
8920
|
+
cancelCameraTween();
|
|
8076
8921
|
currentFocusIds = null;
|
|
8077
|
-
|
|
8922
|
+
commitCamera(camera);
|
|
8078
8923
|
}
|
|
8079
8924
|
};
|
|
8080
8925
|
}
|
|
@@ -8083,16 +8928,19 @@ export {
|
|
|
8083
8928
|
cancelAnimations,
|
|
8084
8929
|
setupAnimationCleanup,
|
|
8085
8930
|
setupTableAnimationCleanup,
|
|
8931
|
+
captureMarkFocus,
|
|
8932
|
+
restoreMarkFocus,
|
|
8933
|
+
wireMarkNavigation,
|
|
8086
8934
|
createMeasureText,
|
|
8087
8935
|
resolveFontFamily,
|
|
8088
8936
|
scheduleFontReload,
|
|
8089
8937
|
VECTOR_EXPORT_MAX_POINTS,
|
|
8090
8938
|
materializeCanvasModeSVG,
|
|
8091
|
-
applySrOnlyStyles,
|
|
8092
|
-
createTextEditOverlay,
|
|
8093
8939
|
DEFAULT_UPDATE_MAX_MARKS,
|
|
8094
8940
|
CANVAS_DEFAULT_UPDATE_MAX_MARKS,
|
|
8095
8941
|
canTransitionSpecShape,
|
|
8942
|
+
applySrOnlyStyles,
|
|
8943
|
+
createTextEditOverlay,
|
|
8096
8944
|
createYouDrawIt,
|
|
8097
8945
|
createChart,
|
|
8098
8946
|
easingFns,
|
|
@@ -8108,4 +8956,4 @@ export {
|
|
|
8108
8956
|
scrubCamera,
|
|
8109
8957
|
createGeoMap
|
|
8110
8958
|
};
|
|
8111
|
-
//# sourceMappingURL=chunk-
|
|
8959
|
+
//# sourceMappingURL=chunk-6TD7GWR4.js.map
|