@pixerate/editor-react 0.35.0 → 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 +263 -28
- 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 +308 -60
- package/dist/chart/index.js.map +1 -1
- package/package.json +7 -7
package/dist/chart/index.js
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
3
|
import {
|
|
4
4
|
ChartController,
|
|
5
|
-
|
|
5
|
+
ChartDirector,
|
|
6
|
+
ChartPresence,
|
|
7
|
+
createChartCommands,
|
|
8
|
+
createChartToolProvider,
|
|
9
|
+
layoutChart
|
|
6
10
|
} from "@pixerate/editor/chart";
|
|
7
11
|
function useChartController(options = {}) {
|
|
8
12
|
const ref = useRef(null);
|
|
@@ -41,6 +45,26 @@ function useChartCommands(registry, controller, options = {}) {
|
|
|
41
45
|
return registry.register(createChartCommands(controller, { prefix, group, scope }));
|
|
42
46
|
}, [registry, controller, prefix, group, scope]);
|
|
43
47
|
}
|
|
48
|
+
function useChartToolProvider(registry, controller, options = {}) {
|
|
49
|
+
const latest = useRef(options);
|
|
50
|
+
latest.current = options;
|
|
51
|
+
const { id, namespace } = options;
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!registry) return;
|
|
54
|
+
return registry.register(createChartToolProvider(controller, { ...latest.current, id, namespace }));
|
|
55
|
+
}, [registry, controller, id, namespace]);
|
|
56
|
+
}
|
|
57
|
+
function useChartDirector(controller, options = {}) {
|
|
58
|
+
const sceneRef = useRef(null);
|
|
59
|
+
const ref = useRef(null);
|
|
60
|
+
if (!ref.current) {
|
|
61
|
+
const presence = new ChartPresence();
|
|
62
|
+
const size = options.size ?? { width: 640, height: 400 };
|
|
63
|
+
const director = new ChartDirector(controller, presence, { scene: () => sceneRef.current ?? layoutChart(controller.getSpec(), size), speed: options.speed });
|
|
64
|
+
ref.current = { presence, director };
|
|
65
|
+
}
|
|
66
|
+
return { ...ref.current, sceneRef };
|
|
67
|
+
}
|
|
44
68
|
|
|
45
69
|
// src/chart/styles.ts
|
|
46
70
|
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";
|
|
@@ -52,7 +76,7 @@ var chartSectionClass = "space-y-2 border-b border-border pb-3 last:border-b-0";
|
|
|
52
76
|
var chartCellClass = "h-7 w-full min-w-[4.5rem] bg-transparent px-2 text-right text-xs tabular-nums text-foreground focus-visible:bg-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary";
|
|
53
77
|
|
|
54
78
|
// src/chart/ChartCanvas.tsx
|
|
55
|
-
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
|
|
79
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
56
80
|
import {
|
|
57
81
|
CSS_VARIABLE_CHART_THEME,
|
|
58
82
|
chartClientToScene,
|
|
@@ -60,17 +84,17 @@ import {
|
|
|
60
84
|
chartTextTransform,
|
|
61
85
|
describeChartSelection,
|
|
62
86
|
handleChartKey,
|
|
63
|
-
layoutChart,
|
|
87
|
+
layoutChart as layoutChart2,
|
|
64
88
|
startChartPointDrag
|
|
65
89
|
} from "@pixerate/editor/chart";
|
|
66
|
-
import { jsx } from "react/jsx-runtime";
|
|
67
|
-
function
|
|
90
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
91
|
+
function renderChartMark(mark, fontFamily, interactive) {
|
|
68
92
|
const common = {
|
|
69
93
|
"data-role": mark.role,
|
|
70
94
|
"data-chart-key": mark.target ? mark.key : void 0,
|
|
71
95
|
"data-selected": mark.selected ? "" : void 0,
|
|
72
96
|
opacity: mark.opacity,
|
|
73
|
-
pointerEvents: mark.role === "selection" || !mark.target && mark.role !== "background" ? "none" : void 0,
|
|
97
|
+
pointerEvents: mark.role === "selection" || mark.role === "drag-readout" || !mark.target && mark.role !== "background" ? "none" : void 0,
|
|
74
98
|
cursor: interactive && mark.target && mark.role !== "background" ? "pointer" : void 0
|
|
75
99
|
};
|
|
76
100
|
switch (mark.kind) {
|
|
@@ -110,12 +134,26 @@ function renderMark(mark, fontFamily, interactive) {
|
|
|
110
134
|
);
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
|
-
function
|
|
137
|
+
function ChartPresenceLayer({ presence }) {
|
|
138
|
+
const { actors } = useSyncExternalStore2(presence.subscribe, presence.getState, presence.getState);
|
|
139
|
+
return /* @__PURE__ */ jsx("g", { "data-chart-presence": "", pointerEvents: "none", children: actors.map((a) => /* @__PURE__ */ jsxs("g", { transform: `translate(${a.x} ${a.y})`, "data-actor": a.id, children: [
|
|
140
|
+
/* @__PURE__ */ 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 }),
|
|
141
|
+
/* @__PURE__ */ jsxs("g", { transform: "translate(13 16)", children: [
|
|
142
|
+
/* @__PURE__ */ 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 } }),
|
|
143
|
+
/* @__PURE__ */ jsx("text", { x: 6, y: 9, dominantBaseline: "central", style: { fill: "#ffffff", fontSize: 11, fontWeight: 600, fontFamily: "inherit" }, children: a.message ?? a.name })
|
|
144
|
+
] })
|
|
145
|
+
] }, a.id)) });
|
|
146
|
+
}
|
|
147
|
+
function ChartSceneSvg({ scene, label, className }) {
|
|
148
|
+
return /* @__PURE__ */ 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)) });
|
|
149
|
+
}
|
|
150
|
+
function ChartCanvas({ controller, width = 640, height = 400, responsive = true, theme, editable = true, measureText, emptyMessage, className, presence, sceneRef }) {
|
|
114
151
|
const state = useChartState(controller);
|
|
115
152
|
const wrapper = useRef2(null);
|
|
116
153
|
const svg = useRef2(null);
|
|
117
154
|
const drag = useRef2(null);
|
|
118
155
|
const [measured, setMeasured] = useState(null);
|
|
156
|
+
const [frozen, setFrozen] = useState(null);
|
|
119
157
|
useEffect2(() => {
|
|
120
158
|
const el = wrapper.current;
|
|
121
159
|
if (!responsive || !el || typeof ResizeObserver === "undefined") return;
|
|
@@ -129,9 +167,21 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
129
167
|
const sceneWidth = responsive && measured || width;
|
|
130
168
|
const fullTheme = useMemo2(() => ({ ...CSS_VARIABLE_CHART_THEME, ...theme }), [theme]);
|
|
131
169
|
const scene = useMemo2(
|
|
132
|
-
() =>
|
|
133
|
-
|
|
170
|
+
() => layoutChart2(state.spec, {
|
|
171
|
+
width: sceneWidth,
|
|
172
|
+
height,
|
|
173
|
+
theme: fullTheme,
|
|
174
|
+
selection: editable ? state.selection : null,
|
|
175
|
+
measureText,
|
|
176
|
+
emptyMessage,
|
|
177
|
+
valueDomain: frozen?.valueDomain,
|
|
178
|
+
valueDomain2: frozen?.valueDomain2,
|
|
179
|
+
xDomain: frozen?.xDomain,
|
|
180
|
+
readout: frozen?.readout
|
|
181
|
+
}),
|
|
182
|
+
[state.spec, state.selection, sceneWidth, height, fullTheme, editable, measureText, emptyMessage, frozen]
|
|
134
183
|
);
|
|
184
|
+
if (sceneRef) sceneRef.current = scene;
|
|
135
185
|
const byKey = useMemo2(() => new Map(scene.marks.map((m) => [m.key, m])), [scene]);
|
|
136
186
|
useEffect2(() => () => drag.current?.end(), []);
|
|
137
187
|
const scenePoint = (e) => chartClientToScene(scene, svg.current.getBoundingClientRect(), e.clientX, e.clientY);
|
|
@@ -141,20 +191,32 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
141
191
|
const target = key ? byKey.get(key)?.target : void 0;
|
|
142
192
|
controller.select(target ?? null);
|
|
143
193
|
drag.current = startChartPointDrag(controller, scene, target, scenePoint(e));
|
|
144
|
-
if (drag.current)
|
|
194
|
+
if (drag.current) {
|
|
195
|
+
const d = drag.current;
|
|
196
|
+
setFrozen({ valueDomain: d.valueDomain, valueDomain2: d.valueDomain2 ?? null, xDomain: d.xDomain ?? null, readout: d.target });
|
|
197
|
+
svg.current?.setPointerCapture?.(e.pointerId);
|
|
198
|
+
}
|
|
145
199
|
};
|
|
146
200
|
const onPointerMove = (e) => {
|
|
147
|
-
if (drag.current) drag.current.move(scenePoint(e));
|
|
201
|
+
if (drag.current) drag.current.move(scenePoint(e), { fine: e.altKey, lockAxis: e.shiftKey, noSnap: e.ctrlKey || e.metaKey });
|
|
148
202
|
};
|
|
149
203
|
const endDrag = () => {
|
|
150
204
|
drag.current?.end();
|
|
151
205
|
drag.current = null;
|
|
206
|
+
setFrozen(null);
|
|
152
207
|
};
|
|
153
208
|
const onKeyDown = (e) => {
|
|
209
|
+
if (drag.current && e.key === "Escape") {
|
|
210
|
+
drag.current.cancel();
|
|
211
|
+
drag.current = null;
|
|
212
|
+
setFrozen(null);
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
154
216
|
if (editable && handleChartKey(controller, scene, e)) e.preventDefault();
|
|
155
217
|
};
|
|
156
218
|
const label = state.spec.title || "Chart";
|
|
157
|
-
return /* @__PURE__ */ jsx("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */
|
|
219
|
+
return /* @__PURE__ */ jsx("div", { ref: wrapper, className: `w-full ${className ?? ""}`, "data-chart-canvas": "", children: /* @__PURE__ */ jsxs(
|
|
158
220
|
"svg",
|
|
159
221
|
{
|
|
160
222
|
ref: svg,
|
|
@@ -170,7 +232,10 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
170
232
|
onPointerUp: endDrag,
|
|
171
233
|
onPointerCancel: endDrag,
|
|
172
234
|
onKeyDown,
|
|
173
|
-
children:
|
|
235
|
+
children: [
|
|
236
|
+
scene.marks.map((m) => renderChartMark(m, scene.theme.fontFamily, editable)),
|
|
237
|
+
presence && /* @__PURE__ */ jsx(ChartPresenceLayer, { presence })
|
|
238
|
+
]
|
|
174
239
|
}
|
|
175
240
|
) });
|
|
176
241
|
}
|
|
@@ -178,7 +243,7 @@ function ChartCanvas({ controller, width = 640, height = 400, responsive = true,
|
|
|
178
243
|
// src/chart/ChartDataGrid.tsx
|
|
179
244
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
180
245
|
import { chartColorInputValue, chartSeriesColor, formatChartNumber } from "@pixerate/editor/chart";
|
|
181
|
-
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
246
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
182
247
|
function ChartCommitInput({ value, onCommit, onKeyDown, onBlur, ...rest }) {
|
|
183
248
|
const [draft, setDraft] = useState2(value);
|
|
184
249
|
const typing = useRef3(false);
|
|
@@ -226,14 +291,14 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
|
|
|
226
291
|
e.preventDefault();
|
|
227
292
|
controller.pasteCells(row, column, text);
|
|
228
293
|
};
|
|
229
|
-
return /* @__PURE__ */
|
|
230
|
-
/* @__PURE__ */
|
|
231
|
-
/* @__PURE__ */ jsx2("thead", { className: "bg-muted/50", children: /* @__PURE__ */
|
|
294
|
+
return /* @__PURE__ */ jsxs2("div", { className: `overflow-auto rounded-md border border-border ${className ?? ""}`, "data-chart-grid": "", children: [
|
|
295
|
+
/* @__PURE__ */ jsxs2("table", { className: "w-full border-collapse text-xs", children: [
|
|
296
|
+
/* @__PURE__ */ jsx2("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs2("tr", { children: [
|
|
232
297
|
/* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border px-2 py-1 text-left font-medium text-muted-foreground", children: categoryHeader }),
|
|
233
298
|
spec.series.map((s, si) => {
|
|
234
299
|
const color = chartSeriesColor(spec, si);
|
|
235
300
|
const selected = selection?.kind === "series" && selection.seriesId === s.id;
|
|
236
|
-
return /* @__PURE__ */ jsx2("th", { "data-series-id": s.id, className: `border-b border-r border-border px-1 py-1 font-medium ${selected ? "bg-primary/10" : ""}`, children: /* @__PURE__ */
|
|
301
|
+
return /* @__PURE__ */ jsx2("th", { "data-series-id": s.id, className: `border-b border-r border-border px-1 py-1 font-medium ${selected ? "bg-primary/10" : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
|
|
237
302
|
/* @__PURE__ */ jsx2(
|
|
238
303
|
"input",
|
|
239
304
|
{
|
|
@@ -257,7 +322,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
|
|
|
257
322
|
className: `${chartInputClass} h-6 min-w-[5rem] border-transparent bg-transparent font-medium ${s.hidden ? "text-muted-foreground line-through" : ""}`
|
|
258
323
|
}
|
|
259
324
|
),
|
|
260
|
-
!readOnly && /* @__PURE__ */
|
|
325
|
+
!readOnly && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
261
326
|
/* @__PURE__ */ jsx2(
|
|
262
327
|
"button",
|
|
263
328
|
{
|
|
@@ -279,7 +344,7 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
|
|
|
279
344
|
] }) }),
|
|
280
345
|
/* @__PURE__ */ jsx2("tbody", { children: spec.categories.map((category, i) => {
|
|
281
346
|
const rowSelected = selection?.kind === "category" && selection.index === i;
|
|
282
|
-
return /* @__PURE__ */
|
|
347
|
+
return /* @__PURE__ */ jsxs2("tr", { "data-category-index": i, className: rowSelected ? "bg-primary/10" : void 0, children: [
|
|
283
348
|
/* @__PURE__ */ jsx2("th", { className: "border-b border-r border-border p-0 text-left font-normal", children: /* @__PURE__ */ jsx2(
|
|
284
349
|
ChartCommitInput,
|
|
285
350
|
{
|
|
@@ -320,6 +385,15 @@ function ChartDataGrid({ controller, categoryHeader = "Category", readOnly = fal
|
|
|
320
385
|
// src/chart/ChartInspector.tsx
|
|
321
386
|
import { useId } from "react";
|
|
322
387
|
import {
|
|
388
|
+
CHART_COLOR_SCHEMES,
|
|
389
|
+
CHART_LABEL_POSITIONS,
|
|
390
|
+
CHART_LAYOUT_PRESETS,
|
|
391
|
+
CHART_MARKERS,
|
|
392
|
+
CHART_TRENDLINE_TYPES,
|
|
393
|
+
checkChartColors,
|
|
394
|
+
chartFamily,
|
|
395
|
+
chartSupportsTrendlines,
|
|
396
|
+
chartTrendlineName,
|
|
323
397
|
CHART_CURVES,
|
|
324
398
|
CHART_LEGEND_POSITIONS,
|
|
325
399
|
CHART_NUMBER_STYLES,
|
|
@@ -332,16 +406,16 @@ import {
|
|
|
332
406
|
chartSeriesType,
|
|
333
407
|
isCartesianChart
|
|
334
408
|
} from "@pixerate/editor/chart";
|
|
335
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs as
|
|
409
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
336
410
|
var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
337
411
|
function Field({ label, htmlFor, children }) {
|
|
338
|
-
return /* @__PURE__ */
|
|
412
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
339
413
|
/* @__PURE__ */ jsx3("label", { htmlFor, className: `${chartLabelClass} w-20 shrink-0`, children: label }),
|
|
340
414
|
/* @__PURE__ */ jsx3("div", { className: "min-w-0 flex-1", children })
|
|
341
415
|
] });
|
|
342
416
|
}
|
|
343
417
|
function Toggle({ label, checked, onChange, name }) {
|
|
344
|
-
return /* @__PURE__ */
|
|
418
|
+
return /* @__PURE__ */ jsxs3("label", { className: "flex cursor-pointer items-center gap-2 text-xs text-foreground", children: [
|
|
345
419
|
/* @__PURE__ */ jsx3("input", { type: "checkbox", className: "h-3.5 w-3.5 accent-primary", checked, "data-option": name, onChange: (e) => onChange(e.currentTarget.checked) }),
|
|
346
420
|
label
|
|
347
421
|
] });
|
|
@@ -349,43 +423,60 @@ function Toggle({ label, checked, onChange, name }) {
|
|
|
349
423
|
function Select({ id, value, options, onChange, name, labels }) {
|
|
350
424
|
return /* @__PURE__ */ jsx3("select", { id, className: chartInputClass, value, "data-option": name, onChange: (e) => onChange(e.currentTarget.value), children: options.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: labels?.[o] ?? cap(o) }, o)) });
|
|
351
425
|
}
|
|
352
|
-
function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
426
|
+
function AxisSection({ controller, axis, value, idBase, horizontal, numeric = false }) {
|
|
353
427
|
const set = (patch) => controller.setAxis(axis, patch);
|
|
354
428
|
const setFormat = (patch) => set({ format: { ...value.format, ...patch } });
|
|
355
|
-
const bound = (
|
|
429
|
+
const bound = (key2) => /* @__PURE__ */ jsx3(
|
|
356
430
|
ChartCommitInput,
|
|
357
431
|
{
|
|
358
|
-
id: `${idBase}-${axis}-${
|
|
359
|
-
value: value[
|
|
432
|
+
id: `${idBase}-${axis}-${key2}`,
|
|
433
|
+
value: value[key2] === null ? "" : String(value[key2]),
|
|
360
434
|
placeholder: "Auto",
|
|
361
435
|
inputMode: "decimal",
|
|
362
|
-
"data-option": `${axis}Axis.${
|
|
436
|
+
"data-option": `${axis}Axis.${key2}`,
|
|
363
437
|
className: chartInputClass,
|
|
364
|
-
onCommit: (text) => set({ [
|
|
438
|
+
onCommit: (text) => set({ [key2]: text.trim() === "" ? null : Number(text) }).ok
|
|
365
439
|
}
|
|
366
440
|
);
|
|
367
|
-
const name = axis === "x" ? "Category axis" : "Value axis";
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
441
|
+
const name = axis === "x" ? numeric ? "X axis" : "Category axis" : axis === "y" ? "Value axis" : "Secondary axis";
|
|
442
|
+
const valueAxis = axis !== "x" || numeric;
|
|
443
|
+
const key = axis === "y2" ? "yAxis2" : `${axis}Axis`;
|
|
444
|
+
return /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": `axis-${axis}`, children: [
|
|
445
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
446
|
+
/* @__PURE__ */ jsxs3("h4", { className: "text-xs font-semibold text-foreground", children: [
|
|
371
447
|
name,
|
|
372
|
-
/* @__PURE__ */
|
|
448
|
+
/* @__PURE__ */ jsxs3("span", { className: "ml-1 font-normal text-muted-foreground", children: [
|
|
373
449
|
"(",
|
|
374
|
-
axis === "x" ? horizontal ? "left" : "bottom" : horizontal ? "bottom" : "left",
|
|
450
|
+
axis === "x" ? horizontal ? "left" : "bottom" : axis === "y2" ? horizontal ? "top" : "right" : horizontal ? "bottom" : "left",
|
|
375
451
|
")"
|
|
376
452
|
] })
|
|
377
453
|
] }),
|
|
378
454
|
/* @__PURE__ */ jsx3(Toggle, { label: "Show", name: `${axis}Axis.show`, checked: value.show, onChange: (show) => set({ show }) })
|
|
379
455
|
] }),
|
|
380
456
|
/* @__PURE__ */ jsx3(Field, { label: "Title", htmlFor: `${idBase}-${axis}-title`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-${axis}-title`, value: value.title, "data-option": `${axis}Axis.title`, className: chartInputClass, onCommit: (title) => void set({ title }) }) }),
|
|
381
|
-
/* @__PURE__ */
|
|
382
|
-
|
|
383
|
-
/* @__PURE__ */ jsx3(
|
|
457
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
|
|
458
|
+
/* @__PURE__ */ jsx3(Toggle, { label: "Grid lines", name: `${key}.gridlines`, checked: value.gridlines, onChange: (gridlines) => set({ gridlines }) }),
|
|
459
|
+
axis === "x" && !numeric && /* @__PURE__ */ jsx3(Toggle, { label: "Dates", name: "xAxis.kind", checked: value.kind === "date", onChange: (dates) => set({ kind: dates ? "date" : void 0 }) })
|
|
460
|
+
] }),
|
|
461
|
+
valueAxis && /* @__PURE__ */ jsx3(Field, { label: "Step", htmlFor: `${idBase}-${axis}-step`, children: /* @__PURE__ */ jsx3(
|
|
462
|
+
ChartCommitInput,
|
|
463
|
+
{
|
|
464
|
+
id: `${idBase}-${axis}-step`,
|
|
465
|
+
value: value.step === null ? "" : String(value.step),
|
|
466
|
+
placeholder: "Auto",
|
|
467
|
+
inputMode: "decimal",
|
|
468
|
+
"data-option": `${key}.step`,
|
|
469
|
+
className: chartInputClass,
|
|
470
|
+
onCommit: (text) => set({ step: text.trim() === "" ? null : Number(text) }).ok
|
|
471
|
+
}
|
|
472
|
+
) }),
|
|
473
|
+
valueAxis && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
474
|
+
/* @__PURE__ */ jsx3(Field, { label: "Min / max", children: /* @__PURE__ */ jsxs3("div", { className: "flex gap-1", children: [
|
|
384
475
|
bound("min"),
|
|
385
476
|
bound("max")
|
|
386
477
|
] }) }),
|
|
387
|
-
/* @__PURE__ */ jsx3(Field, { label: "Format", htmlFor: `${idBase}-
|
|
388
|
-
/* @__PURE__ */ jsx3(Select, { id: `${idBase}-
|
|
478
|
+
/* @__PURE__ */ jsx3(Field, { label: "Format", htmlFor: `${idBase}-${axis}-format`, children: /* @__PURE__ */ jsxs3("div", { className: "flex gap-1", children: [
|
|
479
|
+
/* @__PURE__ */ jsx3(Select, { id: `${idBase}-${axis}-format`, name: `${key}.format.style`, value: value.format.style ?? "number", options: CHART_NUMBER_STYLES, onChange: (style) => setFormat({ style }) }),
|
|
389
480
|
/* @__PURE__ */ jsx3(
|
|
390
481
|
ChartCommitInput,
|
|
391
482
|
{
|
|
@@ -394,7 +485,7 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
|
394
485
|
"aria-label": "Decimals",
|
|
395
486
|
title: "Decimals",
|
|
396
487
|
inputMode: "numeric",
|
|
397
|
-
"data-option":
|
|
488
|
+
"data-option": `${key}.format.decimals`,
|
|
398
489
|
className: `${chartInputClass} w-14 shrink-0`,
|
|
399
490
|
onCommit: (text) => {
|
|
400
491
|
const format = { ...value.format };
|
|
@@ -405,7 +496,23 @@ function AxisSection({ controller, axis, value, idBase, horizontal }) {
|
|
|
405
496
|
}
|
|
406
497
|
)
|
|
407
498
|
] }) }),
|
|
408
|
-
value.format.style === "currency" && /* @__PURE__ */ jsx3(Field, { label: "Currency", htmlFor: `${idBase}-
|
|
499
|
+
value.format.style === "currency" && /* @__PURE__ */ jsx3(Field, { label: "Currency", htmlFor: `${idBase}-${axis}-currency`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-${axis}-currency`, value: value.format.currency ?? "USD", "data-option": `${key}.format.currency`, className: chartInputClass, onCommit: (currency) => void setFormat({ currency: currency.toUpperCase() }) }) }),
|
|
500
|
+
/* @__PURE__ */ jsx3(Field, { label: "Code", htmlFor: `${idBase}-${axis}-code`, children: /* @__PURE__ */ jsx3(
|
|
501
|
+
ChartCommitInput,
|
|
502
|
+
{
|
|
503
|
+
id: `${idBase}-${axis}-code`,
|
|
504
|
+
value: value.format.code ?? "",
|
|
505
|
+
placeholder: 'e.g. #,##0.0,,"M"',
|
|
506
|
+
"data-option": `${key}.format.code`,
|
|
507
|
+
className: chartInputClass,
|
|
508
|
+
onCommit: (code) => {
|
|
509
|
+
const format = { ...value.format };
|
|
510
|
+
if (code.trim()) format.code = code.trim();
|
|
511
|
+
else delete format.code;
|
|
512
|
+
return set({ format }).ok;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
) })
|
|
409
516
|
] })
|
|
410
517
|
] });
|
|
411
518
|
}
|
|
@@ -414,17 +521,33 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
414
521
|
const idBase = useId();
|
|
415
522
|
const show = (section) => !sections || sections.includes(section);
|
|
416
523
|
const cartesian = isCartesianChart(spec.type);
|
|
524
|
+
const family = chartFamily(spec);
|
|
525
|
+
const hasAxes = family === "cartesian" || family === "xy" || family === "waterfall" || family === "histogram" || family === "box";
|
|
417
526
|
const types = spec.series.map((s) => chartSeriesType(spec, s));
|
|
527
|
+
const usesSecondary = family === "cartesian" && spec.series.some((s) => s.axis === "secondary" && !s.hidden);
|
|
528
|
+
const colorCheck = checkChartColors(spec.series.filter((s) => !s.hidden).map((s) => chartSeriesColor2(spec, spec.series.indexOf(s))));
|
|
418
529
|
const hasBars = cartesian && types.includes("bar");
|
|
419
530
|
const hasLines = cartesian && types.some((t) => t === "line" || t === "area");
|
|
420
531
|
const seriesIndex = selectedSeries ? spec.series.findIndex((s) => s.id === selectedSeries.id) : -1;
|
|
421
|
-
return /* @__PURE__ */
|
|
532
|
+
return /* @__PURE__ */ jsxs3("div", { className: `space-y-3 text-xs ${className ?? ""}`, "data-chart-inspector": "", children: [
|
|
422
533
|
show("type") && /* @__PURE__ */ jsx3("div", { className: chartSectionClass, "data-section": "type", children: /* @__PURE__ */ jsx3("div", { className: "grid grid-cols-3 gap-0.5 rounded-md bg-muted p-0.5", role: "radiogroup", "aria-label": "Chart type", children: CHART_TYPES.map((type) => /* @__PURE__ */ jsx3("button", { type: "button", role: "radio", "aria-checked": spec.type === type, "data-chart-type": type, className: chartSegmentClass(spec.type === type), onClick: () => controller.setType(type), children: CHART_TYPE_LABELS[type] }, type)) }) }),
|
|
423
|
-
show("
|
|
534
|
+
show("style") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "style", children: [
|
|
535
|
+
/* @__PURE__ */ jsx3(Field, { label: "Layout", htmlFor: `${idBase}-layout`, children: /* @__PURE__ */ jsxs3("select", { id: `${idBase}-layout`, className: chartInputClass, value: "", "data-option": "preset.layout", onChange: (e) => e.currentTarget.value && controller.applyPreset({ layout: e.currentTarget.value }), children: [
|
|
536
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Quick layout\u2026" }),
|
|
537
|
+
CHART_LAYOUT_PRESETS.map((p) => /* @__PURE__ */ jsx3("option", { value: p.id, title: p.description, children: p.label }, p.id))
|
|
538
|
+
] }) }),
|
|
539
|
+
/* @__PURE__ */ jsx3(Field, { label: "Colours", htmlFor: `${idBase}-colors`, children: /* @__PURE__ */ jsxs3("select", { id: `${idBase}-colors`, className: chartInputClass, value: 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: [
|
|
540
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Custom" }),
|
|
541
|
+
CHART_COLOR_SCHEMES.map((c) => /* @__PURE__ */ jsx3("option", { value: c.id, title: c.description, children: c.label }, c.id))
|
|
542
|
+
] }) }),
|
|
543
|
+
!colorCheck.ok && /* @__PURE__ */ jsx3("p", { className: "text-[11px] leading-snug text-amber-600 dark:text-amber-400", "data-chart-color-warning": "", children: colorCheck.issues[0] }),
|
|
544
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "data-action": "transpose", onClick: () => controller.transpose(), children: "Switch rows and columns" })
|
|
545
|
+
] }),
|
|
546
|
+
show("titles") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "titles", children: [
|
|
424
547
|
/* @__PURE__ */ jsx3(Field, { label: "Title", htmlFor: `${idBase}-title`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-title`, value: spec.title, "data-option": "title", className: chartInputClass, onCommit: (title) => void controller.setTitle(title) }) }),
|
|
425
548
|
/* @__PURE__ */ jsx3(Field, { label: "Subtitle", htmlFor: `${idBase}-subtitle`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-subtitle`, value: spec.subtitle, "data-option": "subtitle", className: chartInputClass, onCommit: (subtitle) => void controller.setSubtitle(subtitle) }) })
|
|
426
549
|
] }),
|
|
427
|
-
show("options") && /* @__PURE__ */
|
|
550
|
+
show("options") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "options", children: [
|
|
428
551
|
cartesian && /* @__PURE__ */ jsx3(Field, { label: "Stacking", htmlFor: `${idBase}-stacking`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-stacking`, name: "stacking", value: spec.stacking, options: CHART_STACKINGS, labels: { none: "None", stacked: "Stacked", percent: "100%" }, onChange: (stacking) => controller.update({ stacking }) }) }),
|
|
429
552
|
hasBars && /* @__PURE__ */ jsx3(Field, { label: "Bars", children: /* @__PURE__ */ jsx3("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: ["vertical", "horizontal"].map((o) => /* @__PURE__ */ jsx3("button", { type: "button", "aria-pressed": spec.orientation === o, "data-option": `orientation.${o}`, className: chartSegmentClass(spec.orientation === o), onClick: () => controller.update({ orientation: o }), children: cap(o) }, o)) }) }),
|
|
430
553
|
hasLines && /* @__PURE__ */ jsx3(Field, { label: "Lines", htmlFor: `${idBase}-curve`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-curve`, name: "curve", value: spec.curve, options: CHART_CURVES, onChange: (curve) => controller.update({ curve }) }) }),
|
|
@@ -444,26 +567,46 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
444
567
|
onChange: (e) => controller.update({ innerRadius: Number(e.currentTarget.value) })
|
|
445
568
|
}
|
|
446
569
|
) }),
|
|
447
|
-
/* @__PURE__ */
|
|
570
|
+
hasBars && /* @__PURE__ */ jsx3(Field, { label: "Spacing", htmlFor: `${idBase}-gap`, children: /* @__PURE__ */ jsx3(
|
|
571
|
+
"input",
|
|
572
|
+
{
|
|
573
|
+
id: `${idBase}-gap`,
|
|
574
|
+
type: "range",
|
|
575
|
+
min: 0,
|
|
576
|
+
max: 0.9,
|
|
577
|
+
step: 0.02,
|
|
578
|
+
value: spec.gapWidth,
|
|
579
|
+
"data-option": "gapWidth",
|
|
580
|
+
className: "w-full accent-primary",
|
|
581
|
+
onPointerDown: () => controller.beginGesture("gapWidth"),
|
|
582
|
+
onPointerUp: () => controller.endGesture(),
|
|
583
|
+
onChange: (e) => controller.update({ gapWidth: Number(e.currentTarget.value) })
|
|
584
|
+
}
|
|
585
|
+
) }),
|
|
586
|
+
spec.dataLabels && hasBars && /* @__PURE__ */ jsx3(Field, { label: "Labels", htmlFor: `${idBase}-label-position`, children: /* @__PURE__ */ jsx3(Select, { id: `${idBase}-label-position`, name: "labelPosition", value: spec.labelPosition, options: CHART_LABEL_POSITIONS, onChange: (labelPosition) => controller.update({ labelPosition }) }) }),
|
|
587
|
+
spec.type === "histogram" && /* @__PURE__ */ jsx3(Field, { label: "Bins", htmlFor: `${idBase}-bins`, children: /* @__PURE__ */ jsx3(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 }) }),
|
|
588
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: [
|
|
589
|
+
spec.type === "waterfall" && /* @__PURE__ */ jsx3(Toggle, { label: "Total bar", name: "showTotal", checked: spec.showTotal, onChange: (showTotal) => controller.update({ showTotal }) }),
|
|
448
590
|
hasLines && /* @__PURE__ */ jsx3(Toggle, { label: "Points", name: "points", checked: spec.points, onChange: (points) => controller.update({ points }) }),
|
|
449
591
|
/* @__PURE__ */ jsx3(Toggle, { label: "Data labels", name: "dataLabels", checked: spec.dataLabels, onChange: (dataLabels) => controller.update({ dataLabels }) })
|
|
450
592
|
] })
|
|
451
593
|
] }),
|
|
452
|
-
show("legend") && /* @__PURE__ */
|
|
453
|
-
/* @__PURE__ */
|
|
594
|
+
show("legend") && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "legend", children: [
|
|
595
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
454
596
|
/* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Legend" }),
|
|
455
597
|
/* @__PURE__ */ jsx3(Toggle, { label: "Show", name: "legend.show", checked: spec.legend.show, onChange: (v) => controller.setLegend({ show: v }) })
|
|
456
598
|
] }),
|
|
457
599
|
spec.legend.show && /* @__PURE__ */ jsx3("div", { className: "flex gap-0.5 rounded-md bg-muted p-0.5", children: CHART_LEGEND_POSITIONS.map((p) => /* @__PURE__ */ jsx3("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)) })
|
|
458
600
|
] }),
|
|
459
|
-
show("axes") &&
|
|
460
|
-
/* @__PURE__ */ jsx3(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal" }),
|
|
461
|
-
/* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" })
|
|
601
|
+
show("axes") && hasAxes && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
602
|
+
family !== "box" && family !== "histogram" && /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "x", value: spec.xAxis, idBase, horizontal: spec.orientation === "horizontal", numeric: family === "xy" }),
|
|
603
|
+
/* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y", value: spec.yAxis, idBase, horizontal: spec.orientation === "horizontal" }),
|
|
604
|
+
usesSecondary && /* @__PURE__ */ jsx3(AxisSection, { controller, axis: "y2", value: spec.yAxis2, idBase, horizontal: spec.orientation === "horizontal" })
|
|
462
605
|
] }),
|
|
463
|
-
show("series") && selectedSeries && /* @__PURE__ */
|
|
606
|
+
show("series") && selectedSeries && /* @__PURE__ */ jsxs3("div", { className: chartSectionClass, "data-section": "series", "data-series-id": selectedSeries.id, children: [
|
|
464
607
|
/* @__PURE__ */ jsx3("h4", { className: "text-xs font-semibold text-foreground", children: "Series" }),
|
|
465
608
|
/* @__PURE__ */ jsx3(Field, { label: "Name", htmlFor: `${idBase}-series-name`, children: /* @__PURE__ */ jsx3(ChartCommitInput, { id: `${idBase}-series-name`, value: selectedSeries.name, "data-option": "series.name", className: chartInputClass, onCommit: (name) => void controller.updateSeries(selectedSeries.id, { name }) }) }),
|
|
466
|
-
/* @__PURE__ */ jsx3(Field, { label: "Colour", htmlFor: `${idBase}-series-color`, children: /* @__PURE__ */
|
|
609
|
+
/* @__PURE__ */ jsx3(Field, { label: "Colour", htmlFor: `${idBase}-series-color`, children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
467
610
|
/* @__PURE__ */ jsx3(
|
|
468
611
|
"input",
|
|
469
612
|
{
|
|
@@ -479,7 +622,7 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
479
622
|
),
|
|
480
623
|
selectedSeries.color && /* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "data-action": "reset-color", onClick: () => controller.updateSeries(selectedSeries.id, { color: null }), children: "Palette" })
|
|
481
624
|
] }) }),
|
|
482
|
-
cartesian && /* @__PURE__ */ jsx3(Field, { label: "Draw as", htmlFor: `${idBase}-series-type`, children: /* @__PURE__ */
|
|
625
|
+
cartesian && /* @__PURE__ */ jsx3(Field, { label: "Draw as", htmlFor: `${idBase}-series-type`, children: /* @__PURE__ */ jsxs3(
|
|
483
626
|
"select",
|
|
484
627
|
{
|
|
485
628
|
id: `${idBase}-series-type`,
|
|
@@ -493,8 +636,44 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
493
636
|
]
|
|
494
637
|
}
|
|
495
638
|
) }),
|
|
496
|
-
/* @__PURE__ */
|
|
639
|
+
(cartesian || family === "xy") && ["line", "area", "scatter"].includes(chartSeriesType(spec, selectedSeries)) && /* @__PURE__ */ jsx3(Field, { label: "Marker", htmlFor: `${idBase}-series-marker`, children: /* @__PURE__ */ jsxs3(
|
|
640
|
+
"select",
|
|
641
|
+
{
|
|
642
|
+
id: `${idBase}-series-marker`,
|
|
643
|
+
className: chartInputClass,
|
|
644
|
+
value: selectedSeries.marker ?? "",
|
|
645
|
+
"data-option": "series.marker",
|
|
646
|
+
onChange: (e) => controller.updateSeries(selectedSeries.id, { marker: e.currentTarget.value || null }),
|
|
647
|
+
children: [
|
|
648
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Default" }),
|
|
649
|
+
CHART_MARKERS.map((m) => /* @__PURE__ */ jsx3("option", { value: m, children: cap(m) }, m))
|
|
650
|
+
]
|
|
651
|
+
}
|
|
652
|
+
) }),
|
|
653
|
+
chartSupportsTrendlines(spec.type, spec.stacking) && /* @__PURE__ */ jsxs3("div", { className: "space-y-1", "data-section": "trendlines", children: [
|
|
654
|
+
(selectedSeries.trendlines ?? []).map((t, i) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", "data-trendline": i, children: [
|
|
655
|
+
/* @__PURE__ */ jsx3("span", { className: "flex-1 truncate text-muted-foreground", children: chartTrendlineName(t, selectedSeries.name) }),
|
|
656
|
+
/* @__PURE__ */ jsx3(Toggle, { label: "R\xB2", name: `trendline.${i}.showR2`, checked: !!t.showR2, onChange: (showR2) => controller.updateTrendline(selectedSeries.id, i, { showR2 }) }),
|
|
657
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: chartButtonClass, "aria-label": "Delete trendline", "data-action": "trendline-delete", onClick: () => controller.removeTrendline(selectedSeries.id, i), children: "\xD7" })
|
|
658
|
+
] }, i)),
|
|
659
|
+
/* @__PURE__ */ jsxs3(
|
|
660
|
+
"select",
|
|
661
|
+
{
|
|
662
|
+
className: chartInputClass,
|
|
663
|
+
value: "",
|
|
664
|
+
"aria-label": "Add trendline",
|
|
665
|
+
"data-option": "trendline.add",
|
|
666
|
+
onChange: (e) => e.currentTarget.value && controller.addTrendline(selectedSeries.id, { type: e.currentTarget.value }),
|
|
667
|
+
children: [
|
|
668
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Add trendline\u2026" }),
|
|
669
|
+
CHART_TRENDLINE_TYPES.map((t) => /* @__PURE__ */ jsx3("option", { value: t, children: cap(t.replace("-", " ")) }, t))
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
)
|
|
673
|
+
] }),
|
|
674
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
497
675
|
/* @__PURE__ */ jsx3(Toggle, { label: "Hidden", name: "series.hidden", checked: !!selectedSeries.hidden, onChange: (hidden) => controller.updateSeries(selectedSeries.id, { hidden }) }),
|
|
676
|
+
family === "cartesian" && /* @__PURE__ */ jsx3(Toggle, { label: "Secondary axis", name: "series.axis", checked: selectedSeries.axis === "secondary", onChange: (secondary) => controller.updateSeries(selectedSeries.id, { axis: secondary ? "secondary" : null }) }),
|
|
498
677
|
/* @__PURE__ */ jsx3("span", { className: "flex-1" }),
|
|
499
678
|
/* @__PURE__ */ jsx3("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" }),
|
|
500
679
|
/* @__PURE__ */ jsx3("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" }),
|
|
@@ -506,29 +685,95 @@ function ChartInspector({ controller, sections, className }) {
|
|
|
506
685
|
|
|
507
686
|
// src/chart/ChartEditor.tsx
|
|
508
687
|
import { describeChartSelection as describeChartSelection2 } from "@pixerate/editor/chart";
|
|
509
|
-
import { jsx as jsx4, jsxs as
|
|
688
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
510
689
|
function ChartEditor({ controller, showInspector = true, showDataGrid = true, showToolbar = true, height = 380, canvasProps, className }) {
|
|
511
690
|
const state = useChartState(controller);
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
showToolbar && /* @__PURE__ */
|
|
691
|
+
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-chart-editor": "", children: [
|
|
692
|
+
showToolbar && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", "data-chart-toolbar": "", children: [
|
|
514
693
|
/* @__PURE__ */ jsx4("button", { type: "button", className: chartButtonClass, disabled: !state.canUndo, title: state.undoLabel ? `Undo ${state.undoLabel}` : "Undo", "data-action": "undo", onClick: () => controller.undo(), children: "Undo" }),
|
|
515
694
|
/* @__PURE__ */ jsx4("button", { type: "button", className: chartButtonClass, disabled: !state.canRedo, title: state.redoLabel ? `Redo ${state.redoLabel}` : "Redo", "data-action": "redo", onClick: () => controller.redo(), children: "Redo" }),
|
|
516
695
|
/* @__PURE__ */ jsx4("span", { className: "ml-2 truncate text-xs text-muted-foreground", "data-chart-selection": "", "aria-live": "polite", children: describeChartSelection2(controller, state.selection) })
|
|
517
696
|
] }),
|
|
518
697
|
state.issues.length > 0 && /* @__PURE__ */ jsx4("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-2 py-1 text-xs text-destructive", "data-chart-issues": "", children: state.issues.map((i) => i.message).join(" ") }),
|
|
519
|
-
/* @__PURE__ */
|
|
698
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
520
699
|
/* @__PURE__ */ jsx4("div", { className: "min-w-0 flex-1 rounded-md border border-border bg-background p-1", children: /* @__PURE__ */ jsx4(ChartCanvas, { controller, height, ...canvasProps }) }),
|
|
521
700
|
showInspector && /* @__PURE__ */ jsx4(ChartInspector, { controller, className: "w-full shrink-0 overflow-y-auto rounded-md border border-border p-3 md:w-64" })
|
|
522
701
|
] }),
|
|
523
702
|
showDataGrid && /* @__PURE__ */ jsx4(ChartDataGrid, { controller })
|
|
524
703
|
] });
|
|
525
704
|
}
|
|
705
|
+
|
|
706
|
+
// src/chart/ChartStreamingView.tsx
|
|
707
|
+
import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
708
|
+
import {
|
|
709
|
+
CSS_VARIABLE_CHART_THEME as CSS_VARIABLE_CHART_THEME2,
|
|
710
|
+
ChartStream,
|
|
711
|
+
blendChartScenes,
|
|
712
|
+
easeChartTween,
|
|
713
|
+
layoutChart as layoutChart3,
|
|
714
|
+
layoutSparkline
|
|
715
|
+
} from "@pixerate/editor/chart";
|
|
716
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
717
|
+
function ChartStreamingView({ source, complete = false, format = "auto", width = 640, height = 360, theme, adoptInterval = 300, tween = 450, placeholder, onIssues, className }) {
|
|
718
|
+
const stream = useMemo3(() => new ChartStream(format), [format]);
|
|
719
|
+
const fullTheme = useMemo3(() => ({ ...CSS_VARIABLE_CHART_THEME2, ...theme }), [theme]);
|
|
720
|
+
const [scene, setScene] = useState3(null);
|
|
721
|
+
const shown = useRef4(null);
|
|
722
|
+
const lastAdopt = useRef4(0);
|
|
723
|
+
const frame = useRef4(null);
|
|
724
|
+
const pending = useRef4(null);
|
|
725
|
+
useEffect4(() => {
|
|
726
|
+
const adopt = () => {
|
|
727
|
+
const read = stream.update(source, complete);
|
|
728
|
+
if (complete && onIssues) onIssues(read.issues);
|
|
729
|
+
if (!read.spec) return;
|
|
730
|
+
const next = layoutChart3(read.spec, { width, height, theme: fullTheme, valueDomain: read.valueDomain });
|
|
731
|
+
const from = shown.current;
|
|
732
|
+
lastAdopt.current = Date.now();
|
|
733
|
+
if (frame.current !== null) cancelAnimationFrame(frame.current);
|
|
734
|
+
if (!from || tween <= 0 || typeof requestAnimationFrame === "undefined") {
|
|
735
|
+
shown.current = next;
|
|
736
|
+
setScene(next);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const start = performance.now();
|
|
740
|
+
const step = () => {
|
|
741
|
+
const t = easeChartTween((performance.now() - start) / tween);
|
|
742
|
+
const s = blendChartScenes(from, next, t);
|
|
743
|
+
shown.current = s;
|
|
744
|
+
setScene(s);
|
|
745
|
+
frame.current = t < 1 ? requestAnimationFrame(step) : null;
|
|
746
|
+
};
|
|
747
|
+
frame.current = requestAnimationFrame(step);
|
|
748
|
+
};
|
|
749
|
+
if (pending.current) clearTimeout(pending.current);
|
|
750
|
+
const wait = complete ? 0 : Math.max(0, adoptInterval - (Date.now() - lastAdopt.current));
|
|
751
|
+
if (wait === 0) adopt();
|
|
752
|
+
else pending.current = setTimeout(adopt, wait);
|
|
753
|
+
return () => {
|
|
754
|
+
if (pending.current) clearTimeout(pending.current);
|
|
755
|
+
};
|
|
756
|
+
}, [source, complete, stream, width, height, fullTheme, adoptInterval, tween, onIssues]);
|
|
757
|
+
useEffect4(() => () => {
|
|
758
|
+
if (frame.current !== null) cancelAnimationFrame(frame.current);
|
|
759
|
+
}, []);
|
|
760
|
+
return /* @__PURE__ */ jsx5("div", { className, "data-chart-streaming": complete ? "complete" : "streaming", "aria-busy": !complete, children: scene ? /* @__PURE__ */ jsx5(ChartSceneSvg, { scene, label: complete ? void 0 : "Chart loading" }) : placeholder ?? /* @__PURE__ */ jsx5("div", { style: { height }, className: "flex items-center justify-center text-xs text-muted-foreground", children: "Drawing chart\u2026" }) });
|
|
761
|
+
}
|
|
762
|
+
function ChartSparkline({ values, width = 80, height = 20, label, className, ...options }) {
|
|
763
|
+
const scene = useMemo3(() => layoutSparkline(values, { ...options, width, height }), [values, width, height, options.kind, options.color, options.negativeColor, options.markers, options.highLow, options.min, options.max]);
|
|
764
|
+
const present = values.filter((v) => v !== null);
|
|
765
|
+
return /* @__PURE__ */ jsx5(ChartSceneSvg, { scene, label: label ?? (present.length ? `Trend from ${present[0]} to ${present[present.length - 1]}` : "No data"), className: `inline-block align-middle ${className ?? ""}` });
|
|
766
|
+
}
|
|
526
767
|
export {
|
|
527
768
|
ChartCanvas,
|
|
528
769
|
ChartCommitInput,
|
|
529
770
|
ChartDataGrid,
|
|
530
771
|
ChartEditor,
|
|
531
772
|
ChartInspector,
|
|
773
|
+
ChartPresenceLayer,
|
|
774
|
+
ChartSceneSvg,
|
|
775
|
+
ChartSparkline,
|
|
776
|
+
ChartStreamingView,
|
|
532
777
|
chartButtonClass,
|
|
533
778
|
chartCellClass,
|
|
534
779
|
chartIconButtonClass,
|
|
@@ -536,9 +781,12 @@ export {
|
|
|
536
781
|
chartLabelClass,
|
|
537
782
|
chartSectionClass,
|
|
538
783
|
chartSegmentClass,
|
|
784
|
+
renderChartMark,
|
|
539
785
|
useChartCommands,
|
|
540
786
|
useChartController,
|
|
787
|
+
useChartDirector,
|
|
541
788
|
useChartEditor,
|
|
542
|
-
useChartState
|
|
789
|
+
useChartState,
|
|
790
|
+
useChartToolProvider
|
|
543
791
|
};
|
|
544
792
|
//# sourceMappingURL=index.js.map
|