@opendata-ai/openchart-vanilla 6.0.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -3
- package/dist/index.js +214 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__test-fixtures__/specs.ts +7 -7
- package/src/__tests__/svg-renderer.test.ts +2 -2
- package/src/export.ts +1 -1
- package/src/mount.ts +169 -14
- package/src/svg-renderer.ts +116 -12
- package/src/table-mount.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ChartLayout, ChartSpec, CompileOptions, TableLayout, TableSpec, VizSpec } from '@opendata-ai/openchart-engine';
|
|
2
|
-
import { GraphSpec, ThemeConfig, DarkMode, ChartSpec, ChartLayout, ChartEventHandlers, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, Mark, TableSpec, SortState, TableLayout, TooltipContent } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { GraphSpec, ThemeConfig, DarkMode, ChartSpec, LayerSpec, ChartLayout, ChartEventHandlers, BarTableCell, CategoryTableCell, TableCell, FlagTableCell, HeatmapTableCell, ImageTableCell, SparklineTableCell, TextTableCell, Mark, TableSpec, SortState, TableLayout, TooltipContent } from '@opendata-ai/openchart-core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Export utilities: serialize charts to SVG, PNG, JPG, or CSV.
|
|
@@ -166,7 +166,7 @@ interface ExportOptions extends JPGExportOptions {
|
|
|
166
166
|
}
|
|
167
167
|
interface ChartInstance {
|
|
168
168
|
/** Re-compile and re-render with a new spec. */
|
|
169
|
-
update(spec: ChartSpec | GraphSpec): void;
|
|
169
|
+
update(spec: ChartSpec | LayerSpec | GraphSpec): void;
|
|
170
170
|
/** Re-compile at current container dimensions. */
|
|
171
171
|
resize(): void;
|
|
172
172
|
/** Export the chart. */
|
|
@@ -189,7 +189,7 @@ interface ChartInstance {
|
|
|
189
189
|
* @param options - Mount options (theme, darkMode, responsive, etc.).
|
|
190
190
|
* @returns A ChartInstance with update/resize/export/destroy methods.
|
|
191
191
|
*/
|
|
192
|
-
declare function createChart(container: HTMLElement, spec: ChartSpec | GraphSpec, options?: MountOptions): ChartInstance;
|
|
192
|
+
declare function createChart(container: HTMLElement, spec: ChartSpec | LayerSpec | GraphSpec, options?: MountOptions): ChartInstance;
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
195
|
* Table cell renderers: produce DOM elements for each cell type.
|
package/dist/index.js
CHANGED
|
@@ -223,7 +223,7 @@ function exportCSV(data) {
|
|
|
223
223
|
return rows.join("\n");
|
|
224
224
|
}
|
|
225
225
|
function csvEscape(value) {
|
|
226
|
-
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
226
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
227
227
|
return `"${value.replace(/"/g, '""')}"`;
|
|
228
228
|
}
|
|
229
229
|
return value;
|
|
@@ -3210,7 +3210,8 @@ function escapeHtml(str) {
|
|
|
3210
3210
|
}
|
|
3211
3211
|
|
|
3212
3212
|
// src/mount.ts
|
|
3213
|
-
import {
|
|
3213
|
+
import { isLayerSpec } from "@opendata-ai/openchart-core";
|
|
3214
|
+
import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
|
|
3214
3215
|
|
|
3215
3216
|
// src/svg-renderer.ts
|
|
3216
3217
|
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
@@ -3251,11 +3252,7 @@ function applyTextStyle(el, style) {
|
|
|
3251
3252
|
el.setAttribute("text-anchor", style.textAnchor);
|
|
3252
3253
|
}
|
|
3253
3254
|
if (style.dominantBaseline) {
|
|
3254
|
-
|
|
3255
|
-
el.setAttribute("dy", `${style.fontSize * 0.8}px`);
|
|
3256
|
-
} else {
|
|
3257
|
-
el.setAttribute("dominant-baseline", style.dominantBaseline);
|
|
3258
|
-
}
|
|
3255
|
+
el.setAttribute("dominant-baseline", style.dominantBaseline);
|
|
3259
3256
|
}
|
|
3260
3257
|
if (style.fontVariant) {
|
|
3261
3258
|
el.setAttribute("font-variant", style.fontVariant);
|
|
@@ -3423,7 +3420,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3423
3420
|
y2: gridline.position,
|
|
3424
3421
|
stroke: layout.theme.colors.gridline,
|
|
3425
3422
|
"stroke-width": 1,
|
|
3426
|
-
"stroke-opacity": 0.
|
|
3423
|
+
"stroke-opacity": 0.6
|
|
3427
3424
|
});
|
|
3428
3425
|
} else {
|
|
3429
3426
|
setAttrs(gl, {
|
|
@@ -3433,7 +3430,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3433
3430
|
y2: area.y + area.height,
|
|
3434
3431
|
stroke: layout.theme.colors.gridline,
|
|
3435
3432
|
"stroke-width": 1,
|
|
3436
|
-
"stroke-opacity": 0.
|
|
3433
|
+
"stroke-opacity": 0.6
|
|
3437
3434
|
});
|
|
3438
3435
|
}
|
|
3439
3436
|
g.appendChild(gl);
|
|
@@ -3637,11 +3634,86 @@ function renderPointMark(mark, index2) {
|
|
|
3637
3634
|
}
|
|
3638
3635
|
return circle;
|
|
3639
3636
|
}
|
|
3637
|
+
function renderTextMark(mark, index2) {
|
|
3638
|
+
const text = createSVGElement("text");
|
|
3639
|
+
text.setAttribute("data-mark-id", `textMark-${index2}`);
|
|
3640
|
+
text.setAttribute("class", "viz-mark viz-mark-text");
|
|
3641
|
+
setAttrs(text, {
|
|
3642
|
+
x: mark.x,
|
|
3643
|
+
y: mark.y,
|
|
3644
|
+
"font-size": mark.fontSize,
|
|
3645
|
+
"text-anchor": mark.textAnchor
|
|
3646
|
+
});
|
|
3647
|
+
text.style.setProperty("fill", mark.fill);
|
|
3648
|
+
if (mark.fontWeight) {
|
|
3649
|
+
text.setAttribute("font-weight", String(mark.fontWeight));
|
|
3650
|
+
}
|
|
3651
|
+
if (mark.fontFamily) {
|
|
3652
|
+
text.setAttribute("font-family", mark.fontFamily);
|
|
3653
|
+
}
|
|
3654
|
+
if (mark.angle) {
|
|
3655
|
+
text.setAttribute("transform", `rotate(${mark.angle}, ${mark.x}, ${mark.y})`);
|
|
3656
|
+
}
|
|
3657
|
+
text.textContent = mark.text;
|
|
3658
|
+
return text;
|
|
3659
|
+
}
|
|
3660
|
+
function renderRuleMark(mark, index2) {
|
|
3661
|
+
const line = createSVGElement("line");
|
|
3662
|
+
line.setAttribute("data-mark-id", `rule-${index2}`);
|
|
3663
|
+
line.setAttribute("class", "viz-mark viz-mark-rule");
|
|
3664
|
+
setAttrs(line, {
|
|
3665
|
+
x1: mark.x1,
|
|
3666
|
+
y1: mark.y1,
|
|
3667
|
+
x2: mark.x2,
|
|
3668
|
+
y2: mark.y2,
|
|
3669
|
+
stroke: mark.stroke,
|
|
3670
|
+
"stroke-width": mark.strokeWidth
|
|
3671
|
+
});
|
|
3672
|
+
if (mark.strokeDasharray) {
|
|
3673
|
+
line.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
3674
|
+
}
|
|
3675
|
+
if (mark.opacity != null) {
|
|
3676
|
+
line.setAttribute("opacity", String(mark.opacity));
|
|
3677
|
+
}
|
|
3678
|
+
return line;
|
|
3679
|
+
}
|
|
3680
|
+
function renderTickMark(mark, index2) {
|
|
3681
|
+
const line = createSVGElement("line");
|
|
3682
|
+
line.setAttribute("data-mark-id", `tick-${index2}`);
|
|
3683
|
+
line.setAttribute("class", "viz-mark viz-mark-tick");
|
|
3684
|
+
const half = mark.length / 2;
|
|
3685
|
+
if (mark.orient === "vertical") {
|
|
3686
|
+
setAttrs(line, {
|
|
3687
|
+
x1: mark.x,
|
|
3688
|
+
y1: mark.y - half,
|
|
3689
|
+
x2: mark.x,
|
|
3690
|
+
y2: mark.y + half,
|
|
3691
|
+
stroke: mark.stroke,
|
|
3692
|
+
"stroke-width": mark.strokeWidth
|
|
3693
|
+
});
|
|
3694
|
+
} else {
|
|
3695
|
+
setAttrs(line, {
|
|
3696
|
+
x1: mark.x - half,
|
|
3697
|
+
y1: mark.y,
|
|
3698
|
+
x2: mark.x + half,
|
|
3699
|
+
y2: mark.y,
|
|
3700
|
+
stroke: mark.stroke,
|
|
3701
|
+
"stroke-width": mark.strokeWidth
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
if (mark.opacity != null) {
|
|
3705
|
+
line.setAttribute("opacity", String(mark.opacity));
|
|
3706
|
+
}
|
|
3707
|
+
return line;
|
|
3708
|
+
}
|
|
3640
3709
|
registerMarkRenderer("line", renderLineMark);
|
|
3641
3710
|
registerMarkRenderer("area", renderAreaMark);
|
|
3642
3711
|
registerMarkRenderer("rect", renderRectMark);
|
|
3643
3712
|
registerMarkRenderer("arc", renderArcMark);
|
|
3644
3713
|
registerMarkRenderer("point", renderPointMark);
|
|
3714
|
+
registerMarkRenderer("textMark", renderTextMark);
|
|
3715
|
+
registerMarkRenderer("rule", renderRuleMark);
|
|
3716
|
+
registerMarkRenderer("tick", renderTickMark);
|
|
3645
3717
|
function getMarkSeries(mark) {
|
|
3646
3718
|
if (mark.type === "line" || mark.type === "area") {
|
|
3647
3719
|
return mark.seriesKey;
|
|
@@ -3978,7 +4050,7 @@ function renderBrand(parent, layout) {
|
|
|
3978
4050
|
setAttrs(text, {
|
|
3979
4051
|
x: rightEdge,
|
|
3980
4052
|
y: chromeY,
|
|
3981
|
-
|
|
4053
|
+
"dominant-baseline": "hanging",
|
|
3982
4054
|
"font-family": layout.theme.fonts.family,
|
|
3983
4055
|
"font-size": BRAND_FONT_SIZE,
|
|
3984
4056
|
"text-anchor": "end",
|
|
@@ -4001,7 +4073,13 @@ function renderChartSVG(layout, container) {
|
|
|
4001
4073
|
const svg = createSVGElement("svg");
|
|
4002
4074
|
setAttrs(svg, {
|
|
4003
4075
|
viewBox: `0 0 ${width} ${height}`,
|
|
4004
|
-
xmlns: SVG_NS
|
|
4076
|
+
xmlns: SVG_NS,
|
|
4077
|
+
// WebKit/iOS Safari getBBox() bug: text with dominant-baseline:hanging
|
|
4078
|
+
// reports bounding boxes extending above y=0. The SVG spec default
|
|
4079
|
+
// overflow is "hidden", which clips this phantom extent. Setting
|
|
4080
|
+
// overflow:visible prevents the clipping. Chart marks are already
|
|
4081
|
+
// constrained by a clipPath, so nothing bleeds out.
|
|
4082
|
+
overflow: "visible"
|
|
4005
4083
|
});
|
|
4006
4084
|
svg.style.height = `${height}px`;
|
|
4007
4085
|
svg.setAttribute("role", layout.a11y.role);
|
|
@@ -4034,6 +4112,23 @@ function renderChartSVG(layout, container) {
|
|
|
4034
4112
|
const clippedGroup = createSVGElement("g");
|
|
4035
4113
|
clippedGroup.setAttribute("clip-path", `url(#${clipId})`);
|
|
4036
4114
|
renderMarks(clippedGroup, layout);
|
|
4115
|
+
const hasLineOrAreaWithDataPoints = layout.marks.some(
|
|
4116
|
+
(m2) => (m2.type === "line" || m2.type === "area") && m2.dataPoints && m2.dataPoints.length > 0
|
|
4117
|
+
);
|
|
4118
|
+
const hasPointMarks = layout.marks.some((m2) => m2.type === "point");
|
|
4119
|
+
if (hasLineOrAreaWithDataPoints && !hasPointMarks) {
|
|
4120
|
+
const overlay = createSVGElement("rect");
|
|
4121
|
+
setAttrs(overlay, {
|
|
4122
|
+
x: layout.area.x,
|
|
4123
|
+
y: layout.area.y,
|
|
4124
|
+
width: layout.area.width,
|
|
4125
|
+
height: layout.area.height,
|
|
4126
|
+
fill: "transparent"
|
|
4127
|
+
});
|
|
4128
|
+
overlay.setAttribute("class", "viz-voronoi-overlay");
|
|
4129
|
+
overlay.setAttribute("data-voronoi-overlay", "true");
|
|
4130
|
+
clippedGroup.appendChild(overlay);
|
|
4131
|
+
}
|
|
4037
4132
|
svg.appendChild(clippedGroup);
|
|
4038
4133
|
renderAnnotations(svg, layout);
|
|
4039
4134
|
renderLegend(svg, layout.legend);
|
|
@@ -4124,6 +4219,89 @@ function wireTooltipEvents(svg, tooltipDescriptors, tooltipManager) {
|
|
|
4124
4219
|
}
|
|
4125
4220
|
};
|
|
4126
4221
|
}
|
|
4222
|
+
function collectVoronoiPoints(layout) {
|
|
4223
|
+
const points = [];
|
|
4224
|
+
for (const mark of layout.marks) {
|
|
4225
|
+
if ((mark.type === "line" || mark.type === "area") && mark.dataPoints) {
|
|
4226
|
+
const color = mark.type === "line" ? mark.stroke : mark.fill;
|
|
4227
|
+
for (const dp of mark.dataPoints) {
|
|
4228
|
+
points.push({ ...dp, color });
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
return points;
|
|
4233
|
+
}
|
|
4234
|
+
function findNearestPoint(points, x3, y3) {
|
|
4235
|
+
if (points.length === 0) return null;
|
|
4236
|
+
let nearest = points[0];
|
|
4237
|
+
let minDist = (points[0].x - x3) ** 2 + (points[0].y - y3) ** 2;
|
|
4238
|
+
for (let i = 1; i < points.length; i++) {
|
|
4239
|
+
const dist = (points[i].x - x3) ** 2 + (points[i].y - y3) ** 2;
|
|
4240
|
+
if (dist < minDist) {
|
|
4241
|
+
minDist = dist;
|
|
4242
|
+
nearest = points[i];
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
return nearest;
|
|
4246
|
+
}
|
|
4247
|
+
function wireVoronoiTooltipEvents(svg, layout, tooltipManager) {
|
|
4248
|
+
const overlay = svg.querySelector("[data-voronoi-overlay]");
|
|
4249
|
+
if (!overlay) return () => {
|
|
4250
|
+
};
|
|
4251
|
+
const voronoiPoints = collectVoronoiPoints(layout);
|
|
4252
|
+
if (voronoiPoints.length === 0) return () => {
|
|
4253
|
+
};
|
|
4254
|
+
const cleanups = [];
|
|
4255
|
+
const handleMouseMove = (e) => {
|
|
4256
|
+
const mouseEvent = e;
|
|
4257
|
+
const svgEl = svg;
|
|
4258
|
+
const svgRect = svgEl.getBoundingClientRect();
|
|
4259
|
+
const viewBox = svgEl.viewBox?.baseVal;
|
|
4260
|
+
const scaleX = viewBox?.width && svgRect.width ? viewBox.width / svgRect.width : 1;
|
|
4261
|
+
const scaleY = viewBox?.height && svgRect.height ? viewBox.height / svgRect.height : 1;
|
|
4262
|
+
const svgX = (mouseEvent.clientX - svgRect.left) * scaleX;
|
|
4263
|
+
const svgY = (mouseEvent.clientY - svgRect.top) * scaleY;
|
|
4264
|
+
const nearest = findNearestPoint(voronoiPoints, svgX, svgY);
|
|
4265
|
+
if (!nearest?.tooltip) return;
|
|
4266
|
+
const containerX = mouseEvent.clientX - svgRect.left;
|
|
4267
|
+
const containerY = mouseEvent.clientY - svgRect.top;
|
|
4268
|
+
tooltipManager.show(nearest.tooltip, containerX, containerY);
|
|
4269
|
+
};
|
|
4270
|
+
const handleMouseLeave = () => {
|
|
4271
|
+
tooltipManager.hide();
|
|
4272
|
+
};
|
|
4273
|
+
const handleTouchStart = (e) => {
|
|
4274
|
+
const touchEvent = e;
|
|
4275
|
+
if (touchEvent.touches.length > 0) {
|
|
4276
|
+
const touch = touchEvent.touches[0];
|
|
4277
|
+
const svgEl = svg;
|
|
4278
|
+
const svgRect = svgEl.getBoundingClientRect();
|
|
4279
|
+
const viewBox = svgEl.viewBox?.baseVal;
|
|
4280
|
+
const scaleX = viewBox?.width && svgRect.width ? viewBox.width / svgRect.width : 1;
|
|
4281
|
+
const scaleY = viewBox?.height && svgRect.height ? viewBox.height / svgRect.height : 1;
|
|
4282
|
+
const svgX = (touch.clientX - svgRect.left) * scaleX;
|
|
4283
|
+
const svgY = (touch.clientY - svgRect.top) * scaleY;
|
|
4284
|
+
const nearest = findNearestPoint(voronoiPoints, svgX, svgY);
|
|
4285
|
+
if (!nearest?.tooltip) return;
|
|
4286
|
+
const containerX = touch.clientX - svgRect.left;
|
|
4287
|
+
const containerY = touch.clientY - svgRect.top;
|
|
4288
|
+
tooltipManager.show(nearest.tooltip, containerX, containerY);
|
|
4289
|
+
}
|
|
4290
|
+
};
|
|
4291
|
+
overlay.addEventListener("mousemove", handleMouseMove);
|
|
4292
|
+
overlay.addEventListener("mouseleave", handleMouseLeave);
|
|
4293
|
+
overlay.addEventListener("touchstart", handleTouchStart);
|
|
4294
|
+
cleanups.push(() => {
|
|
4295
|
+
overlay.removeEventListener("mousemove", handleMouseMove);
|
|
4296
|
+
overlay.removeEventListener("mouseleave", handleMouseLeave);
|
|
4297
|
+
overlay.removeEventListener("touchstart", handleTouchStart);
|
|
4298
|
+
});
|
|
4299
|
+
return () => {
|
|
4300
|
+
for (const cleanup of cleanups) {
|
|
4301
|
+
cleanup();
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4127
4305
|
function buildMarkDataMap(layout) {
|
|
4128
4306
|
const map = /* @__PURE__ */ new Map();
|
|
4129
4307
|
for (let i = 0; i < layout.marks.length; i++) {
|
|
@@ -4452,10 +4630,10 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
4452
4630
|
if (!connectorLine && !curvedPath) continue;
|
|
4453
4631
|
let fromX, fromY, toX, toY;
|
|
4454
4632
|
if (connectorLine) {
|
|
4455
|
-
fromX = Number(connectorLine.getAttribute("x1"));
|
|
4456
|
-
fromY = Number(connectorLine.getAttribute("y1"));
|
|
4457
|
-
toX = Number(connectorLine.getAttribute("x2"));
|
|
4458
|
-
toY = Number(connectorLine.getAttribute("y2"));
|
|
4633
|
+
fromX = Number(connectorLine.getAttribute("x1")) || 0;
|
|
4634
|
+
fromY = Number(connectorLine.getAttribute("y1")) || 0;
|
|
4635
|
+
toX = Number(connectorLine.getAttribute("x2")) || 0;
|
|
4636
|
+
toY = Number(connectorLine.getAttribute("y2")) || 0;
|
|
4459
4637
|
} else {
|
|
4460
4638
|
const pathD = curvedPath.getAttribute("d") ?? "";
|
|
4461
4639
|
const mMatch = pathD.match(/M\s*([\d.e+-]+)\s+([\d.e+-]+)/);
|
|
@@ -4465,8 +4643,8 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
4465
4643
|
const points = arrowhead?.getAttribute("points") ?? "";
|
|
4466
4644
|
const firstPoint = points.split(" ")[0] ?? "0,0";
|
|
4467
4645
|
const [px, py] = firstPoint.split(",");
|
|
4468
|
-
toX = Number(px);
|
|
4469
|
-
toY = Number(py);
|
|
4646
|
+
toX = Number(px) || 0;
|
|
4647
|
+
toY = Number(py) || 0;
|
|
4470
4648
|
}
|
|
4471
4649
|
const endpoints = [
|
|
4472
4650
|
{ name: "from", cx: fromX, cy: fromY },
|
|
@@ -4474,6 +4652,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
4474
4652
|
];
|
|
4475
4653
|
const createdHandles = [];
|
|
4476
4654
|
for (const ep of endpoints) {
|
|
4655
|
+
if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
|
|
4477
4656
|
const handleEl = document.createElementNS(SVG_NS2, "circle");
|
|
4478
4657
|
handleEl.setAttribute("class", "viz-connector-handle");
|
|
4479
4658
|
handleEl.setAttribute("data-endpoint", ep.name);
|
|
@@ -4910,6 +5089,7 @@ function createChart(container, spec, options) {
|
|
|
4910
5089
|
let tooltipManager = null;
|
|
4911
5090
|
let disconnectResize = null;
|
|
4912
5091
|
let cleanupTooltipEvents = null;
|
|
5092
|
+
let cleanupVoronoiEvents = null;
|
|
4913
5093
|
let cleanupKeyboardNav = null;
|
|
4914
5094
|
let cleanupLegend = null;
|
|
4915
5095
|
let cleanupChartEvents = null;
|
|
@@ -4931,6 +5111,9 @@ function createChart(container, spec, options) {
|
|
|
4931
5111
|
darkMode,
|
|
4932
5112
|
measureText
|
|
4933
5113
|
};
|
|
5114
|
+
if (isLayerSpec(currentSpec)) {
|
|
5115
|
+
return compileLayer(currentSpec, compileOpts);
|
|
5116
|
+
}
|
|
4934
5117
|
return compileChart(currentSpec, compileOpts);
|
|
4935
5118
|
}
|
|
4936
5119
|
function getContainerDimensions() {
|
|
@@ -4949,6 +5132,10 @@ function createChart(container, spec, options) {
|
|
|
4949
5132
|
cleanupTooltipEvents();
|
|
4950
5133
|
cleanupTooltipEvents = null;
|
|
4951
5134
|
}
|
|
5135
|
+
if (cleanupVoronoiEvents) {
|
|
5136
|
+
cleanupVoronoiEvents();
|
|
5137
|
+
cleanupVoronoiEvents = null;
|
|
5138
|
+
}
|
|
4952
5139
|
if (cleanupKeyboardNav) {
|
|
4953
5140
|
cleanupKeyboardNav();
|
|
4954
5141
|
cleanupKeyboardNav = null;
|
|
@@ -4987,6 +5174,7 @@ function createChart(container, spec, options) {
|
|
|
4987
5174
|
currentLayout.tooltipDescriptors,
|
|
4988
5175
|
tooltipManager
|
|
4989
5176
|
);
|
|
5177
|
+
cleanupVoronoiEvents = wireVoronoiTooltipEvents(svgElement, currentLayout, tooltipManager);
|
|
4990
5178
|
cleanupKeyboardNav = wireKeyboardNav(
|
|
4991
5179
|
svgElement,
|
|
4992
5180
|
container,
|
|
@@ -5029,9 +5217,10 @@ function createChart(container, spec, options) {
|
|
|
5029
5217
|
editCleanups.push(
|
|
5030
5218
|
wireAnnotationLabelDrag(svgElement, dragAnnotations, options.onEdit, setDragging)
|
|
5031
5219
|
);
|
|
5032
|
-
|
|
5033
|
-
editCleanups.push(
|
|
5034
|
-
editCleanups.push(
|
|
5220
|
+
const editSpec = currentSpec;
|
|
5221
|
+
editCleanups.push(wireChromeDrag(svgElement, editSpec, options.onEdit, setDragging));
|
|
5222
|
+
editCleanups.push(wireLegendDrag(svgElement, editSpec, options.onEdit, setDragging));
|
|
5223
|
+
editCleanups.push(wireSeriesLabelDrag(svgElement, editSpec, options.onEdit, setDragging));
|
|
5035
5224
|
cleanupEditDrags = () => {
|
|
5036
5225
|
for (const cleanup of editCleanups) {
|
|
5037
5226
|
cleanup();
|
|
@@ -5088,6 +5277,10 @@ function createChart(container, spec, options) {
|
|
|
5088
5277
|
cleanupTooltipEvents();
|
|
5089
5278
|
cleanupTooltipEvents = null;
|
|
5090
5279
|
}
|
|
5280
|
+
if (cleanupVoronoiEvents) {
|
|
5281
|
+
cleanupVoronoiEvents();
|
|
5282
|
+
cleanupVoronoiEvents = null;
|
|
5283
|
+
}
|
|
5091
5284
|
if (cleanupKeyboardNav) {
|
|
5092
5285
|
cleanupKeyboardNav();
|
|
5093
5286
|
cleanupKeyboardNav = null;
|
|
@@ -5924,7 +6117,7 @@ function resolveDarkMode3(mode) {
|
|
|
5924
6117
|
return false;
|
|
5925
6118
|
}
|
|
5926
6119
|
function csvEscape2(value) {
|
|
5927
|
-
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
6120
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
5928
6121
|
return `"${value.replace(/"/g, '""')}"`;
|
|
5929
6122
|
}
|
|
5930
6123
|
return value;
|