@weasel-js/plot 1.15.0 → 1.15.2
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/README.md +40 -5
- package/dist/chunk-HA77BIQF.js +320 -0
- package/dist/chunk-HA77BIQF.js.map +1 -0
- package/dist/index.d.ts +1013 -235
- package/dist/index.js +3528 -500
- package/dist/index.js.map +1 -1
- 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 +9 -5
package/dist/index.js
CHANGED
|
@@ -1,249 +1,12 @@
|
|
|
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';
|
|
1
3
|
import { ellipsePath, pathFromD, boundsOfPath } from '@weasel-js/geom';
|
|
2
4
|
import { THEMES } from '@weasel-js/theme';
|
|
3
5
|
import { measureTextBounds } from '@weasel-js/text';
|
|
4
|
-
import { composeWorldPose, scenePoseAdapter, composeAffordanceLayer, WeaselProvider, useScene,
|
|
5
|
-
import { useState, useEffect
|
|
6
|
+
import { isTextData, useLatest, composeWorldPose, scenePoseAdapter, composeAffordanceLayer, WeaselProvider, useScene, SceneCanvas } from '@weasel-js/core';
|
|
7
|
+
import { useMemo, useState, useEffect } from 'react';
|
|
6
8
|
import { jsx } from 'react/jsx-runtime';
|
|
7
9
|
|
|
8
|
-
// src/scales/linear.ts
|
|
9
|
-
var DEFAULT_TICKS = 5;
|
|
10
|
-
function tickStep(lo, hi, count = DEFAULT_TICKS) {
|
|
11
|
-
const span = Math.abs(hi - lo);
|
|
12
|
-
if (span === 0 || !Number.isFinite(span)) return 1;
|
|
13
|
-
const raw = span / Math.max(1, count);
|
|
14
|
-
const power = 10 ** Math.floor(Math.log10(raw));
|
|
15
|
-
const error = raw / power;
|
|
16
|
-
const factor = error >= Math.sqrt(50) ? 10 : error >= Math.sqrt(10) ? 5 : error >= Math.sqrt(2) ? 2 : 1;
|
|
17
|
-
return factor * power;
|
|
18
|
-
}
|
|
19
|
-
var decimalsOf = (step) => Math.max(0, -Math.floor(Math.log10(step) + 1e-9));
|
|
20
|
-
var onGrid = (i, step) => Number((i * step).toFixed(decimalsOf(step)));
|
|
21
|
-
function stepTicks(lo, hi, step) {
|
|
22
|
-
const [a, b] = lo <= hi ? [lo, hi] : [hi, lo];
|
|
23
|
-
const out = [];
|
|
24
|
-
for (let i = Math.ceil(a / step - 1e-9); i <= Math.floor(b / step + 1e-9); i++) out.push(onGrid(i, step));
|
|
25
|
-
return lo <= hi ? out : out.reverse();
|
|
26
|
-
}
|
|
27
|
-
function formatNumber(value, step) {
|
|
28
|
-
const fixed = value.toFixed(Math.min(20, decimalsOf(step)));
|
|
29
|
-
return fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
30
|
-
}
|
|
31
|
-
function niceDomain(domain, count = DEFAULT_TICKS) {
|
|
32
|
-
const [lo, hi] = domain;
|
|
33
|
-
if (lo === hi) return lo === 0 ? [0, 1] : [Math.min(0, lo), Math.max(0, hi)];
|
|
34
|
-
const step = tickStep(lo, hi, count);
|
|
35
|
-
const down = (v) => onGrid(Math.floor(v / step + 1e-9), step);
|
|
36
|
-
const up = (v) => onGrid(Math.ceil(v / step - 1e-9), step);
|
|
37
|
-
return lo < hi ? [down(lo), up(hi)] : [up(lo), down(hi)];
|
|
38
|
-
}
|
|
39
|
-
function linearScale(opts) {
|
|
40
|
-
const domain = opts.nice !== void 0 ? niceDomain(opts.domain, opts.nice) : [opts.domain[0], opts.domain[1]];
|
|
41
|
-
const range = [opts.range[0], opts.range[1]];
|
|
42
|
-
const [d0, d1] = domain;
|
|
43
|
-
const [r0, r1] = range;
|
|
44
|
-
const span = d1 - d0;
|
|
45
|
-
const map = (v) => span === 0 ? (r0 + r1) / 2 : r0 + (v - d0) / span * (r1 - r0);
|
|
46
|
-
const step = (count = DEFAULT_TICKS) => tickStep(d0, d1, count);
|
|
47
|
-
return {
|
|
48
|
-
kind: "linear",
|
|
49
|
-
domain,
|
|
50
|
-
range,
|
|
51
|
-
map,
|
|
52
|
-
position: map,
|
|
53
|
-
invert: (at2) => r1 === r0 ? d0 : d0 + (at2 - r0) / (r1 - r0) * span,
|
|
54
|
-
tickStep: step,
|
|
55
|
-
ticks: (count = DEFAULT_TICKS) => stepTicks(d0, d1, step(count)),
|
|
56
|
-
format: (v) => formatNumber(v, step())
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/scales/log.ts
|
|
61
|
-
function logScale(opts) {
|
|
62
|
-
const base = opts.base ?? 10;
|
|
63
|
-
const [d0, d1] = opts.domain;
|
|
64
|
-
if (!(d0 > 0 && d1 > 0)) throw new Error(`logScale: a domain of ${d0}..${d1} does not lie above zero`);
|
|
65
|
-
const [r0, r1] = opts.range;
|
|
66
|
-
const log = (v) => Math.log(v) / Math.log(base);
|
|
67
|
-
const l0 = log(d0);
|
|
68
|
-
const l1 = log(d1);
|
|
69
|
-
const map = (v) => l1 === l0 ? (r0 + r1) / 2 : r0 + (log(v) - l0) / (l1 - l0) * (r1 - r0);
|
|
70
|
-
const ticks = (count = 5) => {
|
|
71
|
-
const [lo, hi] = l0 <= l1 ? [l0, l1] : [l1, l0];
|
|
72
|
-
const first = Math.floor(lo + 1e-9);
|
|
73
|
-
const last = Math.ceil(hi - 1e-9);
|
|
74
|
-
const steps = last - first < count / 2 && base === 10 ? [1, 2, 5] : [1];
|
|
75
|
-
const out = [];
|
|
76
|
-
for (let p = first; p <= last; p++) {
|
|
77
|
-
for (const m of steps) {
|
|
78
|
-
const v = Number((m * base ** p).toPrecision(12));
|
|
79
|
-
if (log(v) >= lo - 1e-9 && log(v) <= hi + 1e-9) out.push(v);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return l0 <= l1 ? out : out.reverse();
|
|
83
|
-
};
|
|
84
|
-
return {
|
|
85
|
-
kind: "log",
|
|
86
|
-
base,
|
|
87
|
-
domain: [d0, d1],
|
|
88
|
-
range: [r0, r1],
|
|
89
|
-
map,
|
|
90
|
-
position: map,
|
|
91
|
-
invert: (at2) => base ** (r1 === r0 ? l0 : l0 + (at2 - r0) / (r1 - r0) * (l1 - l0)),
|
|
92
|
-
ticks,
|
|
93
|
-
format: (v) => v >= 1 ? formatNumber(v, 1) : formatNumber(v, 10 ** Math.floor(Math.log10(v)))
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// src/scales/band.ts
|
|
98
|
-
function bandScale(opts) {
|
|
99
|
-
const domain = [...opts.domain];
|
|
100
|
-
const [r0, r1] = opts.range;
|
|
101
|
-
const inner = Math.min(1, Math.max(0, opts.paddingInner ?? 0.2));
|
|
102
|
-
const outer = Math.max(0, opts.paddingOuter ?? inner / 2);
|
|
103
|
-
const n2 = domain.length;
|
|
104
|
-
const step = n2 === 0 ? 0 : (r1 - r0) / Math.max(1e-9, n2 - inner + 2 * outer);
|
|
105
|
-
const bandwidth = Math.abs(step * (1 - inner));
|
|
106
|
-
const index = new Map(domain.map((key, i) => [key, i]));
|
|
107
|
-
const startAt = (i) => r0 + step * (outer + i) + (step < 0 ? step * (1 - inner) : 0);
|
|
108
|
-
const start = (key) => {
|
|
109
|
-
const i = index.get(key);
|
|
110
|
-
return i === void 0 ? Number.NaN : startAt(i);
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
kind: "band",
|
|
114
|
-
domain,
|
|
115
|
-
range: [r0, r1],
|
|
116
|
-
bandwidth,
|
|
117
|
-
step: Math.abs(step),
|
|
118
|
-
start,
|
|
119
|
-
position: (key) => start(key) + bandwidth / 2,
|
|
120
|
-
invert: (at2) => {
|
|
121
|
-
if (n2 === 0) return void 0;
|
|
122
|
-
const i = Math.round((at2 - r0) / step - outer - (1 - inner) / 2);
|
|
123
|
-
return domain[Math.min(n2 - 1, Math.max(0, i))];
|
|
124
|
-
},
|
|
125
|
-
ticks: () => [...domain],
|
|
126
|
-
format: (key) => key
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/scales/time.ts
|
|
131
|
-
var SECOND = 1e3;
|
|
132
|
-
var MINUTE = 60 * SECOND;
|
|
133
|
-
var HOUR = 60 * MINUTE;
|
|
134
|
-
var DAY = 24 * HOUR;
|
|
135
|
-
var INTERVALS = [
|
|
136
|
-
{ ms: SECOND, unit: "second" },
|
|
137
|
-
{ ms: 5 * SECOND, unit: "second" },
|
|
138
|
-
{ ms: 15 * SECOND, unit: "second" },
|
|
139
|
-
{ ms: 30 * SECOND, unit: "second" },
|
|
140
|
-
{ ms: MINUTE, unit: "minute" },
|
|
141
|
-
{ ms: 5 * MINUTE, unit: "minute" },
|
|
142
|
-
{ ms: 15 * MINUTE, unit: "minute" },
|
|
143
|
-
{ ms: 30 * MINUTE, unit: "minute" },
|
|
144
|
-
{ ms: HOUR, unit: "hour" },
|
|
145
|
-
{ ms: 3 * HOUR, unit: "hour" },
|
|
146
|
-
{ ms: 6 * HOUR, unit: "hour" },
|
|
147
|
-
{ ms: 12 * HOUR, unit: "hour" },
|
|
148
|
-
{ ms: DAY, unit: "day" },
|
|
149
|
-
{ ms: 2 * DAY, unit: "day" },
|
|
150
|
-
{ ms: 7 * DAY, unit: "day" },
|
|
151
|
-
{ months: 1, unit: "month" },
|
|
152
|
-
{ months: 3, unit: "month" },
|
|
153
|
-
{ months: 12, unit: "year" }
|
|
154
|
-
];
|
|
155
|
-
var MONTH_MS = 30 * DAY;
|
|
156
|
-
var spanOf = (i) => i.ms ?? i.months * MONTH_MS;
|
|
157
|
-
function intervalFor(span, count) {
|
|
158
|
-
const target = span / Math.max(1, count);
|
|
159
|
-
let best = INTERVALS[0];
|
|
160
|
-
for (const i of INTERVALS) if (Math.abs(Math.log(spanOf(i) / target)) < Math.abs(Math.log(spanOf(best) / target))) best = i;
|
|
161
|
-
if (best.unit === "year" && target > 12 * MONTH_MS) {
|
|
162
|
-
const years = 10 ** Math.floor(Math.log10(target / (12 * MONTH_MS)));
|
|
163
|
-
return { months: 12 * years, unit: "year" };
|
|
164
|
-
}
|
|
165
|
-
return best;
|
|
166
|
-
}
|
|
167
|
-
function gridTicks(lo, hi, interval) {
|
|
168
|
-
const out = [];
|
|
169
|
-
if (interval.ms !== void 0) {
|
|
170
|
-
for (let t = Math.ceil(lo / interval.ms) * interval.ms; t <= hi; t += interval.ms) out.push(t);
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
const at2 = new Date(lo);
|
|
174
|
-
let month = at2.getUTCFullYear() * 12 + at2.getUTCMonth();
|
|
175
|
-
month = Math.ceil(month / interval.months) * interval.months;
|
|
176
|
-
for (; ; month += interval.months) {
|
|
177
|
-
const t = Date.UTC(Math.floor(month / 12), month % 12, 1);
|
|
178
|
-
if (t < lo) continue;
|
|
179
|
-
if (t > hi) break;
|
|
180
|
-
out.push(t);
|
|
181
|
-
}
|
|
182
|
-
return out;
|
|
183
|
-
}
|
|
184
|
-
var pad = (n2) => String(n2).padStart(2, "0");
|
|
185
|
-
function formatInstant(t, unit) {
|
|
186
|
-
const d = new Date(t);
|
|
187
|
-
const date = `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
188
|
-
const time = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
189
|
-
switch (unit) {
|
|
190
|
-
case "year":
|
|
191
|
-
return String(d.getUTCFullYear());
|
|
192
|
-
case "month":
|
|
193
|
-
return date.slice(0, 7);
|
|
194
|
-
case "day":
|
|
195
|
-
return date;
|
|
196
|
-
case "hour":
|
|
197
|
-
case "minute":
|
|
198
|
-
return d.getUTCHours() === 0 && d.getUTCMinutes() === 0 ? date : time;
|
|
199
|
-
case "second":
|
|
200
|
-
return `${time}:${pad(d.getUTCSeconds())}`;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function timeScale(opts) {
|
|
204
|
-
const [d0, d1] = opts.domain;
|
|
205
|
-
const [r0, r1] = opts.range;
|
|
206
|
-
const span = d1 - d0;
|
|
207
|
-
const map = (v) => span === 0 ? (r0 + r1) / 2 : r0 + (v - d0) / span * (r1 - r0);
|
|
208
|
-
const interval = (count = 5) => intervalFor(Math.abs(span), count);
|
|
209
|
-
return {
|
|
210
|
-
kind: "time",
|
|
211
|
-
domain: [d0, d1],
|
|
212
|
-
range: [r0, r1],
|
|
213
|
-
map,
|
|
214
|
-
position: map,
|
|
215
|
-
invert: (at2) => r1 === r0 ? d0 : d0 + (at2 - r0) / (r1 - r0) * span,
|
|
216
|
-
ticks: (count = 5) => {
|
|
217
|
-
const out = gridTicks(Math.min(d0, d1), Math.max(d0, d1), interval(count));
|
|
218
|
-
return d0 <= d1 ? out : out.reverse();
|
|
219
|
-
},
|
|
220
|
-
format: (v) => formatInstant(v, interval().unit)
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// src/scales/ordinal.ts
|
|
225
|
-
function ordinalScale(opts) {
|
|
226
|
-
if (opts.range.length === 0) throw new Error("ordinalScale: an empty range");
|
|
227
|
-
const domain = [...opts.domain];
|
|
228
|
-
const index = new Map(domain.map((key, i) => [key, i]));
|
|
229
|
-
const range = [...opts.range];
|
|
230
|
-
return {
|
|
231
|
-
kind: "ordinal",
|
|
232
|
-
domain,
|
|
233
|
-
range,
|
|
234
|
-
map: (key) => {
|
|
235
|
-
let i = index.get(key);
|
|
236
|
-
if (i === void 0) {
|
|
237
|
-
i = domain.length;
|
|
238
|
-
domain.push(key);
|
|
239
|
-
index.set(key, i);
|
|
240
|
-
}
|
|
241
|
-
return range[i % range.length];
|
|
242
|
-
},
|
|
243
|
-
ticks: () => [...domain],
|
|
244
|
-
format: (key) => key
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
10
|
var rectOf = (b) => ({ kind: "rect", x: b.x, y: b.y, width: b.width, height: b.height });
|
|
248
11
|
var fillOf = (color, opacity) => ({ paint: { color, ...opacity !== void 0 ? { opacity } : {} } });
|
|
249
12
|
function barMark(opts) {
|
|
@@ -338,18 +101,18 @@ function ribbonMark(opts) {
|
|
|
338
101
|
// src/marks/text.ts
|
|
339
102
|
var ALIGN = { start: "left", middle: "center", end: "right" };
|
|
340
103
|
function textMark(opts) {
|
|
341
|
-
const { width, height } = opts.measure(opts.text, opts.font);
|
|
104
|
+
const { width: width2, height } = opts.measure(opts.text, opts.font);
|
|
342
105
|
const anchor = opts.anchor ?? "middle";
|
|
343
106
|
const baseline = opts.baseline ?? "middle";
|
|
344
|
-
const along = anchor === "start" ? 0 : anchor === "middle" ? -
|
|
107
|
+
const along = anchor === "start" ? 0 : anchor === "middle" ? -width2 / 2 : -width2;
|
|
345
108
|
const across = baseline === "top" ? 0 : baseline === "middle" ? -height / 2 : -height;
|
|
346
|
-
const center = opts.vertical ? { x: opts.x + across + height / 2, y: opts.y - along -
|
|
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 };
|
|
347
110
|
return {
|
|
348
111
|
key: opts.key,
|
|
349
112
|
pose: {
|
|
350
|
-
x: center.x -
|
|
113
|
+
x: center.x - width2 / 2,
|
|
351
114
|
y: center.y - height / 2,
|
|
352
|
-
width,
|
|
115
|
+
width: width2,
|
|
353
116
|
height,
|
|
354
117
|
...opts.vertical ? { rotation: -Math.PI / 2 } : {}
|
|
355
118
|
},
|
|
@@ -371,12 +134,12 @@ var outward = (side) => side === "bottom" || side === "right" ? 1 : -1;
|
|
|
371
134
|
function axisRoom(opts) {
|
|
372
135
|
const { scale, side, theme, measure } = opts;
|
|
373
136
|
const sizes = scale.ticks(opts.ticks).map((t) => measure(scale.format(t), theme.font));
|
|
374
|
-
const
|
|
137
|
+
const labels2 = sizes.length === 0 ? 0 : Math.max(...sizes.map((s) => isHorizontal(side) ? s.height : s.width));
|
|
375
138
|
const title = opts.title ? measure(opts.title, theme.font).height + GAP : 0;
|
|
376
|
-
return TICK + GAP +
|
|
139
|
+
return TICK + GAP + labels2 + title;
|
|
377
140
|
}
|
|
378
141
|
function axisMarks(opts) {
|
|
379
|
-
const { scale, side, at:
|
|
142
|
+
const { scale, side, at: at3, theme, measure } = opts;
|
|
380
143
|
const key = opts.key ?? `axis:${side}`;
|
|
381
144
|
const h = isHorizontal(side);
|
|
382
145
|
const out = outward(side);
|
|
@@ -387,15 +150,15 @@ function axisMarks(opts) {
|
|
|
387
150
|
ticks.forEach((t, i) => {
|
|
388
151
|
const p = scale.position(t);
|
|
389
152
|
if (opts.gridTo !== void 0) {
|
|
390
|
-
marks.push(ruleMark({ key: `${key}:grid:${i}`, from: pt(p,
|
|
153
|
+
marks.push(ruleMark({ key: `${key}:grid:${i}`, from: pt(p, at3), to: pt(p, opts.gridTo), color: theme.grid }));
|
|
391
154
|
}
|
|
392
155
|
});
|
|
393
|
-
marks.push(ruleMark({ key: `${key}:rule`, from: pt(r0,
|
|
156
|
+
marks.push(ruleMark({ key: `${key}:rule`, from: pt(r0, at3), to: pt(r1, at3), color: theme.axis }));
|
|
394
157
|
let reach = 0;
|
|
395
158
|
ticks.forEach((t, i) => {
|
|
396
159
|
const p = scale.position(t);
|
|
397
|
-
marks.push(ruleMark({ key: `${key}:tick:${i}`, from: pt(p,
|
|
398
|
-
const anchorAt = pt(p,
|
|
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));
|
|
399
162
|
const label = textMark({
|
|
400
163
|
key: `${key}:label:${i}`,
|
|
401
164
|
text: scale.format(t),
|
|
@@ -410,7 +173,7 @@ function axisMarks(opts) {
|
|
|
410
173
|
marks.push(label);
|
|
411
174
|
});
|
|
412
175
|
if (opts.title) {
|
|
413
|
-
const across =
|
|
176
|
+
const across = at3 + out * (TICK + GAP + reach + GAP);
|
|
414
177
|
marks.push(textMark({
|
|
415
178
|
key: `${key}:title`,
|
|
416
179
|
text: opts.title,
|
|
@@ -427,12 +190,12 @@ function axisMarks(opts) {
|
|
|
427
190
|
}
|
|
428
191
|
var SWATCHES = ["sky", "amber", "green", "rose", "violet", "teal", "citron", "fuchsia", "blue", "red"];
|
|
429
192
|
function plotThemeFrom(resolved, font = PLOT_FONT) {
|
|
430
|
-
const
|
|
193
|
+
const at3 = (token) => resolved[`--wzl-${token}`] ?? "#888888";
|
|
431
194
|
return {
|
|
432
|
-
text:
|
|
433
|
-
axis:
|
|
434
|
-
grid:
|
|
435
|
-
series: SWATCHES.map((s) =>
|
|
195
|
+
text: at3("fg"),
|
|
196
|
+
axis: at3("border-strong"),
|
|
197
|
+
grid: at3("border"),
|
|
198
|
+
series: SWATCHES.map((s) => at3(`swatch-${s}`)),
|
|
436
199
|
font
|
|
437
200
|
};
|
|
438
201
|
}
|
|
@@ -440,270 +203,2094 @@ var PLOT_FONT = { fontFamily: "sans-serif", fontSize: 12 };
|
|
|
440
203
|
var PLOT_THEME = plotThemeFrom(THEMES.weasel.selections["mode=dark,density=comfortable"]);
|
|
441
204
|
var seriesColor = (theme, i) => theme.series[(i % theme.series.length + theme.series.length) % theme.series.length] ?? theme.text;
|
|
442
205
|
var textMeasure = (text, font) => {
|
|
443
|
-
const { width } = measureTextBounds(text, { fontFamily: font.fontFamily, fontSize: font.fontSize });
|
|
444
|
-
return { width: Math.ceil(
|
|
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) };
|
|
445
208
|
};
|
|
446
209
|
|
|
447
|
-
// src/
|
|
448
|
-
var
|
|
210
|
+
// src/chartTitle.ts
|
|
211
|
+
var CHART_PAD = 12;
|
|
449
212
|
var GAP2 = 8;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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 };
|
|
455
228
|
}
|
|
456
|
-
function
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
const min = data.y?.min ?? niceLo;
|
|
462
|
-
const max = data.y?.max ?? niceHi;
|
|
463
|
-
return min === max ? [min, min + 1] : [min, max];
|
|
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 };
|
|
464
234
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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;
|
|
472
276
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const slots = Array.from({ length: n2 }, (_, i) => i < categories.length ? scale.position(categories[i]) : Number.NaN);
|
|
476
|
-
return { scale, slots, slotWidth: scale.bandwidth };
|
|
277
|
+
if (literal !== "") parts.push({ literal });
|
|
278
|
+
return parts;
|
|
477
279
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
+
}
|
|
496
326
|
}
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
const categoryTitle = data.x?.title !== void 0 ? { title: data.x.title } : {};
|
|
501
|
-
const probe = categoryAxis(data, [0, 1]);
|
|
502
|
-
const valueRoom = axisRoom({ ...base, ...valueTitle, scale: linearScale({ domain, range: [0, 1] }), side: vertical ? "left" : "bottom" });
|
|
503
|
-
const categoryRoom = axisRoom({ ...base, ...categoryTitle, scale: probe.scale, side: vertical ? "bottom" : "left" });
|
|
504
|
-
const left = PAD + (vertical ? valueRoom : categoryRoom);
|
|
505
|
-
const bottom = size.height - PAD - (vertical ? categoryRoom : valueRoom);
|
|
506
|
-
const plot = { x: left, y: top, width: Math.max(1, size.width - PAD - left), height: Math.max(1, bottom - top) };
|
|
507
|
-
const right = plot.x + plot.width;
|
|
508
|
-
const value = linearScale({ domain, range: vertical ? [bottom, top] : [left, right] });
|
|
509
|
-
const category = categoryAxis(data, vertical ? [left, right] : [top, bottom]);
|
|
510
|
-
marks.push(...axisMarks({ ...base, ...valueTitle, key: "axis:value", scale: value, side: vertical ? "left" : "bottom", at: vertical ? left : bottom, gridTo: vertical ? right : top }));
|
|
511
|
-
marks.push(...axisMarks({ ...base, ...categoryTitle, key: "axis:category", scale: category.scale, side: vertical ? "bottom" : "left", at: vertical ? bottom : left }));
|
|
512
|
-
const layout = { orientation: vertical ? "vertical" : "horizontal", plot, value, slots: category.slots, slotWidth: category.slotWidth };
|
|
513
|
-
marks.push(...seriesMarks(data, layout, theme));
|
|
514
|
-
return { marks, layout };
|
|
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;
|
|
515
330
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
from: baseline,
|
|
538
|
-
to: value.map(v),
|
|
539
|
-
color,
|
|
540
|
-
datum: datum(i),
|
|
541
|
-
orient: layout.orientation
|
|
542
|
-
}));
|
|
543
|
-
}
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
if (placed.length > 0) {
|
|
547
|
-
marks.push(lineMark({ key: `line:${s}`, points: placed.map(({ v, i }) => at2(slots[i], v)), color, datum: { series: s } }));
|
|
548
|
-
}
|
|
549
|
-
for (const { v, i } of placed) marks.push(pointMark({ key: `point:${s}:${i}`, ...at2(slots[i], v), radius: 3.5, color, datum: datum(i) }));
|
|
550
|
-
});
|
|
551
|
-
return marks;
|
|
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("");
|
|
552
352
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
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);
|
|
562
378
|
}
|
|
563
379
|
|
|
564
|
-
// src/
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
}
|
|
569
403
|
}
|
|
570
|
-
if (
|
|
571
|
-
|
|
572
|
-
return { ...data, series: data.series.map((s, i) => i === series ? { ...s, values } : s) };
|
|
404
|
+
if (weekdays.size === 7) return () => false;
|
|
405
|
+
return (t) => weekdays.has(new Date(t).getUTCDay()) || dates.has(dayOf(t));
|
|
573
406
|
}
|
|
574
|
-
function
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
|
|
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;
|
|
578
412
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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;
|
|
584
423
|
}
|
|
585
|
-
function
|
|
586
|
-
const
|
|
587
|
-
|
|
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;
|
|
588
432
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return marks.map((m) => ({
|
|
592
|
-
kind: "mark",
|
|
593
|
-
id: plotMarkId(rootId, m.key),
|
|
594
|
-
parent: rootId,
|
|
595
|
-
layer,
|
|
596
|
-
pose: { x: m.pose.x, y: m.pose.y, width: m.pose.width, height: m.pose.height, ...m.pose.rotation ? { rotation: m.pose.rotation } : {} },
|
|
597
|
-
data: m.data
|
|
598
|
-
}));
|
|
433
|
+
function ganttTasks(data) {
|
|
434
|
+
return data.sections.flatMap((s, section) => s.tasks.map((task, index) => ({ task, section, index })));
|
|
599
435
|
}
|
|
600
|
-
function
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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] };
|
|
608
485
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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;
|
|
614
495
|
}
|
|
615
|
-
function
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
s.batch(opts.label ?? "Edit chart", () => {
|
|
622
|
-
s.update(node.id, { data: { ...node.data, plot: { data, theme } } });
|
|
623
|
-
const children = s.childrenOf(node.id);
|
|
624
|
-
if (children.length > 0) s.removeMany(children);
|
|
625
|
-
for (const spec of markSpecs(rootId, node.layer, marks)) s.add(spec);
|
|
626
|
-
});
|
|
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) };
|
|
627
502
|
}
|
|
628
|
-
function
|
|
629
|
-
|
|
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 };
|
|
630
507
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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) }));
|
|
653
556
|
}
|
|
654
|
-
|
|
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
|
+
}
|
|
655
600
|
|
|
656
|
-
// src/
|
|
657
|
-
var
|
|
658
|
-
|
|
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;
|
|
659
605
|
};
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
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;
|
|
666
615
|
}
|
|
667
|
-
function
|
|
668
|
-
const
|
|
669
|
-
|
|
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" });
|
|
670
626
|
}
|
|
671
|
-
function
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const node = scene.get(id);
|
|
680
|
-
const mark = plotMarkOf(node);
|
|
681
|
-
if (node === void 0 || mark === null) continue;
|
|
682
|
-
const handle = editor.handle(trait.data, { trait: mark, pose: node.pose });
|
|
683
|
-
if (handle === null) continue;
|
|
684
|
-
yield { rootId, markId: id, cursor: handle.cursor, at: { x: origin.x + handle.at.x, y: origin.y + handle.at.y } };
|
|
685
|
-
}
|
|
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(", ")}`;
|
|
686
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 });
|
|
687
638
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
+
}
|
|
700
667
|
}
|
|
701
|
-
function
|
|
702
|
-
|
|
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;
|
|
703
686
|
}
|
|
704
687
|
|
|
705
|
-
// src/
|
|
706
|
-
var
|
|
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";
|
|
707
2294
|
function pressOf(ctx, measure) {
|
|
708
2295
|
const pressed = plotHandleScratchOf(ctx.drag?.affordance);
|
|
709
2296
|
const scene = ctx.deps.scene;
|
|
@@ -713,7 +2300,7 @@ function pressOf(ctx, measure) {
|
|
|
713
2300
|
const editor = trait && plotEditorOf(trait.data);
|
|
714
2301
|
if (pressed === null || scene === void 0 || root === void 0 || trait === null || mark === null || editor === null) return null;
|
|
715
2302
|
const size = { width: root.pose.width, height: root.pose.height };
|
|
716
|
-
const propose = editor.drag(trait.data, mark, size, trait.theme, measure);
|
|
2303
|
+
const propose = editor.drag(trait.data, mark, size, trait.theme, measure, pressed.part);
|
|
717
2304
|
if (propose === null) return null;
|
|
718
2305
|
const origin = plotOrigin(scene, pressed.rootId);
|
|
719
2306
|
const start = ctx.drag?.start ?? ctx.world;
|
|
@@ -802,14 +2389,14 @@ function plotEditing(opts) {
|
|
|
802
2389
|
};
|
|
803
2390
|
}
|
|
804
2391
|
var ROOT = "plot";
|
|
805
|
-
var FEATURES = ["pick"];
|
|
2392
|
+
var FEATURES = ["pick", "edit"];
|
|
806
2393
|
function PlotView(props) {
|
|
807
2394
|
return /* @__PURE__ */ jsx(WeaselProvider, { children: /* @__PURE__ */ jsx(Inner, { ...props }) });
|
|
808
2395
|
}
|
|
809
|
-
function Inner({ data, onChange, width, height, theme, measure, background, className, "aria-label": label }) {
|
|
2396
|
+
function Inner({ data, onChange, width: width2, height, theme, measure, background, className, "aria-label": label }) {
|
|
810
2397
|
const [initial] = useState(() => plotSpecs(data, {
|
|
811
2398
|
id: ROOT,
|
|
812
|
-
box: { x: 0, y: 0, width, height },
|
|
2399
|
+
box: { x: 0, y: 0, width: width2, height },
|
|
813
2400
|
...theme ? { theme } : {},
|
|
814
2401
|
...measure ? { measure } : {}
|
|
815
2402
|
}));
|
|
@@ -819,13 +2406,13 @@ function Inner({ data, onChange, width, height, theme, measure, background, clas
|
|
|
819
2406
|
const root = scene.get(ROOT);
|
|
820
2407
|
const pose = root?.pose;
|
|
821
2408
|
const trait = plotRootOf(root);
|
|
822
|
-
const sized = pose !== void 0 && pose.width ===
|
|
2409
|
+
const sized = pose !== void 0 && pose.width === width2 && pose.height === height;
|
|
823
2410
|
if (trait === null || trait.data === data && sized && (theme === void 0 || trait.theme === theme)) return;
|
|
824
2411
|
scene.untracked(() => {
|
|
825
|
-
if (!sized) scene.setPose(ROOT, { x: 0, y: 0, width, height });
|
|
2412
|
+
if (!sized) scene.setPose(ROOT, { x: 0, y: 0, width: width2, height });
|
|
826
2413
|
replacePlotData(scene, ROOT, data, { ...theme ? { theme } : {}, ...measure ? { measure } : {} });
|
|
827
2414
|
});
|
|
828
|
-
}, [scene, data,
|
|
2415
|
+
}, [scene, data, width2, height, theme, measure]);
|
|
829
2416
|
useEffect(() => {
|
|
830
2417
|
let last = plotRootOf(scene.get(ROOT))?.data;
|
|
831
2418
|
return scene.subscribe(() => {
|
|
@@ -837,6 +2424,10 @@ function Inner({ data, onChange, width, height, theme, measure, background, clas
|
|
|
837
2424
|
}, [scene, changed]);
|
|
838
2425
|
const editing = useMemo(() => plotEditing({ scene, ...measure ? { measure } : {} }), [scene, measure]);
|
|
839
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
|
+
}));
|
|
840
2431
|
const [canvas, setCanvas] = useState(null);
|
|
841
2432
|
useEffect(() => canvas === null ? void 0 : canvas.registerLayer(editing.layer), [canvas, editing]);
|
|
842
2433
|
return /* @__PURE__ */ jsx("div", { className, "data-wzl-plot": "", children: /* @__PURE__ */ jsx(
|
|
@@ -847,7 +2438,8 @@ function Inner({ data, onChange, width, height, theme, measure, background, clas
|
|
|
847
2438
|
features: FEATURES,
|
|
848
2439
|
enableKeybindings: true,
|
|
849
2440
|
ambient,
|
|
850
|
-
|
|
2441
|
+
textEditor,
|
|
2442
|
+
width: width2,
|
|
851
2443
|
height,
|
|
852
2444
|
...background ? { backgroundFill: background } : {},
|
|
853
2445
|
...label ? { "aria-label": label } : {}
|
|
@@ -855,6 +2447,1442 @@ function Inner({ data, onChange, width, height, theme, measure, background, clas
|
|
|
855
2447
|
) });
|
|
856
2448
|
}
|
|
857
2449
|
|
|
858
|
-
|
|
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 };
|
|
859
3887
|
//# sourceMappingURL=index.js.map
|
|
860
3888
|
//# sourceMappingURL=index.js.map
|