@graphysdk/geom-funnel 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 +67 -2
- package/dist/engine/funnel-data-labels.d.ts +7 -0
- package/dist/engine/funnel.geom.d.ts +120 -0
- package/dist/engine/funnel.stat.d.ts +13 -0
- package/dist/engine/utils/measure-stages.d.ts +16 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +478 -0
- package/dist/renderer/funnel-edit-outline.d.ts +3 -0
- package/dist/renderer/funnel-highlight.d.ts +11 -0
- package/dist/renderer/funnel-hover.d.ts +12 -0
- package/dist/renderer/funnel-layer.d.ts +19 -0
- package/dist/renderer/funnel-paint.d.ts +26 -0
- package/dist/renderer/funnel.d.ts +17 -0
- package/dist/renderer/utils/connector-geometry.d.ts +27 -0
- package/dist/renderer/utils/funnel-keys.d.ts +7 -0
- package/dist/utils/funnel-columns.d.ts +10 -0
- package/dist/utils/funnel-params.d.ts +6 -0
- package/dist/utils/read-funnel.d.ts +21 -0
- package/dist/utils/stage-geometry.d.ts +11 -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,478 @@
|
|
|
1
|
+
import { Geom as z, GeomPath as J, GeomRect as Q, IntroFade as ee, POSITION_VARIABLES as S, Stat as te, UserInputError as D, buildPanelAnchorBox as re, composePoint as E, createStableKeyGenerator as ne, defineGeomRenderer as ae, defineGeomStyleTarget as ie, getX as oe, getXMax as I, getXMin as L, getY as se, getYMax as B, getYMin as _, readMainAxis as h, readVariableName as v, resolveAnchoredLabelCenter as le, toPaintColor as x, toPercent as w, toViewBoxX as M, toViewBoxY as V, token as F } from "@graphysdk/react";
|
|
2
|
+
import { useMemo as p } from "react";
|
|
3
|
+
import { Fragment as ue, jsx as d, jsxs as ce } from "react/jsx-runtime";
|
|
4
|
+
var g = {
|
|
5
|
+
percentOfFirst: "funnelPercentOfFirst",
|
|
6
|
+
percentOfPrevious: "funnelPercentOfPrevious"
|
|
7
|
+
}, de = 0.7, G = ({ width: e }) => e ?? de, K = (e, r) => {
|
|
8
|
+
const t = r === "x" ? oe(e) : se(e), n = r === "x" ? L(e) : _(e), a = r === "x" ? I(e) : B(e), i = r === "x" ? _(e) : L(e), o = r === "x" ? B(e) : I(e);
|
|
9
|
+
return t === null || n === null || a === null || i === null || o === null ? null : {
|
|
10
|
+
observation: e,
|
|
11
|
+
band: t,
|
|
12
|
+
bandStart: n,
|
|
13
|
+
bandEnd: a,
|
|
14
|
+
low: i,
|
|
15
|
+
high: o
|
|
16
|
+
};
|
|
17
|
+
}, m = (e, r) => {
|
|
18
|
+
const t = [];
|
|
19
|
+
for (const n of e) {
|
|
20
|
+
const a = K(n, r);
|
|
21
|
+
a !== null && t.push(a);
|
|
22
|
+
}
|
|
23
|
+
return t;
|
|
24
|
+
}, R = (e) => e === null || !Number.isFinite(e) || e < 0 ? null : e, he = (e) => {
|
|
25
|
+
let r = null, t = null;
|
|
26
|
+
return e.map((n) => {
|
|
27
|
+
const a = R(n);
|
|
28
|
+
if (a === null) return {
|
|
29
|
+
percentOfFirst: null,
|
|
30
|
+
percentOfPrevious: null
|
|
31
|
+
};
|
|
32
|
+
r ??= a;
|
|
33
|
+
const i = {
|
|
34
|
+
percentOfFirst: $(a, r),
|
|
35
|
+
percentOfPrevious: t === null ? 1 : $(a, t)
|
|
36
|
+
};
|
|
37
|
+
return t = a, i;
|
|
38
|
+
});
|
|
39
|
+
}, $ = (e, r) => r === 0 ? null : e / r, Y = {
|
|
40
|
+
percentOfFirst: g.percentOfFirst,
|
|
41
|
+
percentOfPrevious: g.percentOfPrevious
|
|
42
|
+
}, U = new Set(Object.keys(Y)), H = {
|
|
43
|
+
percentOfFirst: null,
|
|
44
|
+
percentOfPrevious: null
|
|
45
|
+
}, pe = class extends te {
|
|
46
|
+
constructor(...e) {
|
|
47
|
+
super(...e), this.type = "funnel", this.computedVariables = U;
|
|
48
|
+
}
|
|
49
|
+
computeStat({ data: e, mapping: r }) {
|
|
50
|
+
const t = v(r.y);
|
|
51
|
+
if (t === null || !e.hasVariable(t)) return {
|
|
52
|
+
data: e,
|
|
53
|
+
mapping: {}
|
|
54
|
+
};
|
|
55
|
+
if (e.size() > 0 && e.getType(t) !== "numeric") {
|
|
56
|
+
const o = e.getType(t);
|
|
57
|
+
throw new D({
|
|
58
|
+
code: "INCOMPATIBLE_TYPE",
|
|
59
|
+
message: `'funnel' stat requires a numeric 'y' (got '${o}' for variable '${t}')`,
|
|
60
|
+
context: {
|
|
61
|
+
aesthetic: "y",
|
|
62
|
+
variableName: t,
|
|
63
|
+
variableType: o
|
|
64
|
+
},
|
|
65
|
+
suggestion: "Map each stage's count to 'y', or its shares to 'percentOfFirst' and 'percentOfPrevious' under stat 'identity'."
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const n = v(r.group), a = me(e, n !== null && e.hasVariable(n) ? [n] : []), i = v(r.x);
|
|
69
|
+
return i !== null && e.hasVariable(i) && ve(e, i, a), {
|
|
70
|
+
data: ge(e, t, a),
|
|
71
|
+
mapping: Y
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}, ge = (e, r, t) => {
|
|
75
|
+
const n = e.getValues(r, { type: "numeric" }), a = fe(n, t), i = { type: "percentage" };
|
|
76
|
+
return e.addVariable(g.percentOfFirst, "numeric", a.map((o) => o.percentOfFirst), i).addVariable(g.percentOfPrevious, "numeric", a.map((o) => o.percentOfPrevious), i);
|
|
77
|
+
}, fe = (e, r) => {
|
|
78
|
+
const t = e.map(() => H);
|
|
79
|
+
for (const n of X(r).values()) {
|
|
80
|
+
const a = he(n.map((i) => e[i] ?? null));
|
|
81
|
+
n.forEach((i, o) => {
|
|
82
|
+
t[i] = a[o] ?? H;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return t;
|
|
86
|
+
}, ve = (e, r, t) => {
|
|
87
|
+
const n = e.getValues(r);
|
|
88
|
+
for (const a of X(t).values()) {
|
|
89
|
+
const i = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const o of a) {
|
|
91
|
+
const s = String(n[o]);
|
|
92
|
+
if (i.has(s)) throw new D({
|
|
93
|
+
code: "INVALID_DATA_SHAPE",
|
|
94
|
+
message: `funnel stage '${s}' appears more than once in one funnel (variable '${r}')`,
|
|
95
|
+
context: {
|
|
96
|
+
aesthetic: "x",
|
|
97
|
+
variableName: r
|
|
98
|
+
},
|
|
99
|
+
suggestion: "Aggregate to one row per stage, or map 'group' to the variable that tells the funnels apart."
|
|
100
|
+
});
|
|
101
|
+
i.add(s);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, X = (e) => {
|
|
105
|
+
const r = /* @__PURE__ */ new Map();
|
|
106
|
+
return e.forEach((t, n) => {
|
|
107
|
+
const a = r.get(t);
|
|
108
|
+
a === void 0 ? r.set(t, [n]) : a.push(n);
|
|
109
|
+
}), r;
|
|
110
|
+
}, me = (e, r) => {
|
|
111
|
+
const t = r.map((n) => e.getValues(n));
|
|
112
|
+
return Array.from({ length: e.size() }, (n, a) => JSON.stringify(t.map((i) => i[a])));
|
|
113
|
+
}, be = 5e-4, k = (e, r) => {
|
|
114
|
+
const t = (e.low + e.high) / 2, n = Math.max(e.high - e.low, be) / 2, a = E(r, {
|
|
115
|
+
main: e.bandStart,
|
|
116
|
+
cross: t - n
|
|
117
|
+
}), i = E(r, {
|
|
118
|
+
main: e.bandEnd,
|
|
119
|
+
cross: t + n
|
|
120
|
+
}), [o, s] = W(M(a.x), M(i.x)), [l, c] = W(V(a.y), V(i.y));
|
|
121
|
+
return {
|
|
122
|
+
x: o,
|
|
123
|
+
y: l,
|
|
124
|
+
width: s - o,
|
|
125
|
+
height: c - l
|
|
126
|
+
};
|
|
127
|
+
}, W = (e, r) => e <= r ? [e, r] : [r, e], ye = (e) => {
|
|
128
|
+
const r = [];
|
|
129
|
+
if (!e.layer.dataLabels.showDataLabels) return {
|
|
130
|
+
labels: r,
|
|
131
|
+
droppedCount: 0
|
|
132
|
+
};
|
|
133
|
+
const { position: t, justify: n, align: a, offset: i } = e.layer.dataLabels, o = h(e.coordSystem), s = re(e.panelRect), { width: l, height: c } = e.panelRect;
|
|
134
|
+
for (const u of m(e.layer.data, o)) {
|
|
135
|
+
const O = e.formatLabel(u.observation);
|
|
136
|
+
if (O === "") continue;
|
|
137
|
+
const b = k(u, o), j = {
|
|
138
|
+
x: b.x * l,
|
|
139
|
+
y: b.y * c,
|
|
140
|
+
width: b.width * l,
|
|
141
|
+
height: b.height * c
|
|
142
|
+
}, P = (T, q, Z) => {
|
|
143
|
+
const y = e.measureDataLabel("observation", T, O);
|
|
144
|
+
return {
|
|
145
|
+
...le({
|
|
146
|
+
box: j,
|
|
147
|
+
panel: s,
|
|
148
|
+
orientation: we(o),
|
|
149
|
+
placement: T,
|
|
150
|
+
justify: q,
|
|
151
|
+
align: Z,
|
|
152
|
+
offset: i,
|
|
153
|
+
labelWidth: y.width,
|
|
154
|
+
labelHeight: y.height
|
|
155
|
+
}),
|
|
156
|
+
width: y.width,
|
|
157
|
+
height: y.height
|
|
158
|
+
};
|
|
159
|
+
}, A = t === "auto" ? P("inside", "center", "center") : null, f = A === null ? P(t === "auto" ? "outside" : t, n, a) : A.position === "inside" ? A : P("outside", "end", "center");
|
|
160
|
+
r.push({
|
|
161
|
+
layerId: e.layer.id,
|
|
162
|
+
x: f.x,
|
|
163
|
+
y: f.y,
|
|
164
|
+
text: O,
|
|
165
|
+
width: f.width,
|
|
166
|
+
height: f.height,
|
|
167
|
+
isRotated: !1,
|
|
168
|
+
role: "observation",
|
|
169
|
+
position: f.position
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
labels: r,
|
|
174
|
+
droppedCount: 0
|
|
175
|
+
};
|
|
176
|
+
}, we = (e) => e === "x" ? {
|
|
177
|
+
valueAxis: "y",
|
|
178
|
+
valueDirectionSign: -1
|
|
179
|
+
} : {
|
|
180
|
+
valueAxis: "x",
|
|
181
|
+
valueDirectionSign: 1
|
|
182
|
+
}, Se = 0.25, xe = { strokeWidth: "pixels" }, Oe = {
|
|
183
|
+
fill: "color",
|
|
184
|
+
fillAlpha: "unitInterval",
|
|
185
|
+
stroke: "color",
|
|
186
|
+
strokeWidth: "pixels"
|
|
187
|
+
}, Pe = class extends z {
|
|
188
|
+
constructor(...e) {
|
|
189
|
+
super(...e), this.type = "funnel", this.defaultParams = {}, this.defaultStat = new pe(), this.positionRoles = [
|
|
190
|
+
{
|
|
191
|
+
axis: "x",
|
|
192
|
+
role: "point",
|
|
193
|
+
valueKind: "value"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
axis: "y",
|
|
197
|
+
role: "min",
|
|
198
|
+
valueKind: "value",
|
|
199
|
+
aes: "y"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
axis: "y",
|
|
203
|
+
role: "max",
|
|
204
|
+
valueKind: "value",
|
|
205
|
+
aes: "y"
|
|
206
|
+
}
|
|
207
|
+
], this.aesthetics = [
|
|
208
|
+
{
|
|
209
|
+
kind: "visual",
|
|
210
|
+
name: "color"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
kind: "data",
|
|
214
|
+
name: "percentOfFirst"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
kind: "data",
|
|
218
|
+
name: "percentOfPrevious"
|
|
219
|
+
}
|
|
220
|
+
], this.derivedVariables = [g.percentOfFirst, g.percentOfPrevious], this.supportedPositions = ["identity"], this.supportedCoordTypes = ["cartesian", "flip"], this.scaleConstraints = { discreteMainAxis: !0 }, this.grid = {
|
|
221
|
+
cartesian: {
|
|
222
|
+
hideGridX: !0,
|
|
223
|
+
hideGridY: !0
|
|
224
|
+
},
|
|
225
|
+
flip: {
|
|
226
|
+
hideGridX: !0,
|
|
227
|
+
hideGridY: !0
|
|
228
|
+
}
|
|
229
|
+
}, this.legend = { suppressWhenSingleItem: !0 }, this.dataLabelCoordTypes = ["cartesian", "flip"], this.getDataLabelPlacements = ye, this.spatialKind = "rects", this.highlightStrategy = "observation-rerender", this.tooltip = [
|
|
230
|
+
{ aes: "y" },
|
|
231
|
+
{
|
|
232
|
+
key: "% of first",
|
|
233
|
+
aes: "percentOfFirst"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
key: "% of previous",
|
|
237
|
+
aes: "percentOfPrevious"
|
|
238
|
+
}
|
|
239
|
+
], this.styleTarget = ie({
|
|
240
|
+
observation: {
|
|
241
|
+
vocabulary: xe,
|
|
242
|
+
aesthetics: {
|
|
243
|
+
fill: "color",
|
|
244
|
+
alpha: "alpha"
|
|
245
|
+
},
|
|
246
|
+
rest: {
|
|
247
|
+
fill: F("geom"),
|
|
248
|
+
fillAlpha: 1,
|
|
249
|
+
strokeWidth: 0
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
parts: { connector: {
|
|
253
|
+
vocabulary: Oe,
|
|
254
|
+
aesthetics: { fill: "color" },
|
|
255
|
+
rest: {
|
|
256
|
+
fill: F("geom"),
|
|
257
|
+
fillAlpha: Se,
|
|
258
|
+
stroke: F("geom"),
|
|
259
|
+
strokeWidth: 0
|
|
260
|
+
}
|
|
261
|
+
} }
|
|
262
|
+
}), this.resolveBandFraction = (r) => G(r), this.validateMapping = ({ mapping: r, computedVariables: t }) => t.has("percentOfFirst") ? [...U].filter((n) => r[n] !== void 0).map((n) => ({
|
|
263
|
+
code: "CONFLICTING_STAT_MAPPING",
|
|
264
|
+
message: `funnel stat computes '${n}' from 'y'; remove its mapping, or set the layer's stat to 'identity'`
|
|
265
|
+
})) : [], this.resolveAnchorPosition = (r, { coordSystem: t }) => {
|
|
266
|
+
const n = h(t), a = K(r, n);
|
|
267
|
+
return a === null ? null : n === "x" ? {
|
|
268
|
+
x: a.band,
|
|
269
|
+
y: a.high
|
|
270
|
+
} : {
|
|
271
|
+
x: a.high,
|
|
272
|
+
y: a.band
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
resolveParams(e) {
|
|
277
|
+
const { width: r, ...t } = super.resolveParams(e);
|
|
278
|
+
return {
|
|
279
|
+
...t,
|
|
280
|
+
...Ae(r, e.diagnostics)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
compile({ data: e, mapping: r, params: t }) {
|
|
284
|
+
const n = v(r.y);
|
|
285
|
+
if (n === null || !e.hasVariable(n)) return {
|
|
286
|
+
data: e,
|
|
287
|
+
mapping: {}
|
|
288
|
+
};
|
|
289
|
+
const a = G(t) / 2;
|
|
290
|
+
return {
|
|
291
|
+
data: Fe(e, n).addConstantVariable(S.xMin, "numeric", -a).addConstantVariable(S.xMax, "numeric", a),
|
|
292
|
+
mapping: {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}, Ae = (e, r) => {
|
|
296
|
+
if (e === void 0) return {};
|
|
297
|
+
const t = typeof e == "number" && Number.isFinite(e) ? e : null;
|
|
298
|
+
if (t !== null && t > 0 && t <= 1) return { width: t };
|
|
299
|
+
const n = t !== null && t > 1 ? 1 : void 0;
|
|
300
|
+
return r?.addWarning({
|
|
301
|
+
code: "INVALID_GEOM_PARAM",
|
|
302
|
+
message: `Funnel width must be a fraction in (0, 1], got ${String(e)}; falling back to ${n ?? "the default band"}.`,
|
|
303
|
+
context: {
|
|
304
|
+
geom: "funnel",
|
|
305
|
+
param: "width",
|
|
306
|
+
expected: "0 < width <= 1",
|
|
307
|
+
actual: t ?? String(e)
|
|
308
|
+
},
|
|
309
|
+
suggestion: "Set params.width to a fraction of the stage band in (0, 1]."
|
|
310
|
+
}), n === void 0 ? {} : { width: n };
|
|
311
|
+
}, Fe = (e, r) => {
|
|
312
|
+
const t = e.getValues(r, { type: "numeric" }).map(R);
|
|
313
|
+
return e.addVariable(S.yMin, "numeric", t.map((n) => n === null ? null : -n / 2)).addVariable(S.yMax, "numeric", t.map((n) => n === null ? null : n / 2));
|
|
314
|
+
}, Ee = ({ layer: e, coordSystem: r, panelRect: t }) => {
|
|
315
|
+
const n = h(r);
|
|
316
|
+
return m(e.data, n).map((a) => {
|
|
317
|
+
const i = k(a, n), o = i.x * t.width;
|
|
318
|
+
return {
|
|
319
|
+
kind: "region",
|
|
320
|
+
pathData: `M${o},${i.y * t.height}H${(i.x + i.width) * t.width}V${(i.y + i.height) * t.height}H${o}Z`
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}, C = (e) => {
|
|
324
|
+
const r = ne(e.data, e.mapping, e.id);
|
|
325
|
+
return (t) => r(t.observation);
|
|
326
|
+
}, Me = (e, r, t, n = null) => Ve(e, n).flatMap((a) => {
|
|
327
|
+
const i = ke(a);
|
|
328
|
+
return i.slice(1).flatMap((o, s) => {
|
|
329
|
+
const l = i[s];
|
|
330
|
+
return l === void 0 ? [] : [Ce(l, o, r, t)];
|
|
331
|
+
});
|
|
332
|
+
}), Ve = (e, r) => {
|
|
333
|
+
if (r === null) return [[...e]];
|
|
334
|
+
const t = /* @__PURE__ */ new Map();
|
|
335
|
+
for (const n of e) {
|
|
336
|
+
const a = n.observation[r], i = t.get(a);
|
|
337
|
+
i === void 0 ? t.set(a, [n]) : i.push(n);
|
|
338
|
+
}
|
|
339
|
+
return [...t.values()];
|
|
340
|
+
}, ke = (e) => {
|
|
341
|
+
const r = e[0], t = e[e.length - 1], n = r !== void 0 && t !== void 0 && r.band > t.band ? -1 : 1;
|
|
342
|
+
return [...e].sort((a, i) => (a.band - i.band) * n);
|
|
343
|
+
}, Ce = (e, r, t, n) => {
|
|
344
|
+
const a = r.band >= e.band, i = a ? e.bandEnd : e.bandStart, o = a ? r.bandStart : r.bandEnd, s = (l, c) => {
|
|
345
|
+
const u = E(t, {
|
|
346
|
+
main: l,
|
|
347
|
+
cross: c
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
x: M(u.x) * n.width,
|
|
351
|
+
y: V(u.y) * n.height
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
from: e,
|
|
356
|
+
to: r,
|
|
357
|
+
corners: [
|
|
358
|
+
s(i, e.low),
|
|
359
|
+
s(i, e.high),
|
|
360
|
+
s(o, r.high),
|
|
361
|
+
s(o, r.low)
|
|
362
|
+
]
|
|
363
|
+
};
|
|
364
|
+
}, Ne = ({ corners: e }) => `${e.map((r, t) => `${t === 0 ? "M" : "L"}${r.x},${r.y}`).join("")}Z`, N = ({ stages: e, connectors: r = [], connectorSource: t, mainAxis: n, styleReaders: a, getKey: i, isHovered: o = !1, shouldAnimateTransitions: s = !1 }) => {
|
|
365
|
+
const l = o ? "hovered" : void 0, c = a.part("connector");
|
|
366
|
+
return /* @__PURE__ */ ce(ue, { children: [c && t !== void 0 && r.map((u) => /* @__PURE__ */ d(Te, {
|
|
367
|
+
connector: u,
|
|
368
|
+
source: t,
|
|
369
|
+
readers: c,
|
|
370
|
+
shouldAnimateTransitions: s
|
|
371
|
+
}, `${i(u.from)}-connector`)), e.map((u) => /* @__PURE__ */ d(Ie, {
|
|
372
|
+
stage: u,
|
|
373
|
+
mainAxis: n,
|
|
374
|
+
styleReaders: a,
|
|
375
|
+
state: l,
|
|
376
|
+
shouldAnimateTransitions: s
|
|
377
|
+
}, i(u)))] });
|
|
378
|
+
}, Te = ({ connector: e, source: r, readers: t, shouldAnimateTransitions: n }) => {
|
|
379
|
+
const { observation: a } = e.from, i = t.get("fill", a), o = t.get("stroke", a);
|
|
380
|
+
return /* @__PURE__ */ d(J, {
|
|
381
|
+
"data-testid": "funnel-connector",
|
|
382
|
+
d: Ne(e),
|
|
383
|
+
source: r,
|
|
384
|
+
fill: i === void 0 ? void 0 : x(i),
|
|
385
|
+
fillOpacity: t.get("fillAlpha", a),
|
|
386
|
+
stroke: o === void 0 ? void 0 : x(o),
|
|
387
|
+
strokeWidth: t.get("strokeWidth", a),
|
|
388
|
+
shouldAnimateTransitions: n
|
|
389
|
+
});
|
|
390
|
+
}, Ie = ({ stage: e, mainAxis: r, styleReaders: t, state: n, shouldAnimateTransitions: a }) => {
|
|
391
|
+
const i = k(e, r), o = t.get("fill", e.observation, n), s = t.get("stroke", e.observation, n);
|
|
392
|
+
return /* @__PURE__ */ d(Q, {
|
|
393
|
+
"data-testid": "funnel-bar",
|
|
394
|
+
x: w(i.x),
|
|
395
|
+
y: w(i.y),
|
|
396
|
+
width: w(i.width),
|
|
397
|
+
height: w(i.height),
|
|
398
|
+
fill: o === void 0 ? void 0 : x(o),
|
|
399
|
+
opacity: t.get("alpha", e.observation, n),
|
|
400
|
+
fillOpacity: t.get("fillAlpha", e.observation, n),
|
|
401
|
+
stroke: s === void 0 ? void 0 : x(s),
|
|
402
|
+
strokeWidth: t.get("strokeWidth", e.observation, n),
|
|
403
|
+
shouldAnimateTransitions: a
|
|
404
|
+
});
|
|
405
|
+
}, Le = ({ layer: e, sourceLayer: r, mainAxis: t, styleReaders: n }) => /* @__PURE__ */ d(N, {
|
|
406
|
+
stages: p(() => m(e.data, t), [e.data, t]),
|
|
407
|
+
mainAxis: t,
|
|
408
|
+
styleReaders: n,
|
|
409
|
+
getKey: p(() => C(r), [r])
|
|
410
|
+
}), Be = ({ layer: e, primary: r, siblings: t, mainAxis: n, styleReaders: a }) => /* @__PURE__ */ d("g", {
|
|
411
|
+
"data-testid": "hovered-stages",
|
|
412
|
+
children: /* @__PURE__ */ d(N, {
|
|
413
|
+
stages: m([r, ...t].map((i) => i.observation), n),
|
|
414
|
+
mainAxis: n,
|
|
415
|
+
styleReaders: a,
|
|
416
|
+
getKey: p(() => C(e), [e]),
|
|
417
|
+
isHovered: !0
|
|
418
|
+
})
|
|
419
|
+
}), _e = ({ layer: e, mainAxis: r, panelWidth: t, panelHeight: n, styleReaders: a, shouldAnimateTransitions: i, intro: o }) => {
|
|
420
|
+
const s = p(() => m(e.data, r), [e.data, r]), l = v(e.mapping.group), c = /* @__PURE__ */ d(N, {
|
|
421
|
+
stages: s,
|
|
422
|
+
connectors: p(() => Me(s, r, {
|
|
423
|
+
width: t,
|
|
424
|
+
height: n
|
|
425
|
+
}, l), [
|
|
426
|
+
s,
|
|
427
|
+
r,
|
|
428
|
+
t,
|
|
429
|
+
n,
|
|
430
|
+
l
|
|
431
|
+
]),
|
|
432
|
+
connectorSource: e,
|
|
433
|
+
mainAxis: r,
|
|
434
|
+
styleReaders: a,
|
|
435
|
+
getKey: p(() => C(e), [e]),
|
|
436
|
+
shouldAnimateTransitions: i
|
|
437
|
+
}), u = Ge(o);
|
|
438
|
+
return u === null ? c : /* @__PURE__ */ d(ee, {
|
|
439
|
+
intro: u,
|
|
440
|
+
children: c
|
|
441
|
+
});
|
|
442
|
+
}, Ge = (e) => e == null ? null : {
|
|
443
|
+
type: "fade",
|
|
444
|
+
layerId: e.layerId,
|
|
445
|
+
durationSeconds: e.durationSeconds
|
|
446
|
+
}, De = ae(new Pe(), {
|
|
447
|
+
coord: "cartesian",
|
|
448
|
+
swatchShape: "square",
|
|
449
|
+
guideMode: "band",
|
|
450
|
+
render: ({ layer: e, coordSystem: r, panelRect: t, styleReaders: n, shouldAnimateTransitions: a, intro: i }) => /* @__PURE__ */ d(_e, {
|
|
451
|
+
layer: e,
|
|
452
|
+
mainAxis: h(r),
|
|
453
|
+
panelWidth: t.width,
|
|
454
|
+
panelHeight: t.height,
|
|
455
|
+
styleReaders: n,
|
|
456
|
+
shouldAnimateTransitions: a,
|
|
457
|
+
intro: i
|
|
458
|
+
}),
|
|
459
|
+
renderHighlight: ({ layer: e, sourceLayer: r, coordSystem: t, styleReaders: n }) => /* @__PURE__ */ d(Le, {
|
|
460
|
+
layer: e,
|
|
461
|
+
sourceLayer: r,
|
|
462
|
+
mainAxis: h(t),
|
|
463
|
+
styleReaders: n
|
|
464
|
+
}),
|
|
465
|
+
renderHover: ({ layer: e, primary: r, group: t, related: n, coordSystem: a, styleReaders: i }) => /* @__PURE__ */ d(Be, {
|
|
466
|
+
layer: e,
|
|
467
|
+
primary: r,
|
|
468
|
+
siblings: [...t, ...n],
|
|
469
|
+
mainAxis: h(a),
|
|
470
|
+
styleReaders: i
|
|
471
|
+
}),
|
|
472
|
+
renderHoverCompanions: () => null,
|
|
473
|
+
getEditOutlineShapes: Ee
|
|
474
|
+
});
|
|
475
|
+
export {
|
|
476
|
+
Pe as FunnelGeom,
|
|
477
|
+
De as funnel
|
|
478
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { EditOutlineShape, GeomEditOutlineShapesRendererInput } from '@graphysdk/react';
|
|
2
|
+
/** What point and edit outlines round the stages they take, each as its bar. A connector belongs to no one stage. */
|
|
3
|
+
export declare const getFunnelEditOutlineShapes: ({ layer, coordSystem, panelRect, }: GeomEditOutlineShapesRendererInput) => EditOutlineShape[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GeomStyleReaders, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface HighlightedStagesProps {
|
|
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 HighlightedStages: ({ layer, sourceLayer, mainAxis, styleReaders }: HighlightedStagesProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GeomStyleReaders, HoverHit, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface HoveredStagesProps {
|
|
3
|
+
layer: SceneLayer;
|
|
4
|
+
primary: HoverHit;
|
|
5
|
+
/** Any other bar of the hovered stage, from duplicate rows. */
|
|
6
|
+
siblings: readonly HoverHit[];
|
|
7
|
+
mainAxis: MainAxis;
|
|
8
|
+
styleReaders: GeomStyleReaders;
|
|
9
|
+
}
|
|
10
|
+
/** The hovered stage's bar, repainted above the dimmed layer. */
|
|
11
|
+
export declare const HoveredStages: ({ layer, primary, siblings, mainAxis, styleReaders }: HoveredStagesProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GeomStyleReaders, LayerIntroPlan, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
interface FunnelLayerProps {
|
|
3
|
+
layer: SceneLayer;
|
|
4
|
+
mainAxis: MainAxis;
|
|
5
|
+
/** The panel's pixel size, which the connectors are drawn in. */
|
|
6
|
+
panelWidth: number;
|
|
7
|
+
panelHeight: number;
|
|
8
|
+
styleReaders: GeomStyleReaders;
|
|
9
|
+
shouldAnimateTransitions: boolean;
|
|
10
|
+
intro: LayerIntroPlan | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The layer's own paint, connectors included. The whole layer fades up on one clock, whatever entrance the engine
|
|
14
|
+
* plans: a bar is a rect to the engine, which plans a bar's grow, but a funnel's bars are centred, with no baseline
|
|
15
|
+
* to grow from. The fade is independent of `shouldAnimateTransitions`: it rides the group rather than moving the
|
|
16
|
+
* marks, so it plays even when data changes snap.
|
|
17
|
+
*/
|
|
18
|
+
export declare const FunnelLayer: ({ layer, mainAxis, panelWidth, panelHeight, styleReaders, shouldAnimateTransitions, intro, }: FunnelLayerProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GeomStyleReaders, MainAxis, SceneLayer } from '@graphysdk/react';
|
|
2
|
+
import { Stage } from '../utils/read-funnel';
|
|
3
|
+
import { Connector } from './utils/connector-geometry';
|
|
4
|
+
/** What one pass paints: its connectors below, its bars above them. */
|
|
5
|
+
export interface FunnelPaintProps {
|
|
6
|
+
stages: readonly Stage[];
|
|
7
|
+
/**
|
|
8
|
+
* The bands between bars. Only the layer's own paint draws them: a hover or highlight repaint raises bars, and
|
|
9
|
+
* a connector belongs to no one stage.
|
|
10
|
+
*/
|
|
11
|
+
connectors?: readonly Connector[];
|
|
12
|
+
/** The layer the connectors are drawn from, so a resize redraws them where the bars already are. */
|
|
13
|
+
connectorSource?: SceneLayer;
|
|
14
|
+
mainAxis: MainAxis;
|
|
15
|
+
styleReaders: GeomStyleReaders;
|
|
16
|
+
/** What identifies each bar across a data change, so a spring moves a mark rather than swapping it. */
|
|
17
|
+
getKey: (stage: Stage) => string;
|
|
18
|
+
/** Paints in the `hovered` state. */
|
|
19
|
+
isHovered?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether bars and connectors spring to their new shapes when the data changes. The hover and highlight
|
|
22
|
+
* repaints mount and unmount with the interaction, so only the layer's own paint animates.
|
|
23
|
+
*/
|
|
24
|
+
shouldAnimateTransitions?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare const FunnelPaint: ({ stages, connectors, connectorSource, mainAxis, styleReaders, getKey, isHovered, shouldAnimateTransitions, }: FunnelPaintProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BoundGeomRendererDefinition } from '@graphysdk/react';
|
|
2
|
+
import { FunnelGeom } from '../engine/funnel.geom';
|
|
3
|
+
/**
|
|
4
|
+
* The funnel plugin: pass it to `createGraphyKit({ plugins: [funnel] })` for `kit.geom.funnel()`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const kit = createGraphyKit({ plugins: [funnel] });
|
|
8
|
+
* const spec = kit.pipe(
|
|
9
|
+
* kit.createSpec({ x: 'stage', y: 'visitors' }),
|
|
10
|
+
* kit.geom.funnel({ dataLabels: { showDataLabels: true } }),
|
|
11
|
+
* kit.scale.x.discrete(),
|
|
12
|
+
* kit.scale.y.continuous(),
|
|
13
|
+
* kit.coord.flip(),
|
|
14
|
+
* kit.config({ axes: { y: { isVisible: false } } })
|
|
15
|
+
* );
|
|
16
|
+
*/
|
|
17
|
+
export declare const funnel: BoundGeomRendererDefinition<FunnelGeom>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MainAxis } from '@graphysdk/react';
|
|
2
|
+
import { Stage } from '../../utils/read-funnel';
|
|
3
|
+
/** A point in the panel's local pixels, y growing downward. */
|
|
4
|
+
export interface PanelPoint {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
/** The band joining one stage's bar to the next, and the two stages it joins. */
|
|
9
|
+
export interface Connector {
|
|
10
|
+
from: Stage;
|
|
11
|
+
to: Stage;
|
|
12
|
+
/** Its four corners, round the shape: the first bar's two ends, then the second's, far end first. */
|
|
13
|
+
corners: readonly [PanelPoint, PanelPoint, PanelPoint, PanelPoint];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One connector per pair of stages next to each other on the stage axis, within each `group`: from the first
|
|
17
|
+
* bar's edge facing the second to the second's edge facing the first, narrowing from the one's extent to the
|
|
18
|
+
* other's, so it runs across the gap whichever way the stage axis runs. The funnel runs from its first stage in
|
|
19
|
+
* the data towards its last, so a connector leaves the stage on that side. In pixels, since a path has no
|
|
20
|
+
* percentage units.
|
|
21
|
+
*/
|
|
22
|
+
export declare const readConnectors: (stages: readonly Stage[], mainAxis: MainAxis, panelSize: {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}, groupVariable?: string | null) => Connector[];
|
|
26
|
+
/** The connector's outline as SVG path data. Always four corners, so a change of shape morphs point for point. */
|
|
27
|
+
export declare const toConnectorPath: ({ corners }: Connector) => string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SceneLayer } from '@graphysdk/react';
|
|
2
|
+
import { Stage } from '../../utils/read-funnel';
|
|
3
|
+
/**
|
|
4
|
+
* A bar keys on its stage, read from the raw value rather than the band: the band moves with the domain, the
|
|
5
|
+
* very change a mark should spring through, not remount on.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createFunnelKeys: (layer: SceneLayer) => ((stage: Stage) => string);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The columns `compile()` writes beside the position columns. Authors name them in a `highlight`, an entry's
|
|
3
|
+
* `where` or a label mapping, so they are part of the geom's API.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FUNNEL_COLUMNS: {
|
|
6
|
+
/** A stage's value as a share of the first stage's, which is what a funnel's conversion reads from. */
|
|
7
|
+
readonly percentOfFirst: "funnelPercentOfFirst";
|
|
8
|
+
/** A stage's value as a share of the stage before it: the step's own conversion. */
|
|
9
|
+
readonly percentOfPrevious: "funnelPercentOfPrevious";
|
|
10
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Funnel parameters: geometry the compiler places. */
|
|
2
|
+
export interface FunnelGeomParams {
|
|
3
|
+
/** Bar width as a fraction of the band the discrete scale allocates to the stage, in `(0, 1]`. */
|
|
4
|
+
width?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const readBarWidth: ({ width }: FunnelGeomParams) => number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MainAxis, Observation } from '@graphysdk/react';
|
|
2
|
+
/** A stage's bar, placed by its band on the main axis and its centred extent on the other, as `[0, 1]` fractions. */
|
|
3
|
+
export interface Stage {
|
|
4
|
+
observation: Observation;
|
|
5
|
+
/** The centre of the bar's band. */
|
|
6
|
+
band: number;
|
|
7
|
+
/** The bar's two edges on the main axis. */
|
|
8
|
+
bandStart: number;
|
|
9
|
+
bandEnd: number;
|
|
10
|
+
/** The bar's two ends on the value axis, lower first, either side of the funnel's centre line. */
|
|
11
|
+
low: number;
|
|
12
|
+
high: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads where a stage's bar sits. The flip is already baked into the position columns, so under `coord.flip()`
|
|
16
|
+
* the band is `y`/`yMin`/`yMax` and the bar runs `xMin` to `xMax`. `null` for a stage with no value, which
|
|
17
|
+
* draws nothing.
|
|
18
|
+
*/
|
|
19
|
+
export declare const readStage: (observation: Observation, mainAxis: MainAxis) => Stage | null;
|
|
20
|
+
/** Reads every stage of a layer in its data order, which is the order the funnel runs in. */
|
|
21
|
+
export declare const readStages: (observations: Iterable<Observation>, mainAxis: MainAxis) => Stage[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MainAxis } from '@graphysdk/react';
|
|
2
|
+
import { Stage } from './read-funnel';
|
|
3
|
+
/** A box in the `[0, 1]` view box, from its top left, y growing downward. */
|
|
4
|
+
export interface ViewBoxRect {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
/** A stage's bar across its share of the band, centred on the funnel's centre line. */
|
|
11
|
+
export declare const readStageBox: (stage: Stage, mainAxis: MainAxis) => ViewBoxRect;
|