@pixerate/editor-react 0.35.1 → 0.36.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/chart/index.cjs +260 -30
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.d.cts +87 -6
- package/dist/chart/index.d.ts +87 -6
- package/dist/chart/index.js +305 -62
- package/dist/chart/index.js.map +1 -1
- package/package.json +3 -3
package/dist/chart/index.cjs
CHANGED
|
@@ -25,6 +25,10 @@ __export(chart_exports, {
|
|
|
25
25
|
ChartDataGrid: () => ChartDataGrid,
|
|
26
26
|
ChartEditor: () => ChartEditor,
|
|
27
27
|
ChartInspector: () => ChartInspector,
|
|
28
|
+
ChartPresenceLayer: () => ChartPresenceLayer,
|
|
29
|
+
ChartSceneSvg: () => ChartSceneSvg,
|
|
30
|
+
ChartSparkline: () => ChartSparkline,
|
|
31
|
+
ChartStreamingView: () => ChartStreamingView,
|
|
28
32
|
chartButtonClass: () => chartButtonClass,
|
|
29
33
|
chartCellClass: () => chartCellClass,
|
|
30
34
|
chartIconButtonClass: () => chartIconButtonClass,
|
|
@@ -32,10 +36,13 @@ __export(chart_exports, {
|
|
|
32
36
|
chartLabelClass: () => chartLabelClass,
|
|
33
37
|
chartSectionClass: () => chartSectionClass,
|
|
34
38
|
chartSegmentClass: () => chartSegmentClass,
|
|
39
|
+
renderChartMark: () => renderChartMark,
|
|
35
40
|
useChartCommands: () => useChartCommands,
|
|
36
41
|
useChartController: () => useChartController,
|
|
42
|
+
useChartDirector: () => useChartDirector,
|
|
37
43
|
useChartEditor: () => useChartEditor,
|
|
38
|
-
useChartState: () => useChartState
|
|
44
|
+
useChartState: () => useChartState,
|
|
45
|
+
useChartToolProvider: () => useChartToolProvider
|
|
39
46
|
});
|
|
40
47
|
module.exports = __toCommonJS(chart_exports);
|
|
41
48
|
|
|
@@ -79,6 +86,26 @@ function useChartCommands(registry, controller, options = {}) {
|
|
|
79
86
|
return registry.register((0, import_chart.createChartCommands)(controller, { prefix, group, scope }));
|
|
80
87
|
}, [registry, controller, prefix, group, scope]);
|
|
81
88
|
}
|
|
89
|
+
function useChartToolProvider(registry, controller, options = {}) {
|
|
90
|
+
const latest = (0, import_react.useRef)(options);
|
|
91
|
+
latest.current = options;
|
|
92
|
+
const { id, namespace } = options;
|
|
93
|
+
(0, import_react.useEffect)(() => {
|
|
94
|
+
if (!registry) return;
|
|
95
|
+
return registry.register((0, import_chart.createChartToolProvider)(controller, { ...latest.current, id, namespace }));
|
|
96
|
+
}, [registry, controller, id, namespace]);
|
|
97
|
+
}
|
|
98
|
+
function useChartDirector(controller, options = {}) {
|
|
99
|
+
const sceneRef = (0, import_react.useRef)(null);
|
|
100
|
+
const ref = (0, import_react.useRef)(null);
|
|
101
|
+
if (!ref.current) {
|
|
102
|
+
const presence = new import_chart.ChartPresence();
|
|
103
|
+
const size = options.size ?? { width: 640, height: 400 };
|
|
104
|
+
const director = new import_chart.ChartDirector(controller, presence, { scene: () => sceneRef.current ?? (0, import_chart.layoutChart)(controller.getSpec(), size), speed: options.speed });
|
|
105
|
+
ref.current = { presence, director };
|
|
106
|
+
}
|
|
107
|
+
return { ...ref.current, sceneRef };
|
|
108
|
+
}
|
|
82
109
|
|
|
83
110
|
// src/chart/styles.ts
|
|
84
111
|
var chartInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
@@ -93,13 +120,13 @@ var chartCellClass = "h-7 w-full min-w-[4.5rem] bg-transparent px-2 text-right t
|
|
|
93
120
|
var import_react2 = require("react");
|
|
94
121
|
var import_chart2 = require("@pixerate/editor/chart");
|
|
95
122
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
96
|
-
function
|
|
123
|
+
function renderChartMark(mark, fontFamily, interactive) {
|
|
97
124
|
const common = {
|
|
98
125
|
"data-role": mark.role,
|
|
99
126
|
"data-chart-key": mark.target ? mark.key : void 0,
|
|
100
127
|
"data-selected": mark.selected ? "" : void 0,
|
|
101
128
|
opacity: mark.opacity,
|
|
102
|
-
pointerEvents: mark.role === "selection" || !mark.target && mark.role !== "background" ? "none" : void 0,
|
|
129
|
+
pointerEvents: mark.role === "selection" || mark.role === "drag-readout" || !mark.target && mark.role !== "background" ? "none" : void 0,
|
|
103
130
|
cursor: interactive && mark.target && mark.role !== "background" ? "pointer" : void 0
|
|
104
131
|
};
|
|
105
132
|
switch (mark.kind) {
|
|
@@ -139,13 +166,26 @@ function renderMark(mark, fontFamily, interactive) {
|
|
|
139
166
|
);
|
|
140
167
|
}
|
|
141
168
|
}
|
|
142
|
-
function
|
|
169
|
+
function ChartPresenceLayer({ presence }) {
|
|
170
|
+
const { actors } = (0, import_react2.useSyncExternalStore)(presence.subscribe, presence.getState, presence.getState);
|
|
171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-chart-presence": "", pointerEvents: "none", children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { transform: `translate(${a.x} ${a.y})`, "data-actor": a.id, children: [
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M0,0 L0,14 L4,10.5 L7,17 L9.5,16 L6.5,9.5 L11.5,9.5 Z", style: { fill: a.color, stroke: "#ffffff", strokeWidth: 1 }, transform: a.pressed ? "scale(0.9)" : void 0 }),
|
|
173
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { transform: "translate(13 16)", children: [
|
|
174
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: 0, y: 0, rx: 4, height: 18, width: Math.max(28, (a.message ?? a.name).length * 6.4 + 12), style: { fill: a.color } }),
|
|
175
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: 6, y: 9, dominantBaseline: "central", style: { fill: "#ffffff", fontSize: 11, fontWeight: 600, fontFamily: "inherit" }, children: a.message ?? a.name })
|
|
176
|
+
] })
|
|
177
|
+
] }, a.id)) });
|
|
178
|
+
}
|
|
179
|
+
function ChartSceneSvg({ scene, label, className }) {
|
|
180
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: scene.width, height: scene.height, viewBox: `0 0 ${scene.width} ${scene.height}`, role: "img", "aria-label": label, className: `block max-w-full ${className ?? ""}`, children: scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, false)) });
|
|
181
|
+
}
|
|
182
|
+
function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className, presence, sceneRef }) {
|
|
143
183
|
const state = useChartState(controller);
|
|
144
184
|
const wrapper = (0, import_react2.useRef)(null);
|
|
145
185
|
const svg = (0, import_react2.useRef)(null);
|
|
146
186
|
const drag = (0, import_react2.useRef)(null);
|
|
147
187
|
const [measured, setMeasured] = (0, import_react2.useState)(null);
|
|
148
|
-
const [
|
|
188
|
+
const [frozen, setFrozen] = (0, import_react2.useState)(null);
|
|
149
189
|
(0, import_react2.useEffect)(() => {
|
|
150
190
|
const el = wrapper.current;
|
|
151
191
|
if (!responsive || !el || typeof ResizeObserver === "undefined") return;
|
|
@@ -159,9 +199,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
159
199
|
const sceneWidth = responsive && measured || width;
|
|
160
200
|
const fullTheme = (0, import_react2.useMemo)(() => ({ ...import_chart2.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
|
|
161
201
|
const scene = (0, import_react2.useMemo)(
|
|
162
|
-
() => (0, import_chart2.layoutChart)(state.spec, {
|
|
163
|
-
|
|
202
|
+
() => (0, import_chart2.layoutChart)(state.spec, {
|
|
203
|
+
width: sceneWidth,
|
|
204
|
+
height,
|
|
205
|
+
theme: fullTheme,
|
|
206
|
+
selection: editable ? state.selection : null,
|
|
207
|
+
measureText,
|
|
208
|
+
emptyMessage,
|
|
209
|
+
valueDomain: frozen?.valueDomain,
|
|
210
|
+
valueDomain2: frozen?.valueDomain2,
|
|
211
|
+
xDomain: frozen?.xDomain,
|
|
212
|
+
readout: frozen?.readout
|
|
213
|
+
}),
|
|
214
|
+
[state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, frozen]
|
|
164
215
|
);
|
|
216
|
+
if (sceneRef) sceneRef.current = scene;
|
|
165
217
|
const byKey = (0, import_react2.useMemo)(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
|
|
166
218
|
(0, import_react2.useEffect)(() => () => drag.current?.end(), []);
|
|
167
219
|
const scenePoint = (e) => (0, import_chart2.chartClientToScene)(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
|
|
@@ -172,23 +224,31 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
172
224
|
controller.select(target ?? null);
|
|
173
225
|
drag.current = (0, import_chart2.startChartPointDrag)(controller, scene, target, scenePoint(e));
|
|
174
226
|
if (drag.current) {
|
|
175
|
-
|
|
227
|
+
const d = drag.current;
|
|
228
|
+
setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
|
|
176
229
|
svg.current?.setPointerCapture?.(e.pointerId);
|
|
177
230
|
}
|
|
178
231
|
};
|
|
179
232
|
const onPointerMove = (e) => {
|
|
180
|
-
if (drag.current) drag.current.move(scenePoint(e));
|
|
233
|
+
if (drag.current) drag.current.move(scenePoint(e), { fine: e.altKey, lockAxis: e.shiftKey, noSnap: e.ctrlKey || e.metaKey });
|
|
181
234
|
};
|
|
182
235
|
const endDrag = () => {
|
|
183
236
|
drag.current?.end();
|
|
184
237
|
drag.current = null;
|
|
185
|
-
|
|
238
|
+
setFrozen(null);
|
|
186
239
|
};
|
|
187
240
|
const onKeyDown = (e) => {
|
|
241
|
+
if (drag.current && e.key === "Escape") {
|
|
242
|
+
drag.current.cancel();
|
|
243
|
+
drag.current = null;
|
|
244
|
+
setFrozen(null);
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
188
248
|
if (editable && (0, import_chart2.handleChartKey)(controller, scene, e)) e.preventDefault();
|
|
189
249
|
};
|
|
190
250
|
const label = state.spec.title || "Chart";
|
|
191
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
192
252
|
"svg",
|
|
193
253
|
{
|
|
194
254
|
ref: svg,
|
|
@@ -204,7 +264,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
204
264
|
onPointerUp: endDrag,
|
|
205
265
|
onPointerCancel: endDrag,
|
|
206
266
|
onKeyDown,
|
|
207
|
-
children:
|
|
267
|
+
children: [
|
|
268
|
+
scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, editable)),
|
|
269
|
+
presence && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartPresenceLayer, { presence })
|
|
270
|
+
]
|
|
208
271
|
}
|
|
209
272
|
) });
|
|
210
273
|
}
|
|
@@ -371,43 +434,60 @@ function Toggle({ label, checked, onChange, name }) {
|
|
|
371
434
|
function Select({ id, value, options, onChange, name, labels }) {
|
|
372
435
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { id, className: chartInputClass, value, "data-option": name, onChange: (e) => onChange(e.currentTarget.value), children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o, children: labels?.[o] ?? cap(o) }, o)) });
|
|
373
436
|
}
|
|
374
|
-
function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
437
|
+
function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
|
|
375
438
|
const set = (patch) => controller.setAxis(axis, patch);
|
|
376
439
|
const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
|
|
377
|
-
const bound = (
|
|
440
|
+
const bound = (key2) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
378
441
|
ChartCommitInput,
|
|
379
442
|
{
|
|
380
|
-
id: `${idBase}-${axis}-${
|
|
381
|
-
value: value[
|
|
443
|
+
id: `${idBase}-${axis}-${key2}`,
|
|
444
|
+
value: value[key2] === null ? "" : String(value[key2]),
|
|
382
445
|
placeholder: "Auto",
|
|
383
446
|
inputMode: "decimal",
|
|
384
|
-
"data-option": `${axis}Axis.${
|
|
447
|
+
"data-option": `${axis}Axis.${key2}`,
|
|
385
448
|
className: chartInputClass,
|
|
386
|
-
onCommit: (text) => set({ [
|
|
449
|
+
onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
|
|
387
450
|
}
|
|
388
451
|
);
|
|
389
|
-
const name = axis === "x" ? "Category axis" : "Value axis";
|
|
452
|
+
const name = axis === "x" ? numeric ? "X axis" : "Category axis" : axis === "y" ? "Value axis" : "Secondary axis";
|
|
453
|
+
const valueAxis = axis !== "x" || numeric;
|
|
454
|
+
const key = axis === "y2" ? "yAxis2" : `${axis}Axis`;
|
|
390
455
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
|
|
391
456
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
392
457
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h4", { className: "text-xs font-semibold text-foreground", children: [
|
|
393
458
|
name,
|
|
394
459
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "ml-1 font-normal text-muted-foreground", children: [
|
|
395
460
|
"(",
|
|
396
|
-
axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
|
|
461
|
+
axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
|
|
397
462
|
")"
|
|
398
463
|
] })
|
|
399
464
|
] }),
|
|
400
465
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
|
|
401
466
|
] }),
|
|
402
467
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Title", htmlFor: `${idBase}-${axis}-title`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-${axis}-title`, value: value.title, "data-option": `${axis}Axis.title`, className: chartInputClass, onCommit: (title) => void set({ title }) }) }),
|
|
403
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
404
|
-
|
|
468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Grid lines", name: `${key}.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
|
|
470
|
+
axis === "x" && !numeric && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Dates", name: "xAxis.kind", checked: value.kind === "date", onChange: (dates) => set({ kind: dates ? "date" : void 0 }) })
|
|
471
|
+
] }),
|
|
472
|
+
valueAxis && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Step", htmlFor: `${idBase}-${axis}-step`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
473
|
+
ChartCommitInput,
|
|
474
|
+
{
|
|
475
|
+
id: `${idBase}-${axis}-step`,
|
|
476
|
+
value: value.step === null ? "" : String(value.step),
|
|
477
|
+
placeholder: "Auto",
|
|
478
|
+
inputMode: "decimal",
|
|
479
|
+
"data-option": `${key}.step`,
|
|
480
|
+
className: chartInputClass,
|
|
481
|
+
onCommit: (text) => set({ step: text.trim() === "" ? null : Number(text) }).ok
|
|
482
|
+
}
|
|
483
|
+
) }),
|
|
484
|
+
valueAxis && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
405
485
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Min / max", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
|
|
406
486
|
bound("min"),
|
|
407
487
|
bound("max")
|
|
408
488
|
] }) }),
|
|
409
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Format", htmlFor: `${idBase}-
|
|
410
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-
|
|
489
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Format", htmlFor: `${idBase}-${axis}-format`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-1", children: [
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-${axis}-format`, name: `${key}.format.style`, value: value.format.style ?? "number", options: import_chart4.CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
|
|
411
491
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
412
492
|
ChartCommitInput,
|
|
413
493
|
{
|
|
@@ -416,7 +496,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
|
416
496
|
"aria-label": "Decimals",
|
|
417
497
|
title: "Decimals",
|
|
418
498
|
inputMode: "numeric",
|
|
419
|
-
"data-option":
|
|
499
|
+
"data-option": `${key}.format.decimals`,
|
|
420
500
|
className: `${chartInputClass} w-14 shrink-0`,
|
|
421
501
|
onCommit: (text) => {
|
|
422
502
|
const format = { ...value.format };
|
|
@@ -427,7 +507,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
|
427
507
|
}
|
|
428
508
|
)
|
|
429
509
|
] }) }),
|
|
430
|
-
value.format.style === "currency" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Currency", htmlFor: `${idBase}-
|
|
510
|
+
value.format.style === "currency" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Currency", htmlFor: `${idBase}-${axis}-currency`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-${axis}-currency`, value: value.format.currency ?? "USD", "data-option": `${key}.format.currency`, className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) }),
|
|
511
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Code", htmlFor: `${idBase}-${axis}-code`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
512
|
+
ChartCommitInput,
|
|
513
|
+
{
|
|
514
|
+
id: `${idBase}-${axis}-code`,
|
|
515
|
+
value: value.format.code ?? "",
|
|
516
|
+
placeholder: 'e.g. #,##0.0,,"M"',
|
|
517
|
+
"data-option": `${key}.format.code`,
|
|
518
|
+
className: chartInputClass,
|
|
519
|
+
onCommit: (code) => {
|
|
520
|
+
const format = { ...value.format };
|
|
521
|
+
if (code.trim()) format.code = code.trim();
|
|
522
|
+
else delete format.code;
|
|
523
|
+
return set({ format }).ok;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
) })
|
|
431
527
|
] })
|
|
432
528
|
] });
|
|
433
529
|
}
|
|
@@ -436,12 +532,28 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
436
532
|
const idBase = (0, import_react4.useId)();
|
|
437
533
|
const show = (section) => !sections || sections.includes(section);
|
|
438
534
|
const cartesian = (0, import_chart4.isCartesianChart)(spec.type);
|
|
535
|
+
const family = (0, import_chart4.chartFamily)(spec);
|
|
536
|
+
const hasAxes = family === "cartesian" || family === "xy" || family === "waterfall" || family === "histogram" || family === "box";
|
|
439
537
|
const types = spec.series.map((s) => (0, import_chart4.chartSeriesType)(spec, s));
|
|
538
|
+
const usesSecondary = family === "cartesian" && spec.series.some((s) => s.axis === "secondary" && !s.hidden);
|
|
539
|
+
const colorCheck = (0, import_chart4.checkChartColors)(spec.series.filter((s) => !s.hidden).map((s) => (0, import_chart4.chartSeriesColor)(spec, spec.series.indexOf(s))));
|
|
440
540
|
const hasBars = cartesian && types.includes("bar");
|
|
441
541
|
const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
|
|
442
542
|
const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
|
|
443
543
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
|
|
444
544
|
show("type") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: chartSectionClass, "data-section": "type", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "grid grid-cols-3 gap-0.5 rounded-md bg-muted p-0.5", role: "radiogroup", "aria-label": "Chart type", children: import_chart4.CHART_TYPES.map((type) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "radio", "aria-checked": spec.type === type, "data-chart-type": type, className: chartSegmentClass(spec.type === type), onClick: () => controller.setType(type), children: import_chart4.CHART_TYPE_LABELS[type] }, type)) }) }),
|
|
545
|
+
show("style") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "style", children: [
|
|
546
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Layout", htmlFor: `${idBase}-layout`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { id: `${idBase}-layout`, className: chartInputClass, value: "", "data-option": "preset.layout", onChange: (e) => e.currentTarget.value && controller.applyPreset({ layout: e.currentTarget.value }), children: [
|
|
547
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Quick layout\u2026" }),
|
|
548
|
+
import_chart4.CHART_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, title: p.description, children: p.label }, p.id))
|
|
549
|
+
] }) }),
|
|
550
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Colours", htmlFor: `${idBase}-colors`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { id: `${idBase}-colors`, className: chartInputClass, value: import_chart4.CHART_COLOR_SCHEMES.find((c) => c.palette.join() === spec.palette.join())?.id ?? "", "data-option": "preset.colors", onChange: (e) => e.currentTarget.value && controller.applyPreset({ colors: e.currentTarget.value }), children: [
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Custom" }),
|
|
552
|
+
import_chart4.CHART_COLOR_SCHEMES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: c.id, title: c.description, children: c.label }, c.id))
|
|
553
|
+
] }) }),
|
|
554
|
+
!colorCheck.ok && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] leading-snug text-amber-600 dark:text-amber-400", "data-chart-color-warning": "", children: colorCheck.issues[0] }),
|
|
555
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, "data-action": "transpose", onClick: () => controller.transpose(), children: "Switch rows and columns" })
|
|
556
|
+
] }),
|
|
445
557
|
show("titles") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "titles", children: [
|
|
446
558
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Title", htmlFor: `${idBase}-title`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-title`, value: spec.title, "data-option": "title", className: chartInputClass, onCommit: (title) => void controller.setTitle(title) }) }),
|
|
447
559
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Subtitle", htmlFor: `${idBase}-subtitle`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-subtitle`, value: spec.subtitle, "data-option": "subtitle", className: chartInputClass, onCommit: (subtitle) => void controller.setSubtitle(subtitle) }) })
|
|
@@ -466,7 +578,26 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
466
578
|
onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
|
|
467
579
|
}
|
|
468
580
|
) }),
|
|
581
|
+
hasBars && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Spacing", htmlFor: `${idBase}-gap`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
582
|
+
"input",
|
|
583
|
+
{
|
|
584
|
+
id: `${idBase}-gap`,
|
|
585
|
+
type: "range",
|
|
586
|
+
min: 0,
|
|
587
|
+
max: 0.9,
|
|
588
|
+
step: 0.02,
|
|
589
|
+
value: spec.gapWidth,
|
|
590
|
+
"data-option": "gapWidth",
|
|
591
|
+
className: "w-full accent-primary",
|
|
592
|
+
onPointerDown: () => controller.beginGesture("gapWidth"),
|
|
593
|
+
onPointerUp: () => controller.endGesture(),
|
|
594
|
+
onChange: (e) => controller.update({ gapWidth: Number(e.currentTarget.value) })
|
|
595
|
+
}
|
|
596
|
+
) }),
|
|
597
|
+
spec.dataLabels && hasBars && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Labels", htmlFor: `${idBase}-label-position`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Select, { id: `${idBase}-label-position`, name: "labelPosition", value: spec.labelPosition, options: import_chart4.CHART_LABEL_POSITIONS, onChange: (labelPosition) => controller.update({ labelPosition }) }) }),
|
|
598
|
+
spec.type === "histogram" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Bins", htmlFor: `${idBase}-bins`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartCommitInput, { id: `${idBase}-bins`, value: spec.bins === null ? "" : String(spec.bins), placeholder: "Auto", inputMode: "numeric", "data-option": "bins", className: chartInputClass, onCommit: (text) => controller.update({ bins: text.trim() === "" ? null : Number(text) }).ok }) }),
|
|
469
599
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
|
|
600
|
+
spec.type === "waterfall" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Total bar", name: "showTotal", checked: spec.showTotal, onChange: (showTotal) => controller.update({ showTotal }) }),
|
|
470
601
|
hasLines && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
|
|
471
602
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
|
|
472
603
|
] })
|
|
@@ -478,9 +609,10 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
478
609
|
] }),
|
|
479
610
|
spec.legend.show && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: import_chart4.CHART_LEGEND_POSITIONS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-pressed": spec.legend.position === p, "data-option": `legend.${p}`, className: chartSegmentClass(spec.legend.position === p), onClick: () => controller.setLegend({ position: p }), children: cap(p) }, p)) })
|
|
480
611
|
] }),
|
|
481
|
-
show("axes") &&
|
|
482
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
|
|
483
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" })
|
|
612
|
+
show("axes") && hasAxes && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
613
|
+
family !== "box" && family !== "histogram" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal", numeric: family === "xy" }),
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" }),
|
|
615
|
+
usesSecondary && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AxisSection, { controller, axis: "y2", value: spec.yAxis2, idBase, horizontal: spec.orientation === "horizontal" })
|
|
484
616
|
] }),
|
|
485
617
|
show("series") && selectedSeries && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
|
|
486
618
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
|
|
@@ -515,8 +647,44 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
515
647
|
]
|
|
516
648
|
}
|
|
517
649
|
) }),
|
|
650
|
+
(cartesian || family === "xy") && ["line", "area", "scatter"].includes((0, import_chart4.chartSeriesType)(spec, selectedSeries)) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Field, { label: "Marker", htmlFor: `${idBase}-series-marker`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
651
|
+
"select",
|
|
652
|
+
{
|
|
653
|
+
id: `${idBase}-series-marker`,
|
|
654
|
+
className: chartInputClass,
|
|
655
|
+
value: selectedSeries.marker ?? "",
|
|
656
|
+
"data-option": "series.marker",
|
|
657
|
+
onChange: (e) => controller.updateSeries(selectedSeries.id, { marker: e.currentTarget.value || null }),
|
|
658
|
+
children: [
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Default" }),
|
|
660
|
+
import_chart4.CHART_MARKERS.map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: m, children: cap(m) }, m))
|
|
661
|
+
]
|
|
662
|
+
}
|
|
663
|
+
) }),
|
|
664
|
+
(0, import_chart4.chartSupportsTrendlines)(spec.type, spec.stacking) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-1", "data-section": "trendlines", children: [
|
|
665
|
+
(selectedSeries.trendlines ?? []).map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", "data-trendline": i, children: [
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1 truncate text-muted-foreground", children: (0, import_chart4.chartTrendlineName)(t, selectedSeries.name) }),
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "R\xB2", name: `trendline.${i}.showR2`, checked: !!t.showR2, onChange: (showR2) => controller.updateTrendline(selectedSeries.id, i, { showR2 }) }),
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, "aria-label": "Delete trendline", "data-action": "trendline-delete", onClick: () => controller.removeTrendline(selectedSeries.id, i), children: "\xD7" })
|
|
669
|
+
] }, i)),
|
|
670
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
671
|
+
"select",
|
|
672
|
+
{
|
|
673
|
+
className: chartInputClass,
|
|
674
|
+
value: "",
|
|
675
|
+
"aria-label": "Add trendline",
|
|
676
|
+
"data-option": "trendline.add",
|
|
677
|
+
onChange: (e) => e.currentTarget.value && controller.addTrendline(selectedSeries.id, { type: e.currentTarget.value }),
|
|
678
|
+
children: [
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Add trendline\u2026" }),
|
|
680
|
+
import_chart4.CHART_TRENDLINE_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: t, children: cap(t.replace("-", " ")) }, t))
|
|
681
|
+
]
|
|
682
|
+
}
|
|
683
|
+
)
|
|
684
|
+
] }),
|
|
518
685
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
519
686
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Hidden", name: "series.hidden", checked: !!selectedSeries.hidden, onChange: (hidden) => controller.updateSeries(selectedSeries.id, { hidden }) }),
|
|
687
|
+
family === "cartesian" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toggle, { label: "Secondary axis", name: "series.axis", checked: selectedSeries.axis === "secondary", onChange: (secondary) => controller.updateSeries(selectedSeries.id, { axis: secondary ? "secondary" : null }) }),
|
|
520
688
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1" }),
|
|
521
689
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, disabled: seriesIndex <= 0, "aria-label": "Move series earlier", "data-action": "series-up", onClick: () => controller.moveSeries(selectedSeries.id, seriesIndex - 1), children: "\u2191" }),
|
|
522
690
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: chartButtonClass, disabled: seriesIndex >= spec.series.length - 1, "aria-label": "Move series later", "data-action": "series-down", onClick: () => controller.moveSeries(selectedSeries.id, seriesIndex + 1), children: "\u2193" }),
|
|
@@ -545,6 +713,61 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
|
|
|
545
713
|
showDataGrid && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChartDataGrid, { controller })
|
|
546
714
|
] });
|
|
547
715
|
}
|
|
716
|
+
|
|
717
|
+
// src/chart/ChartStreamingView.tsx
|
|
718
|
+
var import_react5 = require("react");
|
|
719
|
+
var import_chart6 = require("@pixerate/editor/chart");
|
|
720
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
721
|
+
function ChartStreamingView({ source, complete = false, format = "auto", width = 640, height = 360, theme, adoptInterval = 300, tween = 450, placeholder, onIssues, className }) {
|
|
722
|
+
const stream = (0, import_react5.useMemo)(() => new import_chart6.ChartStream(format), [format]);
|
|
723
|
+
const fullTheme = (0, import_react5.useMemo)(() => ({ ...import_chart6.CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
|
|
724
|
+
const [scene, setScene] = (0, import_react5.useState)(null);
|
|
725
|
+
const shown = (0, import_react5.useRef)(null);
|
|
726
|
+
const lastAdopt = (0, import_react5.useRef)(0);
|
|
727
|
+
const frame = (0, import_react5.useRef)(null);
|
|
728
|
+
const pending = (0, import_react5.useRef)(null);
|
|
729
|
+
(0, import_react5.useEffect)(() => {
|
|
730
|
+
const adopt = () => {
|
|
731
|
+
const read = stream.update(source, complete);
|
|
732
|
+
if (complete && onIssues) onIssues(read.issues);
|
|
733
|
+
if (!read.spec) return;
|
|
734
|
+
const next = (0, import_chart6.layoutChart)(read.spec, { width, height, theme: fullTheme, valueDomain: read.valueDomain });
|
|
735
|
+
const from = shown.current;
|
|
736
|
+
lastAdopt.current = Date.now();
|
|
737
|
+
if (frame.current !== null) cancelAnimationFrame(frame.current);
|
|
738
|
+
if (!from || tween <= 0 || typeof requestAnimationFrame === "undefined") {
|
|
739
|
+
shown.current = next;
|
|
740
|
+
setScene(next);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const start = performance.now();
|
|
744
|
+
const step = () => {
|
|
745
|
+
const t = (0, import_chart6.easeChartTween)((performance.now() - start) / tween);
|
|
746
|
+
const s = (0, import_chart6.blendChartScenes)(from, next, t);
|
|
747
|
+
shown.current = s;
|
|
748
|
+
setScene(s);
|
|
749
|
+
frame.current = t < 1 ? requestAnimationFrame(step) : null;
|
|
750
|
+
};
|
|
751
|
+
frame.current = requestAnimationFrame(step);
|
|
752
|
+
};
|
|
753
|
+
if (pending.current) clearTimeout(pending.current);
|
|
754
|
+
const wait = complete ? 0 : Math.max(0, adoptInterval - (Date.now() - lastAdopt.current));
|
|
755
|
+
if (wait === 0) adopt();
|
|
756
|
+
else pending.current = setTimeout(adopt, wait);
|
|
757
|
+
return () => {
|
|
758
|
+
if (pending.current) clearTimeout(pending.current);
|
|
759
|
+
};
|
|
760
|
+
}, [source, complete, stream, width, height, fullTheme, adoptInterval, tween, onIssues]);
|
|
761
|
+
(0, import_react5.useEffect)(() => () => {
|
|
762
|
+
if (frame.current !== null) cancelAnimationFrame(frame.current);
|
|
763
|
+
}, []);
|
|
764
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, "data-chart-streaming": complete ? "complete" : "streaming", "aria-busy": !complete, children: scene ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChartSceneSvg, { scene, label: complete ? void 0 : "Chart loading" }) : placeholder ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height }, className: "flex items-center justify-center text-xs text-muted-foreground", children: "Drawing chart\u2026" }) });
|
|
765
|
+
}
|
|
766
|
+
function ChartSparkline({ values, width = 80, height = 20, label, className, ...options }) {
|
|
767
|
+
const scene = (0, import_react5.useMemo)(() => (0, import_chart6.layoutSparkline)(values, { ...options, width, height }), [values, width, height, options.kind, options.color, options.negativeColor, options.markers, options.highLow, options.min, options.max]);
|
|
768
|
+
const present = values.filter((v) => v !== null);
|
|
769
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChartSceneSvg, { scene, label: label ?? (present.length ? `Trend from ${present[0]} to ${present[present.length - 1]}` : "No data"), className: `inline-block align-middle ${className ?? ""}` });
|
|
770
|
+
}
|
|
548
771
|
// Annotate the CommonJS export names for ESM import in node:
|
|
549
772
|
0 && (module.exports = {
|
|
550
773
|
ChartCanvas,
|
|
@@ -552,6 +775,10 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
|
|
|
552
775
|
ChartDataGrid,
|
|
553
776
|
ChartEditor,
|
|
554
777
|
ChartInspector,
|
|
778
|
+
ChartPresenceLayer,
|
|
779
|
+
ChartSceneSvg,
|
|
780
|
+
ChartSparkline,
|
|
781
|
+
ChartStreamingView,
|
|
555
782
|
chartButtonClass,
|
|
556
783
|
chartCellClass,
|
|
557
784
|
chartIconButtonClass,
|
|
@@ -559,9 +786,12 @@ function ChartEditor({ controller, showInspector = true, showDataGrid = true, sh
|
|
|
559
786
|
chartLabelClass,
|
|
560
787
|
chartSectionClass,
|
|
561
788
|
chartSegmentClass,
|
|
789
|
+
renderChartMark,
|
|
562
790
|
useChartCommands,
|
|
563
791
|
useChartController,
|
|
792
|
+
useChartDirector,
|
|
564
793
|
useChartEditor,
|
|
565
|
-
useChartState
|
|
794
|
+
useChartState,
|
|
795
|
+
useChartToolProvider
|
|
566
796
|
});
|
|
567
797
|
//# sourceMappingURL=index.cjs.map
|