@graphysdk/geom-waterfall 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +17 -0
- package/README.md +70 -2
- package/dist/engine/utils/accumulate-steps.d.ts +28 -0
- package/dist/engine/waterfall-data-labels.d.ts +9 -0
- package/dist/engine/waterfall.geom.d.ts +132 -0
- package/dist/engine/waterfall.stat.d.ts +14 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +568 -0
- package/dist/renderer/utils/step-geometry.d.ts +34 -0
- package/dist/renderer/utils/waterfall-keys.d.ts +12 -0
- package/dist/renderer/waterfall-edit-outline.d.ts +3 -0
- package/dist/renderer/waterfall-highlight.d.ts +11 -0
- package/dist/renderer/waterfall-hover.d.ts +12 -0
- package/dist/renderer/waterfall-layer.d.ts +16 -0
- package/dist/renderer/waterfall-paint.d.ts +27 -0
- package/dist/renderer/waterfall.d.ts +15 -0
- package/dist/utils/read-waterfall.d.ts +29 -0
- package/dist/utils/waterfall-columns.d.ts +19 -0
- package/dist/utils/waterfall-params.d.ts +16 -0
- package/licenses/PolyForm-Free-Trial-1.0.0.md +93 -0
- package/licenses/PolyForm-NonCommercial-1.0.0.md +131 -0
- package/licenses/PolyForm-Small-Business-1.0.0.md +121 -0
- package/package.json +71 -3
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { Geom as q, GeomLine as z, GeomRect as J, IntroFade as Z, POSITION_VARIABLES as x, Stat as Q, UserInputError as G, buildPanelAnchorBox as ee, composePoint as b, createStableBandKeyGenerator as te, defineGeomRenderer as re, defineGeomStyleTarget as ae, getScaledAesthetic as _, getX as ne, getXMax as B, getXMin as W, getY as se, getYMax as P, getYMin as O, readBackdropColor as oe, readMainAxis as m, resolveAnchoredLabelCenter as ie, toPaintColor as T, toPercent as g, toStrokeDasharray as le, toViewBoxX as k, toViewBoxY as M, token as f } from "@graphysdk/react";
|
|
2
|
+
import { useMemo as S } from "react";
|
|
3
|
+
import { Fragment as de, jsx as u, jsxs as ce } from "react/jsx-runtime";
|
|
4
|
+
var c = {
|
|
5
|
+
start: "waterfallStart",
|
|
6
|
+
end: "waterfallEnd",
|
|
7
|
+
kind: "waterfallKind",
|
|
8
|
+
value: "waterfallValue"
|
|
9
|
+
}, K = (e, t) => {
|
|
10
|
+
const r = t === "x" ? ne(e) : se(e), a = t === "x" ? W(e) : O(e), s = t === "x" ? B(e) : P(e), n = t === "x" ? O(e) : W(e), l = t === "x" ? P(e) : B(e), o = _(e, "start"), i = _(e, "end"), d = ue(e[c.kind]);
|
|
11
|
+
return r === null || a === null || s === null || n === null || l === null || o === null || i === null || d === null ? null : {
|
|
12
|
+
observation: e,
|
|
13
|
+
band: r,
|
|
14
|
+
bandStart: a,
|
|
15
|
+
bandEnd: s,
|
|
16
|
+
low: Math.min(n, l),
|
|
17
|
+
high: Math.max(n, l),
|
|
18
|
+
start: o,
|
|
19
|
+
end: i,
|
|
20
|
+
kind: d
|
|
21
|
+
};
|
|
22
|
+
}, w = (e, t) => {
|
|
23
|
+
const r = [];
|
|
24
|
+
for (const a of e) {
|
|
25
|
+
const s = K(a, t);
|
|
26
|
+
s !== null && r.push(s);
|
|
27
|
+
}
|
|
28
|
+
return r;
|
|
29
|
+
}, F = (e) => e === void 0 ? null : typeof e == "string" ? e : "variable" in e ? e.variable : null, ue = (e) => e === "increase" || e === "decrease" || e === "total" ? e : null, H = ["between", "spanning"], he = 0.8, $ = ({ width: e }) => e ?? he, ge = ({ connector: e }) => Y(e) ? e : "spanning", Y = (e) => H.some((t) => t === e), pe = /* @__PURE__ */ new Set([
|
|
30
|
+
"relative",
|
|
31
|
+
"total",
|
|
32
|
+
"absolute"
|
|
33
|
+
]), ve = (e) => e == null || e === "", j = (e, t) => e.map((r) => {
|
|
34
|
+
if (ve(r)) return "relative";
|
|
35
|
+
if (pe.has(r)) return r;
|
|
36
|
+
throw new G({
|
|
37
|
+
code: "INVALID_DATA_SHAPE",
|
|
38
|
+
message: `waterfall measure must be 'relative', 'total' or 'absolute' (got '${String(r)}' in variable '${t}')`,
|
|
39
|
+
context: {
|
|
40
|
+
aesthetic: "measure",
|
|
41
|
+
variableName: t
|
|
42
|
+
},
|
|
43
|
+
suggestion: "Mark each total 'total' and each opening balance 'absolute'; leave a step empty or 'relative'."
|
|
44
|
+
});
|
|
45
|
+
}), fe = (e, t) => {
|
|
46
|
+
let r = 0;
|
|
47
|
+
return e.map((a, s) => {
|
|
48
|
+
const n = t[s] ?? "relative";
|
|
49
|
+
if (n === "total") return {
|
|
50
|
+
start: 0,
|
|
51
|
+
end: r
|
|
52
|
+
};
|
|
53
|
+
if (a === null || !Number.isFinite(a)) return null;
|
|
54
|
+
if (n === "absolute")
|
|
55
|
+
return r = a, {
|
|
56
|
+
start: 0,
|
|
57
|
+
end: a
|
|
58
|
+
};
|
|
59
|
+
const l = r;
|
|
60
|
+
return r += a, {
|
|
61
|
+
start: l,
|
|
62
|
+
end: r
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}, C = (e, t) => t !== "relative" ? "total" : e.end < e.start ? "decrease" : "increase", U = {
|
|
66
|
+
start: c.start,
|
|
67
|
+
end: c.end
|
|
68
|
+
}, I = new Set(Object.keys(U)), me = class extends Q {
|
|
69
|
+
constructor(...e) {
|
|
70
|
+
super(...e), this.type = "waterfall", this.computedVariables = I;
|
|
71
|
+
}
|
|
72
|
+
computeStat({ data: e, mapping: t }) {
|
|
73
|
+
const r = A(t.y);
|
|
74
|
+
if (r === null || !e.hasVariable(r)) return {
|
|
75
|
+
data: e,
|
|
76
|
+
mapping: {}
|
|
77
|
+
};
|
|
78
|
+
if (e.size() > 0 && e.getType(r) !== "numeric") {
|
|
79
|
+
const o = e.getType(r);
|
|
80
|
+
throw new G({
|
|
81
|
+
code: "INCOMPATIBLE_TYPE",
|
|
82
|
+
message: `'waterfall' stat requires a numeric 'y' (got '${o}' for variable '${r}')`,
|
|
83
|
+
context: {
|
|
84
|
+
aesthetic: "y",
|
|
85
|
+
variableName: r,
|
|
86
|
+
variableType: o
|
|
87
|
+
},
|
|
88
|
+
suggestion: "Map each bar's change to 'y', or running totals to 'start' and 'end' under stat 'identity'."
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const a = A(t.measure), s = a !== null && e.hasVariable(a) ? j(e.getValues(a), a) : [], n = A(t.group), l = n !== null && e.hasVariable(n) ? [n] : [];
|
|
92
|
+
return {
|
|
93
|
+
data: be(e, r, s, Se(e, l)),
|
|
94
|
+
mapping: U
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}, be = (e, t, r, a) => {
|
|
98
|
+
const s = e.getValues(t, { type: "numeric" }), n = ye(s, r, a), l = e.getValueFormat(t);
|
|
99
|
+
return e.addVariable(c.start, "numeric", n.map((o) => o?.start ?? null), l).addVariable(c.end, "numeric", n.map((o) => o?.end ?? null), l).addVariable(c.kind, "categorical", n.map((o, i) => o === null ? null : C(o, r[i] ?? "relative")));
|
|
100
|
+
}, ye = (e, t, r) => {
|
|
101
|
+
const a = /* @__PURE__ */ new Map();
|
|
102
|
+
r.forEach((n, l) => {
|
|
103
|
+
const o = a.get(n);
|
|
104
|
+
o === void 0 ? a.set(n, [l]) : o.push(l);
|
|
105
|
+
});
|
|
106
|
+
const s = e.map(() => null);
|
|
107
|
+
for (const n of a.values()) {
|
|
108
|
+
const l = fe(n.map((o) => e[o] ?? null), n.map((o) => t[o] ?? "relative"));
|
|
109
|
+
n.forEach((o, i) => {
|
|
110
|
+
s[o] = l[i] ?? null;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return s;
|
|
114
|
+
}, Se = (e, t) => {
|
|
115
|
+
const r = t.map((a) => e.getValues(a));
|
|
116
|
+
return Array.from({ length: e.size() }, (a, s) => JSON.stringify(r.map((n) => n[s])));
|
|
117
|
+
}, A = (e) => e === void 0 ? null : typeof e == "string" ? e : "variable" in e ? e.variable : null, we = (e) => {
|
|
118
|
+
const t = [];
|
|
119
|
+
if (!e.layer.dataLabels.showDataLabels) return {
|
|
120
|
+
labels: t,
|
|
121
|
+
droppedCount: 0
|
|
122
|
+
};
|
|
123
|
+
const { position: r, justify: a, align: s, offset: n } = e.layer.dataLabels, l = r === "auto" ? "outside" : r, o = m(e.coordSystem), i = ee(e.panelRect);
|
|
124
|
+
for (const d of w(e.layer.data, o)) {
|
|
125
|
+
const h = e.formatLabel(d.observation);
|
|
126
|
+
if (h === "") continue;
|
|
127
|
+
const { width: p, height: y } = e.measureDataLabel("observation", l, h), v = ie({
|
|
128
|
+
box: xe(d, o, e.panelRect),
|
|
129
|
+
panel: i,
|
|
130
|
+
orientation: ke(d, o),
|
|
131
|
+
placement: l,
|
|
132
|
+
justify: r === "auto" ? "end" : a,
|
|
133
|
+
align: s,
|
|
134
|
+
offset: n,
|
|
135
|
+
labelWidth: p,
|
|
136
|
+
labelHeight: y
|
|
137
|
+
});
|
|
138
|
+
t.push({
|
|
139
|
+
layerId: e.layer.id,
|
|
140
|
+
x: v.x,
|
|
141
|
+
y: v.y,
|
|
142
|
+
text: h,
|
|
143
|
+
width: p,
|
|
144
|
+
height: y,
|
|
145
|
+
isRotated: !1,
|
|
146
|
+
role: "observation",
|
|
147
|
+
position: v.position,
|
|
148
|
+
backdropColor: v.position === "inside" ? oe(e.styleReaders, d.observation, e.styleReaders.part(d.kind)?.get("fill", d.observation)) : void 0
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
labels: t,
|
|
153
|
+
droppedCount: 0
|
|
154
|
+
};
|
|
155
|
+
}, xe = (e, t, r) => {
|
|
156
|
+
const a = b(t, {
|
|
157
|
+
main: e.bandStart,
|
|
158
|
+
cross: e.low
|
|
159
|
+
}), s = b(t, {
|
|
160
|
+
main: e.bandEnd,
|
|
161
|
+
cross: e.high
|
|
162
|
+
}), n = Math.min(a.x, s.x) * r.width, l = Math.max(a.x, s.x) * r.width, o = (1 - Math.max(a.y, s.y)) * r.height, i = (1 - Math.min(a.y, s.y)) * r.height;
|
|
163
|
+
return {
|
|
164
|
+
x: n,
|
|
165
|
+
y: o,
|
|
166
|
+
width: l - n,
|
|
167
|
+
height: i - o
|
|
168
|
+
};
|
|
169
|
+
}, ke = (e, t) => {
|
|
170
|
+
const r = e.end >= e.start;
|
|
171
|
+
return t === "x" ? {
|
|
172
|
+
valueAxis: "y",
|
|
173
|
+
valueDirectionSign: r ? -1 : 1
|
|
174
|
+
} : {
|
|
175
|
+
valueAxis: "x",
|
|
176
|
+
valueDirectionSign: r ? 1 : -1
|
|
177
|
+
};
|
|
178
|
+
}, Me = 1, Ae = {
|
|
179
|
+
alpha: "unitInterval",
|
|
180
|
+
fillAlpha: "unitInterval",
|
|
181
|
+
strokeWidth: "pixels"
|
|
182
|
+
}, E = {
|
|
183
|
+
fill: "color",
|
|
184
|
+
stroke: "color"
|
|
185
|
+
}, Ee = {
|
|
186
|
+
stroke: "color",
|
|
187
|
+
alpha: "unitInterval",
|
|
188
|
+
strokeWidth: "pixels",
|
|
189
|
+
dashArray: "dashArray"
|
|
190
|
+
}, Ve = class extends q {
|
|
191
|
+
constructor(...e) {
|
|
192
|
+
super(...e), this.type = "waterfall", this.defaultParams = {}, this.defaultStat = new me(), this.derivedVariables = Object.values(c), this.positionRoles = [
|
|
193
|
+
{
|
|
194
|
+
axis: "x",
|
|
195
|
+
role: "point",
|
|
196
|
+
valueKind: "value"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
axis: "y",
|
|
200
|
+
role: "min",
|
|
201
|
+
valueKind: "value"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
axis: "y",
|
|
205
|
+
role: "max",
|
|
206
|
+
valueKind: "value"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
axis: "y",
|
|
210
|
+
role: "scalar",
|
|
211
|
+
valueKind: "value",
|
|
212
|
+
aes: "start"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
axis: "y",
|
|
216
|
+
role: "scalar",
|
|
217
|
+
valueKind: "value",
|
|
218
|
+
aes: "end"
|
|
219
|
+
}
|
|
220
|
+
], this.aesthetics = [
|
|
221
|
+
{
|
|
222
|
+
kind: "data",
|
|
223
|
+
name: "y"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
kind: "data",
|
|
227
|
+
name: "measure"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
kind: "visual",
|
|
231
|
+
name: "color"
|
|
232
|
+
}
|
|
233
|
+
], this.supportedPositions = ["identity"], this.supportedCoordTypes = ["cartesian", "flip"], this.scaleConstraints = {
|
|
234
|
+
discreteMainAxis: !0,
|
|
235
|
+
zeroBaseline: !0
|
|
236
|
+
}, this.dataLabelCoordTypes = ["cartesian", "flip"], this.getDataLabelPlacements = we, this.spatialKind = "rects", this.highlightStrategy = "observation-rerender", this.tooltip = [{
|
|
237
|
+
key: "Value",
|
|
238
|
+
aes: "value"
|
|
239
|
+
}, {
|
|
240
|
+
key: "Running total",
|
|
241
|
+
aes: "end"
|
|
242
|
+
}], this.styleTarget = ae({
|
|
243
|
+
observation: {
|
|
244
|
+
vocabulary: Ae,
|
|
245
|
+
sharedPaint: !1,
|
|
246
|
+
aesthetics: {},
|
|
247
|
+
rest: {
|
|
248
|
+
alpha: 1,
|
|
249
|
+
fillAlpha: 1,
|
|
250
|
+
strokeWidth: 0
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
parts: {
|
|
254
|
+
increase: {
|
|
255
|
+
vocabulary: E,
|
|
256
|
+
aesthetics: {
|
|
257
|
+
fill: "color",
|
|
258
|
+
stroke: "color"
|
|
259
|
+
},
|
|
260
|
+
rest: {
|
|
261
|
+
fill: f("trendPositive"),
|
|
262
|
+
stroke: f("trendPositive")
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
decrease: {
|
|
266
|
+
vocabulary: E,
|
|
267
|
+
aesthetics: {
|
|
268
|
+
fill: "color",
|
|
269
|
+
stroke: "color"
|
|
270
|
+
},
|
|
271
|
+
rest: {
|
|
272
|
+
fill: f("trendNegative"),
|
|
273
|
+
stroke: f("trendNegative")
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
total: {
|
|
277
|
+
vocabulary: E,
|
|
278
|
+
aesthetics: {
|
|
279
|
+
fill: "color",
|
|
280
|
+
stroke: "color"
|
|
281
|
+
},
|
|
282
|
+
rest: {
|
|
283
|
+
fill: f("trendNeutral"),
|
|
284
|
+
stroke: f("trendNeutral")
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
connector: {
|
|
288
|
+
vocabulary: Ee,
|
|
289
|
+
rest: {
|
|
290
|
+
stroke: f("geomBorder"),
|
|
291
|
+
alpha: 1,
|
|
292
|
+
strokeWidth: Me,
|
|
293
|
+
dashArray: []
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}), this.resolveBandFraction = (t) => $(t), this.resolveValueSource = () => ({ variable: c.value }), this.validateMapping = ({ mapping: t, computedVariables: r }) => r.has("end") ? [...[...I].filter((a) => t[a] !== void 0).map((a) => ({
|
|
298
|
+
code: "CONFLICTING_STAT_MAPPING",
|
|
299
|
+
message: `waterfall stat computes '${a}' from 'y'; remove its mapping, or set the layer's stat to 'identity'`
|
|
300
|
+
})), ...t.y === void 0 ? [{
|
|
301
|
+
code: "MISSING_AESTHETIC",
|
|
302
|
+
message: "waterfall stat runs the total through 'y'; map it to the changes",
|
|
303
|
+
context: { aesthetic: "y" }
|
|
304
|
+
}] : []] : [...I].filter((a) => t[a] === void 0).map((a) => ({
|
|
305
|
+
code: "MISSING_AESTHETIC",
|
|
306
|
+
message: `waterfall geom requires '${a}' when its running totals are already in the data`,
|
|
307
|
+
context: { aesthetic: a }
|
|
308
|
+
})), this.resolveAnchorPosition = (t, { coordSystem: r }) => {
|
|
309
|
+
const a = m(r), s = K(t, a);
|
|
310
|
+
return s === null ? null : a === "x" ? {
|
|
311
|
+
x: s.band,
|
|
312
|
+
y: s.end
|
|
313
|
+
} : {
|
|
314
|
+
x: s.end,
|
|
315
|
+
y: s.band
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
resolveParams(e) {
|
|
320
|
+
const { width: t, connector: r, ...a } = super.resolveParams(e);
|
|
321
|
+
return {
|
|
322
|
+
...a,
|
|
323
|
+
...Te(t, e.diagnostics),
|
|
324
|
+
...Ce(r, e.diagnostics)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
compile({ data: e, mapping: t, params: r }) {
|
|
328
|
+
const a = V(t.start), s = V(t.end);
|
|
329
|
+
if (a === null || s === null) return {
|
|
330
|
+
data: e,
|
|
331
|
+
mapping: {}
|
|
332
|
+
};
|
|
333
|
+
if (!e.hasVariable(a) || !e.hasVariable(s)) return {
|
|
334
|
+
data: e,
|
|
335
|
+
mapping: {}
|
|
336
|
+
};
|
|
337
|
+
const n = $(r) / 2;
|
|
338
|
+
return {
|
|
339
|
+
data: Ie(e, a, s, V(t.measure)).addConstantVariable(x.xMin, "numeric", -n).addConstantVariable(x.xMax, "numeric", n),
|
|
340
|
+
mapping: {
|
|
341
|
+
y: t.end,
|
|
342
|
+
value: c.value
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}, Te = (e, t) => {
|
|
347
|
+
if (e === void 0) return {};
|
|
348
|
+
const r = typeof e == "number" && Number.isFinite(e) ? e : null;
|
|
349
|
+
if (r !== null && r > 0 && r <= 1) return { width: r };
|
|
350
|
+
const a = r !== null && r > 1 ? 1 : void 0;
|
|
351
|
+
return t?.addWarning({
|
|
352
|
+
code: "INVALID_GEOM_PARAM",
|
|
353
|
+
message: `Waterfall width must be a fraction in (0, 1], got ${String(e)}; falling back to ${a ?? "the default band"}.`,
|
|
354
|
+
context: {
|
|
355
|
+
geom: "waterfall",
|
|
356
|
+
param: "width",
|
|
357
|
+
expected: "0 < width <= 1",
|
|
358
|
+
actual: r ?? String(e)
|
|
359
|
+
},
|
|
360
|
+
suggestion: "Set params.width to a fraction of the category band in (0, 1]."
|
|
361
|
+
}), a === void 0 ? {} : { width: a };
|
|
362
|
+
}, Ce = (e, t) => {
|
|
363
|
+
if (e === void 0) return {};
|
|
364
|
+
if (Y(e)) return { connector: e };
|
|
365
|
+
const r = H.map((a) => `'${a}'`).join(", ");
|
|
366
|
+
return t?.addWarning({
|
|
367
|
+
code: "INVALID_GEOM_PARAM",
|
|
368
|
+
message: `Waterfall connector must be one of ${r}, got ${String(e)}; falling back to 'spanning'.`,
|
|
369
|
+
context: {
|
|
370
|
+
geom: "waterfall",
|
|
371
|
+
param: "connector",
|
|
372
|
+
expected: r,
|
|
373
|
+
actual: String(e)
|
|
374
|
+
},
|
|
375
|
+
suggestion: `Set params.connector to one of ${r}.`
|
|
376
|
+
}), {};
|
|
377
|
+
}, V = (e) => e === void 0 ? null : typeof e == "string" ? e : "variable" in e ? e.variable : null, Ie = (e, t, r, a) => {
|
|
378
|
+
const s = e.getValues(t, { type: "numeric" }), n = e.getValues(r, { type: "numeric" }), l = Le(e, a), o = s.map((i, d) => {
|
|
379
|
+
const h = n[d] ?? null;
|
|
380
|
+
if (i === null || h === null || !Number.isFinite(i) || !Number.isFinite(h)) return null;
|
|
381
|
+
const p = {
|
|
382
|
+
start: i,
|
|
383
|
+
end: h
|
|
384
|
+
};
|
|
385
|
+
return {
|
|
386
|
+
...p,
|
|
387
|
+
kind: l(p, d)
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
return e.addVariable(c.kind, "categorical", o.map((i) => i?.kind ?? null)).addVariable(c.value, "numeric", o.map((i) => i === null ? null : i.kind === "total" ? i.end : i.end - i.start), e.getValueFormat(r)).addVariable(x.yMin, "numeric", o.map((i) => i === null ? null : Math.min(i.start, i.end))).addVariable(x.yMax, "numeric", o.map((i) => i === null ? null : Math.max(i.start, i.end)));
|
|
391
|
+
}, Le = (e, t) => {
|
|
392
|
+
if (e.hasVariable(c.kind)) {
|
|
393
|
+
const a = e.getValues(c.kind);
|
|
394
|
+
return (s, n) => _e(a[n]) ? a[n] : C(s, "relative");
|
|
395
|
+
}
|
|
396
|
+
const r = t !== null && e.hasVariable(t) ? j(e.getValues(t), t) : [];
|
|
397
|
+
return (a, s) => C(a, r[s] ?? "relative");
|
|
398
|
+
}, Ne = /* @__PURE__ */ new Set([
|
|
399
|
+
"increase",
|
|
400
|
+
"decrease",
|
|
401
|
+
"total"
|
|
402
|
+
]), _e = (e) => Ne.has(e), Be = 5e-4, X = (e, t) => {
|
|
403
|
+
const r = (e.low + e.high) / 2, a = Math.max(e.high - e.low, Be) / 2, s = b(t, {
|
|
404
|
+
main: e.bandStart,
|
|
405
|
+
cross: r - a
|
|
406
|
+
}), n = b(t, {
|
|
407
|
+
main: e.bandEnd,
|
|
408
|
+
cross: r + a
|
|
409
|
+
}), [l, o] = D(k(s.x), k(n.x)), [i, d] = D(M(s.y), M(n.y));
|
|
410
|
+
return {
|
|
411
|
+
x: l,
|
|
412
|
+
y: i,
|
|
413
|
+
width: o - l,
|
|
414
|
+
height: d - i
|
|
415
|
+
};
|
|
416
|
+
}, We = (e, t, r = "spanning", a = null) => {
|
|
417
|
+
const s = /* @__PURE__ */ new Map();
|
|
418
|
+
return e.flatMap((n) => {
|
|
419
|
+
const l = a === null ? null : n.observation[a], o = s.get(l);
|
|
420
|
+
if (s.set(l, n), o === void 0 || !Pe(o, n)) return [];
|
|
421
|
+
const i = n.band >= o.band, d = r === "spanning", h = i === d ? o.bandStart : o.bandEnd, p = i === d ? n.bandEnd : n.bandStart, y = b(t, {
|
|
422
|
+
main: h,
|
|
423
|
+
cross: o.end
|
|
424
|
+
}), v = b(t, {
|
|
425
|
+
main: p,
|
|
426
|
+
cross: o.end
|
|
427
|
+
});
|
|
428
|
+
return [{
|
|
429
|
+
from: o,
|
|
430
|
+
to: n,
|
|
431
|
+
segment: {
|
|
432
|
+
x1: k(y.x),
|
|
433
|
+
y1: M(y.y),
|
|
434
|
+
x2: k(v.x),
|
|
435
|
+
y2: M(v.y)
|
|
436
|
+
}
|
|
437
|
+
}];
|
|
438
|
+
});
|
|
439
|
+
}, R = 1e-9, Pe = (e, t) => Math.abs(t.start - e.end) < R || Math.abs(t.end - e.end) < R, D = (e, t) => e <= t ? [e, t] : [t, e], Oe = ({ layer: e, coordSystem: t, panelRect: r }) => {
|
|
440
|
+
const a = m(t);
|
|
441
|
+
return w(e.data, a).map((s) => {
|
|
442
|
+
const n = X(s, a), l = n.x * r.width;
|
|
443
|
+
return {
|
|
444
|
+
kind: "region",
|
|
445
|
+
pathData: `M${l},${n.y * r.height}H${(n.x + n.width) * r.width}V${(n.y + n.height) * r.height}H${l}Z`
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
}, L = (e, t) => {
|
|
449
|
+
const r = te(e.data, e.mapping, e.id), a = F(e.mapping.group), s = /* @__PURE__ */ new Map();
|
|
450
|
+
return new Map(t.map((n) => {
|
|
451
|
+
const l = r(n.observation) ?? e.id, o = a === null ? l : `${l}-${String(n.observation[a])}`, i = s.get(o) ?? 0;
|
|
452
|
+
return s.set(o, i + 1), [n, i === 0 ? o : `${o}#${i}`];
|
|
453
|
+
}));
|
|
454
|
+
}, N = ({ steps: e, connectors: t = [], mainAxis: r, styleReaders: a, keys: s, isHovered: n = !1, shouldAnimateTransitions: l = !1 }) => {
|
|
455
|
+
const o = n ? "hovered" : void 0, i = a.part("connector");
|
|
456
|
+
return /* @__PURE__ */ ce(de, { children: [e.map((d) => /* @__PURE__ */ u(Re, {
|
|
457
|
+
step: d,
|
|
458
|
+
mainAxis: r,
|
|
459
|
+
styleReaders: a,
|
|
460
|
+
state: o,
|
|
461
|
+
shouldAnimateTransitions: l
|
|
462
|
+
}, s.get(d))), i && t.map((d) => /* @__PURE__ */ u($e, {
|
|
463
|
+
connector: d,
|
|
464
|
+
readers: i,
|
|
465
|
+
shouldAnimateTransitions: l
|
|
466
|
+
}, `${s.get(d.from)}-connector`))] });
|
|
467
|
+
}, $e = ({ connector: e, readers: t, shouldAnimateTransitions: r }) => {
|
|
468
|
+
const { observation: a } = e.from, s = t.get("strokeWidth", a) ?? 0, n = t.get("stroke", a);
|
|
469
|
+
return /* @__PURE__ */ u(z, {
|
|
470
|
+
"data-testid": "waterfall-connector",
|
|
471
|
+
x1: g(e.segment.x1),
|
|
472
|
+
y1: g(e.segment.y1),
|
|
473
|
+
x2: g(e.segment.x2),
|
|
474
|
+
y2: g(e.segment.y2),
|
|
475
|
+
stroke: n === void 0 ? void 0 : T(n),
|
|
476
|
+
strokeOpacity: t.get("alpha", a),
|
|
477
|
+
strokeWidth: s,
|
|
478
|
+
strokeDasharray: le(t.get("dashArray", a), s),
|
|
479
|
+
shouldAnimateTransitions: r
|
|
480
|
+
});
|
|
481
|
+
}, Re = ({ step: e, mainAxis: t, styleReaders: r, state: a, shouldAnimateTransitions: s }) => {
|
|
482
|
+
const n = X(e, t), l = r.part(e.kind), o = l?.get("fill", e.observation, a), i = l?.get("stroke", e.observation, a);
|
|
483
|
+
return /* @__PURE__ */ u(J, {
|
|
484
|
+
"data-testid": "waterfall-bar",
|
|
485
|
+
x: g(n.x),
|
|
486
|
+
y: g(n.y),
|
|
487
|
+
width: g(n.width),
|
|
488
|
+
height: g(n.height),
|
|
489
|
+
fill: o === void 0 ? void 0 : T(o),
|
|
490
|
+
opacity: r.get("alpha", e.observation, a),
|
|
491
|
+
fillOpacity: r.get("fillAlpha", e.observation, a),
|
|
492
|
+
stroke: i === void 0 ? void 0 : T(i),
|
|
493
|
+
strokeWidth: r.get("strokeWidth", e.observation, a),
|
|
494
|
+
shouldAnimateTransitions: s
|
|
495
|
+
});
|
|
496
|
+
}, De = ({ layer: e, sourceLayer: t, mainAxis: r, styleReaders: a }) => {
|
|
497
|
+
const s = S(() => w(e.data, r), [e.data, r]);
|
|
498
|
+
return /* @__PURE__ */ u(N, {
|
|
499
|
+
steps: s,
|
|
500
|
+
mainAxis: r,
|
|
501
|
+
styleReaders: a,
|
|
502
|
+
keys: S(() => L(t, s), [t, s])
|
|
503
|
+
});
|
|
504
|
+
}, Ge = ({ layer: e, primary: t, siblings: r, mainAxis: a, styleReaders: s }) => {
|
|
505
|
+
const n = w([t, ...r].map((l) => l.observation), a);
|
|
506
|
+
return /* @__PURE__ */ u("g", {
|
|
507
|
+
"data-testid": "hovered-steps",
|
|
508
|
+
children: /* @__PURE__ */ u(N, {
|
|
509
|
+
steps: n,
|
|
510
|
+
mainAxis: a,
|
|
511
|
+
styleReaders: s,
|
|
512
|
+
keys: L(e, n),
|
|
513
|
+
isHovered: !0
|
|
514
|
+
})
|
|
515
|
+
});
|
|
516
|
+
}, Ke = ({ layer: e, mainAxis: t, styleReaders: r, shouldAnimateTransitions: a, intro: s }) => {
|
|
517
|
+
const n = S(() => w(e.data, t), [e.data, t]), l = ge(e.params), o = F(e.mapping.group), i = /* @__PURE__ */ u(N, {
|
|
518
|
+
steps: n,
|
|
519
|
+
connectors: S(() => We(n, t, l, o), [
|
|
520
|
+
n,
|
|
521
|
+
t,
|
|
522
|
+
l,
|
|
523
|
+
o
|
|
524
|
+
]),
|
|
525
|
+
mainAxis: t,
|
|
526
|
+
styleReaders: r,
|
|
527
|
+
keys: S(() => L(e, n), [e, n]),
|
|
528
|
+
shouldAnimateTransitions: a
|
|
529
|
+
}), d = Fe(s);
|
|
530
|
+
return d === null ? i : /* @__PURE__ */ u(Z, {
|
|
531
|
+
intro: d,
|
|
532
|
+
children: i
|
|
533
|
+
});
|
|
534
|
+
}, Fe = (e) => e == null ? null : {
|
|
535
|
+
type: "fade",
|
|
536
|
+
layerId: e.layerId,
|
|
537
|
+
durationSeconds: e.durationSeconds
|
|
538
|
+
}, Ue = re(new Ve(), {
|
|
539
|
+
coord: "cartesian",
|
|
540
|
+
swatchShape: "square",
|
|
541
|
+
guideMode: "band",
|
|
542
|
+
render: ({ layer: e, coordSystem: t, styleReaders: r, shouldAnimateTransitions: a, intro: s }) => /* @__PURE__ */ u(Ke, {
|
|
543
|
+
layer: e,
|
|
544
|
+
mainAxis: m(t),
|
|
545
|
+
styleReaders: r,
|
|
546
|
+
shouldAnimateTransitions: a,
|
|
547
|
+
intro: s
|
|
548
|
+
}),
|
|
549
|
+
renderHighlight: ({ layer: e, sourceLayer: t, coordSystem: r, styleReaders: a }) => /* @__PURE__ */ u(De, {
|
|
550
|
+
layer: e,
|
|
551
|
+
sourceLayer: t,
|
|
552
|
+
mainAxis: m(r),
|
|
553
|
+
styleReaders: a
|
|
554
|
+
}),
|
|
555
|
+
renderHover: ({ layer: e, primary: t, group: r, related: a, coordSystem: s, styleReaders: n }) => /* @__PURE__ */ u(Ge, {
|
|
556
|
+
layer: e,
|
|
557
|
+
primary: t,
|
|
558
|
+
siblings: [...r, ...a],
|
|
559
|
+
mainAxis: m(s),
|
|
560
|
+
styleReaders: n
|
|
561
|
+
}),
|
|
562
|
+
renderHoverCompanions: () => null,
|
|
563
|
+
getEditOutlineShapes: Oe
|
|
564
|
+
});
|
|
565
|
+
export {
|
|
566
|
+
Ve as WaterfallGeom,
|
|
567
|
+
Ue as waterfall
|
|
568
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MainAxis } from '@graphysdk/react';
|
|
2
|
+
import { Step } from '../../utils/read-waterfall';
|
|
3
|
+
import { WaterfallConnectorMode } from '../../utils/waterfall-params';
|
|
4
|
+
/** A box in the `[0, 1]` view box, from its top left, y growing downward. */
|
|
5
|
+
export interface ViewBoxRect {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
/** A line in the `[0, 1]` view box. */
|
|
12
|
+
export interface ViewBoxSegment {
|
|
13
|
+
x1: number;
|
|
14
|
+
y1: number;
|
|
15
|
+
x2: number;
|
|
16
|
+
y2: number;
|
|
17
|
+
}
|
|
18
|
+
/** The connector from one bar to the next, and the two bars it joins. */
|
|
19
|
+
export interface Connector {
|
|
20
|
+
from: Step;
|
|
21
|
+
to: Step;
|
|
22
|
+
segment: ViewBoxSegment;
|
|
23
|
+
}
|
|
24
|
+
/** The bar from its start to its end, across its share of the band. */
|
|
25
|
+
export declare const readBarBox: (step: Step, mainAxis: MainAxis) => ViewBoxRect;
|
|
26
|
+
/**
|
|
27
|
+
* One connector from each bar to the next in its group, at the running total the first ends on. The total
|
|
28
|
+
* runs per `group`, so a connector never joins two groups' bars even when their rows interleave. A bar that
|
|
29
|
+
* does not pick up that total, such as an absolute bar resetting it, gets no connector in.
|
|
30
|
+
* `'between'` runs from the first bar's edge facing the second to the second's edge facing the first;
|
|
31
|
+
* `'spanning'` runs from the first bar's far edge to the second's far edge, across both bands. Either way it
|
|
32
|
+
* reads whichever way the category axis runs.
|
|
33
|
+
*/
|
|
34
|
+
export declare const readConnectors: (steps: readonly Step[], mainAxis: MainAxis, mode?: WaterfallConnectorMode, groupVariable?: string | null) => Connector[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SceneLayer } from '@graphysdk/react';
|
|
2
|
+
import { Step } from '../../utils/read-waterfall';
|
|
3
|
+
/**
|
|
4
|
+
* A bar keys on its category, read from the raw value rather than the band: the band moves with the domain,
|
|
5
|
+
* the very change a mark should spring through, not remount on. Only an explicit `group` joins the key, as it
|
|
6
|
+
* is the only mapping that splits the total: a mapped `color` only paints, and a bar whose colour changes
|
|
7
|
+
* (an up-or-down column flipping with its sign) is the same bar.
|
|
8
|
+
*
|
|
9
|
+
* A category can repeat within a group (a two-year monthly bridge has two Januaries), so repeats carry their
|
|
10
|
+
* occurrence in the order painted: every bar keeps its own key, and the keys survive a recompile.
|
|
11
|
+
*/
|
|
12
|
+
export declare const readWaterfallKeys: (layer: SceneLayer, steps: readonly Step[]) => Map<Step, string>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { EditOutlineShape, GeomEditOutlineShapesRendererInput } from '@graphysdk/react';
|
|
2
|
+
/** What point and edit outlines round the bars they take, each as its box. A connector belongs to no one bar. */
|
|
3
|
+
export declare const getWaterfallEditOutlineShapes: ({ layer, coordSystem, panelRect, }: GeomEditOutlineShapesRendererInput) => EditOutlineShape[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GeomStyleReaders, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface HighlightedStepsProps {
|
|
3
|
+
layer: SceneLayer;
|
|
4
|
+
/** The whole layer the matched observations were taken from. */
|
|
5
|
+
sourceLayer: SceneLayer;
|
|
6
|
+
mainAxis: MainAxis;
|
|
7
|
+
styleReaders: GeomStyleReaders;
|
|
8
|
+
}
|
|
9
|
+
/** Repaints the matched bars above the dimmed layer. */
|
|
10
|
+
export declare const HighlightedSteps: ({ layer, sourceLayer, mainAxis, styleReaders }: HighlightedStepsProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GeomStyleReaders, HoverHit, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface HoveredStepsProps {
|
|
3
|
+
layer: SceneLayer;
|
|
4
|
+
primary: HoverHit;
|
|
5
|
+
/** Any other bar of the hovered category, from duplicate rows. */
|
|
6
|
+
siblings: readonly HoverHit[];
|
|
7
|
+
mainAxis: MainAxis;
|
|
8
|
+
styleReaders: GeomStyleReaders;
|
|
9
|
+
}
|
|
10
|
+
/** The hovered category's bar, repainted above the dimmed layer. */
|
|
11
|
+
export declare const HoveredSteps: ({ layer, primary, siblings, mainAxis, styleReaders }: HoveredStepsProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GeomStyleReaders, LayerIntroPlan, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface WaterfallLayerProps {
|
|
3
|
+
layer: SceneLayer;
|
|
4
|
+
mainAxis: MainAxis;
|
|
5
|
+
styleReaders: GeomStyleReaders;
|
|
6
|
+
shouldAnimateTransitions: boolean;
|
|
7
|
+
intro: LayerIntroPlan | null | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The layer's own paint, connectors included. The whole layer fades up on one clock, whatever entrance the engine
|
|
11
|
+
* plans: a bar is a rect to the engine, which plans a bar's grow, but a waterfall's bars float on the running
|
|
12
|
+
* total, with no baseline to grow from. The fade is independent of `shouldAnimateTransitions`: it rides the group
|
|
13
|
+
* rather than moving the marks, so it plays even when data changes snap.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WaterfallLayer: ({ layer, mainAxis, styleReaders, shouldAnimateTransitions, intro, }: WaterfallLayerProps) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { GeomStyleReaders, MainAxis } from '@graphysdk/react';
|
|
2
|
+
import { Step } from '../utils/read-waterfall';
|
|
3
|
+
import { Connector } from './utils/step-geometry';
|
|
4
|
+
/**
|
|
5
|
+
* What one pass paints: its bars, then its connectors above them, so a spanning connector runs visibly along
|
|
6
|
+
* the edges of the bars it joins.
|
|
7
|
+
*/
|
|
8
|
+
export interface WaterfallPaintProps {
|
|
9
|
+
steps: readonly Step[];
|
|
10
|
+
/**
|
|
11
|
+
* The lines between bars. Only the layer's own paint draws them: a hover or highlight repaint raises bars,
|
|
12
|
+
* and a connector belongs to no one bar.
|
|
13
|
+
*/
|
|
14
|
+
connectors?: readonly Connector[];
|
|
15
|
+
mainAxis: MainAxis;
|
|
16
|
+
styleReaders: GeomStyleReaders;
|
|
17
|
+
/** What identifies each bar across a data change, so a spring moves a mark rather than swapping it. */
|
|
18
|
+
keys: ReadonlyMap<Step, string>;
|
|
19
|
+
/** Paints in the `hovered` state. */
|
|
20
|
+
isHovered?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether bars and connectors spring to their new positions when the data changes. The hover and highlight
|
|
23
|
+
* repaints mount and unmount with the interaction, so only the layer's own paint animates.
|
|
24
|
+
*/
|
|
25
|
+
shouldAnimateTransitions?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const WaterfallPaint: ({ steps, connectors, mainAxis, styleReaders, keys, isHovered, shouldAnimateTransitions, }: WaterfallPaintProps) => import("react/jsx-runtime").JSX.Element;
|