@weasel-js/plot 0.0.0-stage → 1.15.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/LICENSE +21 -0
- package/README.md +93 -2
- package/dist/chunk-HA77BIQF.js +320 -0
- package/dist/chunk-HA77BIQF.js.map +1 -0
- package/dist/index.d.ts +1457 -0
- package/dist/index.js +3888 -0
- package/dist/index.js.map +1 -0
- package/dist/scales.d.ts +2 -0
- package/dist/scales.entry-BoLQpsxi.d.ts +275 -0
- package/dist/scales.js +3 -0
- package/dist/scales.js.map +1 -0
- package/package.json +54 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,3888 @@
|
|
|
1
|
+
import { ordinalScale, polarScale, niceDomain, linearScale, decimalsOf, timeScale, bandScale } from './chunk-HA77BIQF.js';
|
|
2
|
+
export { bandScale, decimalsOf, formatInstant, formatNumber, formatTick, linearScale, logScale, niceDomain, niceStep, ordinalScale, polarScale, stepTicks, tickDecimals, tickStep, timeScale } from './chunk-HA77BIQF.js';
|
|
3
|
+
import { ellipsePath, pathFromD, boundsOfPath } from '@weasel-js/geom';
|
|
4
|
+
import { THEMES } from '@weasel-js/theme';
|
|
5
|
+
import { measureTextBounds } from '@weasel-js/text';
|
|
6
|
+
import { isTextData, useLatest, composeWorldPose, scenePoseAdapter, composeAffordanceLayer, WeaselProvider, useScene, SceneCanvas } from '@weasel-js/core';
|
|
7
|
+
import { useMemo, useState, useEffect } from 'react';
|
|
8
|
+
import { jsx } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
var rectOf = (b) => ({ kind: "rect", x: b.x, y: b.y, width: b.width, height: b.height });
|
|
11
|
+
var fillOf = (color, opacity) => ({ paint: { color, ...opacity !== void 0 ? { opacity } : {} } });
|
|
12
|
+
function barMark(opts) {
|
|
13
|
+
const lo = Math.min(opts.from, opts.to);
|
|
14
|
+
const length = Math.abs(opts.to - opts.from);
|
|
15
|
+
const pose = opts.orient === "horizontal" ? { x: lo, y: opts.x, width: length, height: opts.width } : { x: opts.x, y: lo, width: opts.width, height: length };
|
|
16
|
+
return {
|
|
17
|
+
key: opts.key,
|
|
18
|
+
pose,
|
|
19
|
+
data: { plotMark: { kind: "bar", ...opts.datum ? { datum: opts.datum } : {} }, path: rectOf(pose), fill: fillOf(opts.color) }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function spanMark(opts) {
|
|
23
|
+
const pose = { x: Math.min(opts.x0, opts.x1), y: opts.y, width: Math.abs(opts.x1 - opts.x0), height: opts.height };
|
|
24
|
+
return {
|
|
25
|
+
key: opts.key,
|
|
26
|
+
pose,
|
|
27
|
+
data: {
|
|
28
|
+
plotMark: { kind: "span", ...opts.datum ? { datum: opts.datum } : {} },
|
|
29
|
+
path: rectOf(pose),
|
|
30
|
+
fill: fillOf(opts.color, opts.opacity)
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function pointMark(opts) {
|
|
35
|
+
const r = opts.radius ?? 3;
|
|
36
|
+
const pose = { x: opts.x - r, y: opts.y - r, width: 2 * r, height: 2 * r };
|
|
37
|
+
return {
|
|
38
|
+
key: opts.key,
|
|
39
|
+
pose,
|
|
40
|
+
data: { plotMark: { kind: "point", ...opts.datum ? { datum: opts.datum } : {} }, path: ellipsePath(pose), fill: fillOf(opts.color) }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var n = (v) => Number(v.toFixed(3));
|
|
44
|
+
var at = (p) => `${n(p.x)} ${n(p.y)}`;
|
|
45
|
+
function pathMark(key, path, data) {
|
|
46
|
+
const b = boundsOfPath(path);
|
|
47
|
+
return { key, pose: { x: b.x, y: b.y, width: b.width, height: b.height }, data: { ...data, path } };
|
|
48
|
+
}
|
|
49
|
+
var traitOf = (kind, datum) => ({ kind, ...datum ? { datum } : {} });
|
|
50
|
+
function lineMark(opts) {
|
|
51
|
+
const [first, ...rest] = opts.points;
|
|
52
|
+
if (first === void 0) throw new Error(`lineMark "${opts.key}": no points`);
|
|
53
|
+
const d = `M ${at(first)}${rest.map((p) => ` L ${at(p)}`).join("")}`;
|
|
54
|
+
const stroke = {
|
|
55
|
+
paint: { color: opts.color },
|
|
56
|
+
width: opts.width ?? 2,
|
|
57
|
+
join: "round",
|
|
58
|
+
cap: "round",
|
|
59
|
+
...opts.dash ? { dash: opts.dash } : {}
|
|
60
|
+
};
|
|
61
|
+
return pathMark(opts.key, pathFromD(d), { plotMark: traitOf("line", opts.datum), stroke });
|
|
62
|
+
}
|
|
63
|
+
function ruleMark(opts) {
|
|
64
|
+
const stroke = { paint: { color: opts.color }, width: opts.width ?? 1, cap: "butt", ...opts.dash ? { dash: opts.dash } : {} };
|
|
65
|
+
return pathMark(opts.key, pathFromD(`M ${at(opts.from)} L ${at(opts.to)}`), { plotMark: traitOf("rule", opts.datum), stroke });
|
|
66
|
+
}
|
|
67
|
+
function areaMark(opts) {
|
|
68
|
+
if (opts.top.length === 0) throw new Error(`areaMark "${opts.key}": no points`);
|
|
69
|
+
const bottom = opts.bottom ?? (opts.baseline !== void 0 ? opts.top.map((p) => ({ x: p.x, y: opts.baseline })) : []);
|
|
70
|
+
const ring = [...opts.top, ...[...bottom].reverse()];
|
|
71
|
+
const d = `M ${ring.map(at).join(" L ")} Z`;
|
|
72
|
+
return pathMark(opts.key, pathFromD(d), {
|
|
73
|
+
plotMark: traitOf("area", opts.datum),
|
|
74
|
+
fill: { paint: { color: opts.color, opacity: opts.opacity ?? 0.35 } }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
var polar = (c, r, a) => ({ x: c.x + r * Math.sin(a), y: c.y - r * Math.cos(a) });
|
|
78
|
+
function arcTo(c, r, a0, a1) {
|
|
79
|
+
const sweep = a1 >= a0 ? 1 : 0;
|
|
80
|
+
const mid = (a0 + a1) / 2;
|
|
81
|
+
const to = (a) => ` A ${n(r)} ${n(r)} 0 0 ${sweep} ${at(polar(c, r, a))}`;
|
|
82
|
+
return Math.abs(a1 - a0) > Math.PI ? to(mid) + to(a1) : to(a1);
|
|
83
|
+
}
|
|
84
|
+
function arcMark(opts) {
|
|
85
|
+
const { center: c, innerRadius: r0, outerRadius: r1, startAngle: a0 } = opts;
|
|
86
|
+
const a1 = Math.abs(opts.endAngle - a0) >= 2 * Math.PI ? a0 + Math.sign(opts.endAngle - a0) * (2 * Math.PI - 1e-6) : opts.endAngle;
|
|
87
|
+
let d = `M ${at(polar(c, r1, a0))}${arcTo(c, r1, a0, a1)}`;
|
|
88
|
+
d += r0 > 0 ? ` L ${at(polar(c, r0, a1))}${arcTo(c, r0, a1, a0)} Z` : ` L ${at(c)} Z`;
|
|
89
|
+
return pathMark(opts.key, pathFromD(d), { plotMark: traitOf("arc", opts.datum), fill: { paint: { color: opts.color } } });
|
|
90
|
+
}
|
|
91
|
+
function ribbonMark(opts) {
|
|
92
|
+
const { from: f, to: t } = opts;
|
|
93
|
+
const mx = (f.x + t.x) / 2;
|
|
94
|
+
const d = `M ${n(f.x)} ${n(f.y0)} C ${n(mx)} ${n(f.y0)} ${n(mx)} ${n(t.y0)} ${n(t.x)} ${n(t.y0)} L ${n(t.x)} ${n(t.y1)} C ${n(mx)} ${n(t.y1)} ${n(mx)} ${n(f.y1)} ${n(f.x)} ${n(f.y1)} Z`;
|
|
95
|
+
return pathMark(opts.key, pathFromD(d), {
|
|
96
|
+
plotMark: traitOf("ribbon", opts.datum),
|
|
97
|
+
fill: { paint: { color: opts.color, opacity: opts.opacity ?? 0.45 } }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/marks/text.ts
|
|
102
|
+
var ALIGN = { start: "left", middle: "center", end: "right" };
|
|
103
|
+
function textMark(opts) {
|
|
104
|
+
const { width: width2, height } = opts.measure(opts.text, opts.font);
|
|
105
|
+
const anchor = opts.anchor ?? "middle";
|
|
106
|
+
const baseline = opts.baseline ?? "middle";
|
|
107
|
+
const along = anchor === "start" ? 0 : anchor === "middle" ? -width2 / 2 : -width2;
|
|
108
|
+
const across = baseline === "top" ? 0 : baseline === "middle" ? -height / 2 : -height;
|
|
109
|
+
const center = opts.vertical ? { x: opts.x + across + height / 2, y: opts.y - along - width2 / 2 } : { x: opts.x + along + width2 / 2, y: opts.y + across + height / 2 };
|
|
110
|
+
return {
|
|
111
|
+
key: opts.key,
|
|
112
|
+
pose: {
|
|
113
|
+
x: center.x - width2 / 2,
|
|
114
|
+
y: center.y - height / 2,
|
|
115
|
+
width: width2,
|
|
116
|
+
height,
|
|
117
|
+
...opts.vertical ? { rotation: -Math.PI / 2 } : {}
|
|
118
|
+
},
|
|
119
|
+
data: {
|
|
120
|
+
plotMark: { kind: "text", ...opts.datum ? { datum: opts.datum } : {} },
|
|
121
|
+
text: opts.text,
|
|
122
|
+
style: { fontFamily: opts.font.fontFamily, fontSize: opts.font.fontSize, align: ALIGN[anchor], ...opts.bold ? { fontWeight: 700 } : {} },
|
|
123
|
+
fill: { paint: { color: opts.color } },
|
|
124
|
+
verticalAlign: "center"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/axes.ts
|
|
130
|
+
var TICK = 5;
|
|
131
|
+
var GAP = 4;
|
|
132
|
+
var isHorizontal = (side) => side === "top" || side === "bottom";
|
|
133
|
+
var outward = (side) => side === "bottom" || side === "right" ? 1 : -1;
|
|
134
|
+
function axisRoom(opts) {
|
|
135
|
+
const { scale, side, theme, measure } = opts;
|
|
136
|
+
const sizes = scale.ticks(opts.ticks).map((t) => measure(scale.format(t), theme.font));
|
|
137
|
+
const labels2 = sizes.length === 0 ? 0 : Math.max(...sizes.map((s) => isHorizontal(side) ? s.height : s.width));
|
|
138
|
+
const title = opts.title ? measure(opts.title, theme.font).height + GAP : 0;
|
|
139
|
+
return TICK + GAP + labels2 + title;
|
|
140
|
+
}
|
|
141
|
+
function axisMarks(opts) {
|
|
142
|
+
const { scale, side, at: at3, theme, measure } = opts;
|
|
143
|
+
const key = opts.key ?? `axis:${side}`;
|
|
144
|
+
const h = isHorizontal(side);
|
|
145
|
+
const out = outward(side);
|
|
146
|
+
const pt = (along, across) => h ? { x: along, y: across } : { x: across, y: along };
|
|
147
|
+
const [r0, r1] = scale.range;
|
|
148
|
+
const marks = [];
|
|
149
|
+
const ticks = scale.ticks(opts.ticks);
|
|
150
|
+
ticks.forEach((t, i) => {
|
|
151
|
+
const p = scale.position(t);
|
|
152
|
+
if (opts.gridTo !== void 0) {
|
|
153
|
+
marks.push(ruleMark({ key: `${key}:grid:${i}`, from: pt(p, at3), to: pt(p, opts.gridTo), color: theme.grid }));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
marks.push(ruleMark({ key: `${key}:rule`, from: pt(r0, at3), to: pt(r1, at3), color: theme.axis }));
|
|
157
|
+
let reach = 0;
|
|
158
|
+
ticks.forEach((t, i) => {
|
|
159
|
+
const p = scale.position(t);
|
|
160
|
+
marks.push(ruleMark({ key: `${key}:tick:${i}`, from: pt(p, at3), to: pt(p, at3 + out * TICK), color: theme.axis }));
|
|
161
|
+
const anchorAt = pt(p, at3 + out * (TICK + GAP));
|
|
162
|
+
const label = textMark({
|
|
163
|
+
key: `${key}:label:${i}`,
|
|
164
|
+
text: scale.format(t),
|
|
165
|
+
...anchorAt,
|
|
166
|
+
font: theme.font,
|
|
167
|
+
color: theme.text,
|
|
168
|
+
measure,
|
|
169
|
+
anchor: h ? "middle" : out > 0 ? "start" : "end",
|
|
170
|
+
baseline: h ? out > 0 ? "top" : "bottom" : "middle"
|
|
171
|
+
});
|
|
172
|
+
reach = Math.max(reach, h ? label.pose.height : label.pose.width);
|
|
173
|
+
marks.push(label);
|
|
174
|
+
});
|
|
175
|
+
if (opts.title) {
|
|
176
|
+
const across = at3 + out * (TICK + GAP + reach + GAP);
|
|
177
|
+
marks.push(textMark({
|
|
178
|
+
key: `${key}:title`,
|
|
179
|
+
text: opts.title,
|
|
180
|
+
...pt((r0 + r1) / 2, across),
|
|
181
|
+
font: theme.font,
|
|
182
|
+
color: theme.text,
|
|
183
|
+
measure,
|
|
184
|
+
anchor: "middle",
|
|
185
|
+
baseline: out > 0 ? "top" : "bottom",
|
|
186
|
+
vertical: !h
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
return marks;
|
|
190
|
+
}
|
|
191
|
+
var SWATCHES = ["sky", "amber", "green", "rose", "violet", "teal", "citron", "fuchsia", "blue", "red"];
|
|
192
|
+
function plotThemeFrom(resolved, font = PLOT_FONT) {
|
|
193
|
+
const at3 = (token) => resolved[`--wzl-${token}`] ?? "#888888";
|
|
194
|
+
return {
|
|
195
|
+
text: at3("fg"),
|
|
196
|
+
axis: at3("border-strong"),
|
|
197
|
+
grid: at3("border"),
|
|
198
|
+
series: SWATCHES.map((s) => at3(`swatch-${s}`)),
|
|
199
|
+
font
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
var PLOT_FONT = { fontFamily: "sans-serif", fontSize: 12 };
|
|
203
|
+
var PLOT_THEME = plotThemeFrom(THEMES.weasel.selections["mode=dark,density=comfortable"]);
|
|
204
|
+
var seriesColor = (theme, i) => theme.series[(i % theme.series.length + theme.series.length) % theme.series.length] ?? theme.text;
|
|
205
|
+
var textMeasure = (text, font) => {
|
|
206
|
+
const { width: width2 } = measureTextBounds(text, { fontFamily: font.fontFamily, fontSize: font.fontSize });
|
|
207
|
+
return { width: Math.ceil(width2), height: Math.ceil(font.fontSize * 1.2) };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// src/chartTitle.ts
|
|
211
|
+
var CHART_PAD = 12;
|
|
212
|
+
var GAP2 = 8;
|
|
213
|
+
function chartTitle(title, width2, theme, measure) {
|
|
214
|
+
if (!title) return { marks: [], top: CHART_PAD };
|
|
215
|
+
const mark = textMark({
|
|
216
|
+
key: "title",
|
|
217
|
+
text: title,
|
|
218
|
+
x: width2 / 2,
|
|
219
|
+
y: CHART_PAD,
|
|
220
|
+
baseline: "top",
|
|
221
|
+
font: theme.font,
|
|
222
|
+
color: theme.text,
|
|
223
|
+
measure,
|
|
224
|
+
bold: true,
|
|
225
|
+
datum: { edit: "title" }
|
|
226
|
+
});
|
|
227
|
+
return { marks: [mark], top: CHART_PAD + mark.pose.height + GAP2 };
|
|
228
|
+
}
|
|
229
|
+
function withTitle(data, text) {
|
|
230
|
+
const title = text.trim();
|
|
231
|
+
if ((data.title ?? "") === title) return data;
|
|
232
|
+
const { title: _, ...rest } = data;
|
|
233
|
+
return title === "" ? rest : { ...rest, title };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/gantt/dates.ts
|
|
237
|
+
var TOKENS = ["YYYY", "SSS", "YY", "MM", "DD", "HH", "mm", "ss", "M", "D", "H", "m", "s", "X", "x"];
|
|
238
|
+
var PATTERN = {
|
|
239
|
+
YYYY: "\\d{4}",
|
|
240
|
+
YY: "\\d{2}",
|
|
241
|
+
MM: "\\d{2}",
|
|
242
|
+
M: "\\d{1,2}",
|
|
243
|
+
DD: "\\d{2}",
|
|
244
|
+
D: "\\d{1,2}",
|
|
245
|
+
HH: "\\d{2}",
|
|
246
|
+
H: "\\d{1,2}",
|
|
247
|
+
mm: "\\d{2}",
|
|
248
|
+
m: "\\d{1,2}",
|
|
249
|
+
ss: "\\d{2}",
|
|
250
|
+
s: "\\d{1,2}",
|
|
251
|
+
SSS: "\\d{3}",
|
|
252
|
+
X: "[+-]?\\d+(?:\\.\\d+)?",
|
|
253
|
+
x: "[+-]?\\d+"
|
|
254
|
+
};
|
|
255
|
+
var GANTT_DATE_FORMAT = "YYYY-MM-DD";
|
|
256
|
+
function partsOf(format) {
|
|
257
|
+
const parts = [];
|
|
258
|
+
let literal = "";
|
|
259
|
+
for (let i = 0; i < format.length; ) {
|
|
260
|
+
const close = format[i] === "[" ? format.indexOf("]", i) : -1;
|
|
261
|
+
if (close > i) {
|
|
262
|
+
literal += format.slice(i + 1, close);
|
|
263
|
+
i = close + 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const token = TOKENS.find((t) => format.startsWith(t, i));
|
|
267
|
+
if (token === void 0) {
|
|
268
|
+
literal += format[i];
|
|
269
|
+
i++;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (literal !== "") parts.push({ literal });
|
|
273
|
+
literal = "";
|
|
274
|
+
parts.push({ token });
|
|
275
|
+
i += token.length;
|
|
276
|
+
}
|
|
277
|
+
if (literal !== "") parts.push({ literal });
|
|
278
|
+
return parts;
|
|
279
|
+
}
|
|
280
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
281
|
+
var formatHasHours = (format) => partsOf(format).some((p) => "token" in p && ["HH", "H", "X", "x"].includes(p.token));
|
|
282
|
+
function parseGanttDate(text, format) {
|
|
283
|
+
const parts = partsOf(format);
|
|
284
|
+
const m = new RegExp(`^${parts.map((p) => "token" in p ? `(${PATTERN[p.token]})` : escapeRe(p.literal)).join("")}$`).exec(text.trim());
|
|
285
|
+
if (m === null) return null;
|
|
286
|
+
const f = { year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, ms: 0 };
|
|
287
|
+
let group = 1;
|
|
288
|
+
for (const p of parts) {
|
|
289
|
+
if (!("token" in p)) continue;
|
|
290
|
+
const n2 = Number(m[group++]);
|
|
291
|
+
switch (p.token) {
|
|
292
|
+
case "X":
|
|
293
|
+
return n2 * 1e3;
|
|
294
|
+
case "x":
|
|
295
|
+
return n2;
|
|
296
|
+
case "YYYY":
|
|
297
|
+
f.year = n2;
|
|
298
|
+
break;
|
|
299
|
+
case "YY":
|
|
300
|
+
f.year = n2 + (n2 > 68 ? 1900 : 2e3);
|
|
301
|
+
break;
|
|
302
|
+
case "MM":
|
|
303
|
+
case "M":
|
|
304
|
+
f.month = n2;
|
|
305
|
+
break;
|
|
306
|
+
case "DD":
|
|
307
|
+
case "D":
|
|
308
|
+
f.day = n2;
|
|
309
|
+
break;
|
|
310
|
+
case "HH":
|
|
311
|
+
case "H":
|
|
312
|
+
f.hour = n2;
|
|
313
|
+
break;
|
|
314
|
+
case "mm":
|
|
315
|
+
case "m":
|
|
316
|
+
f.minute = n2;
|
|
317
|
+
break;
|
|
318
|
+
case "ss":
|
|
319
|
+
case "s":
|
|
320
|
+
f.second = n2;
|
|
321
|
+
break;
|
|
322
|
+
case "SSS":
|
|
323
|
+
f.ms = n2;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (f.month < 1 || f.month > 12 || f.day < 1 || f.hour > 23 || f.minute > 59 || f.second > 59) return null;
|
|
328
|
+
const t = Date.UTC(f.year, f.month - 1, f.day, f.hour, f.minute, f.second, f.ms);
|
|
329
|
+
return new Date(t).getUTCDate() === f.day ? t : null;
|
|
330
|
+
}
|
|
331
|
+
var pad = (n2, width2 = 2) => String(n2).padStart(width2, "0");
|
|
332
|
+
function formatGanttDate(t, format) {
|
|
333
|
+
const d = new Date(t);
|
|
334
|
+
const field = {
|
|
335
|
+
YYYY: () => pad(d.getUTCFullYear(), 4),
|
|
336
|
+
YY: () => pad(d.getUTCFullYear() % 100),
|
|
337
|
+
MM: () => pad(d.getUTCMonth() + 1),
|
|
338
|
+
M: () => String(d.getUTCMonth() + 1),
|
|
339
|
+
DD: () => pad(d.getUTCDate()),
|
|
340
|
+
D: () => String(d.getUTCDate()),
|
|
341
|
+
HH: () => pad(d.getUTCHours()),
|
|
342
|
+
H: () => String(d.getUTCHours()),
|
|
343
|
+
mm: () => pad(d.getUTCMinutes()),
|
|
344
|
+
m: () => String(d.getUTCMinutes()),
|
|
345
|
+
ss: () => pad(d.getUTCSeconds()),
|
|
346
|
+
s: () => String(d.getUTCSeconds()),
|
|
347
|
+
SSS: () => pad(d.getUTCMilliseconds(), 3),
|
|
348
|
+
X: () => String(t / 1e3),
|
|
349
|
+
x: () => String(t)
|
|
350
|
+
};
|
|
351
|
+
return partsOf(format).map((p) => "token" in p ? field[p.token]() : p.literal).join("");
|
|
352
|
+
}
|
|
353
|
+
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
354
|
+
var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
355
|
+
function strftime(t, format) {
|
|
356
|
+
const d = new Date(t);
|
|
357
|
+
const hour = d.getUTCHours();
|
|
358
|
+
const yearDay = Math.floor((t - Date.UTC(d.getUTCFullYear(), 0, 1)) / 864e5) + 1;
|
|
359
|
+
const field = {
|
|
360
|
+
Y: () => String(d.getUTCFullYear()),
|
|
361
|
+
y: () => pad(d.getUTCFullYear() % 100),
|
|
362
|
+
m: () => pad(d.getUTCMonth() + 1),
|
|
363
|
+
d: () => pad(d.getUTCDate()),
|
|
364
|
+
e: () => String(d.getUTCDate()).padStart(2, " "),
|
|
365
|
+
b: () => MONTHS[d.getUTCMonth()].slice(0, 3),
|
|
366
|
+
B: () => MONTHS[d.getUTCMonth()],
|
|
367
|
+
a: () => DAYS[d.getUTCDay()].slice(0, 3),
|
|
368
|
+
A: () => DAYS[d.getUTCDay()],
|
|
369
|
+
H: () => pad(hour),
|
|
370
|
+
I: () => pad(hour % 12 === 0 ? 12 : hour % 12),
|
|
371
|
+
M: () => pad(d.getUTCMinutes()),
|
|
372
|
+
S: () => pad(d.getUTCSeconds()),
|
|
373
|
+
p: () => hour < 12 ? "AM" : "PM",
|
|
374
|
+
j: () => pad(yearDay, 3),
|
|
375
|
+
"%": () => "%"
|
|
376
|
+
};
|
|
377
|
+
return format.replace(/%(.)/g, (all, c) => field[c]?.() ?? all);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/gantt/schedule.ts
|
|
381
|
+
var DAY = 864e5;
|
|
382
|
+
var HOUR = 36e5;
|
|
383
|
+
var FIXED = { ms: 1, s: 1e3, m: 6e4, h: HOUR };
|
|
384
|
+
var WALK_LIMIT = 1e5;
|
|
385
|
+
var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
386
|
+
var dateFormatOf = (data) => data.dateFormat?.trim() || GANTT_DATE_FORMAT;
|
|
387
|
+
var dayOf = (t) => Math.floor(t / DAY);
|
|
388
|
+
function excludedDays(data) {
|
|
389
|
+
const format = dateFormatOf(data);
|
|
390
|
+
const weekdays = /* @__PURE__ */ new Set();
|
|
391
|
+
const dates = /* @__PURE__ */ new Set();
|
|
392
|
+
for (const raw of data.excludes ?? []) {
|
|
393
|
+
const word = raw.trim().toLowerCase();
|
|
394
|
+
if (word === "weekends") {
|
|
395
|
+
weekdays.add(0);
|
|
396
|
+
weekdays.add(6);
|
|
397
|
+
} else if (WEEKDAYS.includes(word)) {
|
|
398
|
+
weekdays.add(WEEKDAYS.indexOf(word));
|
|
399
|
+
} else {
|
|
400
|
+
const t = parseGanttDate(raw, format);
|
|
401
|
+
if (t !== null) dates.add(dayOf(t));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (weekdays.size === 7) return () => false;
|
|
405
|
+
return (t) => weekdays.has(new Date(t).getUTCDay()) || dates.has(dayOf(t));
|
|
406
|
+
}
|
|
407
|
+
function addMonths(t, months) {
|
|
408
|
+
const whole = Math.trunc(months);
|
|
409
|
+
const d = new Date(t);
|
|
410
|
+
d.setUTCMonth(d.getUTCMonth() + whole);
|
|
411
|
+
return d.getTime() + (months - whole) * 30 * DAY;
|
|
412
|
+
}
|
|
413
|
+
function addGanttDuration(start, amount, unit2, excluded) {
|
|
414
|
+
const n2 = Number.isFinite(amount) ? amount : 0;
|
|
415
|
+
const fixed = FIXED[unit2];
|
|
416
|
+
if (fixed !== void 0) return start + n2 * fixed;
|
|
417
|
+
if (unit2 === "M" || unit2 === "y") return addMonths(start, unit2 === "y" ? n2 * 12 : n2);
|
|
418
|
+
const days = unit2 === "w" ? n2 * 7 : n2;
|
|
419
|
+
let end = start + days * DAY;
|
|
420
|
+
if (days > WALK_LIMIT) return end;
|
|
421
|
+
for (let t = start; t < end; t += DAY) if (excluded(t)) end += DAY;
|
|
422
|
+
return end;
|
|
423
|
+
}
|
|
424
|
+
function ganttDurationBetween(start, end, unit2, excluded) {
|
|
425
|
+
const fixed = FIXED[unit2];
|
|
426
|
+
if (fixed !== void 0) return Math.round((end - start) / fixed);
|
|
427
|
+
if (unit2 === "M" || unit2 === "y") return Math.round((end - start) / ((unit2 === "y" ? 365.25 : 30.44) * DAY));
|
|
428
|
+
const steps = Math.min(WALK_LIMIT, Math.round((end - start) / DAY));
|
|
429
|
+
let days = 0;
|
|
430
|
+
for (let k = 0; k < steps; k++) if (!excluded(start + k * DAY)) days++;
|
|
431
|
+
return unit2 === "w" ? Math.round(days / 7) : days;
|
|
432
|
+
}
|
|
433
|
+
function ganttTasks(data) {
|
|
434
|
+
return data.sections.flatMap((s, section) => s.tasks.map((task, index) => ({ task, section, index })));
|
|
435
|
+
}
|
|
436
|
+
function scheduleGantt(data, today = Date.now()) {
|
|
437
|
+
const format = dateFormatOf(data);
|
|
438
|
+
const excluded = excludedDays(data);
|
|
439
|
+
const flat = ganttTasks(data);
|
|
440
|
+
const byId = /* @__PURE__ */ new Map();
|
|
441
|
+
flat.forEach(({ task }, i) => {
|
|
442
|
+
if (task.id !== void 0 && !byId.has(task.id)) byId.set(task.id, i);
|
|
443
|
+
});
|
|
444
|
+
const written = flat.flatMap(({ task }) => task.start.kind === "date" ? [parseGanttDate(task.start.date, format)] : []).filter((t) => t !== null);
|
|
445
|
+
const first = written.length > 0 ? Math.min(...written) : dayOf(today) * DAY;
|
|
446
|
+
const starts = [];
|
|
447
|
+
const ends = [];
|
|
448
|
+
const busy = /* @__PURE__ */ new Set();
|
|
449
|
+
const memo = (cache, i, tag, compute) => {
|
|
450
|
+
if (cache[i] !== void 0) return cache[i];
|
|
451
|
+
if (busy.has(tag + i)) return first;
|
|
452
|
+
busy.add(tag + i);
|
|
453
|
+
const value = compute();
|
|
454
|
+
busy.delete(tag + i);
|
|
455
|
+
cache[i] = value;
|
|
456
|
+
return value;
|
|
457
|
+
};
|
|
458
|
+
const named2 = (ids2, of) => ids2.map((id) => byId.has(id) ? of(byId.get(id)) : first);
|
|
459
|
+
const startOf = (i) => memo(starts, i, "s", () => {
|
|
460
|
+
const start = flat[i].task.start;
|
|
461
|
+
if (start.kind === "date") return parseGanttDate(start.date, format) ?? first;
|
|
462
|
+
if (start.kind === "previous") return i === 0 ? first : endOf(i - 1);
|
|
463
|
+
const after = named2(start.ids, endOf);
|
|
464
|
+
return after.length > 0 ? Math.max(...after) : first;
|
|
465
|
+
});
|
|
466
|
+
const endOf = (i) => memo(ends, i, "e", () => {
|
|
467
|
+
const start = startOf(i);
|
|
468
|
+
const end = flat[i].task.end;
|
|
469
|
+
let at3;
|
|
470
|
+
if (end.kind === "date") at3 = parseGanttDate(end.date, format) ?? start;
|
|
471
|
+
else if (end.kind === "duration") at3 = addGanttDuration(start, end.amount, end.unit, excluded);
|
|
472
|
+
else {
|
|
473
|
+
const until = named2(end.ids, startOf);
|
|
474
|
+
at3 = until.length > 0 ? Math.min(...until) : start;
|
|
475
|
+
}
|
|
476
|
+
return Math.max(start, at3);
|
|
477
|
+
});
|
|
478
|
+
flat.forEach((_, i) => endOf(i));
|
|
479
|
+
const spans = data.sections.map(() => []);
|
|
480
|
+
flat.forEach(({ section }, i) => spans[section].push({ start: starts[i], end: Math.max(starts[i], ends[i]) }));
|
|
481
|
+
const all = spans.flat();
|
|
482
|
+
const lo = all.length > 0 ? Math.min(...all.map((s) => s.start)) : first;
|
|
483
|
+
const hi = all.length > 0 ? Math.max(...all.map((s) => s.end)) : first;
|
|
484
|
+
return { spans, domain: [lo, hi > lo ? hi : lo + DAY] };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/gantt/compile.ts
|
|
488
|
+
var ROW_MAX = 32;
|
|
489
|
+
var BAR = 0.7;
|
|
490
|
+
var GAP3 = 8;
|
|
491
|
+
var LABEL_GAP = 4;
|
|
492
|
+
function ganttTimeScale(domain, range, axisFormat) {
|
|
493
|
+
const scale = timeScale({ domain, range });
|
|
494
|
+
return axisFormat ? { ...scale, format: (v) => strftime(v, axisFormat) } : scale;
|
|
495
|
+
}
|
|
496
|
+
function ganttTaskFill(tags, theme) {
|
|
497
|
+
const done = tags?.includes("done") ?? false;
|
|
498
|
+
if (tags?.includes("crit")) return { color: seriesColor(theme, 3), ...done ? { opacity: 0.5 } : {} };
|
|
499
|
+
if (done) return { color: theme.axis };
|
|
500
|
+
if (tags?.includes("active")) return { color: seriesColor(theme, 1) };
|
|
501
|
+
return { color: seriesColor(theme, 0) };
|
|
502
|
+
}
|
|
503
|
+
function todayStroke(style, theme) {
|
|
504
|
+
const color = /(?:^|[;,])\s*stroke\s*:\s*([^;,]+)/.exec(style ?? "")?.[1]?.trim();
|
|
505
|
+
const width2 = Number(/stroke-width\s*:\s*([\d.]+)/.exec(style ?? "")?.[1]);
|
|
506
|
+
return { color: color || seriesColor(theme, 9), width: Number.isFinite(width2) && width2 > 0 ? width2 : 2 };
|
|
507
|
+
}
|
|
508
|
+
function compileGantt(data, size, theme, measure) {
|
|
509
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
510
|
+
const schedule = scheduleGantt(data);
|
|
511
|
+
const names = data.sections.map((s) => s.name ?? "");
|
|
512
|
+
const sectionWidth = names.some((n2) => n2 !== "") ? Math.max(...names.map((n2) => measure(n2, theme.font).width)) + 2 * GAP3 : 0;
|
|
513
|
+
const left = CHART_PAD + sectionWidth;
|
|
514
|
+
const right = Math.max(left + 1, size.width - CHART_PAD);
|
|
515
|
+
const axis = { theme, measure, side: "bottom" };
|
|
516
|
+
const room = axisRoom({ ...axis, scale: ganttTimeScale(schedule.domain, [0, 1], data.axisFormat)});
|
|
517
|
+
const rows = data.sections.reduce((n2, s) => n2 + s.tasks.length, 0);
|
|
518
|
+
const avail = Math.max(1, size.height - CHART_PAD - room - top);
|
|
519
|
+
const rowHeight = rows === 0 ? 0 : Math.min(ROW_MAX, avail / rows);
|
|
520
|
+
const bottom = top + (rows === 0 ? avail : rowHeight * rows);
|
|
521
|
+
const time = ganttTimeScale(schedule.domain, [left, right], data.axisFormat);
|
|
522
|
+
const layout = {
|
|
523
|
+
plot: { x: left, y: top, width: right - left, height: bottom - top },
|
|
524
|
+
time,
|
|
525
|
+
schedule,
|
|
526
|
+
snap: formatHasHours(dateFormatOf(data)) ? DAY / 24 : DAY
|
|
527
|
+
};
|
|
528
|
+
let row = 0;
|
|
529
|
+
data.sections.forEach((section, s) => {
|
|
530
|
+
const y = top + row * rowHeight;
|
|
531
|
+
const height = section.tasks.length * rowHeight;
|
|
532
|
+
row += section.tasks.length;
|
|
533
|
+
if (height === 0) return;
|
|
534
|
+
marks.push(spanMark({ key: `section:${s}:band`, x0: CHART_PAD, x1: right, y, height, color: theme.grid, opacity: s % 2 === 0 ? 0.45 : 0.2, datum: { section: s } }));
|
|
535
|
+
if (section.name) {
|
|
536
|
+
marks.push(textMark({
|
|
537
|
+
key: `section:${s}:label`,
|
|
538
|
+
text: section.name,
|
|
539
|
+
x: CHART_PAD + GAP3,
|
|
540
|
+
y: y + height / 2,
|
|
541
|
+
anchor: "start",
|
|
542
|
+
font: theme.font,
|
|
543
|
+
color: theme.text,
|
|
544
|
+
measure,
|
|
545
|
+
bold: true,
|
|
546
|
+
datum: { edit: "section", section: s }
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
marks.push(...axisMarks({ ...axis, key: "axis:time", scale: time, at: bottom, gridTo: top }));
|
|
551
|
+
const today = Date.now();
|
|
552
|
+
const [d0, d1] = schedule.domain;
|
|
553
|
+
if (data.todayMarker?.trim() !== "off" && today >= d0 && today <= d1) {
|
|
554
|
+
const x = time.map(today);
|
|
555
|
+
marks.push(ruleMark({ key: "today", from: { x, y: top }, to: { x, y: bottom }, ...todayStroke(data.todayMarker, theme) }));
|
|
556
|
+
}
|
|
557
|
+
marks.push(...taskMarks(data, layout, rowHeight, theme, measure));
|
|
558
|
+
return { marks, layout };
|
|
559
|
+
}
|
|
560
|
+
function taskMarks(data, layout, rowHeight, theme, measure) {
|
|
561
|
+
const { time, plot } = layout;
|
|
562
|
+
const barHeight = rowHeight * BAR;
|
|
563
|
+
const marks = [];
|
|
564
|
+
const labels2 = [];
|
|
565
|
+
let row = 0;
|
|
566
|
+
data.sections.forEach((section, s) => section.tasks.forEach((task, t) => {
|
|
567
|
+
const span = layout.schedule.spans[s][t];
|
|
568
|
+
const cy = plot.y + (row++ + 0.5) * rowHeight;
|
|
569
|
+
const fill = ganttTaskFill(task.tags, theme);
|
|
570
|
+
const datum = { section: s, task: t };
|
|
571
|
+
const key = `task:${s}:${t}`;
|
|
572
|
+
let x0 = time.map(span.start);
|
|
573
|
+
let x1 = time.map(span.end);
|
|
574
|
+
if (task.tags?.includes("milestone")) {
|
|
575
|
+
const r = barHeight / 2;
|
|
576
|
+
const points = [{ x: x0 - r, y: cy }, { x: x0, y: cy - r }, { x: x0 + r, y: cy }, { x: x0, y: cy + r }];
|
|
577
|
+
marks.push(areaMark({ key, top: points, bottom: [], color: fill.color, opacity: fill.opacity ?? 1, datum }));
|
|
578
|
+
[x0, x1] = [x0 - r, x0 + r];
|
|
579
|
+
} else {
|
|
580
|
+
marks.push(spanMark({ key, x0, x1, y: cy - barHeight / 2, height: barHeight, ...fill, datum }));
|
|
581
|
+
}
|
|
582
|
+
const width2 = measure(task.name, theme.font).width;
|
|
583
|
+
const inside = !task.tags?.includes("milestone") && width2 + 2 * LABEL_GAP <= x1 - x0;
|
|
584
|
+
const after = x1 + LABEL_GAP + width2 <= plot.x + plot.width;
|
|
585
|
+
const [x, anchor] = inside ? [(x0 + x1) / 2, "middle"] : after ? [x1 + LABEL_GAP, "start"] : [x0 - LABEL_GAP, "end"];
|
|
586
|
+
labels2.push(textMark({
|
|
587
|
+
key: `${key}:label`,
|
|
588
|
+
text: task.name,
|
|
589
|
+
x,
|
|
590
|
+
y: cy,
|
|
591
|
+
anchor,
|
|
592
|
+
font: theme.font,
|
|
593
|
+
color: theme.text,
|
|
594
|
+
measure,
|
|
595
|
+
datum: { edit: "task", section: s, task: t }
|
|
596
|
+
}));
|
|
597
|
+
}));
|
|
598
|
+
return [...marks, ...labels2];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/gitgraph/replay.ts
|
|
602
|
+
var headOf = (s, branch) => {
|
|
603
|
+
const b = s.branches.get(branch);
|
|
604
|
+
return b?.commits.at(-1) ?? b?.fork;
|
|
605
|
+
};
|
|
606
|
+
function add(s, command, id, rest) {
|
|
607
|
+
const seq = s.history.commits.length;
|
|
608
|
+
const name = id ?? `#${seq}`;
|
|
609
|
+
if (s.byId.has(name)) return `a commit "${name}" already exists`;
|
|
610
|
+
const commit = { command, id: name, branch: s.current, seq, ...rest };
|
|
611
|
+
s.history.commits.push(commit);
|
|
612
|
+
s.byId.set(name, commit);
|
|
613
|
+
s.branches.get(s.current).commits.push(name);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
function merge(s, k, cmd) {
|
|
617
|
+
const other = cmd.branch;
|
|
618
|
+
const head = headOf(s, s.current);
|
|
619
|
+
if (other === s.current) return `"${other}" cannot be merged into itself`;
|
|
620
|
+
if (head === void 0) return `cannot merge into "${s.current}": it has no commits`;
|
|
621
|
+
if (!s.branches.has(other)) return `cannot merge "${other}": there is no such branch`;
|
|
622
|
+
const theirs = headOf(s, other);
|
|
623
|
+
if (theirs === void 0) return `cannot merge "${other}": it has no commits`;
|
|
624
|
+
if (theirs === head) return `cannot merge "${other}" into "${s.current}": both have the same head`;
|
|
625
|
+
return add(s, k, cmd.id, { parents: [head, theirs], type: cmd.type ?? "NORMAL" });
|
|
626
|
+
}
|
|
627
|
+
function cherryPick(s, k, cmd) {
|
|
628
|
+
const source = s.byId.get(cmd.id);
|
|
629
|
+
const head = headOf(s, s.current);
|
|
630
|
+
if (source === void 0) return `cannot cherry-pick "${cmd.id}": there is no such commit`;
|
|
631
|
+
if (source.branch === s.current) return `cannot cherry-pick "${cmd.id}": it is already on "${s.current}"`;
|
|
632
|
+
if (head === void 0) return `cannot cherry-pick onto "${s.current}": it has no commits`;
|
|
633
|
+
if (source.parents.length > 1 && (cmd.parent === void 0 || !source.parents.includes(cmd.parent))) {
|
|
634
|
+
return `cherry-picking the merge "${cmd.id}" needs a parent, one of ${source.parents.map((p) => `"${p}"`).join(", ")}`;
|
|
635
|
+
}
|
|
636
|
+
if (source.parents.length <= 1 && cmd.parent !== void 0) return `"${cmd.id}" is not a merge, so takes no parent`;
|
|
637
|
+
return add(s, k, void 0, { parents: [head], type: "NORMAL", source: source.id });
|
|
638
|
+
}
|
|
639
|
+
function step(s, k, cmd) {
|
|
640
|
+
switch (cmd.kind) {
|
|
641
|
+
case "commit": {
|
|
642
|
+
const head = headOf(s, s.current);
|
|
643
|
+
return add(s, k, cmd.id, { parents: head === void 0 ? [] : [head], type: cmd.type ?? "NORMAL" });
|
|
644
|
+
}
|
|
645
|
+
case "branch": {
|
|
646
|
+
if (s.branches.has(cmd.name)) return `a branch "${cmd.name}" already exists; check it out instead`;
|
|
647
|
+
const fork = headOf(s, s.current);
|
|
648
|
+
s.branches.set(cmd.name, {
|
|
649
|
+
name: cmd.name,
|
|
650
|
+
created: s.branches.size,
|
|
651
|
+
commits: [],
|
|
652
|
+
...cmd.order !== void 0 ? { order: cmd.order } : {},
|
|
653
|
+
...fork !== void 0 ? { fork } : {}
|
|
654
|
+
});
|
|
655
|
+
s.current = cmd.name;
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
case "checkout":
|
|
659
|
+
if (!s.branches.has(cmd.name)) return `cannot check out "${cmd.name}": there is no such branch; create it with "branch ${cmd.name}"`;
|
|
660
|
+
s.current = cmd.name;
|
|
661
|
+
return null;
|
|
662
|
+
case "merge":
|
|
663
|
+
return merge(s, k, cmd);
|
|
664
|
+
case "cherry-pick":
|
|
665
|
+
return cherryPick(s, k, cmd);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function replayGitGraph(data) {
|
|
669
|
+
const main = data.mainBranch ?? "main";
|
|
670
|
+
const s = {
|
|
671
|
+
history: { commits: [], branches: [] },
|
|
672
|
+
byId: /* @__PURE__ */ new Map(),
|
|
673
|
+
current: main,
|
|
674
|
+
branches: /* @__PURE__ */ new Map([[main, { name: main, created: 0, commits: [] }]])
|
|
675
|
+
};
|
|
676
|
+
for (let k = 0; k < data.commands.length; k++) {
|
|
677
|
+
const error = step(s, k, data.commands[k]);
|
|
678
|
+
if (error !== null) {
|
|
679
|
+
s.history.error = { command: k, message: error };
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const rank = (b) => b.name === main ? Number.NEGATIVE_INFINITY : b.order ?? b.created;
|
|
684
|
+
s.history.branches = [...s.branches.values()].sort((a, b) => rank(a) - rank(b) || a.created - b.created).map(({ created: _, ...b }) => b);
|
|
685
|
+
return s.history;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/gitgraph/compile.ts
|
|
689
|
+
var RADIUS = 6;
|
|
690
|
+
var GAP4 = 6;
|
|
691
|
+
var MAX_STEP = 50;
|
|
692
|
+
var MAX_LANE = 60;
|
|
693
|
+
function place(data, history, box, labelRoom) {
|
|
694
|
+
const dir = data.orientation ?? "LR";
|
|
695
|
+
const lr = dir === "LR";
|
|
696
|
+
const along = lr ? { start: box.x + labelRoom, size: box.width - labelRoom } : { start: box.y + (dir === "TB" ? labelRoom : 0), size: box.height - labelRoom };
|
|
697
|
+
const across = lr ? { start: box.y, size: box.height } : { start: box.x, size: box.width };
|
|
698
|
+
const laneGap = Math.min(MAX_LANE, across.size / Math.max(1, history.branches.length));
|
|
699
|
+
const lanes = history.branches.map((_, i) => across.start + (i + 0.5) * laneGap);
|
|
700
|
+
const step2 = Math.min(MAX_STEP, Math.max(0, along.size) / Math.max(1, history.commits.length));
|
|
701
|
+
const laneOf = new Map(history.branches.map((b, i) => [b.name, lanes[i]]));
|
|
702
|
+
const points = history.commits.map((c) => {
|
|
703
|
+
const a = dir === "BT" ? along.start + along.size - (c.seq + 0.5) * step2 : along.start + (c.seq + 0.5) * step2;
|
|
704
|
+
const x = laneOf.get(c.branch);
|
|
705
|
+
return lr ? { x: a, y: x } : { x, y: a };
|
|
706
|
+
});
|
|
707
|
+
return { history, lanes, points };
|
|
708
|
+
}
|
|
709
|
+
var onLane = (lr, lane, p) => lr ? { x: p.x, y: lane } : { x: lane, y: p.y };
|
|
710
|
+
function lines(layout, lr, theme) {
|
|
711
|
+
const { history, lanes, points } = layout;
|
|
712
|
+
const at3 = new Map(history.commits.map((c, i) => [c.id, points[i]]));
|
|
713
|
+
const laneIndex = new Map(history.branches.map((b, i) => [b.name, i]));
|
|
714
|
+
const marks = [];
|
|
715
|
+
history.branches.forEach((b, i) => {
|
|
716
|
+
const own = b.commits.map((id) => at3.get(id));
|
|
717
|
+
const fork = b.fork === void 0 ? void 0 : at3.get(b.fork);
|
|
718
|
+
const path = fork !== void 0 && own.length > 0 ? [fork, onLane(lr, lanes[i], fork), ...own] : own;
|
|
719
|
+
if (path.length > 1) marks.push(lineMark({ key: `branch:${i}`, points: path, color: seriesColor(theme, i), datum: { branch: b.name } }));
|
|
720
|
+
});
|
|
721
|
+
for (const c of history.commits) {
|
|
722
|
+
const from = c.source ?? (c.parents.length > 1 ? c.parents[1] : void 0);
|
|
723
|
+
if (from === void 0) continue;
|
|
724
|
+
const source = history.commits.find((x) => x.id === from);
|
|
725
|
+
const lane = laneIndex.get(source.branch);
|
|
726
|
+
const to = at3.get(c.id);
|
|
727
|
+
marks.push(lineMark({
|
|
728
|
+
key: `${c.source !== void 0 ? "pick" : "merge"}:${c.command}`,
|
|
729
|
+
points: [at3.get(from), onLane(lr, lanes[lane], to), to],
|
|
730
|
+
color: seriesColor(theme, lane),
|
|
731
|
+
datum: { command: c.command },
|
|
732
|
+
...c.source !== void 0 ? { dash: [4, 3] } : {}
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
return marks;
|
|
736
|
+
}
|
|
737
|
+
function commitMarks(c, p, color, theme) {
|
|
738
|
+
const key = `commit:${c.command}`;
|
|
739
|
+
const datum = { command: c.command };
|
|
740
|
+
if (c.type === "HIGHLIGHT") return [barMark({ key, x: p.x - RADIUS, width: 2 * RADIUS, from: p.y - RADIUS, to: p.y + RADIUS, color, datum })];
|
|
741
|
+
const dot = pointMark({ key, ...p, radius: RADIUS, color, datum });
|
|
742
|
+
if (c.type !== "REVERSE") return [dot];
|
|
743
|
+
const r = RADIUS * 0.7;
|
|
744
|
+
return [
|
|
745
|
+
dot,
|
|
746
|
+
ruleMark({ key: `${key}:cross:0`, from: { x: p.x - r, y: p.y - r }, to: { x: p.x + r, y: p.y + r }, color: theme.text, width: 2 }),
|
|
747
|
+
ruleMark({ key: `${key}:cross:1`, from: { x: p.x - r, y: p.y + r }, to: { x: p.x + r, y: p.y - r }, color: theme.text, width: 2 })
|
|
748
|
+
];
|
|
749
|
+
}
|
|
750
|
+
function commitLabels(data, c, p, lr, theme, measure) {
|
|
751
|
+
const cmd = data.commands[c.command];
|
|
752
|
+
const small = { ...theme.font, fontSize: Math.round(theme.font.fontSize * 0.85) };
|
|
753
|
+
const off = RADIUS + 3;
|
|
754
|
+
const marks = [];
|
|
755
|
+
if ((cmd.kind === "commit" || cmd.kind === "merge") && cmd.id !== void 0) {
|
|
756
|
+
marks.push(textMark({
|
|
757
|
+
key: `commit:${c.command}:id`,
|
|
758
|
+
text: cmd.id,
|
|
759
|
+
font: small,
|
|
760
|
+
color: theme.text,
|
|
761
|
+
measure,
|
|
762
|
+
...lr ? { x: p.x, y: p.y + off, baseline: "top" } : { x: p.x + off, y: p.y, anchor: "start" }
|
|
763
|
+
}));
|
|
764
|
+
}
|
|
765
|
+
if (cmd.kind !== "branch" && cmd.kind !== "checkout" && cmd.tag !== void 0) {
|
|
766
|
+
marks.push(textMark({
|
|
767
|
+
key: `commit:${c.command}:tag`,
|
|
768
|
+
text: cmd.tag,
|
|
769
|
+
font: theme.font,
|
|
770
|
+
color: theme.text,
|
|
771
|
+
measure,
|
|
772
|
+
bold: true,
|
|
773
|
+
datum: { edit: "tag", command: c.command },
|
|
774
|
+
...lr ? { x: p.x, y: p.y - off, baseline: "bottom" } : { x: p.x - off, y: p.y, anchor: "end" }
|
|
775
|
+
}));
|
|
776
|
+
}
|
|
777
|
+
return marks;
|
|
778
|
+
}
|
|
779
|
+
function compileGitGraph(data, size, theme, measure) {
|
|
780
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
781
|
+
const dir = data.orientation ?? "LR";
|
|
782
|
+
const lr = dir === "LR";
|
|
783
|
+
const history = replayGitGraph(data);
|
|
784
|
+
const names = history.branches.map((b) => measure(b.name, theme.font));
|
|
785
|
+
const labelRoom = GAP4 + Math.max(0, ...names.map((s) => lr ? s.width : s.height));
|
|
786
|
+
const box = { x: CHART_PAD, y: top, width: Math.max(1, size.width - 2 * CHART_PAD), height: Math.max(1, size.height - CHART_PAD - top) };
|
|
787
|
+
const layout = place(data, history, box, labelRoom);
|
|
788
|
+
marks.push(...lines(layout, lr, theme));
|
|
789
|
+
history.commits.forEach((c, i) => {
|
|
790
|
+
const lane = history.branches.findIndex((b) => b.name === c.branch);
|
|
791
|
+
marks.push(...commitMarks(c, layout.points[i], seriesColor(theme, lane), theme));
|
|
792
|
+
});
|
|
793
|
+
history.commits.forEach((c, i) => marks.push(...commitLabels(data, c, layout.points[i], lr, theme, measure)));
|
|
794
|
+
history.branches.forEach((b, i) => {
|
|
795
|
+
const lane = layout.lanes[i];
|
|
796
|
+
const at3 = lr ? { x: box.x, y: lane, anchor: "start" } : { x: lane, y: dir === "TB" ? box.y : box.y + box.height, baseline: dir === "TB" ? "top" : "bottom" };
|
|
797
|
+
marks.push(textMark({ key: `branch:${i}:label`, text: b.name, ...at3, font: theme.font, color: seriesColor(theme, i), measure, bold: true, datum: { branch: b.name } }));
|
|
798
|
+
});
|
|
799
|
+
return { marks, layout };
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/journey/compile.ts
|
|
803
|
+
var GAP5 = 6;
|
|
804
|
+
var RADIUS2 = 8;
|
|
805
|
+
var DOT = 3;
|
|
806
|
+
var journeySteps = (data) => data.sections.flatMap((s, section) => s.tasks.map((_, task) => ({ section, task })));
|
|
807
|
+
var journeyActors = (data) => [...new Set(data.sections.flatMap((s) => s.tasks.flatMap((t) => t.actors)))];
|
|
808
|
+
function journeyLayout(data, size, top, legend2, theme, measure) {
|
|
809
|
+
const line = measure("Mg", theme.font).height;
|
|
810
|
+
const probe = linearScale({ domain: [0.5, 5.5], range: [0, 1] });
|
|
811
|
+
const left = CHART_PAD + axisRoom({ scale: probe, side: "left", theme, measure });
|
|
812
|
+
const right = size.width - CHART_PAD - (legend2 > 0 ? legend2 + 2 * GAP5 : 0);
|
|
813
|
+
const plotTop = top + line + 2 * GAP5;
|
|
814
|
+
const bottom = size.height - CHART_PAD - line - 3 * GAP5 - 2 * DOT;
|
|
815
|
+
const plot = { x: left, y: plotTop, width: Math.max(1, right - left), height: Math.max(1, bottom - plotTop) };
|
|
816
|
+
return {
|
|
817
|
+
plot,
|
|
818
|
+
score: linearScale({ domain: [0.5, 5.5], range: [plot.y + plot.height, plot.y] }),
|
|
819
|
+
steps: bandScale({ domain: journeySteps(data).map((_, k) => String(k)), range: [plot.x, plot.x + plot.width] })
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function sectionMarks(data, layout, theme, measure) {
|
|
823
|
+
const marks = [];
|
|
824
|
+
const height = measure("Mg", theme.font).height + GAP5;
|
|
825
|
+
let k = 0;
|
|
826
|
+
data.sections.forEach((section, s) => {
|
|
827
|
+
const first = k;
|
|
828
|
+
k += section.tasks.length;
|
|
829
|
+
if (section.tasks.length === 0) return;
|
|
830
|
+
const x0 = layout.steps.start(String(first));
|
|
831
|
+
const x1 = layout.steps.start(String(k - 1)) + layout.steps.bandwidth;
|
|
832
|
+
const y = layout.plot.y - GAP5 - height;
|
|
833
|
+
marks.push(spanMark({ key: `section:${s}`, x0, x1, y, height, color: seriesColor(theme, s), opacity: 0.35, datum: { section: s } }));
|
|
834
|
+
if (section.name !== void 0) {
|
|
835
|
+
marks.push(textMark({
|
|
836
|
+
key: `section:label:${s}`,
|
|
837
|
+
text: section.name,
|
|
838
|
+
x: (x0 + x1) / 2,
|
|
839
|
+
y: y + height / 2,
|
|
840
|
+
font: theme.font,
|
|
841
|
+
color: theme.text,
|
|
842
|
+
measure,
|
|
843
|
+
bold: true,
|
|
844
|
+
datum: { edit: "section", section: s }
|
|
845
|
+
}));
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return marks;
|
|
849
|
+
}
|
|
850
|
+
function stepMarks(data, layout, actors, theme, measure) {
|
|
851
|
+
const marks = [];
|
|
852
|
+
const baseline = layout.plot.y + layout.plot.height;
|
|
853
|
+
const line = measure("Mg", theme.font).height;
|
|
854
|
+
journeySteps(data).forEach(({ section, task }, k) => {
|
|
855
|
+
const step2 = data.sections[section].tasks[task];
|
|
856
|
+
const x = layout.steps.position(String(k));
|
|
857
|
+
const y = layout.score.map(Math.min(5, Math.max(1, step2.score)));
|
|
858
|
+
const id = `${section}:${task}`;
|
|
859
|
+
const datum = { section, task };
|
|
860
|
+
marks.push(ruleMark({ key: `task:rule:${id}`, from: { x, y: y + RADIUS2 }, to: { x, y: baseline }, color: theme.axis, datum }));
|
|
861
|
+
marks.push(pointMark({ key: `task:${id}`, x, y, radius: RADIUS2, color: seriesColor(theme, section), datum }));
|
|
862
|
+
marks.push(textMark({
|
|
863
|
+
key: `task:label:${id}`,
|
|
864
|
+
text: step2.name,
|
|
865
|
+
x,
|
|
866
|
+
y: baseline + GAP5,
|
|
867
|
+
baseline: "top",
|
|
868
|
+
font: theme.font,
|
|
869
|
+
color: theme.text,
|
|
870
|
+
measure,
|
|
871
|
+
datum: { edit: "task", section, task }
|
|
872
|
+
}));
|
|
873
|
+
const dotsY = baseline + GAP5 + line + GAP5 + DOT;
|
|
874
|
+
const spread = 3 * DOT;
|
|
875
|
+
step2.actors.forEach((actor, a) => {
|
|
876
|
+
marks.push(pointMark({
|
|
877
|
+
key: `task:actor:${id}:${a}`,
|
|
878
|
+
x: x + (a - (step2.actors.length - 1) / 2) * spread,
|
|
879
|
+
y: dotsY,
|
|
880
|
+
radius: DOT,
|
|
881
|
+
color: actors.map(actor),
|
|
882
|
+
datum: { section, task, actor }
|
|
883
|
+
}));
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
return marks;
|
|
887
|
+
}
|
|
888
|
+
function legendMarks(names, actors, x, midY, theme, measure) {
|
|
889
|
+
const row = measure("Mg", theme.font).height + GAP5;
|
|
890
|
+
const top = midY - (row * names.length - GAP5) / 2;
|
|
891
|
+
return names.flatMap((actor, k) => {
|
|
892
|
+
const y = top + k * row + (row - GAP5) / 2;
|
|
893
|
+
return [
|
|
894
|
+
pointMark({ key: `legend:actor:${k}`, x: x + 2 * DOT, y, radius: 2 * DOT, color: actors.map(actor), datum: { actor } }),
|
|
895
|
+
textMark({
|
|
896
|
+
key: `legend:label:${k}`,
|
|
897
|
+
text: actor,
|
|
898
|
+
x: x + 4 * DOT + GAP5,
|
|
899
|
+
y,
|
|
900
|
+
anchor: "start",
|
|
901
|
+
font: theme.font,
|
|
902
|
+
color: theme.text,
|
|
903
|
+
measure,
|
|
904
|
+
datum: { actor }
|
|
905
|
+
})
|
|
906
|
+
];
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
function compileJourney(data, size, theme, measure) {
|
|
910
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
911
|
+
const names = journeyActors(data);
|
|
912
|
+
const actors = ordinalScale({ domain: names, range: theme.series });
|
|
913
|
+
const legend2 = names.length === 0 ? 0 : 4 * DOT + GAP5 + Math.max(...names.map((n2) => measure(n2, theme.font).width));
|
|
914
|
+
const layout = journeyLayout(data, size, top, legend2, theme, measure);
|
|
915
|
+
marks.push(...axisMarks({ key: "axis:score", scale: layout.score, side: "left", at: layout.plot.x, gridTo: layout.plot.x + layout.plot.width, theme, measure }));
|
|
916
|
+
marks.push(...sectionMarks(data, layout, theme, measure), ...stepMarks(data, layout, actors, theme, measure));
|
|
917
|
+
marks.push(...legendMarks(names, actors, layout.plot.x + layout.plot.width + 2 * GAP5, layout.plot.y + layout.plot.height / 2, theme, measure));
|
|
918
|
+
return { marks, layout };
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/packet/compile.ts
|
|
922
|
+
var BOX = 32;
|
|
923
|
+
var GAP6 = 6;
|
|
924
|
+
var INSET = 2;
|
|
925
|
+
var packetBitsPerRow = (data) => Math.max(1, Math.floor(data.bitsPerRow ?? 32));
|
|
926
|
+
function packetPieces(field, bitsPerRow) {
|
|
927
|
+
if (!(field.start >= 0 && field.end >= field.start)) return [];
|
|
928
|
+
const pieces = [];
|
|
929
|
+
for (let row = Math.floor(field.start / bitsPerRow); row <= Math.floor(field.end / bitsPerRow); row++) {
|
|
930
|
+
pieces.push({ row, lo: Math.max(field.start, row * bitsPerRow), hi: Math.min(field.end, (row + 1) * bitsPerRow - 1) });
|
|
931
|
+
}
|
|
932
|
+
return pieces;
|
|
933
|
+
}
|
|
934
|
+
function packetLayout(data, size, top, theme, measure) {
|
|
935
|
+
const bitsPerRow = packetBitsPerRow(data);
|
|
936
|
+
const ends = data.fields.filter((f) => f.start >= 0 && f.end >= f.start).map((f) => f.end);
|
|
937
|
+
const rows = ends.length === 0 ? 0 : Math.floor(Math.max(...ends) / bitsPerRow) + 1;
|
|
938
|
+
const line = measure("0", theme.font).height;
|
|
939
|
+
const bits = bandScale({
|
|
940
|
+
domain: Array.from({ length: bitsPerRow }, (_, i) => String(i)),
|
|
941
|
+
range: [CHART_PAD, Math.max(CHART_PAD + 1, size.width - CHART_PAD)],
|
|
942
|
+
paddingInner: 0,
|
|
943
|
+
paddingOuter: 0
|
|
944
|
+
});
|
|
945
|
+
return { bits, bitsPerRow, rows, boxTop: (row) => top + row * (line + INSET + BOX + GAP6) + line + INSET, boxHeight: BOX };
|
|
946
|
+
}
|
|
947
|
+
function fieldMarks(field, i, layout, theme, measure) {
|
|
948
|
+
const { bits, bitsPerRow, boxHeight } = layout;
|
|
949
|
+
const marks = [];
|
|
950
|
+
const font = { font: theme.font, color: theme.text, measure };
|
|
951
|
+
let widest = null;
|
|
952
|
+
for (const { row, lo, hi } of packetPieces(field, bitsPerRow)) {
|
|
953
|
+
const x0 = bits.start(String(lo % bitsPerRow));
|
|
954
|
+
const x1 = bits.start(String(hi % bitsPerRow)) + bits.bandwidth;
|
|
955
|
+
const y = layout.boxTop(row);
|
|
956
|
+
const box = spanMark({ key: `field:${i}:${row}`, x0, x1, y, height: boxHeight, color: seriesColor(theme, i), opacity: 0.3, datum: { field: i } });
|
|
957
|
+
box.data.stroke = { paint: { color: theme.axis }, width: 1 };
|
|
958
|
+
marks.push(box);
|
|
959
|
+
if (widest === null || x1 - x0 > widest.x1 - widest.x0) widest = { x0, x1, y };
|
|
960
|
+
const numberAt = { y: y - INSET, baseline: "bottom" };
|
|
961
|
+
if (hi === lo) {
|
|
962
|
+
marks.push(textMark({ ...font, ...numberAt, key: `bit:${i}:${row}:start`, text: String(lo), x: (x0 + x1) / 2 }));
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
marks.push(textMark({ ...font, ...numberAt, key: `bit:${i}:${row}:start`, text: String(lo), x: x0 + INSET, anchor: "start" }));
|
|
966
|
+
marks.push(textMark({ ...font, ...numberAt, key: `bit:${i}:${row}:end`, text: String(hi), x: x1 - INSET, anchor: "end" }));
|
|
967
|
+
}
|
|
968
|
+
if (widest !== null) {
|
|
969
|
+
marks.push(textMark({
|
|
970
|
+
...font,
|
|
971
|
+
key: `field:label:${i}`,
|
|
972
|
+
text: field.label,
|
|
973
|
+
x: (widest.x0 + widest.x1) / 2,
|
|
974
|
+
y: widest.y + boxHeight / 2,
|
|
975
|
+
datum: { edit: "label", field: i }
|
|
976
|
+
}));
|
|
977
|
+
}
|
|
978
|
+
return marks;
|
|
979
|
+
}
|
|
980
|
+
function compilePacket(data, size, theme, measure) {
|
|
981
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
982
|
+
const layout = packetLayout(data, size, top, theme, measure);
|
|
983
|
+
data.fields.forEach((field, i) => marks.push(...fieldMarks(field, i, layout, theme, measure)));
|
|
984
|
+
return { marks, layout };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/pie/compile.ts
|
|
988
|
+
var SWATCH = 12;
|
|
989
|
+
var GAP7 = 6;
|
|
990
|
+
var LEGEND_GAP = 16;
|
|
991
|
+
var LABEL_RADIUS = 0.75;
|
|
992
|
+
var piePercent = (value, total) => `${total > 0 ? (value / total * 100).toFixed(0) : "0"}%`;
|
|
993
|
+
var pieValueText = (value) => `[${value}]`;
|
|
994
|
+
function pieAngles(data) {
|
|
995
|
+
const values = data.slices.map((s) => Number.isFinite(s.value) && s.value > 0 ? s.value : 0);
|
|
996
|
+
const total = values.reduce((a, b) => a + b, 0);
|
|
997
|
+
let at3 = 0;
|
|
998
|
+
return values.map((v) => {
|
|
999
|
+
const start = at3;
|
|
1000
|
+
at3 += total > 0 ? v / total * 2 * Math.PI : 0;
|
|
1001
|
+
return { start, end: at3 };
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
function legendMarks2(data, x, midY, theme, measure) {
|
|
1005
|
+
const row = measure("Mg", theme.font).height + GAP7;
|
|
1006
|
+
const top = midY - (row * data.slices.length - GAP7) / 2;
|
|
1007
|
+
const marks = [];
|
|
1008
|
+
data.slices.forEach((slice, i) => {
|
|
1009
|
+
const y = top + i * row;
|
|
1010
|
+
const mid = y + (row - GAP7) / 2;
|
|
1011
|
+
const base = { font: theme.font, color: theme.text, measure, anchor: "start", baseline: "middle" };
|
|
1012
|
+
marks.push(spanMark({ key: `legend:swatch:${i}`, x0: x, x1: x + SWATCH, y: mid - SWATCH / 2, height: SWATCH, color: seriesColor(theme, i), datum: { slice: i } }));
|
|
1013
|
+
const label = textMark({ ...base, key: `legend:label:${i}`, text: slice.label, x: x + SWATCH + GAP7, y: mid, datum: { edit: "label", slice: i } });
|
|
1014
|
+
marks.push(label);
|
|
1015
|
+
if (data.showData) {
|
|
1016
|
+
marks.push(textMark({
|
|
1017
|
+
...base,
|
|
1018
|
+
key: `legend:value:${i}`,
|
|
1019
|
+
text: pieValueText(slice.value),
|
|
1020
|
+
x: label.pose.x + label.pose.width + GAP7,
|
|
1021
|
+
y: mid,
|
|
1022
|
+
datum: { edit: "value", slice: i }
|
|
1023
|
+
}));
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
return marks;
|
|
1027
|
+
}
|
|
1028
|
+
function legendWidth(data, theme, measure) {
|
|
1029
|
+
if (data.slices.length === 0) return 0;
|
|
1030
|
+
const widest = Math.max(...data.slices.map((s) => {
|
|
1031
|
+
const label = measure(s.label, theme.font).width;
|
|
1032
|
+
return data.showData ? label + GAP7 + measure(pieValueText(s.value), theme.font).width : label;
|
|
1033
|
+
}));
|
|
1034
|
+
return SWATCH + GAP7 + widest;
|
|
1035
|
+
}
|
|
1036
|
+
function compilePie(data, size, theme, measure) {
|
|
1037
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1038
|
+
const legend2 = legendWidth(data, theme, measure);
|
|
1039
|
+
const right = size.width - CHART_PAD - (legend2 > 0 ? legend2 + LEGEND_GAP : 0);
|
|
1040
|
+
const area = { width: Math.max(0, right - CHART_PAD), height: Math.max(0, size.height - CHART_PAD - top) };
|
|
1041
|
+
const center = { x: CHART_PAD + area.width / 2, y: top + area.height / 2 };
|
|
1042
|
+
const radius = Math.min(area.width, area.height) / 2;
|
|
1043
|
+
const angles = pieAngles(data);
|
|
1044
|
+
const total = data.slices.reduce((sum, s) => sum + (Number.isFinite(s.value) && s.value > 0 ? s.value : 0), 0);
|
|
1045
|
+
data.slices.forEach((slice, i) => {
|
|
1046
|
+
const { start, end } = angles[i];
|
|
1047
|
+
if (end <= start || radius < 1) return;
|
|
1048
|
+
marks.push(arcMark({ key: `slice:${i}`, center, innerRadius: 0, outerRadius: radius, startAngle: start, endAngle: end, color: seriesColor(theme, i), datum: { slice: i } }));
|
|
1049
|
+
const text = piePercent(slice.value, total);
|
|
1050
|
+
const room = measure(text, theme.font);
|
|
1051
|
+
const mid = (start + end) / 2;
|
|
1052
|
+
const r = radius * LABEL_RADIUS;
|
|
1053
|
+
if ((end - start) * r < room.width + 4 || radius * (1 - LABEL_RADIUS) * 2 < room.height) return;
|
|
1054
|
+
marks.push(textMark({
|
|
1055
|
+
key: `percent:${i}`,
|
|
1056
|
+
text,
|
|
1057
|
+
x: center.x + r * Math.sin(mid),
|
|
1058
|
+
y: center.y - r * Math.cos(mid),
|
|
1059
|
+
font: theme.font,
|
|
1060
|
+
color: theme.text,
|
|
1061
|
+
measure,
|
|
1062
|
+
datum: { slice: i }
|
|
1063
|
+
}));
|
|
1064
|
+
});
|
|
1065
|
+
marks.push(...legendMarks2(data, right + (legend2 > 0 ? LEGEND_GAP : 0), center.y, theme, measure));
|
|
1066
|
+
return { marks, layout: { center, radius, angles } };
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/quadrant/compile.ts
|
|
1070
|
+
var GAP8 = 6;
|
|
1071
|
+
var RADIUS3 = 5;
|
|
1072
|
+
var quadrantAt = (layout, x, y) => ({
|
|
1073
|
+
x: layout.plot.x + x * layout.plot.width,
|
|
1074
|
+
y: layout.plot.y + (1 - y) * layout.plot.height
|
|
1075
|
+
});
|
|
1076
|
+
function quadrantValueAt(layout, at3) {
|
|
1077
|
+
const unit2 = (v) => Math.round(Math.min(1, Math.max(0, v)) * 100) / 100;
|
|
1078
|
+
return { x: unit2((at3.x - layout.plot.x) / layout.plot.width), y: unit2(1 - (at3.y - layout.plot.y) / layout.plot.height) };
|
|
1079
|
+
}
|
|
1080
|
+
function quadrantStyleOf(data, point) {
|
|
1081
|
+
const cls = point.className !== void 0 ? data.classes?.[point.className] : void 0;
|
|
1082
|
+
return { ...cls, ...point.style };
|
|
1083
|
+
}
|
|
1084
|
+
function quadrantLayout(size, top, theme, measure) {
|
|
1085
|
+
const line = measure("Mg", theme.font).height;
|
|
1086
|
+
const left = CHART_PAD + line + GAP8;
|
|
1087
|
+
const bottom = size.height - CHART_PAD - line - GAP8;
|
|
1088
|
+
return { plot: { x: left, y: top, width: Math.max(1, size.width - CHART_PAD - left), height: Math.max(1, bottom - top) } };
|
|
1089
|
+
}
|
|
1090
|
+
var CORNERS = [[0.5, 1], [0, 1], [0, 0.5], [0.5, 0.5]];
|
|
1091
|
+
function quadrantMarks(data, layout, theme, measure) {
|
|
1092
|
+
const marks = [];
|
|
1093
|
+
const { plot } = layout;
|
|
1094
|
+
CORNERS.forEach(([x, y], i) => {
|
|
1095
|
+
const corner = quadrantAt(layout, x, y);
|
|
1096
|
+
marks.push(spanMark({
|
|
1097
|
+
key: `quadrant:${i + 1}`,
|
|
1098
|
+
x0: corner.x,
|
|
1099
|
+
x1: corner.x + plot.width / 2,
|
|
1100
|
+
y: corner.y,
|
|
1101
|
+
height: plot.height / 2,
|
|
1102
|
+
color: seriesColor(theme, i),
|
|
1103
|
+
opacity: 0.15,
|
|
1104
|
+
datum: { quadrant: i + 1 }
|
|
1105
|
+
}));
|
|
1106
|
+
});
|
|
1107
|
+
const mid = quadrantAt(layout, 0.5, 0.5);
|
|
1108
|
+
marks.push(ruleMark({ key: "divider:x", from: { x: mid.x, y: plot.y }, to: { x: mid.x, y: plot.y + plot.height }, color: theme.axis }));
|
|
1109
|
+
marks.push(ruleMark({ key: "divider:y", from: { x: plot.x, y: mid.y }, to: { x: plot.x + plot.width, y: mid.y }, color: theme.axis }));
|
|
1110
|
+
CORNERS.forEach(([x, y], i) => {
|
|
1111
|
+
const text = data.quadrants?.[i];
|
|
1112
|
+
if (!text) return;
|
|
1113
|
+
const center = quadrantAt(layout, x + 0.25, y - 0.25);
|
|
1114
|
+
marks.push(textMark({
|
|
1115
|
+
key: `quadrant:label:${i + 1}`,
|
|
1116
|
+
text,
|
|
1117
|
+
...center,
|
|
1118
|
+
font: theme.font,
|
|
1119
|
+
color: theme.text,
|
|
1120
|
+
measure,
|
|
1121
|
+
bold: true,
|
|
1122
|
+
datum: { edit: "quadrant", quadrant: i + 1 }
|
|
1123
|
+
}));
|
|
1124
|
+
});
|
|
1125
|
+
return marks;
|
|
1126
|
+
}
|
|
1127
|
+
function axisLabelMarks(data, layout, theme, measure) {
|
|
1128
|
+
const { plot } = layout;
|
|
1129
|
+
const marks = [];
|
|
1130
|
+
const base = { font: theme.font, color: theme.text, measure };
|
|
1131
|
+
for (const axis of ["x", "y"]) {
|
|
1132
|
+
const ends = data[axis];
|
|
1133
|
+
for (const end of ["low", "high"]) {
|
|
1134
|
+
const text = ends?.[end];
|
|
1135
|
+
if (!text) continue;
|
|
1136
|
+
const along = ends?.high ? end === "low" ? 0.25 : 0.75 : 0.5;
|
|
1137
|
+
const datum = { edit: "axis", axis, end };
|
|
1138
|
+
marks.push(axis === "x" ? textMark({ ...base, key: `axis:x:${end}`, text, x: plot.x + along * plot.width, y: plot.y + plot.height + GAP8, baseline: "top", datum }) : textMark({ ...base, key: `axis:y:${end}`, text, x: plot.x - GAP8, y: plot.y + (1 - along) * plot.height, baseline: "bottom", vertical: true, datum }));
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return marks;
|
|
1142
|
+
}
|
|
1143
|
+
function pointMarks(data, layout, theme, measure) {
|
|
1144
|
+
const marks = [];
|
|
1145
|
+
data.points.forEach((p, i) => {
|
|
1146
|
+
const style = quadrantStyleOf(data, p);
|
|
1147
|
+
const at3 = quadrantAt(layout, p.x, p.y);
|
|
1148
|
+
const radius = style.radius ?? RADIUS3;
|
|
1149
|
+
const mark = pointMark({ key: `point:${i}`, ...at3, radius, color: style.color ?? theme.text, datum: { point: i } });
|
|
1150
|
+
if (style.strokeColor !== void 0 || style.strokeWidth !== void 0) {
|
|
1151
|
+
mark.data.stroke = { paint: { color: style.strokeColor ?? theme.text }, width: style.strokeWidth ?? 1 };
|
|
1152
|
+
}
|
|
1153
|
+
marks.push(mark);
|
|
1154
|
+
marks.push(textMark({
|
|
1155
|
+
key: `point:label:${i}`,
|
|
1156
|
+
text: p.name,
|
|
1157
|
+
x: at3.x + radius + GAP8,
|
|
1158
|
+
y: at3.y,
|
|
1159
|
+
anchor: "start",
|
|
1160
|
+
font: theme.font,
|
|
1161
|
+
color: theme.text,
|
|
1162
|
+
measure,
|
|
1163
|
+
datum: { edit: "point", point: i }
|
|
1164
|
+
}));
|
|
1165
|
+
});
|
|
1166
|
+
return marks;
|
|
1167
|
+
}
|
|
1168
|
+
function compileQuadrant(data, size, theme, measure) {
|
|
1169
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1170
|
+
const layout = quadrantLayout(size, top, theme, measure);
|
|
1171
|
+
marks.push(...quadrantMarks(data, layout, theme, measure), ...axisLabelMarks(data, layout, theme, measure), ...pointMarks(data, layout, theme, measure));
|
|
1172
|
+
return { marks, layout };
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/radar/compile.ts
|
|
1176
|
+
var GAP9 = 6;
|
|
1177
|
+
var SWATCH2 = 10;
|
|
1178
|
+
var RING_STEPS = 72;
|
|
1179
|
+
function radarDomain(data) {
|
|
1180
|
+
const values = data.curves.flatMap((c) => c.values).filter(Number.isFinite);
|
|
1181
|
+
const min = data.min ?? 0;
|
|
1182
|
+
const max = data.max ?? Math.max(min, ...values);
|
|
1183
|
+
return max === min ? [min, min + 1] : [min, max];
|
|
1184
|
+
}
|
|
1185
|
+
var labelOf = (item) => item.label ?? item.id;
|
|
1186
|
+
function legend(data, top, width2, theme, measure) {
|
|
1187
|
+
if (data.showLegend === false || data.curves.length === 0) return { marks: [], width: 0 };
|
|
1188
|
+
const sizes = data.curves.map((c) => measure(labelOf(c), theme.font));
|
|
1189
|
+
const room = SWATCH2 + GAP9 + Math.max(...sizes.map((s) => s.width));
|
|
1190
|
+
const x = width2 - CHART_PAD - room;
|
|
1191
|
+
const marks = [];
|
|
1192
|
+
let y = top;
|
|
1193
|
+
data.curves.forEach((c, i) => {
|
|
1194
|
+
const h = Math.max(SWATCH2, sizes[i].height);
|
|
1195
|
+
const color = seriesColor(theme, i);
|
|
1196
|
+
marks.push(pointMark({ key: `legend:${i}:swatch`, x: x + SWATCH2 / 2, y: y + h / 2, radius: SWATCH2 / 2, color, datum: { curve: i } }));
|
|
1197
|
+
marks.push(textMark({
|
|
1198
|
+
key: `legend:${i}:label`,
|
|
1199
|
+
text: labelOf(c),
|
|
1200
|
+
x: x + SWATCH2 + GAP9,
|
|
1201
|
+
y: y + h / 2,
|
|
1202
|
+
anchor: "start",
|
|
1203
|
+
font: theme.font,
|
|
1204
|
+
color: theme.text,
|
|
1205
|
+
measure,
|
|
1206
|
+
datum: { edit: "curve", curve: i }
|
|
1207
|
+
}));
|
|
1208
|
+
y += h + GAP9;
|
|
1209
|
+
});
|
|
1210
|
+
return { marks, width: room + GAP9 };
|
|
1211
|
+
}
|
|
1212
|
+
function labelPlacement(angle) {
|
|
1213
|
+
const s = Math.sin(angle);
|
|
1214
|
+
const c = Math.cos(angle);
|
|
1215
|
+
return {
|
|
1216
|
+
anchor: s > 0.1 ? "start" : s < -0.1 ? "end" : "middle",
|
|
1217
|
+
baseline: c > 0.1 ? "bottom" : c < -0.1 ? "top" : "middle"
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function graticule(data, scale, theme, measure) {
|
|
1221
|
+
const n2 = data.axes.length;
|
|
1222
|
+
const rings = Math.max(1, Math.floor(data.ticks ?? 5));
|
|
1223
|
+
const [d0, d1] = scale.domain;
|
|
1224
|
+
const { center } = scale;
|
|
1225
|
+
const marks = [];
|
|
1226
|
+
for (let k = 1; k <= rings; k++) {
|
|
1227
|
+
const v = d0 + (d1 - d0) * k / rings;
|
|
1228
|
+
const r = scale.radiusAt(v);
|
|
1229
|
+
const points = data.graticule === "polygon" && n2 >= 3 ? Array.from({ length: n2 + 1 }, (_, i) => scale.point(i % n2, v)) : Array.from({ length: RING_STEPS + 1 }, (_, j) => {
|
|
1230
|
+
const a = 2 * Math.PI * j / RING_STEPS;
|
|
1231
|
+
return { x: center.x + r * Math.sin(a), y: center.y - r * Math.cos(a) };
|
|
1232
|
+
});
|
|
1233
|
+
marks.push(lineMark({ key: `grid:ring:${k}`, points, color: theme.grid, width: 1 }));
|
|
1234
|
+
}
|
|
1235
|
+
data.axes.forEach((axis, i) => {
|
|
1236
|
+
marks.push(ruleMark({ key: `grid:spoke:${i}`, from: scale.point(i, d0), to: scale.point(i, d1), color: theme.axis }));
|
|
1237
|
+
const a = scale.angle(i);
|
|
1238
|
+
const r = scale.radius[1] + GAP9;
|
|
1239
|
+
marks.push(textMark({
|
|
1240
|
+
key: `axis:${i}:label`,
|
|
1241
|
+
text: labelOf(axis),
|
|
1242
|
+
x: center.x + r * Math.sin(a),
|
|
1243
|
+
y: center.y - r * Math.cos(a),
|
|
1244
|
+
...labelPlacement(a),
|
|
1245
|
+
font: theme.font,
|
|
1246
|
+
color: theme.text,
|
|
1247
|
+
measure,
|
|
1248
|
+
datum: { edit: "axis", axis: i }
|
|
1249
|
+
}));
|
|
1250
|
+
});
|
|
1251
|
+
return marks;
|
|
1252
|
+
}
|
|
1253
|
+
function curveMarks(data, scale, theme) {
|
|
1254
|
+
const marks = [];
|
|
1255
|
+
data.curves.forEach((curve, c) => {
|
|
1256
|
+
const color = seriesColor(theme, c);
|
|
1257
|
+
const placed = data.axes.flatMap((_, i) => {
|
|
1258
|
+
const v = curve.values[i];
|
|
1259
|
+
return v !== void 0 && Number.isFinite(v) ? [{ i, at: scale.point(i, v) }] : [];
|
|
1260
|
+
});
|
|
1261
|
+
if (placed.length === 0) return;
|
|
1262
|
+
const ring = placed.map((p) => p.at);
|
|
1263
|
+
marks.push(areaMark({ key: `curve:${c}`, top: ring, color, datum: { curve: c } }));
|
|
1264
|
+
marks.push(lineMark({ key: `curve:${c}:outline`, points: [...ring, ring[0]], color, datum: { curve: c } }));
|
|
1265
|
+
for (const { i, at: at3 } of placed) marks.push(pointMark({ key: `point:${c}:${i}`, ...at3, radius: 3.5, color, datum: { curve: c, axis: i } }));
|
|
1266
|
+
});
|
|
1267
|
+
return marks;
|
|
1268
|
+
}
|
|
1269
|
+
function compileRadar(data, size, theme, measure) {
|
|
1270
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1271
|
+
const key = legend(data, top, size.width, theme, measure);
|
|
1272
|
+
const labels2 = data.axes.map((a) => measure(labelOf(a), theme.font));
|
|
1273
|
+
const across = GAP9 + Math.max(0, ...labels2.map((l) => l.width));
|
|
1274
|
+
const down = GAP9 + Math.max(0, ...labels2.map((l) => l.height));
|
|
1275
|
+
const box = { x: CHART_PAD, y: top, width: size.width - 2 * CHART_PAD - key.width, height: size.height - CHART_PAD - top };
|
|
1276
|
+
const outer = Math.max(1, Math.min(box.width / 2 - across, box.height / 2 - down));
|
|
1277
|
+
const scale = polarScale({
|
|
1278
|
+
center: { x: box.x + box.width / 2, y: box.y + box.height / 2 },
|
|
1279
|
+
radius: [0, outer],
|
|
1280
|
+
domain: radarDomain(data),
|
|
1281
|
+
axes: data.axes.length
|
|
1282
|
+
});
|
|
1283
|
+
marks.push(...graticule(data, scale, theme, measure), ...curveMarks(data, scale, theme), ...key.marks);
|
|
1284
|
+
return { marks, layout: { scale } };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/sankey/layout.ts
|
|
1288
|
+
var SANKEY_NODE_WIDTH = 12;
|
|
1289
|
+
var NODE_GAP = 12;
|
|
1290
|
+
var SWEEPS = 4;
|
|
1291
|
+
var flowOf = (v) => Number.isFinite(v) && v > 0 ? v : 0;
|
|
1292
|
+
var sankeyNodes = (data) => [...new Set(data.links.flatMap((l) => [l.source, l.target]))];
|
|
1293
|
+
function depths(n2, edges) {
|
|
1294
|
+
const out = Array.from({ length: n2 }, () => []);
|
|
1295
|
+
for (const [s, t] of edges) out[s].push(t);
|
|
1296
|
+
const state = new Array(n2).fill(0);
|
|
1297
|
+
const order = [];
|
|
1298
|
+
const forward = Array.from({ length: n2 }, () => []);
|
|
1299
|
+
const visit = (root) => {
|
|
1300
|
+
const stack = [{ node: root, next: 0 }];
|
|
1301
|
+
state[root] = 1;
|
|
1302
|
+
while (stack.length > 0) {
|
|
1303
|
+
const top = stack[stack.length - 1];
|
|
1304
|
+
const t = out[top.node][top.next++];
|
|
1305
|
+
if (t === void 0) {
|
|
1306
|
+
state[top.node] = 2;
|
|
1307
|
+
order.push(top.node);
|
|
1308
|
+
stack.pop();
|
|
1309
|
+
} else if (state[t] !== 1) {
|
|
1310
|
+
forward[top.node].push(t);
|
|
1311
|
+
if (state[t] === 0) {
|
|
1312
|
+
state[t] = 1;
|
|
1313
|
+
stack.push({ node: t, next: 0 });
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
for (let k = 0; k < n2; k++) if (state[k] === 0) visit(k);
|
|
1319
|
+
const depth = new Array(n2).fill(0);
|
|
1320
|
+
for (const s of order.reverse()) for (const t of forward[s]) depth[t] = Math.max(depth[t], depth[s] + 1);
|
|
1321
|
+
return depth;
|
|
1322
|
+
}
|
|
1323
|
+
function orderColumns(columns, center, edges, restack) {
|
|
1324
|
+
const sweep = (col, near) => {
|
|
1325
|
+
const key = new Map(col.map((k) => {
|
|
1326
|
+
const ws = near(k);
|
|
1327
|
+
const total = ws.reduce((a, [, w]) => a + w, 0);
|
|
1328
|
+
return [k, total > 0 ? ws.reduce((a, [o, w]) => a + center(o) * w, 0) / total : center(k)];
|
|
1329
|
+
}));
|
|
1330
|
+
col.sort((a, b) => key.get(a) - key.get(b));
|
|
1331
|
+
restack(col);
|
|
1332
|
+
};
|
|
1333
|
+
const into = (k) => edges.filter(([, t]) => t === k).map(([s, , w]) => [s, Math.max(w, 1e-9)]);
|
|
1334
|
+
const outOf = (k) => edges.filter(([s]) => s === k).map(([, t, w]) => [t, Math.max(w, 1e-9)]);
|
|
1335
|
+
for (let i = 0; i < SWEEPS; i++) {
|
|
1336
|
+
for (let c = 1; c < columns.length; c++) sweep(columns[c], into);
|
|
1337
|
+
for (let c = columns.length - 2; c >= 0; c--) sweep(columns[c], outOf);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
function sankeyLayout(data, box) {
|
|
1341
|
+
const labels2 = sankeyNodes(data);
|
|
1342
|
+
const index = new Map(labels2.map((l, k) => [l, k]));
|
|
1343
|
+
const edges = data.links.map((l) => [index.get(l.source), index.get(l.target), flowOf(l.value)]);
|
|
1344
|
+
const column2 = depths(labels2.length, edges.map(([s, t]) => [s, t]));
|
|
1345
|
+
const ins = labels2.map((_, k) => edges.filter(([, t]) => t === k).reduce((a, [, , w]) => a + w, 0));
|
|
1346
|
+
const outs = labels2.map((_, k) => edges.filter(([s]) => s === k).reduce((a, [, , w]) => a + w, 0));
|
|
1347
|
+
const value = labels2.map((_, k) => Math.max(ins[k], outs[k]));
|
|
1348
|
+
const count = labels2.length === 0 ? 0 : Math.max(...column2) + 1;
|
|
1349
|
+
const columns = Array.from({ length: count }, (_, c) => labels2.flatMap((_2, k) => column2[k] === c ? [k] : []));
|
|
1350
|
+
const tallest = Math.max(1, ...columns.map((c) => c.length));
|
|
1351
|
+
const gap = Math.min(NODE_GAP, box.height / 2 / tallest);
|
|
1352
|
+
const ky = Math.max(0, Math.min(...columns.map((c) => {
|
|
1353
|
+
const total = c.reduce((a, k) => a + value[k], 0);
|
|
1354
|
+
return total > 0 ? (box.height - gap * (c.length - 1)) / total : Number.POSITIVE_INFINITY;
|
|
1355
|
+
})));
|
|
1356
|
+
const scale = Number.isFinite(ky) ? ky : 0;
|
|
1357
|
+
const y = new Array(labels2.length).fill(0);
|
|
1358
|
+
const stack = (col) => {
|
|
1359
|
+
const total = col.reduce((a, k) => a + value[k] * scale, 0) + gap * (col.length - 1);
|
|
1360
|
+
let at3 = box.y + (box.height - total) / 2;
|
|
1361
|
+
for (const k of col) {
|
|
1362
|
+
y[k] = at3;
|
|
1363
|
+
at3 += value[k] * scale + gap;
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
columns.forEach(stack);
|
|
1367
|
+
orderColumns(columns, (k) => y[k] + value[k] * scale / 2, edges, stack);
|
|
1368
|
+
const step2 = count > 1 ? (box.width - SANKEY_NODE_WIDTH) / (count - 1) : 0;
|
|
1369
|
+
const nodes = labels2.map((label, k) => ({
|
|
1370
|
+
label,
|
|
1371
|
+
column: column2[k],
|
|
1372
|
+
value: value[k],
|
|
1373
|
+
x: box.x + column2[k] * step2,
|
|
1374
|
+
y: y[k],
|
|
1375
|
+
width: SANKEY_NODE_WIDTH,
|
|
1376
|
+
height: value[k] * scale
|
|
1377
|
+
}));
|
|
1378
|
+
return { nodes, links: ribbons(nodes, edges, scale), columns: count };
|
|
1379
|
+
}
|
|
1380
|
+
function ribbons(nodes, edges, scale) {
|
|
1381
|
+
const mid = (k) => nodes[k].y + nodes[k].height / 2;
|
|
1382
|
+
const offsets = (side) => {
|
|
1383
|
+
const at3 = new Array(edges.length).fill(0);
|
|
1384
|
+
nodes.forEach((node, k) => {
|
|
1385
|
+
const mine = edges.map((e, i) => ({ e, i })).filter(({ e }) => e[side] === k);
|
|
1386
|
+
mine.sort((a, b) => mid(a.e[1 - side]) - mid(b.e[1 - side]) || a.i - b.i);
|
|
1387
|
+
let y = node.y;
|
|
1388
|
+
for (const { e, i } of mine) {
|
|
1389
|
+
at3[i] = y;
|
|
1390
|
+
y += e[2] * scale;
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
return at3;
|
|
1394
|
+
};
|
|
1395
|
+
const leave = offsets(0);
|
|
1396
|
+
const reach = offsets(1);
|
|
1397
|
+
return edges.map(([s, t, w], i) => {
|
|
1398
|
+
const h = w * scale;
|
|
1399
|
+
return {
|
|
1400
|
+
source: s,
|
|
1401
|
+
target: t,
|
|
1402
|
+
from: { x: nodes[s].x + nodes[s].width, y0: leave[i], y1: leave[i] + h },
|
|
1403
|
+
to: { x: nodes[t].x, y0: reach[i], y1: reach[i] + h }
|
|
1404
|
+
};
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/sankey/compile.ts
|
|
1409
|
+
var LABEL_GAP2 = 6;
|
|
1410
|
+
var formatFlow = (v) => String(Number(v.toPrecision(12)));
|
|
1411
|
+
function nodeLabels(node, k, columns, theme, measure) {
|
|
1412
|
+
const right = node.column * 2 < columns - 1 || columns === 1;
|
|
1413
|
+
const x = right ? node.x + node.width + LABEL_GAP2 : node.x - LABEL_GAP2;
|
|
1414
|
+
const y = node.y + node.height / 2;
|
|
1415
|
+
const anchor = right ? "start" : "end";
|
|
1416
|
+
const base = { x, anchor, font: theme.font, color: theme.text, measure };
|
|
1417
|
+
return [
|
|
1418
|
+
textMark({ ...base, key: `node:${k}:label`, text: node.label, y, baseline: "bottom", datum: { edit: "node", node: node.label } }),
|
|
1419
|
+
textMark({ ...base, key: `node:${k}:value`, text: formatFlow(node.value), y, baseline: "top" })
|
|
1420
|
+
];
|
|
1421
|
+
}
|
|
1422
|
+
function compileSankey(data, size, theme, measure) {
|
|
1423
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1424
|
+
const box = { x: CHART_PAD, y: top, width: Math.max(1, size.width - 2 * CHART_PAD), height: Math.max(1, size.height - CHART_PAD - top) };
|
|
1425
|
+
const layout = sankeyLayout(data, box);
|
|
1426
|
+
layout.links.forEach((r, i) => {
|
|
1427
|
+
marks.push(ribbonMark({ key: `link:${i}`, from: r.from, to: r.to, color: seriesColor(theme, r.source), datum: { link: i } }));
|
|
1428
|
+
});
|
|
1429
|
+
layout.nodes.forEach((node, k) => {
|
|
1430
|
+
marks.push(barMark({ key: `node:${k}`, x: node.x, width: node.width, from: node.y, to: node.y + node.height, color: seriesColor(theme, k), datum: { node: node.label } }));
|
|
1431
|
+
});
|
|
1432
|
+
layout.links.forEach((r, i) => {
|
|
1433
|
+
marks.push(textMark({
|
|
1434
|
+
key: `link:${i}:value`,
|
|
1435
|
+
text: formatFlow(data.links[i].value),
|
|
1436
|
+
x: (r.from.x + r.to.x) / 2,
|
|
1437
|
+
y: (r.from.y0 + r.from.y1 + r.to.y0 + r.to.y1) / 4,
|
|
1438
|
+
font: theme.font,
|
|
1439
|
+
color: theme.text,
|
|
1440
|
+
measure,
|
|
1441
|
+
datum: { edit: "value", link: i }
|
|
1442
|
+
}));
|
|
1443
|
+
});
|
|
1444
|
+
layout.nodes.forEach((node, k) => marks.push(...nodeLabels(node, k, layout.columns, theme, measure)));
|
|
1445
|
+
return { marks, layout };
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/timeline/edit.ts
|
|
1449
|
+
function timelinePeriodAt(data, period) {
|
|
1450
|
+
if (!Number.isInteger(period) || period < 0) return null;
|
|
1451
|
+
let left = period;
|
|
1452
|
+
for (let s = 0; s < data.sections.length; s++) {
|
|
1453
|
+
const n2 = data.sections[s].periods.length;
|
|
1454
|
+
if (left < n2) return { section: s, index: left };
|
|
1455
|
+
left -= n2;
|
|
1456
|
+
}
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
var timelineLength = (data) => data.sections.reduce((n2, s) => n2 + s.periods.length, 0);
|
|
1460
|
+
function locate(data, period, what) {
|
|
1461
|
+
const at3 = timelinePeriodAt(data, period);
|
|
1462
|
+
if (at3 === null) throw new Error(`${what}: the timeline holds no period ${period}`);
|
|
1463
|
+
return at3;
|
|
1464
|
+
}
|
|
1465
|
+
function withPeriod(data, at3, next) {
|
|
1466
|
+
return {
|
|
1467
|
+
...data,
|
|
1468
|
+
sections: data.sections.map((s, i) => i === at3.section ? { ...s, periods: s.periods.map((p, j) => j === at3.index ? next : p) } : s)
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function setTimelineSectionName(data, section, name) {
|
|
1472
|
+
const target = data.sections[section];
|
|
1473
|
+
if (target === void 0) throw new Error(`setTimelineSectionName: the timeline holds no section ${section}`);
|
|
1474
|
+
const text = name.trim();
|
|
1475
|
+
if (text === "" || text === target.name) return data;
|
|
1476
|
+
return { ...data, sections: data.sections.map((s, i) => i === section ? { ...s, name: text } : s) };
|
|
1477
|
+
}
|
|
1478
|
+
function setTimelinePeriodLabel(data, period, label) {
|
|
1479
|
+
const at3 = locate(data, period, "setTimelinePeriodLabel");
|
|
1480
|
+
const target = data.sections[at3.section].periods[at3.index];
|
|
1481
|
+
const text = label.trim();
|
|
1482
|
+
if (text === "" || text === target.label) return data;
|
|
1483
|
+
return withPeriod(data, at3, { ...target, label: text });
|
|
1484
|
+
}
|
|
1485
|
+
function setTimelineEvent(data, period, event, text) {
|
|
1486
|
+
const at3 = locate(data, period, "setTimelineEvent");
|
|
1487
|
+
const target = data.sections[at3.section].periods[at3.index];
|
|
1488
|
+
if (!Number.isInteger(event) || event < 0 || event >= target.events.length) {
|
|
1489
|
+
throw new Error(`setTimelineEvent: period ${period} holds no event ${event}`);
|
|
1490
|
+
}
|
|
1491
|
+
const value = text.trim();
|
|
1492
|
+
if (value === target.events[event]) return data;
|
|
1493
|
+
const events = value === "" ? target.events.filter((_, i) => i !== event) : target.events.map((e, i) => i === event ? value : e);
|
|
1494
|
+
return withPeriod(data, at3, { ...target, events });
|
|
1495
|
+
}
|
|
1496
|
+
function moveTimelinePeriod(data, from, to) {
|
|
1497
|
+
const n2 = timelineLength(data);
|
|
1498
|
+
locate(data, from, "moveTimelinePeriod");
|
|
1499
|
+
if (!Number.isInteger(to) || to < 0 || to >= n2) throw new Error(`moveTimelinePeriod: the timeline holds no period ${to}`);
|
|
1500
|
+
if (from === to) return data;
|
|
1501
|
+
const flat = data.sections.flatMap((s, section) => s.periods.map((period) => ({ section, period })));
|
|
1502
|
+
const [moved] = flat.splice(from, 1);
|
|
1503
|
+
const neighbor = to > from ? flat[to - 1] : flat[to];
|
|
1504
|
+
flat.splice(to, 0, { section: neighbor.section, period: moved.period });
|
|
1505
|
+
const sections = [];
|
|
1506
|
+
data.sections.forEach((s, i) => {
|
|
1507
|
+
const periods = flat.filter((f) => f.section === i).map((f) => f.period);
|
|
1508
|
+
if (periods.length > 0) sections.push({ ...s, periods });
|
|
1509
|
+
});
|
|
1510
|
+
return { ...data, sections };
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/timeline/compile.ts
|
|
1514
|
+
var BOX_PAD = 6;
|
|
1515
|
+
var INSET2 = 4;
|
|
1516
|
+
var GAP10 = 8;
|
|
1517
|
+
var LINE_GAP = 14;
|
|
1518
|
+
var EVENT_GAP = 6;
|
|
1519
|
+
function compileTimeline(data, size, theme, measure) {
|
|
1520
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1521
|
+
const n2 = timelineLength(data);
|
|
1522
|
+
const columnWidth = Math.max(1, size.width - 2 * CHART_PAD) / Math.max(1, n2);
|
|
1523
|
+
const slots = Array.from({ length: n2 }, (_, i) => CHART_PAD + columnWidth * (i + 0.5));
|
|
1524
|
+
const boxHeight = measure("Mg", theme.font).height + 2 * BOX_PAD;
|
|
1525
|
+
const named2 = data.sections.some((s) => s.name !== void 0);
|
|
1526
|
+
const periodTop = top + (named2 ? boxHeight + GAP10 : 0);
|
|
1527
|
+
const lineY = periodTop + boxHeight + LINE_GAP;
|
|
1528
|
+
const bottom = Math.max(lineY, size.height - CHART_PAD);
|
|
1529
|
+
const font = { font: theme.font, color: theme.text, measure };
|
|
1530
|
+
const boxes = [];
|
|
1531
|
+
const texts = [];
|
|
1532
|
+
let p = 0;
|
|
1533
|
+
data.sections.forEach((section, s) => {
|
|
1534
|
+
const color = seriesColor(theme, s);
|
|
1535
|
+
const first = p;
|
|
1536
|
+
for (const period of section.periods) {
|
|
1537
|
+
const x = CHART_PAD + columnWidth * p + INSET2;
|
|
1538
|
+
const width2 = Math.max(1, columnWidth - 2 * INSET2);
|
|
1539
|
+
boxes.push(spanMark({ key: `period:${p}:box`, x0: x, x1: x + width2, y: periodTop, height: boxHeight, color, datum: { period: p } }));
|
|
1540
|
+
texts.push(textMark({ key: `period:${p}:label`, text: period.label, x: slots[p], y: periodTop + boxHeight / 2, ...font, bold: true, datum: { edit: "period", period: p } }));
|
|
1541
|
+
boxes.push(ruleMark({ key: `period:${p}:stem`, from: { x: slots[p], y: lineY }, to: { x: slots[p], y: lineY + LINE_GAP }, color, dash: [3, 3] }));
|
|
1542
|
+
let y = lineY + LINE_GAP;
|
|
1543
|
+
period.events.forEach((event, e) => {
|
|
1544
|
+
boxes.push(spanMark({ key: `event:${p}:${e}:box`, x0: x, x1: x + width2, y, height: boxHeight, color, opacity: 0.35, datum: { period: p, event: e } }));
|
|
1545
|
+
texts.push(textMark({ key: `event:${p}:${e}:label`, text: event, x: slots[p], y: y + boxHeight / 2, ...font, datum: { edit: "event", period: p, event: e } }));
|
|
1546
|
+
y += boxHeight + EVENT_GAP;
|
|
1547
|
+
});
|
|
1548
|
+
p++;
|
|
1549
|
+
}
|
|
1550
|
+
if (section.name === void 0 || p === first) return;
|
|
1551
|
+
const x0 = CHART_PAD + columnWidth * first;
|
|
1552
|
+
const x1 = CHART_PAD + columnWidth * p;
|
|
1553
|
+
marks.push(spanMark({ key: `section:${s}:band`, x0, x1, y: top, height: bottom - top, color, opacity: 0.12, datum: { section: s } }));
|
|
1554
|
+
boxes.push(spanMark({ key: `section:${s}:header`, x0: x0 + INSET2, x1: x1 - INSET2, y: top, height: boxHeight, color, datum: { section: s } }));
|
|
1555
|
+
texts.push(textMark({ key: `section:${s}:label`, text: section.name, x: (x0 + x1) / 2, y: top + boxHeight / 2, ...font, bold: true, datum: { edit: "section", section: s } }));
|
|
1556
|
+
});
|
|
1557
|
+
if (n2 > 0) marks.push(ruleMark({ key: "line", from: { x: CHART_PAD, y: lineY }, to: { x: size.width - CHART_PAD, y: lineY }, color: theme.axis, width: 2 }));
|
|
1558
|
+
marks.push(...boxes);
|
|
1559
|
+
slots.forEach((x, i) => marks.push(pointMark({ key: `period:${i}:dot`, x, y: lineY, radius: 4, color: theme.axis, datum: { period: i } })));
|
|
1560
|
+
marks.push(...texts);
|
|
1561
|
+
return { marks, layout: { slots, columnWidth } };
|
|
1562
|
+
}
|
|
1563
|
+
function timelineSlotAt(layout, x) {
|
|
1564
|
+
let best = -1;
|
|
1565
|
+
layout.slots.forEach((slot, i) => {
|
|
1566
|
+
if (best < 0 || Math.abs(slot - x) < Math.abs(layout.slots[best] - x)) best = i;
|
|
1567
|
+
});
|
|
1568
|
+
return best;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/xychart/compile.ts
|
|
1572
|
+
var BAND_PADDING = 0.2;
|
|
1573
|
+
var xyLength = (data) => Math.max(0, ...data.series.map((s) => s.values.length));
|
|
1574
|
+
function categoriesOf(data) {
|
|
1575
|
+
if (data.x?.kind === "band" && data.x.categories.length > 0) return [...data.x.categories];
|
|
1576
|
+
return Array.from({ length: xyLength(data) }, (_, i) => String(i + 1));
|
|
1577
|
+
}
|
|
1578
|
+
function xyValueDomain(data) {
|
|
1579
|
+
const values = data.series.flatMap((s) => s.values).filter(Number.isFinite);
|
|
1580
|
+
const lo = Math.min(0, ...values);
|
|
1581
|
+
const hi = Math.max(0, ...values);
|
|
1582
|
+
const [niceLo, niceHi] = niceDomain([lo, hi === lo ? lo + 1 : hi]);
|
|
1583
|
+
const min = data.y?.min ?? niceLo;
|
|
1584
|
+
const max = data.y?.max ?? niceHi;
|
|
1585
|
+
return min === max ? [min, min + 1] : [min, max];
|
|
1586
|
+
}
|
|
1587
|
+
function categoryAxis(data, range) {
|
|
1588
|
+
const n2 = xyLength(data);
|
|
1589
|
+
if (data.x?.kind === "range") {
|
|
1590
|
+
const scale2 = linearScale({ domain: [data.x.min, data.x.max], range });
|
|
1591
|
+
const step2 = (range[1] - range[0]) / Math.max(1, n2);
|
|
1592
|
+
const slots2 = Array.from({ length: n2 }, (_, i) => range[0] + step2 * (i + 0.5));
|
|
1593
|
+
return { scale: scale2, slots: slots2, slotWidth: Math.abs(step2) * (1 - BAND_PADDING) };
|
|
1594
|
+
}
|
|
1595
|
+
const categories = categoriesOf(data);
|
|
1596
|
+
const scale = bandScale({ domain: categories, range, paddingInner: BAND_PADDING });
|
|
1597
|
+
const slots = Array.from({ length: n2 }, (_, i) => i < categories.length ? scale.position(categories[i]) : Number.NaN);
|
|
1598
|
+
return { scale, slots, slotWidth: scale.bandwidth };
|
|
1599
|
+
}
|
|
1600
|
+
function compileXyChart(data, size, theme, measure) {
|
|
1601
|
+
const vertical = data.orientation !== "horizontal";
|
|
1602
|
+
const { marks, top } = chartTitle(data.title, size.width, theme, measure);
|
|
1603
|
+
const domain = xyValueDomain(data);
|
|
1604
|
+
const base = { theme, measure, at: 0 };
|
|
1605
|
+
const valueTitle = data.y?.title !== void 0 ? { title: data.y.title } : {};
|
|
1606
|
+
const categoryTitle = data.x?.title !== void 0 ? { title: data.x.title } : {};
|
|
1607
|
+
const probe = categoryAxis(data, [0, 1]);
|
|
1608
|
+
const valueRoom = axisRoom({ ...base, ...valueTitle, scale: linearScale({ domain, range: [0, 1] }), side: vertical ? "left" : "bottom" });
|
|
1609
|
+
const categoryRoom = axisRoom({ ...base, ...categoryTitle, scale: probe.scale, side: vertical ? "bottom" : "left" });
|
|
1610
|
+
const left = CHART_PAD + (vertical ? valueRoom : categoryRoom);
|
|
1611
|
+
const bottom = size.height - CHART_PAD - (vertical ? categoryRoom : valueRoom);
|
|
1612
|
+
const plot = { x: left, y: top, width: Math.max(1, size.width - CHART_PAD - left), height: Math.max(1, bottom - top) };
|
|
1613
|
+
const right = plot.x + plot.width;
|
|
1614
|
+
const value = linearScale({ domain, range: vertical ? [bottom, top] : [left, right] });
|
|
1615
|
+
const category = categoryAxis(data, vertical ? [left, right] : [top, bottom]);
|
|
1616
|
+
marks.push(...axisMarks({ ...base, ...valueTitle, key: "axis:value", scale: value, side: vertical ? "left" : "bottom", at: vertical ? left : bottom, gridTo: vertical ? right : top }));
|
|
1617
|
+
marks.push(...axisMarks({ ...base, ...categoryTitle, key: "axis:category", scale: category.scale, side: vertical ? "bottom" : "left", at: vertical ? bottom : left }));
|
|
1618
|
+
const layout = { orientation: vertical ? "vertical" : "horizontal", plot, value, slots: category.slots, slotWidth: category.slotWidth };
|
|
1619
|
+
marks.push(...seriesMarks(data, layout, theme));
|
|
1620
|
+
return { marks, layout };
|
|
1621
|
+
}
|
|
1622
|
+
function seriesMarks(data, layout, theme) {
|
|
1623
|
+
const { value, slots, slotWidth } = layout;
|
|
1624
|
+
const vertical = layout.orientation === "vertical";
|
|
1625
|
+
const at3 = (along, v) => vertical ? { x: along, y: value.map(v) } : { x: value.map(v), y: along };
|
|
1626
|
+
const [d0, d1] = value.domain;
|
|
1627
|
+
const baseline = value.map(Math.min(Math.max(0, Math.min(d0, d1)), Math.max(d0, d1)));
|
|
1628
|
+
const bars = data.series.filter((s) => s.kind === "bar");
|
|
1629
|
+
const barWidth = slotWidth / Math.max(1, bars.length);
|
|
1630
|
+
const categories = categoriesOf(data);
|
|
1631
|
+
const marks = [];
|
|
1632
|
+
data.series.forEach((series, s) => {
|
|
1633
|
+
const color = seriesColor(theme, s);
|
|
1634
|
+
const datum = (i) => ({ series: s, index: i, value: series.values[i], category: categories[i] ?? String(i + 1) });
|
|
1635
|
+
const placed = series.values.map((v, i) => ({ v, i })).filter(({ v, i }) => Number.isFinite(v) && Number.isFinite(slots[i] ?? Number.NaN));
|
|
1636
|
+
if (series.kind === "bar") {
|
|
1637
|
+
const j = bars.indexOf(series);
|
|
1638
|
+
for (const { v, i } of placed) {
|
|
1639
|
+
marks.push(barMark({
|
|
1640
|
+
key: `bar:${s}:${i}`,
|
|
1641
|
+
x: slots[i] - slotWidth / 2 + j * barWidth,
|
|
1642
|
+
width: barWidth,
|
|
1643
|
+
from: baseline,
|
|
1644
|
+
to: value.map(v),
|
|
1645
|
+
color,
|
|
1646
|
+
datum: datum(i),
|
|
1647
|
+
orient: layout.orientation
|
|
1648
|
+
}));
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (placed.length > 0) {
|
|
1653
|
+
marks.push(lineMark({ key: `line:${s}`, points: placed.map(({ v, i }) => at3(slots[i], v)), color, datum: { series: s } }));
|
|
1654
|
+
}
|
|
1655
|
+
for (const { v, i } of placed) marks.push(pointMark({ key: `point:${s}:${i}`, ...at3(slots[i], v), radius: 3.5, color, datum: datum(i) }));
|
|
1656
|
+
});
|
|
1657
|
+
return marks;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/plotData.ts
|
|
1661
|
+
var COMPILERS = {
|
|
1662
|
+
xychart: compileXyChart,
|
|
1663
|
+
gantt: compileGantt,
|
|
1664
|
+
timeline: compileTimeline,
|
|
1665
|
+
pie: compilePie,
|
|
1666
|
+
quadrant: compileQuadrant,
|
|
1667
|
+
journey: compileJourney,
|
|
1668
|
+
gitgraph: compileGitGraph,
|
|
1669
|
+
sankey: compileSankey,
|
|
1670
|
+
packet: compilePacket,
|
|
1671
|
+
radar: compileRadar
|
|
1672
|
+
};
|
|
1673
|
+
function compilePlot(data, size, theme, measure) {
|
|
1674
|
+
const compile = COMPILERS[data.kind];
|
|
1675
|
+
if (compile === void 0) throw new Error(`compilePlot: no chart of kind "${data.kind}"`);
|
|
1676
|
+
return compile(data, size, theme, measure).marks;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// src/xychart/edit.ts
|
|
1680
|
+
var xyRows = (data) => Math.max(data.x?.kind === "band" ? data.x.categories.length : 0, xyLength(data));
|
|
1681
|
+
function setXyValue(data, series, index, value) {
|
|
1682
|
+
const target = data.series[series];
|
|
1683
|
+
if (target === void 0 || !Number.isInteger(index) || index < 0 || index >= xyRows(data)) {
|
|
1684
|
+
throw new Error(`setXyValue: series ${series} holds no value ${index}`);
|
|
1685
|
+
}
|
|
1686
|
+
if (target.values[index] === value) return data;
|
|
1687
|
+
const values = Array.from({ length: Math.max(target.values.length, index + 1) }, (_, i) => i === index ? value : target.values[i] ?? 0);
|
|
1688
|
+
return { ...data, series: data.series.map((s, i) => i === series ? { ...s, values } : s) };
|
|
1689
|
+
}
|
|
1690
|
+
function xyValueAt(layout, at3) {
|
|
1691
|
+
const raw = layout.value.invert(layout.orientation === "vertical" ? at3.y : at3.x);
|
|
1692
|
+
const step2 = layout.value.tickStep() / 10;
|
|
1693
|
+
return Number((Math.round(raw / step2) * step2).toFixed(decimalsOf(step2)));
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/gantt/edit.ts
|
|
1697
|
+
function taskAt(data, section, task, what) {
|
|
1698
|
+
const found = data.sections[section]?.tasks[task];
|
|
1699
|
+
if (found === void 0) throw new Error(`${what}: the gantt holds no task ${task} in section ${section}`);
|
|
1700
|
+
return found;
|
|
1701
|
+
}
|
|
1702
|
+
function withTask(data, section, task, next) {
|
|
1703
|
+
return {
|
|
1704
|
+
...data,
|
|
1705
|
+
sections: data.sections.map((s, i) => i === section ? { ...s, tasks: s.tasks.map((t, j) => j === task ? next : t) } : s)
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
var snapGantt = (t, step2) => Math.round(t / step2) * step2;
|
|
1709
|
+
function moveGanttTask(data, section, task, start) {
|
|
1710
|
+
const target = taskAt(data, section, task, "moveGanttTask");
|
|
1711
|
+
const date = formatGanttDate(start, dateFormatOf(data));
|
|
1712
|
+
if (target.start.kind === "date" && target.start.date === date) return data;
|
|
1713
|
+
return withTask(data, section, task, { ...target, start: { kind: "date", date } });
|
|
1714
|
+
}
|
|
1715
|
+
function setGanttEnd(data, section, task, end) {
|
|
1716
|
+
const target = taskAt(data, section, task, "setGanttEnd");
|
|
1717
|
+
const start = scheduleGantt(data).spans[section][task].start;
|
|
1718
|
+
const old = target.end;
|
|
1719
|
+
let next;
|
|
1720
|
+
if (old.kind === "date") {
|
|
1721
|
+
next = { kind: "date", date: formatGanttDate(Math.max(start, end), dateFormatOf(data)) };
|
|
1722
|
+
if (next.date === old.date) return data;
|
|
1723
|
+
} else {
|
|
1724
|
+
const unit2 = old.kind === "duration" ? old.unit : "d";
|
|
1725
|
+
const least = target.tags?.includes("milestone") ? 0 : 1;
|
|
1726
|
+
const amount = Math.max(least, ganttDurationBetween(start, end, unit2, excludedDays(data)));
|
|
1727
|
+
if (old.kind === "duration" && old.amount === amount) return data;
|
|
1728
|
+
next = { kind: "duration", amount, unit: unit2 };
|
|
1729
|
+
}
|
|
1730
|
+
return withTask(data, section, task, { ...target, end: next });
|
|
1731
|
+
}
|
|
1732
|
+
function setGanttSectionName(data, section, name) {
|
|
1733
|
+
const target = data.sections[section];
|
|
1734
|
+
if (target === void 0) throw new Error(`setGanttSectionName: the gantt holds no section ${section}`);
|
|
1735
|
+
const text = name.trim();
|
|
1736
|
+
if (text === "" || text === target.name) return data;
|
|
1737
|
+
return { ...data, sections: data.sections.map((s, i) => i === section ? { ...s, name: text } : s) };
|
|
1738
|
+
}
|
|
1739
|
+
function setGanttTaskName(data, section, task, name) {
|
|
1740
|
+
const target = taskAt(data, section, task, "setGanttTaskName");
|
|
1741
|
+
const text = name.trim();
|
|
1742
|
+
if (text === "" || text === target.name) return data;
|
|
1743
|
+
return withTask(data, section, task, { ...target, name: text });
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/pie/edit.ts
|
|
1747
|
+
function withSlice(data, slice, change, who) {
|
|
1748
|
+
const target = data.slices[slice];
|
|
1749
|
+
if (target === void 0) throw new Error(`${who}: the pie holds no slice ${slice}`);
|
|
1750
|
+
if (Object.entries(change).every(([k, v]) => target[k] === v)) return data;
|
|
1751
|
+
return { ...data, slices: data.slices.map((s, i) => i === slice ? { ...s, ...change } : s) };
|
|
1752
|
+
}
|
|
1753
|
+
var setPieValue = (data, slice, value) => withSlice(data, slice, { value }, "setPieValue");
|
|
1754
|
+
var setPieLabel = (data, slice, label) => withSlice(data, slice, { label }, "setPieLabel");
|
|
1755
|
+
function pieValueOf(text) {
|
|
1756
|
+
const bare = text.trim().replace(/^\[\s*(.*?)\s*\]$/, "$1");
|
|
1757
|
+
if (!/^\+?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(bare)) return null;
|
|
1758
|
+
const value = Number(bare);
|
|
1759
|
+
return Number.isFinite(value) ? value : null;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/quadrant/edit.ts
|
|
1763
|
+
function withPoint(data, point, change, who) {
|
|
1764
|
+
const target = data.points[point];
|
|
1765
|
+
if (target === void 0) throw new Error(`${who}: the chart holds no point ${point}`);
|
|
1766
|
+
if (Object.entries(change).every(([k, v]) => target[k] === v)) return data;
|
|
1767
|
+
return { ...data, points: data.points.map((p, i) => i === point ? { ...p, ...change } : p) };
|
|
1768
|
+
}
|
|
1769
|
+
var setQuadrantPoint = (data, point, at3) => withPoint(data, point, { x: at3.x, y: at3.y }, "setQuadrantPoint");
|
|
1770
|
+
var setQuadrantPointName = (data, point, name) => withPoint(data, point, { name }, "setQuadrantPointName");
|
|
1771
|
+
function setQuadrantAxisLabel(data, axis, end, text) {
|
|
1772
|
+
const ends = data[axis] ?? {};
|
|
1773
|
+
if ((ends[end] ?? "") === text) return data;
|
|
1774
|
+
const { [end]: _, ...rest } = ends;
|
|
1775
|
+
const next = text === "" ? rest : { ...rest, [end]: text };
|
|
1776
|
+
const { [axis]: __, ...others } = data;
|
|
1777
|
+
return Object.keys(next).length === 0 ? others : { ...others, [axis]: next };
|
|
1778
|
+
}
|
|
1779
|
+
function setQuadrantLabel(data, quadrant, text) {
|
|
1780
|
+
if (![1, 2, 3, 4].includes(quadrant)) throw new Error(`setQuadrantLabel: there is no quadrant ${quadrant}`);
|
|
1781
|
+
if ((data.quadrants?.[quadrant - 1] ?? "") === text) return data;
|
|
1782
|
+
const q = data.quadrants;
|
|
1783
|
+
const labels2 = [q?.[0], q?.[1], q?.[2], q?.[3]];
|
|
1784
|
+
labels2[quadrant - 1] = text === "" ? void 0 : text;
|
|
1785
|
+
const { quadrants: _, ...rest } = data;
|
|
1786
|
+
while (labels2.length > 0 && labels2[labels2.length - 1] === void 0) labels2.pop();
|
|
1787
|
+
return labels2.length === 0 ? rest : { ...rest, quadrants: labels2 };
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// src/journey/edit.ts
|
|
1791
|
+
function withTask2(data, section, task, change, who) {
|
|
1792
|
+
const target = data.sections[section]?.tasks[task];
|
|
1793
|
+
if (target === void 0) throw new Error(`${who}: section ${section} holds no step ${task}`);
|
|
1794
|
+
if (Object.entries(change).every(([k, v]) => target[k] === v)) return data;
|
|
1795
|
+
return {
|
|
1796
|
+
...data,
|
|
1797
|
+
sections: data.sections.map((s, i) => i === section ? { ...s, tasks: s.tasks.map((t, j) => j === task ? { ...t, ...change } : t) } : s)
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
var setJourneyScore = (data, section, task, score) => withTask2(data, section, task, { score }, "setJourneyScore");
|
|
1801
|
+
var setJourneyTaskName = (data, section, task, name) => withTask2(data, section, task, { name }, "setJourneyTaskName");
|
|
1802
|
+
function setJourneySectionName(data, section, name) {
|
|
1803
|
+
const target = data.sections[section];
|
|
1804
|
+
if (target === void 0) throw new Error(`setJourneySectionName: the journey holds no section ${section}`);
|
|
1805
|
+
if (target.name === name) return data;
|
|
1806
|
+
return { ...data, sections: data.sections.map((s, i) => i === section ? { ...s, name } : s) };
|
|
1807
|
+
}
|
|
1808
|
+
var journeyScoreAt = (layout, at3) => Math.min(5, Math.max(1, Math.round(layout.score.invert(at3.y))));
|
|
1809
|
+
|
|
1810
|
+
// src/gitgraph/edit.ts
|
|
1811
|
+
function setGitTag(data, command, text) {
|
|
1812
|
+
const cmd = data.commands[command];
|
|
1813
|
+
if (cmd === void 0 || cmd.kind === "branch" || cmd.kind === "checkout") throw new Error(`setGitTag: command ${command} makes no commit`);
|
|
1814
|
+
const tag = text.trim();
|
|
1815
|
+
if ((cmd.tag ?? "") === tag) return data;
|
|
1816
|
+
const { tag: _, ...rest } = cmd;
|
|
1817
|
+
const next = tag === "" ? rest : { ...rest, tag };
|
|
1818
|
+
return { ...data, commands: data.commands.map((c, i) => i === command ? next : c) };
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// src/sankey/edit.ts
|
|
1822
|
+
function setSankeyValue(data, link, value) {
|
|
1823
|
+
const target = data.links[link];
|
|
1824
|
+
if (target === void 0) throw new Error(`setSankeyValue: no link ${link}`);
|
|
1825
|
+
if (target.value === value) return data;
|
|
1826
|
+
return { ...data, links: data.links.map((l, i) => i === link ? { ...l, value } : l) };
|
|
1827
|
+
}
|
|
1828
|
+
function renameSankeyNode(data, from, to) {
|
|
1829
|
+
const nodes = sankeyNodes(data);
|
|
1830
|
+
if (!nodes.includes(from)) throw new Error(`renameSankeyNode: no node "${from}"`);
|
|
1831
|
+
if (from === to) return data;
|
|
1832
|
+
if (nodes.includes(to)) throw new Error(`renameSankeyNode: a node is already called "${to}"`);
|
|
1833
|
+
const named2 = (n2) => n2 === from ? to : n2;
|
|
1834
|
+
return { ...data, links: data.links.map((l) => l.source === from || l.target === from ? { ...l, source: named2(l.source), target: named2(l.target) } : l) };
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// src/packet/edit.ts
|
|
1838
|
+
function setPacketLabel(data, field, label) {
|
|
1839
|
+
const target = data.fields[field];
|
|
1840
|
+
if (target === void 0) throw new Error(`setPacketLabel: the packet holds no field ${field}`);
|
|
1841
|
+
if (target.label === label) return data;
|
|
1842
|
+
return { ...data, fields: data.fields.map((f, i) => i === field ? { ...f, label } : f) };
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// src/radar/edit.ts
|
|
1846
|
+
function setRadarValue(data, curve, axis, value) {
|
|
1847
|
+
const target = data.curves[curve];
|
|
1848
|
+
if (target === void 0 || axis < 0 || axis >= data.axes.length) throw new Error(`setRadarValue: curve ${curve} holds no value on axis ${axis}`);
|
|
1849
|
+
if (target.values[axis] === value) return data;
|
|
1850
|
+
const values = data.axes.map((_, i) => i === axis ? value : target.values[i] ?? 0);
|
|
1851
|
+
return { ...data, curves: data.curves.map((c, i) => i === curve ? { ...c, values } : c) };
|
|
1852
|
+
}
|
|
1853
|
+
function relabel(items, index, text, what) {
|
|
1854
|
+
const item = items[index];
|
|
1855
|
+
if (item === void 0) throw new Error(`${what}: no item ${index}`);
|
|
1856
|
+
const label = text.trim();
|
|
1857
|
+
const next = label === "" || label === item.id ? void 0 : label;
|
|
1858
|
+
if (next === item.label) return null;
|
|
1859
|
+
const { label: _, ...rest } = item;
|
|
1860
|
+
return items.map((x, i) => i === index ? next === void 0 ? rest : { ...rest, label: next } : x);
|
|
1861
|
+
}
|
|
1862
|
+
function setRadarAxisLabel(data, axis, text) {
|
|
1863
|
+
const axes = relabel(data.axes, axis, text, "setRadarAxisLabel");
|
|
1864
|
+
return axes === null ? data : { ...data, axes };
|
|
1865
|
+
}
|
|
1866
|
+
function setRadarCurveLabel(data, curve, text) {
|
|
1867
|
+
const curves = relabel(data.curves, curve, text, "setRadarCurveLabel");
|
|
1868
|
+
return curves === null ? data : { ...data, curves };
|
|
1869
|
+
}
|
|
1870
|
+
function radarValueAt(layout, axis, at3) {
|
|
1871
|
+
const raw = layout.scale.valueAt(axis, at3);
|
|
1872
|
+
const step2 = layout.scale.tickStep() / 10;
|
|
1873
|
+
return Number((Math.round(raw / step2) * step2).toFixed(decimalsOf(step2)));
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// src/plotScene.ts
|
|
1877
|
+
function plotRootOf(node) {
|
|
1878
|
+
const plot = node?.data?.plot;
|
|
1879
|
+
return plot !== void 0 && plot !== null && typeof plot === "object" && typeof plot.data?.kind === "string" ? plot : null;
|
|
1880
|
+
}
|
|
1881
|
+
function plotMarkOf(node) {
|
|
1882
|
+
const mark = node?.data?.plotMark;
|
|
1883
|
+
return mark !== void 0 && mark !== null && typeof mark.kind === "string" ? mark : null;
|
|
1884
|
+
}
|
|
1885
|
+
var plotMarkId = (rootId, key) => `${rootId}/${key}`;
|
|
1886
|
+
function markSpecs(rootId, layer, marks) {
|
|
1887
|
+
return marks.map((m) => ({
|
|
1888
|
+
kind: "mark",
|
|
1889
|
+
id: plotMarkId(rootId, m.key),
|
|
1890
|
+
parent: rootId,
|
|
1891
|
+
layer,
|
|
1892
|
+
pose: { x: m.pose.x, y: m.pose.y, width: m.pose.width, height: m.pose.height, ...m.pose.rotation ? { rotation: m.pose.rotation } : {} },
|
|
1893
|
+
data: m.data
|
|
1894
|
+
}));
|
|
1895
|
+
}
|
|
1896
|
+
function plotSpecs(data, opts) {
|
|
1897
|
+
const id = opts.id ?? "plot";
|
|
1898
|
+
const layer = opts.layer ?? "main";
|
|
1899
|
+
const theme = opts.theme ?? PLOT_THEME;
|
|
1900
|
+
const { x, y, width: width2, height } = opts.box;
|
|
1901
|
+
const marks = compilePlot(data, { width: width2, height }, theme, opts.measure ?? textMeasure);
|
|
1902
|
+
const root = { kind: "group", id, layer, pose: { x, y, width: width2, height }, data: { plot: { data, theme } } };
|
|
1903
|
+
return [root, ...markSpecs(id, layer, marks)];
|
|
1904
|
+
}
|
|
1905
|
+
function rootTrait(scene, rootId) {
|
|
1906
|
+
const node = scene.get(rootId);
|
|
1907
|
+
const trait = plotRootOf(node);
|
|
1908
|
+
if (node === void 0 || trait === null) throw new Error(`replacePlotData: "${rootId}" is not a plot root`);
|
|
1909
|
+
return { node, trait };
|
|
1910
|
+
}
|
|
1911
|
+
function replacePlotData(scene, rootId, data, opts = {}) {
|
|
1912
|
+
const s = scene;
|
|
1913
|
+
const { node, trait } = rootTrait(s, rootId);
|
|
1914
|
+
const theme = opts.theme ?? trait.theme;
|
|
1915
|
+
const pose = node.pose;
|
|
1916
|
+
const marks = compilePlot(data, { width: pose.width, height: pose.height }, theme, opts.measure ?? textMeasure);
|
|
1917
|
+
s.batch(opts.label ?? "Edit chart", () => {
|
|
1918
|
+
s.update(node.id, { data: { ...node.data, plot: { data, theme } } });
|
|
1919
|
+
const children = s.childrenOf(node.id);
|
|
1920
|
+
if (children.length > 0) s.removeMany(children);
|
|
1921
|
+
for (const spec of markSpecs(rootId, node.layer, marks)) s.add(spec);
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
function plotRootContaining(scene, id) {
|
|
1925
|
+
const node = scene.get(id);
|
|
1926
|
+
if (node === void 0) return null;
|
|
1927
|
+
if (plotRootOf(node) !== null) return node.id;
|
|
1928
|
+
const parent = plotMarkOf(node) !== null ? node.parent : null;
|
|
1929
|
+
return parent !== null && parent !== void 0 && plotRootOf(scene.get(parent)) !== null ? parent : null;
|
|
1930
|
+
}
|
|
1931
|
+
function plotRootsIn(scene) {
|
|
1932
|
+
return scene.renderOrderNodes().filter((n2) => plotRootOf(n2) !== null).map((n2) => n2.id);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// src/gantt/editor.ts
|
|
1936
|
+
function taskOf(data, datum) {
|
|
1937
|
+
const { section, task } = datum ?? {};
|
|
1938
|
+
if (typeof section !== "number" || typeof task !== "number") return null;
|
|
1939
|
+
return data.sections[section]?.tasks[task] !== void 0 ? { section, task } : null;
|
|
1940
|
+
}
|
|
1941
|
+
function setText(data, datum, text) {
|
|
1942
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
1943
|
+
if (datum.edit === "section" && typeof datum.section === "number" && data.sections[datum.section] !== void 0) {
|
|
1944
|
+
return setGanttSectionName(data, datum.section, text);
|
|
1945
|
+
}
|
|
1946
|
+
const at3 = datum.edit === "task" ? taskOf(data, datum) : null;
|
|
1947
|
+
return at3 === null ? data : setGanttTaskName(data, at3.section, at3.task, text);
|
|
1948
|
+
}
|
|
1949
|
+
var ganttEditor = {
|
|
1950
|
+
handle(data, { trait, pose }) {
|
|
1951
|
+
if (trait.kind !== "span" && trait.kind !== "area" || taskOf(data, trait.datum) === null) return null;
|
|
1952
|
+
const cy = pose.y + pose.height / 2;
|
|
1953
|
+
if (trait.kind === "area") return { at: { x: pose.x + pose.width / 2, y: cy }, cursor: "move", part: "move", box: pose };
|
|
1954
|
+
return [
|
|
1955
|
+
{ at: { x: pose.x, y: cy }, cursor: "move", part: "move", box: pose },
|
|
1956
|
+
{ at: { x: pose.x + pose.width, y: cy }, cursor: "ew-resize", part: "end" }
|
|
1957
|
+
];
|
|
1958
|
+
},
|
|
1959
|
+
drag(data, trait, size, theme, measure, part) {
|
|
1960
|
+
const at3 = taskOf(data, trait.datum);
|
|
1961
|
+
if (at3 === null || part !== "move" && part !== "end") return null;
|
|
1962
|
+
const { layout } = compileGantt(data, size, theme, measure);
|
|
1963
|
+
const instant = (x) => snapGantt(layout.time.invert(x), layout.snap);
|
|
1964
|
+
if (part === "move") return (p) => moveGanttTask(data, at3.section, at3.task, instant(p.x));
|
|
1965
|
+
const snaps = data.sections[at3.section].tasks[at3.task].end.kind === "date";
|
|
1966
|
+
return (p) => setGanttEnd(data, at3.section, at3.task, snaps ? instant(p.x) : layout.time.invert(p.x));
|
|
1967
|
+
},
|
|
1968
|
+
text: setText
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
// src/gitgraph/editor.ts
|
|
1972
|
+
var gitgraphEditor = {
|
|
1973
|
+
handle: () => null,
|
|
1974
|
+
drag: () => null,
|
|
1975
|
+
text(data, datum, text) {
|
|
1976
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
1977
|
+
if (datum.edit === "tag" && typeof datum.command === "number") return setGitTag(data, datum.command, text);
|
|
1978
|
+
return data;
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
// src/journey/editor.ts
|
|
1983
|
+
var stepOf = (data, datum) => {
|
|
1984
|
+
const d = datum;
|
|
1985
|
+
if (typeof d?.section !== "number" || typeof d.task !== "number") return null;
|
|
1986
|
+
return data.sections[d.section]?.tasks[d.task] !== void 0 ? { section: d.section, task: d.task } : null;
|
|
1987
|
+
};
|
|
1988
|
+
var journeyEditor = {
|
|
1989
|
+
handle(data, { trait, pose }) {
|
|
1990
|
+
if (trait.kind !== "point" || trait.datum?.actor !== void 0 || stepOf(data, trait.datum) === null) return null;
|
|
1991
|
+
return { at: { x: pose.x + pose.width / 2, y: pose.y + pose.height / 2 }, cursor: "ns-resize" };
|
|
1992
|
+
},
|
|
1993
|
+
drag(data, trait, size, theme, measure) {
|
|
1994
|
+
const step2 = stepOf(data, trait.datum);
|
|
1995
|
+
if (step2 === null || trait.datum?.actor !== void 0) return null;
|
|
1996
|
+
const { layout } = compileJourney(data, size, theme, measure);
|
|
1997
|
+
return (at3) => setJourneyScore(data, step2.section, step2.task, journeyScoreAt(layout, at3));
|
|
1998
|
+
},
|
|
1999
|
+
text(data, datum, text) {
|
|
2000
|
+
const t = text.trim();
|
|
2001
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
2002
|
+
if (datum.edit === "section" && typeof datum.section === "number" && data.sections[datum.section] !== void 0 && t !== "") {
|
|
2003
|
+
return setJourneySectionName(data, datum.section, t);
|
|
2004
|
+
}
|
|
2005
|
+
const step2 = datum.edit === "task" ? stepOf(data, datum) : null;
|
|
2006
|
+
return step2 === null || t === "" ? data : setJourneyTaskName(data, step2.section, step2.task, t);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
// src/packet/editor.ts
|
|
2011
|
+
var packetEditor = {
|
|
2012
|
+
handle: () => null,
|
|
2013
|
+
drag: () => null,
|
|
2014
|
+
text(data, datum, text) {
|
|
2015
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
2016
|
+
const label = text.trim();
|
|
2017
|
+
const field = datum.field;
|
|
2018
|
+
if (datum.edit !== "label" || typeof field !== "number" || data.fields[field] === void 0 || label === "") return data;
|
|
2019
|
+
return setPacketLabel(data, field, label);
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
|
|
2023
|
+
// src/pie/editor.ts
|
|
2024
|
+
var pieEditor = {
|
|
2025
|
+
handle: () => null,
|
|
2026
|
+
drag: () => null,
|
|
2027
|
+
text(data, datum, text) {
|
|
2028
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
2029
|
+
const slice = datum.slice;
|
|
2030
|
+
if (typeof slice !== "number" || data.slices[slice] === void 0) return data;
|
|
2031
|
+
if (datum.edit === "label") {
|
|
2032
|
+
const label = text.trim();
|
|
2033
|
+
return label === "" ? data : setPieLabel(data, slice, label);
|
|
2034
|
+
}
|
|
2035
|
+
if (datum.edit === "value") {
|
|
2036
|
+
const value = pieValueOf(text);
|
|
2037
|
+
return value === null ? data : setPieValue(data, slice, value);
|
|
2038
|
+
}
|
|
2039
|
+
return data;
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
// src/quadrant/editor.ts
|
|
2044
|
+
var pointOf = (data, datum) => {
|
|
2045
|
+
const p = datum?.point;
|
|
2046
|
+
return typeof p === "number" && data.points[p] !== void 0 ? p : null;
|
|
2047
|
+
};
|
|
2048
|
+
var quadrantEditor = {
|
|
2049
|
+
handle(data, { trait, pose }) {
|
|
2050
|
+
if (trait.kind !== "point" || pointOf(data, trait.datum) === null) return null;
|
|
2051
|
+
return { at: { x: pose.x + pose.width / 2, y: pose.y + pose.height / 2 }, cursor: "move" };
|
|
2052
|
+
},
|
|
2053
|
+
drag(data, trait, size, theme, measure) {
|
|
2054
|
+
const point = pointOf(data, trait.datum);
|
|
2055
|
+
if (point === null) return null;
|
|
2056
|
+
const { layout } = compileQuadrant(data, size, theme, measure);
|
|
2057
|
+
return (at3) => setQuadrantPoint(data, point, quadrantValueAt(layout, at3));
|
|
2058
|
+
},
|
|
2059
|
+
text(data, datum, text) {
|
|
2060
|
+
const t = text.trim();
|
|
2061
|
+
switch (datum.edit) {
|
|
2062
|
+
case "title":
|
|
2063
|
+
return withTitle(data, text);
|
|
2064
|
+
case "axis":
|
|
2065
|
+
return (datum.axis === "x" || datum.axis === "y") && (datum.end === "low" || datum.end === "high") ? setQuadrantAxisLabel(data, datum.axis, datum.end, t) : data;
|
|
2066
|
+
case "quadrant":
|
|
2067
|
+
return typeof datum.quadrant === "number" && [1, 2, 3, 4].includes(datum.quadrant) ? setQuadrantLabel(data, datum.quadrant, t) : data;
|
|
2068
|
+
case "point": {
|
|
2069
|
+
const point = pointOf(data, datum);
|
|
2070
|
+
return point === null || t === "" ? data : setQuadrantPointName(data, point, t);
|
|
2071
|
+
}
|
|
2072
|
+
default:
|
|
2073
|
+
return data;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
// src/radar/editor.ts
|
|
2079
|
+
var curveAxis = (datum) => {
|
|
2080
|
+
const d = datum;
|
|
2081
|
+
return typeof d?.curve === "number" && typeof d.axis === "number" ? { curve: d.curve, axis: d.axis } : null;
|
|
2082
|
+
};
|
|
2083
|
+
var radarEditor = {
|
|
2084
|
+
handle(_data, { trait, pose }) {
|
|
2085
|
+
if (trait.kind !== "point" || curveAxis(trait.datum) === null) return null;
|
|
2086
|
+
return { at: { x: pose.x + pose.width / 2, y: pose.y + pose.height / 2 }, cursor: "move" };
|
|
2087
|
+
},
|
|
2088
|
+
drag(data, trait, size, theme, measure) {
|
|
2089
|
+
const which = curveAxis(trait.datum);
|
|
2090
|
+
if (which === null) return null;
|
|
2091
|
+
const { layout } = compileRadar(data, size, theme, measure);
|
|
2092
|
+
return (at3) => setRadarValue(data, which.curve, which.axis, radarValueAt(layout, which.axis, at3));
|
|
2093
|
+
},
|
|
2094
|
+
text(data, datum, text) {
|
|
2095
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
2096
|
+
if (datum.edit === "axis" && typeof datum.axis === "number") return setRadarAxisLabel(data, datum.axis, text);
|
|
2097
|
+
if (datum.edit === "curve" && typeof datum.curve === "number") return setRadarCurveLabel(data, datum.curve, text);
|
|
2098
|
+
return data;
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
// src/sankey/editor.ts
|
|
2103
|
+
var flowOf2 = (text) => {
|
|
2104
|
+
const t = text.trim();
|
|
2105
|
+
const v = Number(t);
|
|
2106
|
+
return t !== "" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
2107
|
+
};
|
|
2108
|
+
var sankeyEditor = {
|
|
2109
|
+
handle: () => null,
|
|
2110
|
+
drag: () => null,
|
|
2111
|
+
text(data, datum, text) {
|
|
2112
|
+
if (datum.edit === "title") return withTitle(data, text);
|
|
2113
|
+
if (datum.edit === "value" && typeof datum.link === "number") {
|
|
2114
|
+
const v = flowOf2(text);
|
|
2115
|
+
return v === null ? data : setSankeyValue(data, datum.link, v);
|
|
2116
|
+
}
|
|
2117
|
+
if (datum.edit === "node" && typeof datum.node === "string") {
|
|
2118
|
+
const name = text.trim();
|
|
2119
|
+
return name === "" || name === datum.node || sankeyNodes(data).includes(name) ? data : renameSankeyNode(data, datum.node, name);
|
|
2120
|
+
}
|
|
2121
|
+
return data;
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
// src/timeline/editor.ts
|
|
2126
|
+
var periodOf = (datum) => typeof datum?.period === "number" && datum.event === void 0 ? datum.period : null;
|
|
2127
|
+
var numberOf = (v) => typeof v === "number" ? v : null;
|
|
2128
|
+
function setText2(data, datum, text) {
|
|
2129
|
+
const section = numberOf(datum.section);
|
|
2130
|
+
const period = numberOf(datum.period);
|
|
2131
|
+
const event = numberOf(datum.event);
|
|
2132
|
+
switch (datum.edit) {
|
|
2133
|
+
case "title":
|
|
2134
|
+
return withTitle(data, text);
|
|
2135
|
+
case "section":
|
|
2136
|
+
return section !== null && data.sections[section] !== void 0 ? setTimelineSectionName(data, section, text) : data;
|
|
2137
|
+
case "period":
|
|
2138
|
+
return period !== null && timelinePeriodAt(data, period) !== null ? setTimelinePeriodLabel(data, period, text) : data;
|
|
2139
|
+
case "event": {
|
|
2140
|
+
const at3 = period === null ? null : timelinePeriodAt(data, period);
|
|
2141
|
+
const held = at3 !== null && event !== null && event >= 0 && event < data.sections[at3.section].periods[at3.index].events.length;
|
|
2142
|
+
return held ? setTimelineEvent(data, period, event, text) : data;
|
|
2143
|
+
}
|
|
2144
|
+
default:
|
|
2145
|
+
return data;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
var timelineEditor = {
|
|
2149
|
+
handle(_data, { trait, pose }) {
|
|
2150
|
+
if (trait.kind !== "span" || periodOf(trait.datum) === null) return null;
|
|
2151
|
+
return { at: { x: pose.x + pose.width / 2, y: pose.y + pose.height / 2 }, cursor: "ew-resize", box: pose };
|
|
2152
|
+
},
|
|
2153
|
+
drag(data, trait, size, theme, measure) {
|
|
2154
|
+
const from = periodOf(trait.datum);
|
|
2155
|
+
if (from === null || timelinePeriodAt(data, from) === null) return null;
|
|
2156
|
+
const { layout } = compileTimeline(data, size, theme, measure);
|
|
2157
|
+
return (at3) => {
|
|
2158
|
+
const to = timelineSlotAt(layout, at3.x);
|
|
2159
|
+
return to < 0 ? data : moveTimelinePeriod(data, from, to);
|
|
2160
|
+
};
|
|
2161
|
+
},
|
|
2162
|
+
text: setText2
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2165
|
+
// src/xychart/editor.ts
|
|
2166
|
+
var seriesIndex = (datum) => {
|
|
2167
|
+
const d = datum;
|
|
2168
|
+
return typeof d?.series === "number" && typeof d.index === "number" ? { series: d.series, index: d.index } : null;
|
|
2169
|
+
};
|
|
2170
|
+
var xyEditor = {
|
|
2171
|
+
handle(data, { trait, pose }) {
|
|
2172
|
+
if (trait.kind !== "bar" && trait.kind !== "point" || seriesIndex(trait.datum) === null) return null;
|
|
2173
|
+
const vertical = data.orientation !== "horizontal";
|
|
2174
|
+
const cursor = vertical ? "ns-resize" : "ew-resize";
|
|
2175
|
+
const cx = pose.x + pose.width / 2;
|
|
2176
|
+
const cy = pose.y + pose.height / 2;
|
|
2177
|
+
if (trait.kind === "point") return { at: { x: cx, y: cy }, cursor };
|
|
2178
|
+
const up = Number(trait.datum?.value ?? 0) >= 0;
|
|
2179
|
+
return vertical ? { at: { x: cx, y: up ? pose.y : pose.y + pose.height }, cursor } : { at: { x: up ? pose.x + pose.width : pose.x, y: cy }, cursor };
|
|
2180
|
+
},
|
|
2181
|
+
drag(data, trait, size, theme, measure) {
|
|
2182
|
+
const which = seriesIndex(trait.datum);
|
|
2183
|
+
if (which === null) return null;
|
|
2184
|
+
const { layout } = compileXyChart(data, size, theme, measure);
|
|
2185
|
+
return (at3) => setXyValue(data, which.series, which.index, xyValueAt(layout, at3));
|
|
2186
|
+
},
|
|
2187
|
+
text: (data, datum, text) => datum.edit === "title" ? withTitle(data, text) : data
|
|
2188
|
+
};
|
|
2189
|
+
|
|
2190
|
+
// src/editing/editors.ts
|
|
2191
|
+
var EDITORS = {
|
|
2192
|
+
xychart: xyEditor,
|
|
2193
|
+
gantt: ganttEditor,
|
|
2194
|
+
timeline: timelineEditor,
|
|
2195
|
+
pie: pieEditor,
|
|
2196
|
+
quadrant: quadrantEditor,
|
|
2197
|
+
journey: journeyEditor,
|
|
2198
|
+
gitgraph: gitgraphEditor,
|
|
2199
|
+
sankey: sankeyEditor,
|
|
2200
|
+
packet: packetEditor,
|
|
2201
|
+
radar: radarEditor
|
|
2202
|
+
};
|
|
2203
|
+
var plotEditorOf = (data) => EDITORS[data.kind] ?? null;
|
|
2204
|
+
var handlesOf = (h) => h === null ? [] : Array.isArray(h) ? h : [h];
|
|
2205
|
+
function plotTextOf(node) {
|
|
2206
|
+
const mark = plotMarkOf(node);
|
|
2207
|
+
return mark?.kind === "text" && typeof mark.datum?.edit === "string" ? mark.datum : null;
|
|
2208
|
+
}
|
|
2209
|
+
function compiledText(scene, rootId, markId) {
|
|
2210
|
+
const root = scene.get(rootId);
|
|
2211
|
+
const trait = plotRootOf(root);
|
|
2212
|
+
const { width: width2, height } = root.pose;
|
|
2213
|
+
const key = markId.slice(rootId.length + 1);
|
|
2214
|
+
return compilePlot(trait.data, { width: width2, height }, trait.theme, textMeasure).find((m) => m.key === key)?.data.text;
|
|
2215
|
+
}
|
|
2216
|
+
var isPlotText = (data) => isTextData(data) && plotTextOf({ data }) !== null;
|
|
2217
|
+
function foldPlotText(scene, textId, commit) {
|
|
2218
|
+
const node = scene.get(textId);
|
|
2219
|
+
const datum = plotTextOf(node);
|
|
2220
|
+
const rootId = datum === null ? null : node.parent;
|
|
2221
|
+
const trait = rootId === null ? null : plotRootOf(scene.get(rootId));
|
|
2222
|
+
const editor = trait === null ? null : plotEditorOf(trait.data);
|
|
2223
|
+
const text = node?.data?.text;
|
|
2224
|
+
if (datum === null || trait === null || editor?.text === void 0 || typeof text !== "string") return;
|
|
2225
|
+
const next = editor.text(trait.data, datum, text);
|
|
2226
|
+
if (next === trait.data) {
|
|
2227
|
+
if (text !== compiledText(scene, rootId, textId)) scene.undo();
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
scene.undo();
|
|
2231
|
+
commit({ rootId, data: next, label: "Edit text" });
|
|
2232
|
+
}
|
|
2233
|
+
function usePlotTextEdit(scene, commit) {
|
|
2234
|
+
const latest = useLatest(commit);
|
|
2235
|
+
return useMemo(() => ({
|
|
2236
|
+
isText: isPlotText,
|
|
2237
|
+
onEnd: (id) => foldPlotText(scene, id, (edit) => latest.current(edit))
|
|
2238
|
+
}), [scene, latest]);
|
|
2239
|
+
}
|
|
2240
|
+
var PLOT_HANDLE_LAYER_ID = "plot-handles";
|
|
2241
|
+
var PLOT_HANDLE_AFFORDANCE_KIND = `layer:${PLOT_HANDLE_LAYER_ID}`;
|
|
2242
|
+
function plotHandleScratchOf(hit) {
|
|
2243
|
+
const p = hit?.payload;
|
|
2244
|
+
return p !== void 0 && typeof p.rootId === "string" && typeof p.markId === "string" ? p : null;
|
|
2245
|
+
}
|
|
2246
|
+
function plotOrigin(scene, rootId) {
|
|
2247
|
+
const pose = composeWorldPose(scenePoseAdapter(scene), rootId, scene.composition.compose);
|
|
2248
|
+
return { x: pose.x, y: pose.y };
|
|
2249
|
+
}
|
|
2250
|
+
function* plotHandles(scene) {
|
|
2251
|
+
for (const rootId of plotRootsIn(scene)) {
|
|
2252
|
+
const root = scene.get(rootId);
|
|
2253
|
+
const trait = plotRootOf(root);
|
|
2254
|
+
const editor = trait && plotEditorOf(trait.data);
|
|
2255
|
+
if (trait === null || editor === null || scene.isLocked(rootId) || scene.isHidden(rootId)) continue;
|
|
2256
|
+
const origin = plotOrigin(scene, rootId);
|
|
2257
|
+
for (const id of scene.childrenOf(rootId)) {
|
|
2258
|
+
const node = scene.get(id);
|
|
2259
|
+
const mark = plotMarkOf(node);
|
|
2260
|
+
if (node === void 0 || mark === null) continue;
|
|
2261
|
+
for (const handle of handlesOf(editor.handle(trait.data, { trait: mark, pose: node.pose }))) {
|
|
2262
|
+
const { box, part } = handle;
|
|
2263
|
+
yield {
|
|
2264
|
+
rootId,
|
|
2265
|
+
markId: id,
|
|
2266
|
+
cursor: handle.cursor,
|
|
2267
|
+
at: { x: origin.x + handle.at.x, y: origin.y + handle.at.y },
|
|
2268
|
+
...part !== void 0 ? { part } : {},
|
|
2269
|
+
...box !== void 0 ? { box: { x: origin.x + box.x, y: origin.y + box.y, width: box.width, height: box.height } } : {}
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
var HIT_RADIUS_PX = 6;
|
|
2276
|
+
function handleAffordance(scene) {
|
|
2277
|
+
const regions = () => [...plotHandles(scene)].sort((a, b) => Number(b.box !== void 0) - Number(a.box !== void 0)).map(({ rootId, markId, at: at3, cursor, part, box }) => ({
|
|
2278
|
+
id: part === void 0 ? markId : `${markId}#${part}`,
|
|
2279
|
+
targetId: null,
|
|
2280
|
+
shape: box !== void 0 ? { kind: "rect", ...box } : { kind: "point", x: at3.x, y: at3.y, hitRadiusPx: HIT_RADIUS_PX },
|
|
2281
|
+
cursor,
|
|
2282
|
+
strength: "exclusive",
|
|
2283
|
+
claimedKinds: ["pointer"],
|
|
2284
|
+
bind: () => ({ initialScratch: { rootId, markId, at: at3, ...part !== void 0 ? { part } : {} } })
|
|
2285
|
+
}));
|
|
2286
|
+
return { id: PLOT_HANDLE_LAYER_ID, regions, regionsAt: regions };
|
|
2287
|
+
}
|
|
2288
|
+
function plotHandleLayer(scene) {
|
|
2289
|
+
return composeAffordanceLayer(PLOT_HANDLE_LAYER_ID, "Plot handles", [handleAffordance(scene)]);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// src/editing/dragAction.ts
|
|
2293
|
+
var PLOT_DRAG_ACTION_ID = "plot.dragValue";
|
|
2294
|
+
function pressOf(ctx, measure) {
|
|
2295
|
+
const pressed = plotHandleScratchOf(ctx.drag?.affordance);
|
|
2296
|
+
const scene = ctx.deps.scene;
|
|
2297
|
+
const root = pressed === null ? void 0 : scene?.get(pressed.rootId);
|
|
2298
|
+
const trait = plotRootOf(root);
|
|
2299
|
+
const mark = plotMarkOf(pressed === null ? void 0 : scene?.get(pressed.markId));
|
|
2300
|
+
const editor = trait && plotEditorOf(trait.data);
|
|
2301
|
+
if (pressed === null || scene === void 0 || root === void 0 || trait === null || mark === null || editor === null) return null;
|
|
2302
|
+
const size = { width: root.pose.width, height: root.pose.height };
|
|
2303
|
+
const propose = editor.drag(trait.data, mark, size, trait.theme, measure, pressed.part);
|
|
2304
|
+
if (propose === null) return null;
|
|
2305
|
+
const origin = plotOrigin(scene, pressed.rootId);
|
|
2306
|
+
const start = ctx.drag?.start ?? ctx.world;
|
|
2307
|
+
const grab = { x: pressed.at.x - start.x, y: pressed.at.y - start.y };
|
|
2308
|
+
return {
|
|
2309
|
+
rootId: pressed.rootId,
|
|
2310
|
+
propose,
|
|
2311
|
+
toPlot: (w) => ({ x: w.x + grab.x - origin.x, y: w.y + grab.y - origin.y }),
|
|
2312
|
+
redraw: (data) => markSpecs(pressed.rootId, root.layer, compilePlot(data, size, trait.theme, measure)),
|
|
2313
|
+
deps: ctx.deps
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
function commitPlotToScene(edit, ctx, measure = textMeasure) {
|
|
2317
|
+
const scene = ctx.deps.scene;
|
|
2318
|
+
if (scene === void 0 || plotRootOf(scene.get(edit.rootId)) === null) return;
|
|
2319
|
+
replacePlotData(scene, edit.rootId, edit.data, { label: "Edit value", measure });
|
|
2320
|
+
}
|
|
2321
|
+
function createPlotDragAction(opts = {}) {
|
|
2322
|
+
const measure = opts.measure ?? textMeasure;
|
|
2323
|
+
const commit = opts.commit ?? ((edit, ctx) => commitPlotToScene(edit, ctx, measure));
|
|
2324
|
+
return {
|
|
2325
|
+
id: opts.id ?? PLOT_DRAG_ACTION_ID,
|
|
2326
|
+
label: "Edit value",
|
|
2327
|
+
group: "plot",
|
|
2328
|
+
requires: ["scene"],
|
|
2329
|
+
eligible: { mode: "normal" },
|
|
2330
|
+
invoker: {
|
|
2331
|
+
timing: "ongoing",
|
|
2332
|
+
start(ctx) {
|
|
2333
|
+
const p = pressOf(ctx, measure);
|
|
2334
|
+
if (p === null) return {};
|
|
2335
|
+
let next = null;
|
|
2336
|
+
let shown = /* @__PURE__ */ new Map();
|
|
2337
|
+
let open = true;
|
|
2338
|
+
const scene = ctx.deps.scene;
|
|
2339
|
+
const previewing = () => open && next !== null ? shown : null;
|
|
2340
|
+
return {
|
|
2341
|
+
kind: "plot-drag-value",
|
|
2342
|
+
onMove(moveCtx) {
|
|
2343
|
+
next = p.propose(p.toPlot(moveCtx.world));
|
|
2344
|
+
shown = new Map(p.redraw(next).map((spec) => [spec.id, spec]));
|
|
2345
|
+
},
|
|
2346
|
+
previewIds: () => {
|
|
2347
|
+
const specs = previewing();
|
|
2348
|
+
return specs === null ? null : [...specs.keys()].filter((id) => scene.get(id) !== void 0);
|
|
2349
|
+
},
|
|
2350
|
+
previewPose: (id) => previewing()?.get(id)?.pose ?? null,
|
|
2351
|
+
previewData: (id) => previewing()?.get(id)?.data ?? null,
|
|
2352
|
+
onEnd(endCtx, reason) {
|
|
2353
|
+
open = false;
|
|
2354
|
+
if (reason !== "commit" || next === null) return;
|
|
2355
|
+
commit({ rootId: p.rootId, data: p.propose(p.toPlot(endCtx.world)) }, { ...endCtx, deps: p.deps });
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
},
|
|
2360
|
+
enabled: () => true
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/editing/plotEditing.ts
|
|
2365
|
+
var GRAB_PLOT_HANDLE_ACTION_ID = "plot.grabHandle";
|
|
2366
|
+
var grabHandleAction = {
|
|
2367
|
+
id: GRAB_PLOT_HANDLE_ACTION_ID,
|
|
2368
|
+
label: "Grab chart handle",
|
|
2369
|
+
group: "plot",
|
|
2370
|
+
invoker: { timing: "immediate", run: () => {
|
|
2371
|
+
} },
|
|
2372
|
+
enabled: () => true
|
|
2373
|
+
};
|
|
2374
|
+
function plotEditing(opts) {
|
|
2375
|
+
const actionId = opts.id ?? PLOT_DRAG_ACTION_ID;
|
|
2376
|
+
const target = `affordance:${PLOT_HANDLE_AFFORDANCE_KIND}`;
|
|
2377
|
+
return {
|
|
2378
|
+
layer: plotHandleLayer(opts.scene),
|
|
2379
|
+
contribution: {
|
|
2380
|
+
id: "weasel-plot-editing",
|
|
2381
|
+
eligibility: { claimed: true },
|
|
2382
|
+
actions: [createPlotDragAction({ ...opts, id: actionId }), grabHandleAction],
|
|
2383
|
+
bindings: [
|
|
2384
|
+
{ spec: { kind: "drag", target }, actionId, opts: { id: "weasel-plot-editing.drag" } },
|
|
2385
|
+
{ spec: { kind: "pointerDown", target }, actionId: GRAB_PLOT_HANDLE_ACTION_ID, opts: { id: "weasel-plot-editing.pointerDown" } },
|
|
2386
|
+
{ spec: { kind: "click", target }, actionId: GRAB_PLOT_HANDLE_ACTION_ID, opts: { id: "weasel-plot-editing.click" } }
|
|
2387
|
+
]
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
var ROOT = "plot";
|
|
2392
|
+
var FEATURES = ["pick", "edit"];
|
|
2393
|
+
function PlotView(props) {
|
|
2394
|
+
return /* @__PURE__ */ jsx(WeaselProvider, { children: /* @__PURE__ */ jsx(Inner, { ...props }) });
|
|
2395
|
+
}
|
|
2396
|
+
function Inner({ data, onChange, width: width2, height, theme, measure, background, className, "aria-label": label }) {
|
|
2397
|
+
const [initial] = useState(() => plotSpecs(data, {
|
|
2398
|
+
id: ROOT,
|
|
2399
|
+
box: { x: 0, y: 0, width: width2, height },
|
|
2400
|
+
...theme ? { theme } : {},
|
|
2401
|
+
...measure ? { measure } : {}
|
|
2402
|
+
}));
|
|
2403
|
+
const scene = useScene({ systemLayers: [{ id: "main" }], initial });
|
|
2404
|
+
const changed = useLatest(onChange);
|
|
2405
|
+
useEffect(() => {
|
|
2406
|
+
const root = scene.get(ROOT);
|
|
2407
|
+
const pose = root?.pose;
|
|
2408
|
+
const trait = plotRootOf(root);
|
|
2409
|
+
const sized = pose !== void 0 && pose.width === width2 && pose.height === height;
|
|
2410
|
+
if (trait === null || trait.data === data && sized && (theme === void 0 || trait.theme === theme)) return;
|
|
2411
|
+
scene.untracked(() => {
|
|
2412
|
+
if (!sized) scene.setPose(ROOT, { x: 0, y: 0, width: width2, height });
|
|
2413
|
+
replacePlotData(scene, ROOT, data, { ...theme ? { theme } : {}, ...measure ? { measure } : {} });
|
|
2414
|
+
});
|
|
2415
|
+
}, [scene, data, width2, height, theme, measure]);
|
|
2416
|
+
useEffect(() => {
|
|
2417
|
+
let last = plotRootOf(scene.get(ROOT))?.data;
|
|
2418
|
+
return scene.subscribe(() => {
|
|
2419
|
+
const now = plotRootOf(scene.get(ROOT))?.data;
|
|
2420
|
+
if (now === void 0 || now === last) return;
|
|
2421
|
+
last = now;
|
|
2422
|
+
changed.current?.(now);
|
|
2423
|
+
});
|
|
2424
|
+
}, [scene, changed]);
|
|
2425
|
+
const editing = useMemo(() => plotEditing({ scene, ...measure ? { measure } : {} }), [scene, measure]);
|
|
2426
|
+
const ambient = useMemo(() => [editing.contribution], [editing]);
|
|
2427
|
+
const textEditor = usePlotTextEdit(scene, (edit) => replacePlotData(scene, edit.rootId, edit.data, {
|
|
2428
|
+
label: edit.label,
|
|
2429
|
+
...measure ? { measure } : {}
|
|
2430
|
+
}));
|
|
2431
|
+
const [canvas, setCanvas] = useState(null);
|
|
2432
|
+
useEffect(() => canvas === null ? void 0 : canvas.registerLayer(editing.layer), [canvas, editing]);
|
|
2433
|
+
return /* @__PURE__ */ jsx("div", { className, "data-wzl-plot": "", children: /* @__PURE__ */ jsx(
|
|
2434
|
+
SceneCanvas,
|
|
2435
|
+
{
|
|
2436
|
+
ref: setCanvas,
|
|
2437
|
+
scene,
|
|
2438
|
+
features: FEATURES,
|
|
2439
|
+
enableKeybindings: true,
|
|
2440
|
+
ambient,
|
|
2441
|
+
textEditor,
|
|
2442
|
+
width: width2,
|
|
2443
|
+
height,
|
|
2444
|
+
...background ? { backgroundFill: background } : {},
|
|
2445
|
+
...label ? { "aria-label": label } : {}
|
|
2446
|
+
}
|
|
2447
|
+
) });
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// src/editing/schemaKit.ts
|
|
2451
|
+
var textOf = (v) => typeof v === "string" ? v : void 0;
|
|
2452
|
+
var numberOf2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2453
|
+
var integerOf = (v) => {
|
|
2454
|
+
const n2 = numberOf2(v);
|
|
2455
|
+
return n2 !== void 0 && Number.isInteger(n2) ? n2 : void 0;
|
|
2456
|
+
};
|
|
2457
|
+
var choiceOf = (v, options) => options.includes(v) ? v : void 0;
|
|
2458
|
+
var listOf = (text) => text.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
2459
|
+
var titleField = () => ({
|
|
2460
|
+
key: "title",
|
|
2461
|
+
label: "Title",
|
|
2462
|
+
type: { kind: "string" },
|
|
2463
|
+
optional: true,
|
|
2464
|
+
get: (data) => data.title,
|
|
2465
|
+
set: (data, v) => v === void 0 || typeof v === "string" ? withTitle(data, v ?? "") : data
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
// src/editing/lists.ts
|
|
2469
|
+
function insertAt(list, at3, item, who) {
|
|
2470
|
+
if (!Number.isInteger(at3) || at3 < 0 || at3 > list.length) throw new Error(`${who}: cannot insert at ${at3} in a list of ${list.length}`);
|
|
2471
|
+
return [...list.slice(0, at3), item, ...list.slice(at3)];
|
|
2472
|
+
}
|
|
2473
|
+
function removeAt(list, at3, who) {
|
|
2474
|
+
if (!Number.isInteger(at3) || at3 < 0 || at3 >= list.length) throw new Error(`${who}: no item ${at3} in a list of ${list.length}`);
|
|
2475
|
+
return list.filter((_, i) => i !== at3);
|
|
2476
|
+
}
|
|
2477
|
+
function moveAt(list, from, to, who) {
|
|
2478
|
+
const out = removeAt(list, from, who);
|
|
2479
|
+
if (!Number.isInteger(to) || to < 0 || to > out.length) throw new Error(`${who}: cannot move to ${to} in a list of ${list.length}`);
|
|
2480
|
+
out.splice(to, 0, list[from]);
|
|
2481
|
+
return out;
|
|
2482
|
+
}
|
|
2483
|
+
function updateAt(list, at3, change, who) {
|
|
2484
|
+
if (!Number.isInteger(at3) || at3 < 0 || at3 >= list.length) throw new Error(`${who}: no item ${at3} in a list of ${list.length}`);
|
|
2485
|
+
return list.map((x, i) => i === at3 ? change(x) : x);
|
|
2486
|
+
}
|
|
2487
|
+
function freshName(base, taken, from = 1, sep = " ") {
|
|
2488
|
+
const used = new Set(taken);
|
|
2489
|
+
let n2 = from;
|
|
2490
|
+
while (used.has(`${base}${sep}${n2}`)) n2++;
|
|
2491
|
+
return `${base}${sep}${n2}`;
|
|
2492
|
+
}
|
|
2493
|
+
function withOptional(obj, key, value) {
|
|
2494
|
+
const { [key]: _, ...rest } = obj;
|
|
2495
|
+
return value === void 0 ? rest : { ...rest, [key]: value };
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// src/gantt/structure.ts
|
|
2499
|
+
var TAG_ORDER = ["done", "active", "crit", "milestone"];
|
|
2500
|
+
function freshTask(data) {
|
|
2501
|
+
const names = ganttTasks(data).map((t) => t.task.name);
|
|
2502
|
+
return { name: freshName("Task", names, names.length + 1), start: { kind: "previous" }, end: { kind: "duration", amount: 1, unit: "d" } };
|
|
2503
|
+
}
|
|
2504
|
+
function renameRefs(data, from, to) {
|
|
2505
|
+
const swap = (ids2) => ids2.flatMap((id) => id !== from ? [id] : to === void 0 ? [] : [to]);
|
|
2506
|
+
const start = (s) => {
|
|
2507
|
+
if (s.kind !== "after" || !s.ids.includes(from)) return s;
|
|
2508
|
+
const ids2 = swap(s.ids);
|
|
2509
|
+
return ids2.length > 0 ? { kind: "after", ids: ids2 } : { kind: "previous" };
|
|
2510
|
+
};
|
|
2511
|
+
const end = (e) => {
|
|
2512
|
+
if (e.kind !== "until" || !e.ids.includes(from)) return e;
|
|
2513
|
+
const ids2 = swap(e.ids);
|
|
2514
|
+
return ids2.length > 0 ? { kind: "until", ids: ids2 } : { kind: "duration", amount: 1, unit: "d" };
|
|
2515
|
+
};
|
|
2516
|
+
return {
|
|
2517
|
+
...data,
|
|
2518
|
+
sections: data.sections.map((s) => ({ ...s, tasks: s.tasks.map((t) => ({ ...t, start: start(t.start), end: end(t.end) })) }))
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
var dropRefs = (data, tasks) => tasks.reduce((d, t) => t.id === void 0 ? d : renameRefs(d, t.id, void 0), data);
|
|
2522
|
+
function insertGanttSection(data, at3, section) {
|
|
2523
|
+
const next = section ?? {
|
|
2524
|
+
name: freshName("Section", data.sections.flatMap((s) => s.name === void 0 ? [] : [s.name]), data.sections.length + 1),
|
|
2525
|
+
tasks: [freshTask(data)]
|
|
2526
|
+
};
|
|
2527
|
+
return { ...data, sections: insertAt(data.sections, at3, next, "insertGanttSection") };
|
|
2528
|
+
}
|
|
2529
|
+
function removeGanttSection(data, section) {
|
|
2530
|
+
const sections = removeAt(data.sections, section, "removeGanttSection");
|
|
2531
|
+
return dropRefs({ ...data, sections }, data.sections[section].tasks);
|
|
2532
|
+
}
|
|
2533
|
+
var moveGanttSection = (data, from, to) => from === to ? data : { ...data, sections: moveAt(data.sections, from, to, "moveGanttSection") };
|
|
2534
|
+
var withTasks = (data, section, change, who) => ({ ...data, sections: updateAt(data.sections, section, (s) => ({ ...s, tasks: change(s.tasks) }), who) });
|
|
2535
|
+
var insertGanttTask = (data, section, at3, task) => withTasks(data, section, (tasks) => insertAt(tasks, at3, task ?? freshTask(data), "insertGanttTask"), "insertGanttTask");
|
|
2536
|
+
function removeGanttTask(data, section, task) {
|
|
2537
|
+
const gone = data.sections[section]?.tasks[task];
|
|
2538
|
+
const next = withTasks(data, section, (tasks) => removeAt(tasks, task, "removeGanttTask"), "removeGanttTask");
|
|
2539
|
+
return dropRefs(next, gone === void 0 ? [] : [gone]);
|
|
2540
|
+
}
|
|
2541
|
+
var reorderGanttTask = (data, section, from, to) => from === to ? data : withTasks(data, section, (tasks) => moveAt(tasks, from, to, "reorderGanttTask"), "reorderGanttTask");
|
|
2542
|
+
function moveGanttTaskToSection(data, from, task, to, at3) {
|
|
2543
|
+
const moving = data.sections[from]?.tasks[task];
|
|
2544
|
+
if (moving === void 0) throw new Error(`moveGanttTaskToSection: the gantt holds no task ${task} in section ${from}`);
|
|
2545
|
+
if (data.sections[to] === void 0) throw new Error(`moveGanttTaskToSection: the gantt holds no section ${to}`);
|
|
2546
|
+
if (from === to) return reorderGanttTask(data, from, task, at3 ?? data.sections[from].tasks.length - 1);
|
|
2547
|
+
const taken = withTasks(data, from, (tasks) => removeAt(tasks, task, "moveGanttTaskToSection"), "moveGanttTaskToSection");
|
|
2548
|
+
return withTasks(taken, to, (tasks) => insertAt(tasks, at3 ?? tasks.length, moving, "moveGanttTaskToSection"), "moveGanttTaskToSection");
|
|
2549
|
+
}
|
|
2550
|
+
function withTask3(data, section, task, change, who) {
|
|
2551
|
+
const target = data.sections[section]?.tasks[task];
|
|
2552
|
+
if (target === void 0) throw new Error(`${who}: the gantt holds no task ${task} in section ${section}`);
|
|
2553
|
+
const next = change(target);
|
|
2554
|
+
return next === target ? data : withTasks(data, section, (tasks) => tasks.map((t, i) => i === task ? next : t), who);
|
|
2555
|
+
}
|
|
2556
|
+
function setGanttTaskId(data, section, task, id) {
|
|
2557
|
+
const next = id?.trim() || void 0;
|
|
2558
|
+
const target = data.sections[section]?.tasks[task];
|
|
2559
|
+
if (target === void 0) throw new Error(`setGanttTaskId: the gantt holds no task ${task} in section ${section}`);
|
|
2560
|
+
if (target.id === next) return data;
|
|
2561
|
+
if (next !== void 0 && /\s/.test(next)) throw new Error(`setGanttTaskId: an id holds no spaces, and "${next}" does`);
|
|
2562
|
+
if (next !== void 0 && ganttTasks(data).some((t) => t.task.id === next)) throw new Error(`setGanttTaskId: a task already has id "${next}"`);
|
|
2563
|
+
const renamed = withTask3(data, section, task, (t) => withOptional(t, "id", next), "setGanttTaskId");
|
|
2564
|
+
return target.id === void 0 ? renamed : renameRefs(renamed, target.id, next);
|
|
2565
|
+
}
|
|
2566
|
+
var setGanttTaskTags = (data, section, task, tags) => withTask3(data, section, task, (t) => {
|
|
2567
|
+
const next = TAG_ORDER.filter((tag) => tags.includes(tag));
|
|
2568
|
+
const old = TAG_ORDER.filter((tag) => t.tags?.includes(tag));
|
|
2569
|
+
return next.join() === old.join() ? t : withOptional(t, "tags", next.length > 0 ? next : void 0);
|
|
2570
|
+
}, "setGanttTaskTags");
|
|
2571
|
+
var setGanttTaskStart = (data, section, task, start) => withTask3(data, section, task, (t) => JSON.stringify(t.start) === JSON.stringify(start) ? t : { ...t, start }, "setGanttTaskStart");
|
|
2572
|
+
var setGanttTaskEnd = (data, section, task, end) => withTask3(data, section, task, (t) => JSON.stringify(t.end) === JSON.stringify(end) ? t : { ...t, end }, "setGanttTaskEnd");
|
|
2573
|
+
function setGanttSetting(data, key, value) {
|
|
2574
|
+
const text = value?.trim() || void 0;
|
|
2575
|
+
return data[key] === text ? data : withOptional(data, key, text);
|
|
2576
|
+
}
|
|
2577
|
+
function setGanttExcludes(data, excludes) {
|
|
2578
|
+
const next = excludes.map((e) => e.trim()).filter((e) => e !== "");
|
|
2579
|
+
if (next.join() === (data.excludes ?? []).join()) return data;
|
|
2580
|
+
return withOptional(data, "excludes", next.length > 0 ? next : void 0);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/gantt/text.ts
|
|
2584
|
+
var DURATION = /^(\d+(?:\.\d+)?|\.\d+)(ms|[smhdwMy])$/;
|
|
2585
|
+
var ids = (text) => text.trim().split(/\s+/);
|
|
2586
|
+
var formatGanttStart = (start) => start.kind === "date" ? start.date : start.kind === "after" ? `after ${start.ids.join(" ")}` : "";
|
|
2587
|
+
function parseGanttStart(text) {
|
|
2588
|
+
const t = text.trim();
|
|
2589
|
+
if (t === "") return { kind: "previous" };
|
|
2590
|
+
const after = /^after\s+(.+)$/.exec(t);
|
|
2591
|
+
return after !== null ? { kind: "after", ids: ids(after[1]) } : { kind: "date", date: t };
|
|
2592
|
+
}
|
|
2593
|
+
function formatGanttEnd(end) {
|
|
2594
|
+
if (end.kind === "date") return end.date;
|
|
2595
|
+
if (end.kind === "until") return `until ${end.ids.join(" ")}`;
|
|
2596
|
+
return `${end.amount}${end.unit}`;
|
|
2597
|
+
}
|
|
2598
|
+
function parseGanttEnd(text) {
|
|
2599
|
+
const t = text.trim();
|
|
2600
|
+
const until = /^until\s+(.+)$/.exec(t);
|
|
2601
|
+
if (until !== null) return { kind: "until", ids: ids(until[1]) };
|
|
2602
|
+
const d = DURATION.exec(t);
|
|
2603
|
+
return d !== null ? { kind: "duration", amount: Number(d[1]), unit: d[2] } : { kind: "date", date: t };
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// src/gantt/schema.ts
|
|
2607
|
+
var setting = (key, label, placeholder) => ({
|
|
2608
|
+
key,
|
|
2609
|
+
label,
|
|
2610
|
+
type: { kind: "string", placeholder },
|
|
2611
|
+
optional: true,
|
|
2612
|
+
get: (d) => d[key],
|
|
2613
|
+
set: (d, v) => v === void 0 || typeof v === "string" ? setGanttSetting(d, key, v) : d
|
|
2614
|
+
});
|
|
2615
|
+
var taskOf2 = (d, scope, row) => d.sections[scope[0]]?.tasks[row];
|
|
2616
|
+
var reads = (d, at3) => at3.kind !== "date" || parseGanttDate(at3.date, dateFormatOf(d)) !== null;
|
|
2617
|
+
var tagColumn = (tag, label) => ({
|
|
2618
|
+
key: tag,
|
|
2619
|
+
label,
|
|
2620
|
+
type: { kind: "boolean" },
|
|
2621
|
+
get: (d, s, r) => taskOf2(d, s, r)?.tags?.includes(tag) ?? false,
|
|
2622
|
+
set: (d, s, r, v) => {
|
|
2623
|
+
const tags = taskOf2(d, s, r)?.tags ?? [];
|
|
2624
|
+
if (typeof v !== "boolean") return d;
|
|
2625
|
+
return setGanttTaskTags(d, s[0], r, v ? [...tags, tag] : tags.filter((t) => t !== tag));
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
var sectionChoices = (d) => d.sections.map((s, i) => `${i + 1}. ${s.name ?? "Untitled section"}`);
|
|
2629
|
+
var sectionColumn = (d) => ({
|
|
2630
|
+
key: "section",
|
|
2631
|
+
label: "Section",
|
|
2632
|
+
type: { kind: "enum", options: sectionChoices(d) },
|
|
2633
|
+
get: (data, s) => sectionChoices(data)[s[0]],
|
|
2634
|
+
set: (data, s, r, v) => {
|
|
2635
|
+
const to = typeof v === "string" ? sectionChoices(data).indexOf(v) : -1;
|
|
2636
|
+
return to < 0 || to === s[0] ? data : moveGanttTaskToSection(data, s[0], r, to);
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
var TASK_COLUMNS = [
|
|
2640
|
+
{
|
|
2641
|
+
key: "name",
|
|
2642
|
+
label: "Task",
|
|
2643
|
+
type: { kind: "string" },
|
|
2644
|
+
get: (d, s, r) => taskOf2(d, s, r)?.name,
|
|
2645
|
+
set: (d, s, r, v) => setGanttTaskName(d, s[0], r, textOf(v) ?? "")
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
key: "id",
|
|
2649
|
+
label: "Id",
|
|
2650
|
+
type: { kind: "string" },
|
|
2651
|
+
optional: true,
|
|
2652
|
+
get: (d, s, r) => taskOf2(d, s, r)?.id,
|
|
2653
|
+
set: (d, s, r, v) => {
|
|
2654
|
+
const id = textOf(v)?.trim() || void 0;
|
|
2655
|
+
const taken = id !== void 0 && (/\s/.test(id) || d.sections.some((sec) => sec.tasks.some((t) => t.id === id)));
|
|
2656
|
+
return v !== void 0 && typeof v !== "string" || taken ? d : setGanttTaskId(d, s[0], r, id);
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
key: "start",
|
|
2661
|
+
label: "Start",
|
|
2662
|
+
type: { kind: "string", placeholder: "after the task before" },
|
|
2663
|
+
get: (d, s, r) => {
|
|
2664
|
+
const t = taskOf2(d, s, r);
|
|
2665
|
+
return t && formatGanttStart(t.start);
|
|
2666
|
+
},
|
|
2667
|
+
set: (d, s, r, v) => {
|
|
2668
|
+
const start = parseGanttStart(textOf(v) ?? "");
|
|
2669
|
+
return reads(d, start) ? setGanttTaskStart(d, s[0], r, start) : d;
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
{
|
|
2673
|
+
key: "end",
|
|
2674
|
+
label: "End",
|
|
2675
|
+
type: { kind: "string", placeholder: "3d, a date, or until an id" },
|
|
2676
|
+
get: (d, s, r) => {
|
|
2677
|
+
const t = taskOf2(d, s, r);
|
|
2678
|
+
return t && formatGanttEnd(t.end);
|
|
2679
|
+
},
|
|
2680
|
+
set: (d, s, r, v) => {
|
|
2681
|
+
const text = textOf(v)?.trim();
|
|
2682
|
+
if (!text) return d;
|
|
2683
|
+
const end = parseGanttEnd(text);
|
|
2684
|
+
return reads(d, end) ? setGanttTaskEnd(d, s[0], r, end) : d;
|
|
2685
|
+
}
|
|
2686
|
+
},
|
|
2687
|
+
tagColumn("done", "Done"),
|
|
2688
|
+
tagColumn("active", "Active"),
|
|
2689
|
+
tagColumn("crit", "Critical"),
|
|
2690
|
+
tagColumn("milestone", "Milestone")
|
|
2691
|
+
];
|
|
2692
|
+
var ganttSchema = {
|
|
2693
|
+
fields: [
|
|
2694
|
+
titleField(),
|
|
2695
|
+
setting("dateFormat", "Date format", "YYYY-MM-DD"),
|
|
2696
|
+
setting("axisFormat", "Axis format", "%Y-%m-%d"),
|
|
2697
|
+
{
|
|
2698
|
+
key: "excludes",
|
|
2699
|
+
label: "Excludes",
|
|
2700
|
+
type: { kind: "string", placeholder: "weekends, 2024-01-01" },
|
|
2701
|
+
optional: true,
|
|
2702
|
+
get: (d) => d.excludes && d.excludes.length > 0 ? d.excludes.join(", ") : void 0,
|
|
2703
|
+
set: (d, v) => v === void 0 || typeof v === "string" ? setGanttExcludes(d, listOf(v ?? "")) : d
|
|
2704
|
+
},
|
|
2705
|
+
setting("todayMarker", "Today marker", "off, or a style")
|
|
2706
|
+
],
|
|
2707
|
+
tables: [{
|
|
2708
|
+
key: "sections",
|
|
2709
|
+
label: "Sections",
|
|
2710
|
+
item: "section",
|
|
2711
|
+
count: (d) => d.sections.length,
|
|
2712
|
+
columns: () => [{
|
|
2713
|
+
key: "name",
|
|
2714
|
+
label: "Section",
|
|
2715
|
+
type: { kind: "string" },
|
|
2716
|
+
get: (d, _, r) => d.sections[r]?.name,
|
|
2717
|
+
set: (d, _, r, v) => setGanttSectionName(d, r, textOf(v) ?? "")
|
|
2718
|
+
}],
|
|
2719
|
+
insert: (d, _, at3) => insertGanttSection(d, at3),
|
|
2720
|
+
remove: (d, _, r) => removeGanttSection(d, r),
|
|
2721
|
+
move: (d, _, from, to) => moveGanttSection(d, from, to),
|
|
2722
|
+
rowLabel: (d, _, r) => d.sections[r]?.name ?? "Untitled section",
|
|
2723
|
+
children: [{
|
|
2724
|
+
key: "tasks",
|
|
2725
|
+
label: "Tasks",
|
|
2726
|
+
item: "task",
|
|
2727
|
+
count: (d, s) => d.sections[s[0]]?.tasks.length ?? 0,
|
|
2728
|
+
columns: (d) => [...TASK_COLUMNS, sectionColumn(d)],
|
|
2729
|
+
insert: (d, s, at3) => insertGanttTask(d, s[0], at3),
|
|
2730
|
+
remove: (d, s, r) => removeGanttTask(d, s[0], r),
|
|
2731
|
+
move: (d, s, from, to) => reorderGanttTask(d, s[0], from, to),
|
|
2732
|
+
rowLabel: (d, s, r) => taskOf2(d, s, r)?.name ?? ""
|
|
2733
|
+
}]
|
|
2734
|
+
}]
|
|
2735
|
+
};
|
|
2736
|
+
|
|
2737
|
+
// src/gitgraph/structure.ts
|
|
2738
|
+
var GIT_COMMAND_KINDS = ["commit", "branch", "checkout", "merge", "cherry-pick"];
|
|
2739
|
+
var GIT_COMMAND_FIELDS = {
|
|
2740
|
+
commit: ["id", "type", "tag", "message"],
|
|
2741
|
+
branch: ["name", "order"],
|
|
2742
|
+
checkout: ["name"],
|
|
2743
|
+
merge: ["branch", "id", "type", "tag"],
|
|
2744
|
+
"cherry-pick": ["id", "tag", "parent"]
|
|
2745
|
+
};
|
|
2746
|
+
var mainOf = (data) => data.mainBranch ?? "main";
|
|
2747
|
+
var gitBranchNames = (data) => [mainOf(data), ...data.commands.flatMap((c) => c.kind === "branch" && c.name !== mainOf(data) ? [c.name] : [])];
|
|
2748
|
+
var insertGitCommand = (data, at3, command = { kind: "commit" }) => ({ ...data, commands: insertAt(data.commands, at3, command, "insertGitCommand") });
|
|
2749
|
+
var removeGitCommand = (data, command) => ({ ...data, commands: removeAt(data.commands, command, "removeGitCommand") });
|
|
2750
|
+
var moveGitCommand = (data, from, to) => from === to ? data : { ...data, commands: moveAt(data.commands, from, to, "moveGitCommand") };
|
|
2751
|
+
var setGitCommand = (data, at3, command) => data.commands[at3] === command ? data : { ...data, commands: updateAt(data.commands, at3, () => command, "setGitCommand") };
|
|
2752
|
+
function setGitCommandKind(data, at3, kind) {
|
|
2753
|
+
const old = data.commands[at3];
|
|
2754
|
+
if (old === void 0) throw new Error(`setGitCommandKind: no command ${at3}`);
|
|
2755
|
+
if (old.kind === kind) return data;
|
|
2756
|
+
const kept = Object.fromEntries(GIT_COMMAND_FIELDS[kind].flatMap((f) => {
|
|
2757
|
+
const v = old[f];
|
|
2758
|
+
return v === void 0 || f === "id" && old.kind === "cherry-pick" !== (kind === "cherry-pick") ? [] : [[f, v]];
|
|
2759
|
+
}));
|
|
2760
|
+
const branches = gitBranchNames(data);
|
|
2761
|
+
const ids2 = data.commands.flatMap((c) => (c.kind === "commit" || c.kind === "merge") && c.id !== void 0 ? [c.id] : []);
|
|
2762
|
+
const needs = kind === "branch" ? { name: freshName("branch", branches, branches.length, "-") } : kind === "checkout" ? { name: mainOf(data) } : kind === "merge" ? { branch: branches[branches.length - 1] } : kind === "cherry-pick" ? { id: ids2[ids2.length - 1] ?? "" } : {};
|
|
2763
|
+
return setGitCommand(data, at3, { ...needs, ...kept, kind });
|
|
2764
|
+
}
|
|
2765
|
+
var TYPES = ["NORMAL", "REVERSE", "HIGHLIGHT"];
|
|
2766
|
+
function setGitCommandField(data, at3, field, value) {
|
|
2767
|
+
const cmd = data.commands[at3];
|
|
2768
|
+
if (cmd === void 0) throw new Error(`setGitCommandField: no command ${at3}`);
|
|
2769
|
+
if (!GIT_COMMAND_FIELDS[cmd.kind].includes(field)) throw new Error(`setGitCommandField: a ${cmd.kind} holds no ${field}`);
|
|
2770
|
+
const required = field === "name" || field === "branch" || field === "id" && cmd.kind === "cherry-pick";
|
|
2771
|
+
if (value === void 0 && required) throw new Error(`setGitCommandField: a ${cmd.kind} needs its ${field}`);
|
|
2772
|
+
if (value !== void 0 && field === "order" !== (typeof value === "number")) throw new Error(`setGitCommandField: ${field} takes a ${field === "order" ? "number" : "string"}`);
|
|
2773
|
+
if (field === "type" && value !== void 0 && !TYPES.includes(value)) throw new Error(`setGitCommandField: no commit type "${value}"`);
|
|
2774
|
+
if (cmd[field] === value) return data;
|
|
2775
|
+
return setGitCommand(data, at3, withOptional(cmd, field, value));
|
|
2776
|
+
}
|
|
2777
|
+
var setGitOrientation = (data, orientation) => (data.orientation ?? "LR") === orientation ? data : withOptional(data, "orientation", orientation === "LR" ? void 0 : orientation);
|
|
2778
|
+
function setGitMainBranch(data, name) {
|
|
2779
|
+
const next = name.trim();
|
|
2780
|
+
const old = mainOf(data);
|
|
2781
|
+
if (next === old) return data;
|
|
2782
|
+
if (next === "") throw new Error("setGitMainBranch: a branch needs a name");
|
|
2783
|
+
if (gitBranchNames(data).includes(next)) throw new Error(`setGitMainBranch: a branch is already called "${next}"`);
|
|
2784
|
+
const commands = data.commands.map((c) => {
|
|
2785
|
+
if (c.kind === "checkout" && c.name === old) return { ...c, name: next };
|
|
2786
|
+
if (c.kind === "merge" && c.branch === old) return { ...c, branch: next };
|
|
2787
|
+
return c;
|
|
2788
|
+
});
|
|
2789
|
+
return withOptional({ ...data, commands }, "mainBranch", next === "main" ? void 0 : next);
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
// src/gitgraph/schema.ts
|
|
2793
|
+
var ORIENTATIONS = ["LR", "TB", "BT"];
|
|
2794
|
+
var targetOf = (cmd) => cmd.kind === "branch" || cmd.kind === "checkout" ? "name" : cmd.kind === "merge" ? "branch" : cmd.kind === "cherry-pick" ? "id" : null;
|
|
2795
|
+
function column(key, label, type, field, optional) {
|
|
2796
|
+
const fieldOf = (d, r) => {
|
|
2797
|
+
const cmd = d.commands[r];
|
|
2798
|
+
if (cmd === void 0) return null;
|
|
2799
|
+
if (field === "target") return targetOf(cmd);
|
|
2800
|
+
if (field === "id" && cmd.kind === "cherry-pick") return null;
|
|
2801
|
+
return GIT_COMMAND_FIELDS[cmd.kind].includes(field) ? field : null;
|
|
2802
|
+
};
|
|
2803
|
+
return {
|
|
2804
|
+
key,
|
|
2805
|
+
label,
|
|
2806
|
+
type,
|
|
2807
|
+
optional,
|
|
2808
|
+
applies: (d, _, r) => fieldOf(d, r) !== null,
|
|
2809
|
+
get: (d, _, r) => {
|
|
2810
|
+
const f = fieldOf(d, r);
|
|
2811
|
+
return f === null ? void 0 : d.commands[r][f];
|
|
2812
|
+
},
|
|
2813
|
+
set: (d, _, r, v) => {
|
|
2814
|
+
const f = fieldOf(d, r);
|
|
2815
|
+
if (f === null) return d;
|
|
2816
|
+
const value = f === "order" ? integerOf(v) : textOf(v)?.trim() || void 0;
|
|
2817
|
+
if (value === void 0 && (v !== void 0 || !optional)) return d;
|
|
2818
|
+
if (f === "type" && value !== void 0 && type.kind === "enum" && !type.options.includes(String(value))) return d;
|
|
2819
|
+
return setGitCommandField(d, r, f, value);
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
var gitgraphSchema = {
|
|
2824
|
+
fields: [
|
|
2825
|
+
titleField(),
|
|
2826
|
+
{
|
|
2827
|
+
key: "orientation",
|
|
2828
|
+
label: "Orientation",
|
|
2829
|
+
type: { kind: "enum", options: ORIENTATIONS },
|
|
2830
|
+
get: (d) => d.orientation ?? "LR",
|
|
2831
|
+
set: (d, v) => {
|
|
2832
|
+
const o = choiceOf(v, ORIENTATIONS);
|
|
2833
|
+
return o ? setGitOrientation(d, o) : d;
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
key: "mainBranch",
|
|
2838
|
+
label: "Main branch",
|
|
2839
|
+
type: { kind: "string" },
|
|
2840
|
+
get: (d) => d.mainBranch ?? "main",
|
|
2841
|
+
set: (d, v) => {
|
|
2842
|
+
const name = textOf(v)?.trim();
|
|
2843
|
+
if (!name) return d;
|
|
2844
|
+
try {
|
|
2845
|
+
return setGitMainBranch(d, name);
|
|
2846
|
+
} catch {
|
|
2847
|
+
return d;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
],
|
|
2852
|
+
tables: [{
|
|
2853
|
+
key: "commands",
|
|
2854
|
+
label: "Commands",
|
|
2855
|
+
item: "command",
|
|
2856
|
+
count: (d) => d.commands.length,
|
|
2857
|
+
columns: () => [
|
|
2858
|
+
{
|
|
2859
|
+
key: "kind",
|
|
2860
|
+
label: "Command",
|
|
2861
|
+
type: { kind: "enum", options: GIT_COMMAND_KINDS },
|
|
2862
|
+
get: (d, _, r) => d.commands[r]?.kind,
|
|
2863
|
+
set: (d, _, r, v) => {
|
|
2864
|
+
const k = choiceOf(v, GIT_COMMAND_KINDS);
|
|
2865
|
+
return k ? setGitCommandKind(d, r, k) : d;
|
|
2866
|
+
}
|
|
2867
|
+
},
|
|
2868
|
+
column("target", "Branch or commit", { kind: "string" }, "target", false),
|
|
2869
|
+
column("id", "Id", { kind: "string" }, "id", true),
|
|
2870
|
+
column("type", "Type", { kind: "enum", options: ["NORMAL", "REVERSE", "HIGHLIGHT"] }, "type", true),
|
|
2871
|
+
column("tag", "Tag", { kind: "string" }, "tag", true),
|
|
2872
|
+
column("message", "Message", { kind: "string" }, "message", true),
|
|
2873
|
+
column("order", "Order", { kind: "number", step: 1 }, "order", true),
|
|
2874
|
+
column("parent", "Parent", { kind: "string" }, "parent", true)
|
|
2875
|
+
],
|
|
2876
|
+
insert: (d, _, at3) => insertGitCommand(d, at3),
|
|
2877
|
+
remove: (d, _, r) => removeGitCommand(d, r),
|
|
2878
|
+
move: (d, _, from, to) => moveGitCommand(d, from, to),
|
|
2879
|
+
rowLabel: (d, _, r) => {
|
|
2880
|
+
const c = d.commands[r];
|
|
2881
|
+
return c === void 0 ? "" : [c.kind, ...targetOf(c) ? [c[targetOf(c)]] : []].join(" ");
|
|
2882
|
+
}
|
|
2883
|
+
}]
|
|
2884
|
+
};
|
|
2885
|
+
|
|
2886
|
+
// src/journey/structure.ts
|
|
2887
|
+
var freshTask2 = (data) => {
|
|
2888
|
+
const names = data.sections.flatMap((s) => s.tasks.map((t) => t.name));
|
|
2889
|
+
return { name: freshName("Step", names, names.length + 1), score: 3, actors: [] };
|
|
2890
|
+
};
|
|
2891
|
+
function insertJourneySection(data, at3, section) {
|
|
2892
|
+
const next = section ?? {
|
|
2893
|
+
name: freshName("Section", data.sections.flatMap((s) => s.name === void 0 ? [] : [s.name]), data.sections.length + 1),
|
|
2894
|
+
tasks: [freshTask2(data)]
|
|
2895
|
+
};
|
|
2896
|
+
return { ...data, sections: insertAt(data.sections, at3, next, "insertJourneySection") };
|
|
2897
|
+
}
|
|
2898
|
+
var removeJourneySection = (data, section) => ({ ...data, sections: removeAt(data.sections, section, "removeJourneySection") });
|
|
2899
|
+
var moveJourneySection = (data, from, to) => from === to ? data : { ...data, sections: moveAt(data.sections, from, to, "moveJourneySection") };
|
|
2900
|
+
var withTasks2 = (data, section, change, who) => ({ ...data, sections: updateAt(data.sections, section, (s) => ({ ...s, tasks: change(s.tasks) }), who) });
|
|
2901
|
+
var insertJourneyTask = (data, section, at3, task) => withTasks2(data, section, (ts) => insertAt(ts, at3, task ?? freshTask2(data), "insertJourneyTask"), "insertJourneyTask");
|
|
2902
|
+
var removeJourneyTask = (data, section, task) => withTasks2(data, section, (ts) => removeAt(ts, task, "removeJourneyTask"), "removeJourneyTask");
|
|
2903
|
+
var moveJourneyTask = (data, section, from, to) => from === to ? data : withTasks2(data, section, (ts) => moveAt(ts, from, to, "moveJourneyTask"), "moveJourneyTask");
|
|
2904
|
+
function setJourneyActors(data, section, task, actors) {
|
|
2905
|
+
const next = [...new Set(actors.map((a) => a.trim()).filter((a) => a !== ""))];
|
|
2906
|
+
const target = data.sections[section]?.tasks[task];
|
|
2907
|
+
if (target === void 0) throw new Error(`setJourneyActors: section ${section} holds no step ${task}`);
|
|
2908
|
+
if (next.join("\n") === target.actors.join("\n")) return data;
|
|
2909
|
+
return withTasks2(data, section, (ts) => ts.map((t, i) => i === task ? { ...t, actors: next } : t), "setJourneyActors");
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
// src/journey/schema.ts
|
|
2913
|
+
var taskOf3 = (d, s, r) => d.sections[s[0]]?.tasks[r];
|
|
2914
|
+
var journeySchema = {
|
|
2915
|
+
fields: [titleField()],
|
|
2916
|
+
tables: [{
|
|
2917
|
+
key: "sections",
|
|
2918
|
+
label: "Sections",
|
|
2919
|
+
item: "section",
|
|
2920
|
+
count: (d) => d.sections.length,
|
|
2921
|
+
columns: () => [{
|
|
2922
|
+
key: "name",
|
|
2923
|
+
label: "Section",
|
|
2924
|
+
type: { kind: "string" },
|
|
2925
|
+
get: (d, _, r) => d.sections[r]?.name,
|
|
2926
|
+
set: (d, _, r, v) => {
|
|
2927
|
+
const t = textOf(v)?.trim();
|
|
2928
|
+
return t ? setJourneySectionName(d, r, t) : d;
|
|
2929
|
+
}
|
|
2930
|
+
}],
|
|
2931
|
+
insert: (d, _, at3) => insertJourneySection(d, at3),
|
|
2932
|
+
remove: (d, _, r) => removeJourneySection(d, r),
|
|
2933
|
+
move: (d, _, from, to) => moveJourneySection(d, from, to),
|
|
2934
|
+
rowLabel: (d, _, r) => d.sections[r]?.name ?? "Untitled section",
|
|
2935
|
+
children: [{
|
|
2936
|
+
key: "tasks",
|
|
2937
|
+
label: "Steps",
|
|
2938
|
+
item: "step",
|
|
2939
|
+
count: (d, s) => d.sections[s[0]]?.tasks.length ?? 0,
|
|
2940
|
+
columns: () => [
|
|
2941
|
+
{
|
|
2942
|
+
key: "name",
|
|
2943
|
+
label: "Step",
|
|
2944
|
+
type: { kind: "string" },
|
|
2945
|
+
get: (d, s, r) => taskOf3(d, s, r)?.name,
|
|
2946
|
+
set: (d, s, r, v) => {
|
|
2947
|
+
const t = textOf(v)?.trim();
|
|
2948
|
+
return t ? setJourneyTaskName(d, s[0], r, t) : d;
|
|
2949
|
+
}
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
key: "score",
|
|
2953
|
+
label: "Score",
|
|
2954
|
+
type: { kind: "number", min: 1, max: 5, step: 1 },
|
|
2955
|
+
get: (d, s, r) => taskOf3(d, s, r)?.score,
|
|
2956
|
+
set: (d, s, r, v) => {
|
|
2957
|
+
const n2 = integerOf(v);
|
|
2958
|
+
return n2 !== void 0 && n2 >= 1 && n2 <= 5 ? setJourneyScore(d, s[0], r, n2) : d;
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
key: "actors",
|
|
2963
|
+
label: "Actors",
|
|
2964
|
+
type: { kind: "string", placeholder: "Me, Cat" },
|
|
2965
|
+
get: (d, s, r) => taskOf3(d, s, r)?.actors.join(", "),
|
|
2966
|
+
set: (d, s, r, v) => typeof v === "string" ? setJourneyActors(d, s[0], r, listOf(v)) : d
|
|
2967
|
+
}
|
|
2968
|
+
],
|
|
2969
|
+
insert: (d, s, at3) => insertJourneyTask(d, s[0], at3),
|
|
2970
|
+
remove: (d, s, r) => removeJourneyTask(d, s[0], r),
|
|
2971
|
+
move: (d, s, from, to) => moveJourneyTask(d, s[0], from, to),
|
|
2972
|
+
rowLabel: (d, s, r) => taskOf3(d, s, r)?.name ?? ""
|
|
2973
|
+
}]
|
|
2974
|
+
}]
|
|
2975
|
+
};
|
|
2976
|
+
|
|
2977
|
+
// src/packet/structure.ts
|
|
2978
|
+
var width = (f) => f.end - f.start + 1;
|
|
2979
|
+
var shifted = (fields, from, by) => fields.map((f, i) => i < from || by === 0 ? f : { ...f, start: f.start + by, end: f.end + by });
|
|
2980
|
+
function insertPacketField(data, at3, field) {
|
|
2981
|
+
const start = at3 === 0 ? 0 : (data.fields[at3 - 1]?.end ?? -1) + 1;
|
|
2982
|
+
const next = field ?? { start, end: start + 7, label: freshName("Field", data.fields.map((f) => f.label), data.fields.length + 1) };
|
|
2983
|
+
const room = data.fields[at3] === void 0 ? 0 : Math.max(0, next.end + 1 - data.fields[at3].start);
|
|
2984
|
+
return { ...data, fields: insertAt(shifted(data.fields, at3, room), at3, next, "insertPacketField") };
|
|
2985
|
+
}
|
|
2986
|
+
function removePacketField(data, field) {
|
|
2987
|
+
const gone = data.fields[field];
|
|
2988
|
+
const fields = removeAt(data.fields, field, "removePacketField");
|
|
2989
|
+
return { ...data, fields: gone === void 0 ? fields : shifted(fields, field, -width(gone)) };
|
|
2990
|
+
}
|
|
2991
|
+
function movePacketField(data, from, to) {
|
|
2992
|
+
if (from === to) return data;
|
|
2993
|
+
const moved = moveAt(data.fields, from, to, "movePacketField");
|
|
2994
|
+
const gaps = data.fields.map((g, i) => i === 0 ? g.start : g.start - data.fields[i - 1].end - 1);
|
|
2995
|
+
let end = -1;
|
|
2996
|
+
const fields = moved.map((g, i) => {
|
|
2997
|
+
const start = end + 1 + gaps[i];
|
|
2998
|
+
end = start + width(g) - 1;
|
|
2999
|
+
return { ...g, start, end };
|
|
3000
|
+
});
|
|
3001
|
+
return { ...data, fields };
|
|
3002
|
+
}
|
|
3003
|
+
function setPacketBits(data, field, bits) {
|
|
3004
|
+
if (!Number.isInteger(bits) || bits < 1) throw new Error(`setPacketBits: a field is a whole number of bits, at least 1, and ${bits} is not`);
|
|
3005
|
+
const target = data.fields[field];
|
|
3006
|
+
if (target === void 0) throw new Error(`setPacketBits: the packet holds no field ${field}`);
|
|
3007
|
+
const by = bits - width(target);
|
|
3008
|
+
if (by === 0) return data;
|
|
3009
|
+
return { ...data, fields: shifted(updateAt(data.fields, field, (f) => ({ ...f, end: f.start + bits - 1 }), "setPacketBits"), field + 1, by) };
|
|
3010
|
+
}
|
|
3011
|
+
function setPacketStart(data, field, start) {
|
|
3012
|
+
const target = data.fields[field];
|
|
3013
|
+
if (target === void 0) throw new Error(`setPacketStart: the packet holds no field ${field}`);
|
|
3014
|
+
const floor = field === 0 ? 0 : data.fields[field - 1].end + 1;
|
|
3015
|
+
if (!Number.isInteger(start) || start < floor) throw new Error(`setPacketStart: field ${field} starts at bit ${floor} or later, and ${start} is earlier`);
|
|
3016
|
+
return { ...data, fields: shifted(data.fields, field, start - target.start) };
|
|
3017
|
+
}
|
|
3018
|
+
var setPacketRelative = (data, field, relative) => {
|
|
3019
|
+
const fields = updateAt(data.fields, field, (f) => (f.relative ?? false) === relative ? f : withOptional(f, "relative", relative ? true : void 0), "setPacketRelative");
|
|
3020
|
+
return fields[field] === data.fields[field] ? data : { ...data, fields };
|
|
3021
|
+
};
|
|
3022
|
+
function setPacketBitsPerRow(data, bits) {
|
|
3023
|
+
if (bits !== void 0 && (!Number.isInteger(bits) || bits < 1)) throw new Error(`setPacketBitsPerRow: a row is a whole number of bits, at least 1, and ${bits} is not`);
|
|
3024
|
+
return data.bitsPerRow === bits ? data : withOptional(data, "bitsPerRow", bits);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
// src/packet/schema.ts
|
|
3028
|
+
var packetSchema = {
|
|
3029
|
+
fields: [
|
|
3030
|
+
titleField(),
|
|
3031
|
+
{
|
|
3032
|
+
key: "bitsPerRow",
|
|
3033
|
+
label: "Bits per row",
|
|
3034
|
+
type: { kind: "number", min: 1, step: 1 },
|
|
3035
|
+
optional: true,
|
|
3036
|
+
get: (d) => d.bitsPerRow,
|
|
3037
|
+
set: (d, v) => {
|
|
3038
|
+
const n2 = integerOf(v);
|
|
3039
|
+
return v === void 0 || n2 !== void 0 && n2 >= 1 ? setPacketBitsPerRow(d, n2) : d;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
],
|
|
3043
|
+
tables: [{
|
|
3044
|
+
key: "fields",
|
|
3045
|
+
label: "Fields",
|
|
3046
|
+
item: "field",
|
|
3047
|
+
count: (d) => d.fields.length,
|
|
3048
|
+
columns: () => [
|
|
3049
|
+
{
|
|
3050
|
+
key: "label",
|
|
3051
|
+
label: "Label",
|
|
3052
|
+
type: { kind: "string" },
|
|
3053
|
+
get: (d, _, r) => d.fields[r]?.label,
|
|
3054
|
+
set: (d, _, r, v) => typeof v === "string" ? setPacketLabel(d, r, v.trim()) : d
|
|
3055
|
+
},
|
|
3056
|
+
{
|
|
3057
|
+
key: "start",
|
|
3058
|
+
label: "Start",
|
|
3059
|
+
type: { kind: "number", min: 0, step: 1 },
|
|
3060
|
+
get: (d, _, r) => d.fields[r]?.start,
|
|
3061
|
+
set: (d, _, r, v) => {
|
|
3062
|
+
const n2 = integerOf(v);
|
|
3063
|
+
const floor = r === 0 ? 0 : (d.fields[r - 1]?.end ?? -1) + 1;
|
|
3064
|
+
return n2 !== void 0 && n2 >= floor ? setPacketStart(d, r, n2) : d;
|
|
3065
|
+
}
|
|
3066
|
+
},
|
|
3067
|
+
{
|
|
3068
|
+
key: "bits",
|
|
3069
|
+
label: "Bits",
|
|
3070
|
+
type: { kind: "number", min: 1, step: 1 },
|
|
3071
|
+
get: (d, _, r) => {
|
|
3072
|
+
const f = d.fields[r];
|
|
3073
|
+
return f && f.end - f.start + 1;
|
|
3074
|
+
},
|
|
3075
|
+
set: (d, _, r, v) => {
|
|
3076
|
+
const n2 = integerOf(v);
|
|
3077
|
+
return n2 !== void 0 && n2 >= 1 ? setPacketBits(d, r, n2) : d;
|
|
3078
|
+
}
|
|
3079
|
+
},
|
|
3080
|
+
{
|
|
3081
|
+
key: "end",
|
|
3082
|
+
label: "End",
|
|
3083
|
+
type: { kind: "number" },
|
|
3084
|
+
readOnly: true,
|
|
3085
|
+
get: (d, _, r) => d.fields[r]?.end,
|
|
3086
|
+
set: (d) => d
|
|
3087
|
+
},
|
|
3088
|
+
{
|
|
3089
|
+
key: "relative",
|
|
3090
|
+
label: "Relative",
|
|
3091
|
+
type: { kind: "boolean" },
|
|
3092
|
+
get: (d, _, r) => d.fields[r]?.relative ?? false,
|
|
3093
|
+
set: (d, _, r, v) => typeof v === "boolean" ? setPacketRelative(d, r, v) : d
|
|
3094
|
+
}
|
|
3095
|
+
],
|
|
3096
|
+
insert: (d, _, at3) => insertPacketField(d, at3),
|
|
3097
|
+
remove: (d, _, r) => removePacketField(d, r),
|
|
3098
|
+
move: (d, _, from, to) => movePacketField(d, from, to),
|
|
3099
|
+
rowLabel: (d, _, r) => d.fields[r]?.label ?? ""
|
|
3100
|
+
}]
|
|
3101
|
+
};
|
|
3102
|
+
|
|
3103
|
+
// src/pie/structure.ts
|
|
3104
|
+
function insertPieSlice(data, at3, slice) {
|
|
3105
|
+
const next = slice ?? { label: freshName("Slice", data.slices.map((s) => s.label), data.slices.length + 1), value: 1 };
|
|
3106
|
+
return { ...data, slices: insertAt(data.slices, at3, next, "insertPieSlice") };
|
|
3107
|
+
}
|
|
3108
|
+
var removePieSlice = (data, slice) => ({ ...data, slices: removeAt(data.slices, slice, "removePieSlice") });
|
|
3109
|
+
var movePieSlice = (data, from, to) => from === to ? data : { ...data, slices: moveAt(data.slices, from, to, "movePieSlice") };
|
|
3110
|
+
var setPieShowData = (data, show) => (data.showData ?? false) === show ? data : withOptional(data, "showData", show ? true : void 0);
|
|
3111
|
+
|
|
3112
|
+
// src/pie/schema.ts
|
|
3113
|
+
var pieSchema = {
|
|
3114
|
+
fields: [
|
|
3115
|
+
titleField(),
|
|
3116
|
+
{
|
|
3117
|
+
key: "showData",
|
|
3118
|
+
label: "Show values",
|
|
3119
|
+
type: { kind: "boolean" },
|
|
3120
|
+
get: (d) => d.showData ?? false,
|
|
3121
|
+
set: (d, v) => typeof v === "boolean" ? setPieShowData(d, v) : d
|
|
3122
|
+
}
|
|
3123
|
+
],
|
|
3124
|
+
tables: [{
|
|
3125
|
+
key: "slices",
|
|
3126
|
+
label: "Slices",
|
|
3127
|
+
item: "slice",
|
|
3128
|
+
count: (d) => d.slices.length,
|
|
3129
|
+
columns: () => [
|
|
3130
|
+
{
|
|
3131
|
+
key: "label",
|
|
3132
|
+
label: "Label",
|
|
3133
|
+
type: { kind: "string" },
|
|
3134
|
+
get: (d, _, r) => d.slices[r]?.label,
|
|
3135
|
+
set: (d, _, r, v) => {
|
|
3136
|
+
const t = textOf(v)?.trim();
|
|
3137
|
+
return t ? setPieLabel(d, r, t) : d;
|
|
3138
|
+
}
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
key: "value",
|
|
3142
|
+
label: "Value",
|
|
3143
|
+
type: { kind: "number", min: 0 },
|
|
3144
|
+
get: (d, _, r) => d.slices[r]?.value,
|
|
3145
|
+
set: (d, _, r, v) => {
|
|
3146
|
+
const n2 = numberOf2(v);
|
|
3147
|
+
return n2 !== void 0 && n2 >= 0 ? setPieValue(d, r, n2) : d;
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
],
|
|
3151
|
+
insert: (d, _, at3) => insertPieSlice(d, at3),
|
|
3152
|
+
remove: (d, _, r) => removePieSlice(d, r),
|
|
3153
|
+
move: (d, _, from, to) => movePieSlice(d, from, to),
|
|
3154
|
+
rowLabel: (d, _, r) => d.slices[r]?.label ?? ""
|
|
3155
|
+
}]
|
|
3156
|
+
};
|
|
3157
|
+
|
|
3158
|
+
// src/quadrant/structure.ts
|
|
3159
|
+
function insertQuadrantPoint(data, at3, point) {
|
|
3160
|
+
const next = point ?? { name: freshName("Point", data.points.map((p) => p.name), data.points.length + 1), x: 0.5, y: 0.5 };
|
|
3161
|
+
return { ...data, points: insertAt(data.points, at3, next, "insertQuadrantPoint") };
|
|
3162
|
+
}
|
|
3163
|
+
var removeQuadrantPoint = (data, point) => ({ ...data, points: removeAt(data.points, point, "removeQuadrantPoint") });
|
|
3164
|
+
var moveQuadrantPoint = (data, from, to) => from === to ? data : { ...data, points: moveAt(data.points, from, to, "moveQuadrantPoint") };
|
|
3165
|
+
function restyled(style, key, value) {
|
|
3166
|
+
const next = withOptional(style ?? {}, key, value);
|
|
3167
|
+
return Object.keys(next).length > 0 ? next : void 0;
|
|
3168
|
+
}
|
|
3169
|
+
var withPoint2 = (data, point, change, who) => {
|
|
3170
|
+
const points = updateAt(data.points, point, change, who);
|
|
3171
|
+
return points[point] === data.points[point] ? data : { ...data, points };
|
|
3172
|
+
};
|
|
3173
|
+
var setQuadrantPointStyle = (data, point, key, value) => withPoint2(data, point, (p) => p.style?.[key] === value ? p : withOptional(p, "style", restyled(p.style, key, value)), "setQuadrantPointStyle");
|
|
3174
|
+
function setQuadrantPointClass(data, point, className) {
|
|
3175
|
+
if (className !== void 0 && data.classes?.[className] === void 0) throw new Error(`setQuadrantPointClass: no class "${className}"`);
|
|
3176
|
+
return withPoint2(data, point, (p) => p.className === className ? p : withOptional(p, "className", className), "setQuadrantPointClass");
|
|
3177
|
+
}
|
|
3178
|
+
var quadrantClassNames = (data) => Object.keys(data.classes ?? {});
|
|
3179
|
+
var withClasses = (data, entries) => withOptional(data, "classes", entries.length > 0 ? Object.fromEntries(entries) : void 0);
|
|
3180
|
+
function insertQuadrantClass(data, at3, name, style = { radius: 8 }) {
|
|
3181
|
+
const names = quadrantClassNames(data);
|
|
3182
|
+
const next = name ?? freshName("class", names, names.length + 1, "");
|
|
3183
|
+
if (!/^\w+$/.test(next)) throw new Error(`insertQuadrantClass: a class name is letters, digits and underscores, and "${next}" is not`);
|
|
3184
|
+
if (names.includes(next)) throw new Error(`insertQuadrantClass: a class is already called "${next}"`);
|
|
3185
|
+
return withClasses(data, insertAt(Object.entries(data.classes ?? {}), at3, [next, style], "insertQuadrantClass"));
|
|
3186
|
+
}
|
|
3187
|
+
function removeQuadrantClass(data, index) {
|
|
3188
|
+
const name = quadrantClassNames(data)[index];
|
|
3189
|
+
const next = withClasses(data, removeAt(Object.entries(data.classes ?? {}), index, "removeQuadrantClass"));
|
|
3190
|
+
return { ...next, points: next.points.map((p) => p.className === name ? withOptional(p, "className", void 0) : p) };
|
|
3191
|
+
}
|
|
3192
|
+
var moveQuadrantClass = (data, from, to) => from === to ? data : withClasses(data, moveAt(Object.entries(data.classes ?? {}), from, to, "moveQuadrantClass"));
|
|
3193
|
+
function renameQuadrantClass(data, index, name) {
|
|
3194
|
+
const names = quadrantClassNames(data);
|
|
3195
|
+
const from = names[index];
|
|
3196
|
+
if (from === void 0) throw new Error(`renameQuadrantClass: no class ${index}`);
|
|
3197
|
+
if (from === name) return data;
|
|
3198
|
+
if (!/^\w+$/.test(name)) throw new Error(`renameQuadrantClass: a class name is letters, digits and underscores, and "${name}" is not`);
|
|
3199
|
+
if (names.includes(name)) throw new Error(`renameQuadrantClass: a class is already called "${name}"`);
|
|
3200
|
+
const next = withClasses(data, Object.entries(data.classes).map(([n2, s]) => [n2 === from ? name : n2, s]));
|
|
3201
|
+
return { ...next, points: next.points.map((p) => p.className === from ? { ...p, className: name } : p) };
|
|
3202
|
+
}
|
|
3203
|
+
function setQuadrantClassStyle(data, index, key, value) {
|
|
3204
|
+
const entries = Object.entries(data.classes ?? {});
|
|
3205
|
+
const target = entries[index];
|
|
3206
|
+
if (target === void 0) throw new Error(`setQuadrantClassStyle: no class ${index}`);
|
|
3207
|
+
if (target[1][key] === value) return data;
|
|
3208
|
+
return withClasses(data, updateAt(entries, index, ([n2, s]) => [n2, restyled(s, key, value) ?? {}], "setQuadrantClassStyle"));
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// src/quadrant/schema.ts
|
|
3212
|
+
var axisField = (axis, end, label) => ({
|
|
3213
|
+
key: `${axis}.${end}`,
|
|
3214
|
+
label,
|
|
3215
|
+
type: { kind: "string" },
|
|
3216
|
+
optional: true,
|
|
3217
|
+
get: (d) => d[axis]?.[end],
|
|
3218
|
+
set: (d, v) => v === void 0 || typeof v === "string" ? setQuadrantAxisLabel(d, axis, end, v?.trim() ?? "") : d
|
|
3219
|
+
});
|
|
3220
|
+
var quadrantField = (n2, label) => ({
|
|
3221
|
+
key: `quadrant.${n2}`,
|
|
3222
|
+
label,
|
|
3223
|
+
type: { kind: "string" },
|
|
3224
|
+
optional: true,
|
|
3225
|
+
get: (d) => d.quadrants?.[n2 - 1],
|
|
3226
|
+
set: (d, v) => v === void 0 || typeof v === "string" ? setQuadrantLabel(d, n2, v?.trim() ?? "") : d
|
|
3227
|
+
});
|
|
3228
|
+
var STYLE = [
|
|
3229
|
+
{ key: "radius", label: "Radius", color: false },
|
|
3230
|
+
{ key: "color", label: "Color", color: true },
|
|
3231
|
+
{ key: "strokeColor", label: "Stroke", color: true },
|
|
3232
|
+
{ key: "strokeWidth", label: "Stroke width", color: false }
|
|
3233
|
+
];
|
|
3234
|
+
var styleColumns = (styleOf, restyle) => STYLE.map(({ key, label, color }) => ({
|
|
3235
|
+
key,
|
|
3236
|
+
label,
|
|
3237
|
+
optional: true,
|
|
3238
|
+
type: color ? { kind: "color" } : { kind: "number", min: 0 },
|
|
3239
|
+
get: (d, _, r) => styleOf(d, r)?.[key],
|
|
3240
|
+
set: (d, _, r, v) => {
|
|
3241
|
+
if (v === void 0) return restyle(d, r, key, void 0);
|
|
3242
|
+
const value = color ? textOf(v) : numberOf2(v);
|
|
3243
|
+
return value === void 0 || typeof value === "number" && value < 0 ? d : restyle(d, r, key, value);
|
|
3244
|
+
}
|
|
3245
|
+
}));
|
|
3246
|
+
var unit = (axis, label) => ({
|
|
3247
|
+
key: axis,
|
|
3248
|
+
label,
|
|
3249
|
+
type: { kind: "number", min: 0, max: 1, step: 0.01 },
|
|
3250
|
+
get: (d, _, r) => d.points[r]?.[axis],
|
|
3251
|
+
set: (d, _, r, v) => {
|
|
3252
|
+
const n2 = numberOf2(v);
|
|
3253
|
+
const p = d.points[r];
|
|
3254
|
+
return n2 === void 0 || n2 < 0 || n2 > 1 || p === void 0 ? d : setQuadrantPoint(d, r, { x: p.x, y: p.y, [axis]: n2 });
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
var quadrantSchema = {
|
|
3258
|
+
fields: [
|
|
3259
|
+
titleField(),
|
|
3260
|
+
axisField("x", "low", "X low"),
|
|
3261
|
+
axisField("x", "high", "X high"),
|
|
3262
|
+
axisField("y", "low", "Y low"),
|
|
3263
|
+
axisField("y", "high", "Y high"),
|
|
3264
|
+
quadrantField(1, "Top right"),
|
|
3265
|
+
quadrantField(2, "Top left"),
|
|
3266
|
+
quadrantField(3, "Bottom left"),
|
|
3267
|
+
quadrantField(4, "Bottom right")
|
|
3268
|
+
],
|
|
3269
|
+
tables: [
|
|
3270
|
+
{
|
|
3271
|
+
key: "points",
|
|
3272
|
+
label: "Points",
|
|
3273
|
+
item: "point",
|
|
3274
|
+
count: (d) => d.points.length,
|
|
3275
|
+
columns: (d) => [
|
|
3276
|
+
{
|
|
3277
|
+
key: "name",
|
|
3278
|
+
label: "Name",
|
|
3279
|
+
type: { kind: "string" },
|
|
3280
|
+
get: (data, _, r) => data.points[r]?.name,
|
|
3281
|
+
set: (data, _, r, v) => {
|
|
3282
|
+
const t = textOf(v)?.trim();
|
|
3283
|
+
return t ? setQuadrantPointName(data, r, t) : data;
|
|
3284
|
+
}
|
|
3285
|
+
},
|
|
3286
|
+
unit("x", "X"),
|
|
3287
|
+
unit("y", "Y"),
|
|
3288
|
+
{
|
|
3289
|
+
key: "className",
|
|
3290
|
+
label: "Class",
|
|
3291
|
+
type: { kind: "enum", options: quadrantClassNames(d) },
|
|
3292
|
+
optional: true,
|
|
3293
|
+
get: (data, _, r) => data.points[r]?.className,
|
|
3294
|
+
set: (data, _, r, v) => v === void 0 || typeof v === "string" && data.classes?.[v] !== void 0 ? setQuadrantPointClass(data, r, v) : data
|
|
3295
|
+
},
|
|
3296
|
+
...styleColumns((data, r) => data.points[r]?.style, (data, r, key, v) => setQuadrantPointStyle(data, r, key, v))
|
|
3297
|
+
],
|
|
3298
|
+
insert: (d, _, at3) => insertQuadrantPoint(d, at3),
|
|
3299
|
+
remove: (d, _, r) => removeQuadrantPoint(d, r),
|
|
3300
|
+
move: (d, _, from, to) => moveQuadrantPoint(d, from, to),
|
|
3301
|
+
rowLabel: (d, _, r) => d.points[r]?.name ?? ""
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
key: "classes",
|
|
3305
|
+
label: "Classes",
|
|
3306
|
+
item: "class",
|
|
3307
|
+
count: (d) => quadrantClassNames(d).length,
|
|
3308
|
+
columns: () => [
|
|
3309
|
+
{
|
|
3310
|
+
key: "name",
|
|
3311
|
+
label: "Name",
|
|
3312
|
+
type: { kind: "string" },
|
|
3313
|
+
get: (d, _, r) => quadrantClassNames(d)[r],
|
|
3314
|
+
set: (d, _, r, v) => {
|
|
3315
|
+
const name = textOf(v)?.trim() ?? "";
|
|
3316
|
+
return /^\w+$/.test(name) && !quadrantClassNames(d).includes(name) ? renameQuadrantClass(d, r, name) : d;
|
|
3317
|
+
}
|
|
3318
|
+
},
|
|
3319
|
+
...styleColumns((d, r) => Object.values(d.classes ?? {})[r], (d, r, key, v) => setQuadrantClassStyle(d, r, key, v))
|
|
3320
|
+
],
|
|
3321
|
+
insert: (d, _, at3) => insertQuadrantClass(d, at3),
|
|
3322
|
+
remove: (d, _, r) => removeQuadrantClass(d, r),
|
|
3323
|
+
move: (d, _, from, to) => moveQuadrantClass(d, from, to),
|
|
3324
|
+
rowLabel: (d, _, r) => quadrantClassNames(d)[r] ?? ""
|
|
3325
|
+
}
|
|
3326
|
+
]
|
|
3327
|
+
};
|
|
3328
|
+
|
|
3329
|
+
// src/radar/structure.ts
|
|
3330
|
+
var valuesOf = (data, c) => data.axes.map((_, i) => c.values[i] ?? 0);
|
|
3331
|
+
var ID = /^\w+$/;
|
|
3332
|
+
function insertRadarAxis(data, at3, axis) {
|
|
3333
|
+
const next = axis ?? { id: freshName("axis", data.axes.map((a) => a.id), data.axes.length + 1, "") };
|
|
3334
|
+
const axes = insertAt(data.axes, at3, next, "insertRadarAxis");
|
|
3335
|
+
return { ...data, axes, curves: data.curves.map((c) => ({ ...c, values: insertAt(valuesOf(data, c), at3, 0, "insertRadarAxis") })) };
|
|
3336
|
+
}
|
|
3337
|
+
function removeRadarAxis(data, axis) {
|
|
3338
|
+
const axes = removeAt(data.axes, axis, "removeRadarAxis");
|
|
3339
|
+
return { ...data, axes, curves: data.curves.map((c) => ({ ...c, values: removeAt(valuesOf(data, c), axis, "removeRadarAxis") })) };
|
|
3340
|
+
}
|
|
3341
|
+
function moveRadarAxis(data, from, to) {
|
|
3342
|
+
if (from === to) return data;
|
|
3343
|
+
const axes = moveAt(data.axes, from, to, "moveRadarAxis");
|
|
3344
|
+
return { ...data, axes, curves: data.curves.map((c) => ({ ...c, values: moveAt(valuesOf(data, c), from, to, "moveRadarAxis") })) };
|
|
3345
|
+
}
|
|
3346
|
+
function insertRadarCurve(data, at3, curve) {
|
|
3347
|
+
const next = curve ?? { id: freshName("curve", data.curves.map((c) => c.id), data.curves.length + 1, ""), values: data.axes.map(() => 0) };
|
|
3348
|
+
return { ...data, curves: insertAt(data.curves, at3, next, "insertRadarCurve") };
|
|
3349
|
+
}
|
|
3350
|
+
var removeRadarCurve = (data, curve) => ({ ...data, curves: removeAt(data.curves, curve, "removeRadarCurve") });
|
|
3351
|
+
var moveRadarCurve = (data, from, to) => from === to ? data : { ...data, curves: moveAt(data.curves, from, to, "moveRadarCurve") };
|
|
3352
|
+
function reid(items, index, id, who) {
|
|
3353
|
+
const target = items[index];
|
|
3354
|
+
if (target === void 0) throw new Error(`${who}: no item ${index}`);
|
|
3355
|
+
if (target.id === id) return items;
|
|
3356
|
+
if (!ID.test(id)) throw new Error(`${who}: an id is letters, digits and underscores, and "${id}" is not`);
|
|
3357
|
+
if (items.some((x) => x.id === id)) throw new Error(`${who}: an item already has id "${id}"`);
|
|
3358
|
+
return updateAt(items, index, (x) => ({ ...x, id }), who);
|
|
3359
|
+
}
|
|
3360
|
+
function setRadarAxisId(data, axis, id) {
|
|
3361
|
+
const axes = reid(data.axes, axis, id, "setRadarAxisId");
|
|
3362
|
+
return axes === data.axes ? data : { ...data, axes };
|
|
3363
|
+
}
|
|
3364
|
+
function setRadarCurveId(data, curve, id) {
|
|
3365
|
+
const curves = reid(data.curves, curve, id, "setRadarCurveId");
|
|
3366
|
+
return curves === data.curves ? data : { ...data, curves };
|
|
3367
|
+
}
|
|
3368
|
+
function setRadarOption(data, key, value) {
|
|
3369
|
+
if (key === "ticks" && value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
3370
|
+
throw new Error(`setRadarOption: ticks is a whole number at least 1, and ${String(value)} is not`);
|
|
3371
|
+
}
|
|
3372
|
+
return data[key] === value ? data : withOptional(data, key, value);
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
// src/radar/schema.ts
|
|
3376
|
+
var ID2 = /^\w+$/;
|
|
3377
|
+
var bound = (key, label) => ({
|
|
3378
|
+
key,
|
|
3379
|
+
label,
|
|
3380
|
+
type: { kind: "number" },
|
|
3381
|
+
optional: true,
|
|
3382
|
+
get: (d) => d[key],
|
|
3383
|
+
set: (d, v) => v === void 0 || numberOf2(v) !== void 0 ? setRadarOption(d, key, numberOf2(v)) : d
|
|
3384
|
+
});
|
|
3385
|
+
function named(list, setId, setLabel) {
|
|
3386
|
+
return [
|
|
3387
|
+
{
|
|
3388
|
+
key: "id",
|
|
3389
|
+
label: "Id",
|
|
3390
|
+
type: { kind: "string" },
|
|
3391
|
+
get: (d, _, r) => list(d)[r]?.id,
|
|
3392
|
+
set: (d, _, r, v) => {
|
|
3393
|
+
const id = textOf(v)?.trim() ?? "";
|
|
3394
|
+
return ID2.test(id) && !list(d).some((x, i) => i !== r && x.id === id) ? setId(d, r, id) : d;
|
|
3395
|
+
}
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
key: "label",
|
|
3399
|
+
label: "Label",
|
|
3400
|
+
type: { kind: "string" },
|
|
3401
|
+
optional: true,
|
|
3402
|
+
get: (d, _, r) => list(d)[r]?.label,
|
|
3403
|
+
set: (d, _, r, v) => v === void 0 || typeof v === "string" ? setLabel(d, r, v ?? "") : d
|
|
3404
|
+
}
|
|
3405
|
+
];
|
|
3406
|
+
}
|
|
3407
|
+
var valueColumn = (d, curve) => ({
|
|
3408
|
+
key: `curve.${curve}`,
|
|
3409
|
+
label: d.curves[curve]?.label ?? d.curves[curve]?.id ?? `Curve ${curve + 1}`,
|
|
3410
|
+
type: { kind: "number" },
|
|
3411
|
+
get: (data, _, r) => data.curves[curve]?.values[r] ?? 0,
|
|
3412
|
+
set: (data, _, r, v) => {
|
|
3413
|
+
const n2 = numberOf2(v);
|
|
3414
|
+
return n2 !== void 0 ? setRadarValue(data, curve, r, n2) : data;
|
|
3415
|
+
}
|
|
3416
|
+
});
|
|
3417
|
+
var radarSchema = {
|
|
3418
|
+
fields: [
|
|
3419
|
+
titleField(),
|
|
3420
|
+
bound("min", "Center value"),
|
|
3421
|
+
bound("max", "Rim value"),
|
|
3422
|
+
{
|
|
3423
|
+
key: "graticule",
|
|
3424
|
+
label: "Graticule",
|
|
3425
|
+
type: { kind: "enum", options: ["circle", "polygon"] },
|
|
3426
|
+
get: (d) => d.graticule ?? "circle",
|
|
3427
|
+
set: (d, v) => {
|
|
3428
|
+
const g = choiceOf(v, ["circle", "polygon"]);
|
|
3429
|
+
return g ? setRadarOption(d, "graticule", g === "circle" ? void 0 : g) : d;
|
|
3430
|
+
}
|
|
3431
|
+
},
|
|
3432
|
+
{
|
|
3433
|
+
key: "ticks",
|
|
3434
|
+
label: "Rings",
|
|
3435
|
+
type: { kind: "number", min: 1, step: 1 },
|
|
3436
|
+
optional: true,
|
|
3437
|
+
get: (d) => d.ticks,
|
|
3438
|
+
set: (d, v) => {
|
|
3439
|
+
const n2 = integerOf(v);
|
|
3440
|
+
return v === void 0 || n2 !== void 0 && n2 >= 1 ? setRadarOption(d, "ticks", n2) : d;
|
|
3441
|
+
}
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
key: "showLegend",
|
|
3445
|
+
label: "Legend",
|
|
3446
|
+
type: { kind: "boolean" },
|
|
3447
|
+
get: (d) => d.showLegend ?? true,
|
|
3448
|
+
set: (d, v) => typeof v === "boolean" ? setRadarOption(d, "showLegend", v ? void 0 : false) : d
|
|
3449
|
+
}
|
|
3450
|
+
],
|
|
3451
|
+
tables: [
|
|
3452
|
+
{
|
|
3453
|
+
key: "axes",
|
|
3454
|
+
label: "Axes",
|
|
3455
|
+
item: "axis",
|
|
3456
|
+
count: (d) => d.axes.length,
|
|
3457
|
+
columns: (d) => [
|
|
3458
|
+
...named((data) => data.axes, setRadarAxisId, setRadarAxisLabel),
|
|
3459
|
+
...d.curves.map((_, i) => valueColumn(d, i))
|
|
3460
|
+
],
|
|
3461
|
+
insert: (d, _, at3) => insertRadarAxis(d, at3),
|
|
3462
|
+
remove: (d, _, r) => removeRadarAxis(d, r),
|
|
3463
|
+
move: (d, _, from, to) => moveRadarAxis(d, from, to),
|
|
3464
|
+
rowLabel: (d, _, r) => d.axes[r]?.label ?? d.axes[r]?.id ?? ""
|
|
3465
|
+
},
|
|
3466
|
+
{
|
|
3467
|
+
key: "curves",
|
|
3468
|
+
label: "Curves",
|
|
3469
|
+
item: "curve",
|
|
3470
|
+
count: (d) => d.curves.length,
|
|
3471
|
+
columns: () => named((data) => data.curves, setRadarCurveId, setRadarCurveLabel),
|
|
3472
|
+
insert: (d, _, at3) => insertRadarCurve(d, at3),
|
|
3473
|
+
remove: (d, _, r) => removeRadarCurve(d, r),
|
|
3474
|
+
move: (d, _, from, to) => moveRadarCurve(d, from, to),
|
|
3475
|
+
rowLabel: (d, _, r) => d.curves[r]?.label ?? d.curves[r]?.id ?? ""
|
|
3476
|
+
}
|
|
3477
|
+
]
|
|
3478
|
+
};
|
|
3479
|
+
|
|
3480
|
+
// src/sankey/structure.ts
|
|
3481
|
+
function insertSankeyLink(data, at3, link) {
|
|
3482
|
+
const nodes = sankeyNodes(data);
|
|
3483
|
+
const source = data.links[data.links.length - 1]?.target ?? freshName("Node", nodes, nodes.length + 1);
|
|
3484
|
+
const next = link ?? { source, target: freshName("Node", [...nodes, source], nodes.length + 1), value: 1 };
|
|
3485
|
+
return { ...data, links: insertAt(data.links, at3, next, "insertSankeyLink") };
|
|
3486
|
+
}
|
|
3487
|
+
var removeSankeyLink = (data, link) => ({ ...data, links: removeAt(data.links, link, "removeSankeyLink") });
|
|
3488
|
+
var moveSankeyLink = (data, from, to) => from === to ? data : { ...data, links: moveAt(data.links, from, to, "moveSankeyLink") };
|
|
3489
|
+
function setSankeyEnd(data, link, end, name) {
|
|
3490
|
+
const next = name.trim();
|
|
3491
|
+
if (next === "") throw new Error("setSankeyEnd: a node needs a name");
|
|
3492
|
+
const links = updateAt(data.links, link, (l) => l[end] === next ? l : { ...l, [end]: next }, "setSankeyEnd");
|
|
3493
|
+
return links[link] === data.links[link] ? data : { ...data, links };
|
|
3494
|
+
}
|
|
3495
|
+
function removeSankeyNode(data, name) {
|
|
3496
|
+
if (!sankeyNodes(data).includes(name)) throw new Error(`removeSankeyNode: no node "${name}"`);
|
|
3497
|
+
return { ...data, links: data.links.filter((l) => l.source !== name && l.target !== name) };
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// src/sankey/schema.ts
|
|
3501
|
+
var endColumn = (end, label) => ({
|
|
3502
|
+
key: end,
|
|
3503
|
+
label,
|
|
3504
|
+
type: { kind: "string" },
|
|
3505
|
+
get: (d, _, r) => d.links[r]?.[end],
|
|
3506
|
+
set: (d, _, r, v) => {
|
|
3507
|
+
const t = textOf(v)?.trim();
|
|
3508
|
+
return t ? setSankeyEnd(d, r, end, t) : d;
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
var sankeySchema = {
|
|
3512
|
+
fields: [titleField()],
|
|
3513
|
+
tables: [
|
|
3514
|
+
{
|
|
3515
|
+
key: "links",
|
|
3516
|
+
label: "Links",
|
|
3517
|
+
item: "link",
|
|
3518
|
+
count: (d) => d.links.length,
|
|
3519
|
+
columns: () => [
|
|
3520
|
+
endColumn("source", "From"),
|
|
3521
|
+
endColumn("target", "To"),
|
|
3522
|
+
{
|
|
3523
|
+
key: "value",
|
|
3524
|
+
label: "Value",
|
|
3525
|
+
type: { kind: "number", min: 0 },
|
|
3526
|
+
get: (d, _, r) => d.links[r]?.value,
|
|
3527
|
+
set: (d, _, r, v) => {
|
|
3528
|
+
const n2 = numberOf2(v);
|
|
3529
|
+
return n2 !== void 0 && n2 >= 0 ? setSankeyValue(d, r, n2) : d;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
],
|
|
3533
|
+
insert: (d, _, at3) => insertSankeyLink(d, at3),
|
|
3534
|
+
remove: (d, _, r) => removeSankeyLink(d, r),
|
|
3535
|
+
move: (d, _, from, to) => moveSankeyLink(d, from, to),
|
|
3536
|
+
rowLabel: (d, _, r) => `${d.links[r]?.source ?? ""} \u2192 ${d.links[r]?.target ?? ""}`
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
key: "nodes",
|
|
3540
|
+
label: "Nodes",
|
|
3541
|
+
item: "node",
|
|
3542
|
+
count: (d) => sankeyNodes(d).length,
|
|
3543
|
+
columns: () => [{
|
|
3544
|
+
key: "name",
|
|
3545
|
+
label: "Node",
|
|
3546
|
+
type: { kind: "string" },
|
|
3547
|
+
get: (d, _, r) => sankeyNodes(d)[r],
|
|
3548
|
+
set: (d, _, r, v) => {
|
|
3549
|
+
const name = textOf(v)?.trim();
|
|
3550
|
+
const nodes = sankeyNodes(d);
|
|
3551
|
+
return !name || nodes.includes(name) || nodes[r] === void 0 ? d : renameSankeyNode(d, nodes[r], name);
|
|
3552
|
+
}
|
|
3553
|
+
}],
|
|
3554
|
+
remove: (d, _, r) => {
|
|
3555
|
+
const name = sankeyNodes(d)[r];
|
|
3556
|
+
return name === void 0 ? d : removeSankeyNode(d, name);
|
|
3557
|
+
},
|
|
3558
|
+
rowLabel: (d, _, r) => sankeyNodes(d)[r] ?? ""
|
|
3559
|
+
}
|
|
3560
|
+
]
|
|
3561
|
+
};
|
|
3562
|
+
|
|
3563
|
+
// src/timeline/structure.ts
|
|
3564
|
+
function timelineFlatIndex(data, section, index) {
|
|
3565
|
+
if (data.sections[section]?.periods[index] === void 0) throw new Error(`timelineFlatIndex: section ${section} holds no period ${index}`);
|
|
3566
|
+
return data.sections.slice(0, section).reduce((n2, s) => n2 + s.periods.length, 0) + index;
|
|
3567
|
+
}
|
|
3568
|
+
var allLabels = (data) => data.sections.flatMap((s) => s.periods.map((p) => p.label));
|
|
3569
|
+
var freshPeriod = (data) => {
|
|
3570
|
+
const labels2 = allLabels(data);
|
|
3571
|
+
return { label: freshName("Period", labels2, labels2.length + 1), events: [] };
|
|
3572
|
+
};
|
|
3573
|
+
function insertTimelineSection(data, at3, section) {
|
|
3574
|
+
const next = section ?? {
|
|
3575
|
+
name: freshName("Section", data.sections.flatMap((s) => s.name === void 0 ? [] : [s.name]), data.sections.length + 1),
|
|
3576
|
+
periods: [freshPeriod(data)]
|
|
3577
|
+
};
|
|
3578
|
+
return { ...data, sections: insertAt(data.sections, at3, next, "insertTimelineSection") };
|
|
3579
|
+
}
|
|
3580
|
+
var removeTimelineSection = (data, section) => ({ ...data, sections: removeAt(data.sections, section, "removeTimelineSection") });
|
|
3581
|
+
var moveTimelineSection = (data, from, to) => from === to ? data : { ...data, sections: moveAt(data.sections, from, to, "moveTimelineSection") };
|
|
3582
|
+
var withPeriods = (data, section, change, who) => ({ ...data, sections: updateAt(data.sections, section, (s) => ({ ...s, periods: change(s.periods) }), who) });
|
|
3583
|
+
var insertTimelinePeriod = (data, section, at3, period) => withPeriods(data, section, (ps) => insertAt(ps, at3, period ?? freshPeriod(data), "insertTimelinePeriod"), "insertTimelinePeriod");
|
|
3584
|
+
var removeTimelinePeriod = (data, section, period) => withPeriods(data, section, (ps) => removeAt(ps, period, "removeTimelinePeriod"), "removeTimelinePeriod");
|
|
3585
|
+
var reorderTimelinePeriod = (data, section, from, to) => from === to ? data : withPeriods(data, section, (ps) => moveAt(ps, from, to, "reorderTimelinePeriod"), "reorderTimelinePeriod");
|
|
3586
|
+
var withEvents = (data, section, period, change, who) => withPeriods(data, section, (ps) => updateAt(ps, period, (p) => ({ ...p, events: change(p.events) }), who), who);
|
|
3587
|
+
var insertTimelineEvent = (data, section, period, at3, text) => withEvents(data, section, period, (es) => insertAt(es, at3, text ?? freshName("Event", es, es.length + 1), "insertTimelineEvent"), "insertTimelineEvent");
|
|
3588
|
+
var removeTimelineEvent = (data, section, period, event) => withEvents(data, section, period, (es) => removeAt(es, event, "removeTimelineEvent"), "removeTimelineEvent");
|
|
3589
|
+
var moveTimelineEvent = (data, section, period, from, to) => from === to ? data : withEvents(data, section, period, (es) => moveAt(es, from, to, "moveTimelineEvent"), "moveTimelineEvent");
|
|
3590
|
+
|
|
3591
|
+
// src/timeline/schema.ts
|
|
3592
|
+
var periodOf2 = (d, section, period) => d.sections[section]?.periods[period];
|
|
3593
|
+
var at2 = (s) => [s[0], s[1]];
|
|
3594
|
+
var timelineSchema = {
|
|
3595
|
+
fields: [titleField()],
|
|
3596
|
+
tables: [{
|
|
3597
|
+
key: "sections",
|
|
3598
|
+
label: "Sections",
|
|
3599
|
+
item: "section",
|
|
3600
|
+
count: (d) => d.sections.length,
|
|
3601
|
+
columns: () => [{
|
|
3602
|
+
key: "name",
|
|
3603
|
+
label: "Section",
|
|
3604
|
+
type: { kind: "string" },
|
|
3605
|
+
get: (d, _, r) => d.sections[r]?.name,
|
|
3606
|
+
set: (d, _, r, v) => setTimelineSectionName(d, r, textOf(v) ?? "")
|
|
3607
|
+
}],
|
|
3608
|
+
insert: (d, _, i) => insertTimelineSection(d, i),
|
|
3609
|
+
remove: (d, _, r) => removeTimelineSection(d, r),
|
|
3610
|
+
move: (d, _, from, to) => moveTimelineSection(d, from, to),
|
|
3611
|
+
rowLabel: (d, _, r) => d.sections[r]?.name ?? "Untitled section",
|
|
3612
|
+
children: [{
|
|
3613
|
+
key: "periods",
|
|
3614
|
+
label: "Periods",
|
|
3615
|
+
item: "period",
|
|
3616
|
+
count: (d, s) => d.sections[s[0]]?.periods.length ?? 0,
|
|
3617
|
+
columns: () => [{
|
|
3618
|
+
key: "label",
|
|
3619
|
+
label: "Period",
|
|
3620
|
+
type: { kind: "string" },
|
|
3621
|
+
get: (d, s, r) => periodOf2(d, s[0], r)?.label,
|
|
3622
|
+
set: (d, s, r, v) => setTimelinePeriodLabel(d, timelineFlatIndex(d, s[0], r), textOf(v) ?? "")
|
|
3623
|
+
}],
|
|
3624
|
+
insert: (d, s, i) => insertTimelinePeriod(d, s[0], i),
|
|
3625
|
+
remove: (d, s, r) => removeTimelinePeriod(d, s[0], r),
|
|
3626
|
+
move: (d, s, from, to) => reorderTimelinePeriod(d, s[0], from, to),
|
|
3627
|
+
rowLabel: (d, s, r) => periodOf2(d, s[0], r)?.label ?? "",
|
|
3628
|
+
children: [{
|
|
3629
|
+
key: "events",
|
|
3630
|
+
label: "Events",
|
|
3631
|
+
item: "event",
|
|
3632
|
+
count: (d, s) => periodOf2(d, ...at2(s))?.events.length ?? 0,
|
|
3633
|
+
columns: () => [{
|
|
3634
|
+
key: "text",
|
|
3635
|
+
label: "Event",
|
|
3636
|
+
type: { kind: "string" },
|
|
3637
|
+
get: (d, s, r) => periodOf2(d, ...at2(s))?.events[r],
|
|
3638
|
+
set: (d, s, r, v) => {
|
|
3639
|
+
const text = textOf(v)?.trim();
|
|
3640
|
+
return text ? setTimelineEvent(d, timelineFlatIndex(d, ...at2(s)), r, text) : d;
|
|
3641
|
+
}
|
|
3642
|
+
}],
|
|
3643
|
+
insert: (d, s, i) => insertTimelineEvent(d, ...at2(s), i),
|
|
3644
|
+
remove: (d, s, r) => removeTimelineEvent(d, ...at2(s), r),
|
|
3645
|
+
move: (d, s, from, to) => moveTimelineEvent(d, ...at2(s), from, to)
|
|
3646
|
+
}]
|
|
3647
|
+
}]
|
|
3648
|
+
}]
|
|
3649
|
+
};
|
|
3650
|
+
|
|
3651
|
+
// src/xychart/structure.ts
|
|
3652
|
+
var padded = (values, n2) => Array.from({ length: Math.max(n2, values.length) }, (_, i) => values[i] ?? 0);
|
|
3653
|
+
var labels = (categories, n2) => Array.from({ length: n2 }, (_, i) => categories[i] ?? String(i + 1));
|
|
3654
|
+
var labeled = (data) => data.x?.kind === "band" && data.x.categories.length > 0;
|
|
3655
|
+
function eachRow(data, values, names) {
|
|
3656
|
+
const n2 = xyRows(data);
|
|
3657
|
+
const series = data.series.map((s) => ({ ...s, values: values(padded(s.values, n2)) }));
|
|
3658
|
+
if (!labeled(data) || data.x?.kind !== "band") return { ...data, series };
|
|
3659
|
+
return { ...data, series, x: { ...data.x, categories: names(labels(data.x.categories, n2)) } };
|
|
3660
|
+
}
|
|
3661
|
+
function insertXyRow(data, at3, label) {
|
|
3662
|
+
const n2 = xyRows(data);
|
|
3663
|
+
if (!Number.isInteger(at3) || at3 < 0 || at3 > n2) throw new Error(`insertXyRow: cannot insert at ${at3} in ${n2} rows`);
|
|
3664
|
+
const name = label ?? freshName("Category", data.x?.kind === "band" ? data.x.categories : [], n2 + 1);
|
|
3665
|
+
return eachRow(data, (list) => insertAt(list, at3, 0, "insertXyRow"), (list) => insertAt(list, at3, name, "insertXyRow"));
|
|
3666
|
+
}
|
|
3667
|
+
function removeXyRow(data, at3) {
|
|
3668
|
+
const n2 = xyRows(data);
|
|
3669
|
+
if (!Number.isInteger(at3) || at3 < 0 || at3 >= n2) throw new Error(`removeXyRow: no row ${at3} in ${n2}`);
|
|
3670
|
+
return eachRow(data, (list) => removeAt(list, at3, "removeXyRow"), (list) => removeAt(list, at3, "removeXyRow"));
|
|
3671
|
+
}
|
|
3672
|
+
var moveXyRow = (data, from, to) => from === to ? data : eachRow(data, (list) => moveAt(list, from, to, "moveXyRow"), (list) => moveAt(list, from, to, "moveXyRow"));
|
|
3673
|
+
function setXyCategory(data, index, label) {
|
|
3674
|
+
const n2 = xyRows(data);
|
|
3675
|
+
if (!Number.isInteger(index) || index < 0 || index >= n2) throw new Error(`setXyCategory: no row ${index} in ${n2}`);
|
|
3676
|
+
if (data.x?.kind === "range") throw new Error("setXyCategory: the chart runs along a range, not categories");
|
|
3677
|
+
const categories = labels(data.x?.categories ?? [], n2);
|
|
3678
|
+
if (labeled(data) && categories[index] === label) return data;
|
|
3679
|
+
categories[index] = label;
|
|
3680
|
+
return { ...data, x: { ...data.x ?? { kind: "band" }, categories } };
|
|
3681
|
+
}
|
|
3682
|
+
function insertXySeries(data, at3, series) {
|
|
3683
|
+
const next = series ?? {
|
|
3684
|
+
kind: "bar",
|
|
3685
|
+
name: freshName("Series", data.series.flatMap((s) => s.name === void 0 ? [] : [s.name]), data.series.length + 1),
|
|
3686
|
+
values: padded([], xyRows(data))
|
|
3687
|
+
};
|
|
3688
|
+
return { ...data, series: insertAt(data.series, at3, next, "insertXySeries") };
|
|
3689
|
+
}
|
|
3690
|
+
var removeXySeries = (data, series) => ({ ...data, series: removeAt(data.series, series, "removeXySeries") });
|
|
3691
|
+
var moveXySeries = (data, from, to) => from === to ? data : { ...data, series: moveAt(data.series, from, to, "moveXySeries") };
|
|
3692
|
+
function withSeries(data, series, change, who) {
|
|
3693
|
+
const next = updateAt(data.series, series, change, who);
|
|
3694
|
+
return next[series] === data.series[series] ? data : { ...data, series: next };
|
|
3695
|
+
}
|
|
3696
|
+
var setXySeriesName = (data, series, name) => withSeries(data, series, (s) => {
|
|
3697
|
+
const text = name?.trim() || void 0;
|
|
3698
|
+
return s.name === text ? s : withOptional(s, "name", text);
|
|
3699
|
+
}, "setXySeriesName");
|
|
3700
|
+
var setXySeriesKind = (data, series, kind) => withSeries(data, series, (s) => s.kind === kind ? s : { ...s, kind }, "setXySeriesKind");
|
|
3701
|
+
var setXyOrientation = (data, orientation) => (data.orientation ?? "vertical") === orientation ? data : withOptional(data, "orientation", orientation === "horizontal" ? orientation : void 0);
|
|
3702
|
+
function setXyXKind(data, kind) {
|
|
3703
|
+
if ((data.x?.kind ?? "band") === kind) return data;
|
|
3704
|
+
const title = data.x?.title;
|
|
3705
|
+
const n2 = xyRows(data);
|
|
3706
|
+
const x = kind === "range" ? { kind, min: 0, max: n2 } : { kind, categories: labels([], n2) };
|
|
3707
|
+
return { ...data, x: title === void 0 ? x : { ...x, title } };
|
|
3708
|
+
}
|
|
3709
|
+
function setXyAxisTitle(data, axis, title) {
|
|
3710
|
+
const text = title?.trim() || void 0;
|
|
3711
|
+
const current = data[axis];
|
|
3712
|
+
if (current?.title === text) return data;
|
|
3713
|
+
if (axis === "x") {
|
|
3714
|
+
const x = data.x ?? { kind: "band", categories: [] };
|
|
3715
|
+
return { ...data, x: withOptional(x, "title", text) };
|
|
3716
|
+
}
|
|
3717
|
+
return withAxis(data, withOptional(data.y ?? {}, "title", text));
|
|
3718
|
+
}
|
|
3719
|
+
var withAxis = (data, y) => withOptional(data, "y", Object.keys(y).length === 0 ? void 0 : y);
|
|
3720
|
+
function setXyValueBound(data, end, value) {
|
|
3721
|
+
if (data.y?.[end] === value) return data;
|
|
3722
|
+
return withAxis(data, withOptional(data.y ?? {}, end, value));
|
|
3723
|
+
}
|
|
3724
|
+
function setXyRangeBound(data, end, value) {
|
|
3725
|
+
if (data.x?.kind !== "range") throw new Error("setXyRangeBound: the chart runs along categories, not a range");
|
|
3726
|
+
return data.x[end] === value ? data : { ...data, x: { ...data.x, [end]: value } };
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
// src/xychart/schema.ts
|
|
3730
|
+
var isRange = (d) => d.x?.kind === "range";
|
|
3731
|
+
var X_KINDS = ["categories", "range"];
|
|
3732
|
+
var rangeField = (end, label) => ({
|
|
3733
|
+
key: `x.${end}`,
|
|
3734
|
+
label,
|
|
3735
|
+
type: { kind: "number" },
|
|
3736
|
+
applies: isRange,
|
|
3737
|
+
get: (d) => d.x?.kind === "range" ? d.x[end] : void 0,
|
|
3738
|
+
set: (d, v) => {
|
|
3739
|
+
const n2 = numberOf2(v);
|
|
3740
|
+
return n2 !== void 0 && isRange(d) ? setXyRangeBound(d, end, n2) : d;
|
|
3741
|
+
}
|
|
3742
|
+
});
|
|
3743
|
+
var boundField = (end, label) => ({
|
|
3744
|
+
key: `y.${end}`,
|
|
3745
|
+
label,
|
|
3746
|
+
type: { kind: "number" },
|
|
3747
|
+
optional: true,
|
|
3748
|
+
get: (d) => d.y?.[end],
|
|
3749
|
+
set: (d, v) => v === void 0 || numberOf2(v) !== void 0 ? setXyValueBound(d, end, numberOf2(v)) : d
|
|
3750
|
+
});
|
|
3751
|
+
var axisTitle = (axis, label) => ({
|
|
3752
|
+
key: `${axis}.title`,
|
|
3753
|
+
label,
|
|
3754
|
+
type: { kind: "string" },
|
|
3755
|
+
optional: true,
|
|
3756
|
+
get: (d) => d[axis]?.title,
|
|
3757
|
+
set: (d, v) => v === void 0 || typeof v === "string" ? setXyAxisTitle(d, axis, v) : d
|
|
3758
|
+
});
|
|
3759
|
+
var categoryColumn = {
|
|
3760
|
+
key: "category",
|
|
3761
|
+
label: "Category",
|
|
3762
|
+
type: { kind: "string" },
|
|
3763
|
+
applies: (d) => !isRange(d),
|
|
3764
|
+
get: (d, _, r) => d.x?.kind === "band" ? d.x.categories[r] ?? String(r + 1) : isRange(d) ? void 0 : String(r + 1),
|
|
3765
|
+
set: (d, _, r, v) => {
|
|
3766
|
+
const t = textOf(v)?.trim();
|
|
3767
|
+
return t && !isRange(d) ? setXyCategory(d, r, t) : d;
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3770
|
+
var valueColumn2 = (d, series) => ({
|
|
3771
|
+
key: `series.${series}`,
|
|
3772
|
+
label: d.series[series]?.name ?? `Series ${series + 1}`,
|
|
3773
|
+
type: { kind: "number" },
|
|
3774
|
+
get: (data, _, r) => data.series[series]?.values[r] ?? 0,
|
|
3775
|
+
set: (data, _, r, v) => {
|
|
3776
|
+
const n2 = numberOf2(v);
|
|
3777
|
+
return n2 !== void 0 ? setXyValue(data, series, r, n2) : data;
|
|
3778
|
+
}
|
|
3779
|
+
});
|
|
3780
|
+
var xySchema = {
|
|
3781
|
+
fields: [
|
|
3782
|
+
titleField(),
|
|
3783
|
+
{
|
|
3784
|
+
key: "orientation",
|
|
3785
|
+
label: "Orientation",
|
|
3786
|
+
type: { kind: "enum", options: ["vertical", "horizontal"] },
|
|
3787
|
+
get: (d) => d.orientation ?? "vertical",
|
|
3788
|
+
set: (d, v) => {
|
|
3789
|
+
const o = choiceOf(v, ["vertical", "horizontal"]);
|
|
3790
|
+
return o ? setXyOrientation(d, o) : d;
|
|
3791
|
+
}
|
|
3792
|
+
},
|
|
3793
|
+
{
|
|
3794
|
+
key: "x.kind",
|
|
3795
|
+
label: "X axis",
|
|
3796
|
+
type: { kind: "enum", options: X_KINDS },
|
|
3797
|
+
get: (d) => isRange(d) ? "range" : "categories",
|
|
3798
|
+
set: (d, v) => {
|
|
3799
|
+
const k = choiceOf(v, X_KINDS);
|
|
3800
|
+
return k ? setXyXKind(d, k === "range" ? "range" : "band") : d;
|
|
3801
|
+
}
|
|
3802
|
+
},
|
|
3803
|
+
axisTitle("x", "X title"),
|
|
3804
|
+
rangeField("min", "X from"),
|
|
3805
|
+
rangeField("max", "X to"),
|
|
3806
|
+
axisTitle("y", "Y title"),
|
|
3807
|
+
boundField("min", "Y from"),
|
|
3808
|
+
boundField("max", "Y to")
|
|
3809
|
+
],
|
|
3810
|
+
tables: [
|
|
3811
|
+
{
|
|
3812
|
+
key: "rows",
|
|
3813
|
+
label: "Data",
|
|
3814
|
+
item: "row",
|
|
3815
|
+
count: xyRows,
|
|
3816
|
+
columns: (d) => [categoryColumn, ...d.series.map((_, i) => valueColumn2(d, i))],
|
|
3817
|
+
insert: (d, _, at3) => insertXyRow(d, at3),
|
|
3818
|
+
remove: (d, _, r) => removeXyRow(d, r),
|
|
3819
|
+
move: (d, _, from, to) => moveXyRow(d, from, to),
|
|
3820
|
+
rowLabel: (d, _, r) => String(categoryColumn.get(d, [], r) ?? r + 1)
|
|
3821
|
+
},
|
|
3822
|
+
{
|
|
3823
|
+
key: "series",
|
|
3824
|
+
label: "Series",
|
|
3825
|
+
item: "series",
|
|
3826
|
+
count: (d) => d.series.length,
|
|
3827
|
+
columns: () => [
|
|
3828
|
+
{
|
|
3829
|
+
key: "name",
|
|
3830
|
+
label: "Name",
|
|
3831
|
+
type: { kind: "string" },
|
|
3832
|
+
optional: true,
|
|
3833
|
+
get: (d, _, r) => d.series[r]?.name,
|
|
3834
|
+
set: (d, _, r, v) => v === void 0 || typeof v === "string" ? setXySeriesName(d, r, v) : d
|
|
3835
|
+
},
|
|
3836
|
+
{
|
|
3837
|
+
key: "kind",
|
|
3838
|
+
label: "Drawn as",
|
|
3839
|
+
type: { kind: "enum", options: ["bar", "line"] },
|
|
3840
|
+
get: (d, _, r) => d.series[r]?.kind,
|
|
3841
|
+
set: (d, _, r, v) => {
|
|
3842
|
+
const k = choiceOf(v, ["bar", "line"]);
|
|
3843
|
+
return k ? setXySeriesKind(d, r, k) : d;
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
],
|
|
3847
|
+
insert: (d, _, at3) => insertXySeries(d, at3),
|
|
3848
|
+
remove: (d, _, r) => removeXySeries(d, r),
|
|
3849
|
+
move: (d, _, from, to) => moveXySeries(d, from, to),
|
|
3850
|
+
rowLabel: (d, _, r) => d.series[r]?.name ?? `Series ${r + 1}`
|
|
3851
|
+
}
|
|
3852
|
+
]
|
|
3853
|
+
};
|
|
3854
|
+
|
|
3855
|
+
// src/editing/schemas.ts
|
|
3856
|
+
var SCHEMAS = {
|
|
3857
|
+
xychart: xySchema,
|
|
3858
|
+
gantt: ganttSchema,
|
|
3859
|
+
timeline: timelineSchema,
|
|
3860
|
+
pie: pieSchema,
|
|
3861
|
+
quadrant: quadrantSchema,
|
|
3862
|
+
journey: journeySchema,
|
|
3863
|
+
gitgraph: gitgraphSchema,
|
|
3864
|
+
sankey: sankeySchema,
|
|
3865
|
+
packet: packetSchema,
|
|
3866
|
+
radar: radarSchema
|
|
3867
|
+
};
|
|
3868
|
+
function plotSchemaOf(data) {
|
|
3869
|
+
const schema = SCHEMAS[data.kind];
|
|
3870
|
+
if (schema === void 0) throw new Error(`plotSchemaOf: no schema for a chart of kind "${data.kind}"`);
|
|
3871
|
+
return schema;
|
|
3872
|
+
}
|
|
3873
|
+
function plotTablesIn(schema, data) {
|
|
3874
|
+
const out = [];
|
|
3875
|
+
const walk = (tables, scope) => {
|
|
3876
|
+
for (const table of tables) {
|
|
3877
|
+
out.push({ table, scope });
|
|
3878
|
+
if (table.children === void 0) continue;
|
|
3879
|
+
for (let r = 0; r < table.count(data, scope); r++) walk(table.children, [...scope, r]);
|
|
3880
|
+
}
|
|
3881
|
+
};
|
|
3882
|
+
walk(schema.tables, []);
|
|
3883
|
+
return out;
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
export { CHART_PAD, GANTT_DATE_FORMAT, GIT_COMMAND_FIELDS, GIT_COMMAND_KINDS, GRAB_PLOT_HANDLE_ACTION_ID, PLOT_DRAG_ACTION_ID, PLOT_FONT, PLOT_HANDLE_AFFORDANCE_KIND, PLOT_HANDLE_LAYER_ID, PLOT_THEME, PlotView, arcMark, areaMark, axisMarks, axisRoom, barMark, chartTitle, commitPlotToScene, compileGantt, compileGitGraph, compileJourney, compilePacket, compilePie, compilePlot, compileQuadrant, compileRadar, compileSankey, compileTimeline, compileXyChart, createPlotDragAction, foldPlotText, formatGanttDate, formatGanttEnd, formatGanttStart, gitBranchNames, handlesOf, insertGanttSection, insertGanttTask, insertGitCommand, insertJourneySection, insertJourneyTask, insertPacketField, insertPieSlice, insertQuadrantClass, insertQuadrantPoint, insertRadarAxis, insertRadarCurve, insertSankeyLink, insertTimelineEvent, insertTimelinePeriod, insertTimelineSection, insertXyRow, insertXySeries, isPlotText, lineMark, markSpecs, moveGanttSection, moveGanttTask, moveGanttTaskToSection, moveGitCommand, moveJourneySection, moveJourneyTask, movePacketField, movePieSlice, moveQuadrantClass, moveQuadrantPoint, moveRadarAxis, moveRadarCurve, moveSankeyLink, moveTimelineEvent, moveTimelinePeriod, moveTimelineSection, moveXyRow, moveXySeries, parseGanttDate, parseGanttEnd, parseGanttStart, plotEditing, plotEditorOf, plotHandleLayer, plotHandleScratchOf, plotHandles, plotMarkId, plotMarkOf, plotOrigin, plotRootContaining, plotRootOf, plotRootsIn, plotSchemaOf, plotSpecs, plotTablesIn, plotTextOf, plotThemeFrom, pointMark, quadrantClassNames, removeGanttSection, removeGanttTask, removeGitCommand, removeJourneySection, removeJourneyTask, removePacketField, removePieSlice, removeQuadrantClass, removeQuadrantPoint, removeRadarAxis, removeRadarCurve, removeSankeyLink, removeSankeyNode, removeTimelineEvent, removeTimelinePeriod, removeTimelineSection, removeXyRow, removeXySeries, renameQuadrantClass, renameSankeyNode, reorderGanttTask, reorderTimelinePeriod, replacePlotData, replayGitGraph, ribbonMark, ruleMark, sankeyLayout, scheduleGantt, seriesColor, setGanttEnd, setGanttExcludes, setGanttSectionName, setGanttSetting, setGanttTaskEnd, setGanttTaskId, setGanttTaskName, setGanttTaskStart, setGanttTaskTags, setGitCommand, setGitCommandField, setGitCommandKind, setGitMainBranch, setGitOrientation, setGitTag, setJourneyActors, setJourneyScore, setJourneySectionName, setJourneyTaskName, setPacketBits, setPacketBitsPerRow, setPacketLabel, setPacketRelative, setPacketStart, setPieLabel, setPieShowData, setPieValue, setQuadrantAxisLabel, setQuadrantClassStyle, setQuadrantLabel, setQuadrantPoint, setQuadrantPointClass, setQuadrantPointName, setQuadrantPointStyle, setRadarAxisId, setRadarAxisLabel, setRadarCurveId, setRadarCurveLabel, setRadarOption, setRadarValue, setSankeyEnd, setSankeyValue, setTimelineEvent, setTimelinePeriodLabel, setTimelineSectionName, setXyAxisTitle, setXyCategory, setXyOrientation, setXyRangeBound, setXySeriesKind, setXySeriesName, setXyValue, setXyValueBound, setXyXKind, spanMark, textMark, textMeasure, timelineFlatIndex, usePlotTextEdit, withTitle, xyLength, xyRows, xyValueAt, xyValueDomain };
|
|
3887
|
+
//# sourceMappingURL=index.js.map
|
|
3888
|
+
//# sourceMappingURL=index.js.map
|