@object-ui/core 17.5.0 → 17.6.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/CHANGELOG.md +794 -0
- package/README.md +36 -22
- package/dist/actions/ActionEngine.d.ts +1 -1
- package/dist/actions/ActionEngine.js +1 -2
- package/dist/actions/ActionRunner.d.ts +82 -10
- package/dist/actions/ActionRunner.js +128 -22
- package/dist/actions/actionKeys.d.ts +68 -8
- package/dist/actions/actionKeys.js +81 -9
- package/dist/actions/actionResponse.js +1 -1
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordIdParam.d.ts +77 -0
- package/dist/actions/recordIdParam.js +37 -0
- package/dist/actions/serverActionHandler.d.ts +1 -1
- package/dist/actions/serverActionHandler.js +1 -1
- package/dist/evaluator/ExpressionEvaluator.d.ts +42 -1
- package/dist/evaluator/ExpressionEvaluator.js +42 -1
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.js +1 -1
- package/dist/evaluator/fieldRules.d.ts +19 -0
- package/dist/evaluator/fieldRules.js +30 -6
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +49 -2
- package/dist/evaluator/listConditional.js +70 -6
- package/dist/evaluator/optionRules.d.ts +55 -0
- package/dist/evaluator/optionRules.js +60 -0
- package/dist/evaluator/rowPredicateCanon.d.ts +67 -0
- package/dist/evaluator/rowPredicateCanon.js +208 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +17 -0
- package/dist/registry/Registry.d.ts +41 -19
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.ts +92 -13
- package/dist/utils/chart-presentation.d.ts +205 -0
- package/dist/utils/chart-presentation.js +280 -0
- package/dist/utils/chart-series.d.ts +140 -26
- package/dist/utils/chart-series.js +380 -39
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- package/dist/utils/dataset-format.js +18 -6
- package/dist/utils/debug.js +7 -0
- package/dist/utils/expand-fields.d.ts +97 -7
- package/dist/utils/expand-fields.js +97 -7
- package/dist/utils/filter-tokens.js +0 -1
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +27 -2
- package/dist/utils/normalize-list-view.d.ts +10 -0
- package/dist/utils/normalize-list-view.js +63 -1
- package/dist/utils/number-display.d.ts +7 -3
- package/dist/utils/predicate-fields.d.ts +29 -4
- package/dist/utils/predicate-fields.js +44 -4
- package/dist/utils/retired-field-types.d.ts +90 -0
- package/dist/utils/retired-field-types.js +124 -0
- package/dist/utils/sort-query.d.ts +12 -10
- package/dist/utils/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -0
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/schema-validator.js +105 -0
- package/dist/validation/server-owned-value.d.ts +62 -0
- package/dist/validation/server-owned-value.js +99 -0
- package/dist/validation/validators/object-validation-engine.js +0 -2
- package/package.json +4 -4
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
9
|
+
/**
|
|
10
|
+
* An i18n label is a plain string or a `{ en, zh-CN, … }` record; charts render
|
|
11
|
+
* a string. Same pick `normalizeChartSchema` makes, so a label reads the same
|
|
12
|
+
* on both paths.
|
|
13
|
+
*
|
|
14
|
+
* **First-string-wins, deliberately, and deliberately NOT locale-aware** — this
|
|
15
|
+
* package is React-free and holds no i18n provider, so it cannot know which
|
|
16
|
+
* limb a console wants. A caller that CAN resolve the language (the report
|
|
17
|
+
* renderer, via `pickLocalized`) resolves the label itself and overrides the
|
|
18
|
+
* merged one; see objectui#4020, whose whole point is that picking the first
|
|
19
|
+
* limb paints English on a zh console.
|
|
20
|
+
*/
|
|
21
|
+
function labelText(v) {
|
|
22
|
+
if (typeof v === 'string' && v)
|
|
23
|
+
return v;
|
|
24
|
+
if (isRecord(v)) {
|
|
25
|
+
const first = Object.values(v).find((x) => typeof x === 'string' && x);
|
|
26
|
+
return first;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One authored `ChartSeries`, minus its `name` — i.e. everything about it that
|
|
32
|
+
* is presentation rather than membership.
|
|
33
|
+
*
|
|
34
|
+
* `type` is narrowed to the three families that COMPOSE on one cartesian plot,
|
|
35
|
+
* because this array reaches the renderer already speaking the internal shape
|
|
36
|
+
* (`ChartRenderer` forwards a `dataKey`-shaped array untouched, so
|
|
37
|
+
* `normalizeChartSchema`'s own identical narrowing never sees it). Without the
|
|
38
|
+
* narrowing a `type: 'pie'` would not merely be inert — it would count as a
|
|
39
|
+
* family disagreement in `effectiveChartFamily`, flip the whole chart into a
|
|
40
|
+
* combo, and then draw that series as a bar anyway.
|
|
41
|
+
*/
|
|
42
|
+
export function seriesPresentation(raw) {
|
|
43
|
+
const out = {};
|
|
44
|
+
const family = raw.type;
|
|
45
|
+
if (family === 'bar' || family === 'line' || family === 'area')
|
|
46
|
+
out.chartType = family;
|
|
47
|
+
if (raw.yAxis === 'left' || raw.yAxis === 'right')
|
|
48
|
+
out.yAxis = raw.yAxis;
|
|
49
|
+
const label = labelText(raw.label);
|
|
50
|
+
if (label)
|
|
51
|
+
out.label = label;
|
|
52
|
+
if (typeof raw.color === 'string' && raw.color)
|
|
53
|
+
out.color = raw.color;
|
|
54
|
+
if (typeof raw.stack === 'string' && raw.stack)
|
|
55
|
+
out.stack = raw.stack;
|
|
56
|
+
if (raw.variant === 'primary' || raw.variant === 'comparison')
|
|
57
|
+
out.variant = raw.variant;
|
|
58
|
+
if (typeof raw.dashArray === 'string' && raw.dashArray)
|
|
59
|
+
out.dashArray = raw.dashArray;
|
|
60
|
+
if (typeof raw.opacity === 'number' && Number.isFinite(raw.opacity))
|
|
61
|
+
out.opacity = raw.opacity;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One authored `ChartAxis`, minus its `field` — the axis's presentation.
|
|
66
|
+
*
|
|
67
|
+
* `field` is the one DATA key on an axis (it names the plotted column), and
|
|
68
|
+
* dropping it here is what keeps membership with the dataset **structurally**
|
|
69
|
+
* rather than by a guard: `normalizeChartSchema` synthesises series out of
|
|
70
|
+
* `yAxis[].field` when a chart declares no series, so a forwarded `field`
|
|
71
|
+
* would be a live membership channel on an empty selection. With it gone the
|
|
72
|
+
* axis carries scale and chrome only, and the count of entries — which is what
|
|
73
|
+
* turns on the secondary axis (`yAxes.length > 1`) — survives, including for
|
|
74
|
+
* an entry that declares nothing but its own existence.
|
|
75
|
+
*
|
|
76
|
+
* Keys the renderer does not read on a given axis are dropped by
|
|
77
|
+
* `normalizeChartSchema`, the ONE normalization layer (#2880 S1): today it
|
|
78
|
+
* keeps `format`/`title`/`showGridLines` on the x-axis and the full set on the
|
|
79
|
+
* y-axes. That narrowing is deliberately NOT mirrored here — a second copy
|
|
80
|
+
* would drift from the renderer's real capability the moment it grew.
|
|
81
|
+
*/
|
|
82
|
+
export function axisPresentation(raw) {
|
|
83
|
+
const out = {};
|
|
84
|
+
if (!isRecord(raw))
|
|
85
|
+
return out;
|
|
86
|
+
const title = labelText(raw.title);
|
|
87
|
+
if (title)
|
|
88
|
+
out.title = title;
|
|
89
|
+
if (typeof raw.format === 'string' && raw.format)
|
|
90
|
+
out.format = raw.format;
|
|
91
|
+
if (typeof raw.min === 'number' && Number.isFinite(raw.min))
|
|
92
|
+
out.min = raw.min;
|
|
93
|
+
if (typeof raw.max === 'number' && Number.isFinite(raw.max))
|
|
94
|
+
out.max = raw.max;
|
|
95
|
+
if (typeof raw.stepSize === 'number' && Number.isFinite(raw.stepSize) && raw.stepSize > 0) {
|
|
96
|
+
out.stepSize = raw.stepSize;
|
|
97
|
+
}
|
|
98
|
+
if (typeof raw.showGridLines === 'boolean')
|
|
99
|
+
out.showGridLines = raw.showGridLines;
|
|
100
|
+
if (raw.position === 'left' || raw.position === 'right' || raw.position === 'top' || raw.position === 'bottom') {
|
|
101
|
+
out.position = raw.position;
|
|
102
|
+
}
|
|
103
|
+
if (typeof raw.logarithmic === 'boolean')
|
|
104
|
+
out.logarithmic = raw.logarithmic;
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Merge an authored `series[]` array's PRESENTATION onto the bindings the
|
|
109
|
+
* dataset selection derived — the series half of #4229's split, and the whole
|
|
110
|
+
* of it for a surface whose axes are names rather than objects (see the file
|
|
111
|
+
* header).
|
|
112
|
+
*
|
|
113
|
+
* The match rule is **by name/key**: an authored `series[].name` is paired with
|
|
114
|
+
* the derived binding whose `dataKey` it equals, and the pairing decides
|
|
115
|
+
* nothing but presentation:
|
|
116
|
+
*
|
|
117
|
+
* - an authored entry naming a measure that is NOT in the dataset selection is
|
|
118
|
+
* **ignored** — membership belongs to the dataset, so an author cannot add,
|
|
119
|
+
* remove or re-point a series from the chart config;
|
|
120
|
+
* - a derived series with no authored entry keeps the family default, so every
|
|
121
|
+
* surface that never wrote `series` renders byte-for-byte as before;
|
|
122
|
+
* - where both exist the **explicit binding wins** (#2880 S2), which is the
|
|
123
|
+
* whole point: `type: 'line'` + `yAxis: 'right'` is how the spec says "this
|
|
124
|
+
* measure is a line on the secondary axis".
|
|
125
|
+
*
|
|
126
|
+
* Matching on `name` only is deliberate: `name` is the spec's authorable key
|
|
127
|
+
* for a series (`dataKey` is a declared ALIAS of it, resolved where the
|
|
128
|
+
* metadata is parsed), so reading a second spelling here would fossilize a
|
|
129
|
+
* dialect this renderer has no business accepting (AGENTS.md #0.1).
|
|
130
|
+
*
|
|
131
|
+
* The FIRST entry naming a measure wins; a later duplicate cannot silently
|
|
132
|
+
* reconfigure a series the author already described.
|
|
133
|
+
*
|
|
134
|
+
* @param derived the bindings {@link buildChartSeries} produced from the selection
|
|
135
|
+
* @param authoredSeries the authored `series` array (anything, incl. absent)
|
|
136
|
+
*/
|
|
137
|
+
export function mergeAuthoredSeries(derived, authoredSeries) {
|
|
138
|
+
const authored = new Map();
|
|
139
|
+
for (const entry of Array.isArray(authoredSeries) ? authoredSeries : []) {
|
|
140
|
+
if (!isRecord(entry))
|
|
141
|
+
continue;
|
|
142
|
+
const name = typeof entry.name === 'string' ? entry.name : undefined;
|
|
143
|
+
if (name && !authored.has(name))
|
|
144
|
+
authored.set(name, entry);
|
|
145
|
+
}
|
|
146
|
+
return derived.map((s) => {
|
|
147
|
+
const entry = authored.get(s.dataKey);
|
|
148
|
+
return entry ? { ...s, ...seriesPresentation(entry) } : s;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Merge an authored chart config's PRESENTATION onto the series and axes the
|
|
153
|
+
* dataset selection derived — the one place that happens for a surface whose
|
|
154
|
+
* axes are spec `ChartAxis` OBJECTS (#4229).
|
|
155
|
+
*
|
|
156
|
+
* The series half is {@link mergeAuthoredSeries}; see it for the match rule and
|
|
157
|
+
* for why membership is safe. The axes half reads `xAxis` / `yAxis` through
|
|
158
|
+
* {@link axisPresentation}, which is why a surface spelling those as bare NAME
|
|
159
|
+
* strings must call `mergeAuthoredSeries` directly instead (file header).
|
|
160
|
+
*
|
|
161
|
+
* @param derived the bindings {@link buildChartSeries} produced from the selection
|
|
162
|
+
* @param raw the authored chart config (anything, incl. absent)
|
|
163
|
+
* @returns the merged series, plus the presentation-only axes to spread onto
|
|
164
|
+
* the chart schema (absent when the author declared none)
|
|
165
|
+
*/
|
|
166
|
+
export function mergeAuthoredPresentation(derived, raw) {
|
|
167
|
+
const config = isRecord(raw) ? raw : {};
|
|
168
|
+
const series = mergeAuthoredSeries(derived, config.series);
|
|
169
|
+
const axes = {};
|
|
170
|
+
const xAxis = axisPresentation(config.xAxis);
|
|
171
|
+
if (Object.keys(xAxis).length > 0)
|
|
172
|
+
axes.xAxis = xAxis;
|
|
173
|
+
// The COUNT of y-axis entries is itself presentation — it is what declares a
|
|
174
|
+
// secondary axis — so every declared entry keeps its slot even when it
|
|
175
|
+
// carries nothing but `field` (which is data and does not travel).
|
|
176
|
+
const yAxisRaw = Array.isArray(config.yAxis)
|
|
177
|
+
? config.yAxis
|
|
178
|
+
: config.yAxis !== undefined
|
|
179
|
+
? [config.yAxis]
|
|
180
|
+
: [];
|
|
181
|
+
if (yAxisRaw.length > 0)
|
|
182
|
+
axes.yAxis = yAxisRaw.map(axisPresentation);
|
|
183
|
+
return { series, axes };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Lower an authored chart config's CHROME (spec `ChartConfigSchema` /
|
|
187
|
+
* `ReportChartSchema` — the same keys on both) onto the chart schema a
|
|
188
|
+
* dataset-bound surface hands the renderer.
|
|
189
|
+
*
|
|
190
|
+
* ## Why this is a whitelist and not a spread
|
|
191
|
+
*
|
|
192
|
+
* Until #3135 NONE of a dashboard widget's `chartConfig` reached the renderer:
|
|
193
|
+
* the widget read `options` and nothing else, so an author who wrote
|
|
194
|
+
* `showLegend: false` still got a legend and one who wrote `true` only got one
|
|
195
|
+
* because "on" is the renderer's default. #3135 lowered that single flag and
|
|
196
|
+
* left the rest declared and inert. objectstack#7016 lowered the rest of the
|
|
197
|
+
* keys that are actually DELIVERED, admitting a key only when both of these
|
|
198
|
+
* hold:
|
|
199
|
+
*
|
|
200
|
+
* 1. **The chart block draws it end to end.** `{ type: 'chart' }` resolves to
|
|
201
|
+
* `ChartRenderer` → `AdvancedChartImpl`, which draws `title`/`subtitle` in
|
|
202
|
+
* its ChartFrame, turns `description` into the chart container's
|
|
203
|
+
* `role="img"` + `aria-label`, applies `height` as that container's inline
|
|
204
|
+
* height, reads `colors` as the positional palette, prints
|
|
205
|
+
* `showDataLabels` as a Recharts `LabelList`, draws `annotations` as
|
|
206
|
+
* ReferenceLine/ReferenceArea and honours `interaction` as the tooltip
|
|
207
|
+
* toggle plus `Brush`. Forwarding a key the renderer ignores would only
|
|
208
|
+
* move declared-but-not-delivered one layer down, which is the failure this
|
|
209
|
+
* exists to remove.
|
|
210
|
+
* 2. **It does not fight the dataset derivation.** `type` stays out: the
|
|
211
|
+
* calling surface's own type already picks the chart family, which is the
|
|
212
|
+
* dataset path's chart-family channel.
|
|
213
|
+
*
|
|
214
|
+
* `aria` is the one declared key with **no reader at all**: `AdvancedChartImpl`
|
|
215
|
+
* has no `aria` prop, and `SchemaRenderer`'s ARIA injection reads the FLAT
|
|
216
|
+
* `ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object. It is
|
|
217
|
+
* therefore left unforwarded on purpose (criterion 1) rather than papered over
|
|
218
|
+
* with a caller-side flattening that would also collide with the accessible
|
|
219
|
+
* name `description` already sets — reported back to objectstack#5175's
|
|
220
|
+
* narrowing half instead.
|
|
221
|
+
*
|
|
222
|
+
* `title` IS emitted here. A caller that paints the title itself (the report
|
|
223
|
+
* renderer's own `h3` above the chart) must drop it from the result, or the
|
|
224
|
+
* chart draws a second one.
|
|
225
|
+
*
|
|
226
|
+
* @param raw the authored chart config (anything, incl. absent)
|
|
227
|
+
* @param fieldCategoryColors per-category colours resolved from the category
|
|
228
|
+
* dimension's own select/lookup option colours, merged UNDER an explicit
|
|
229
|
+
* author map (see the `colors` note below)
|
|
230
|
+
* @returns only the keys that resolved, so the caller can spread it over the
|
|
231
|
+
* derived chart schema and every undeclared key keeps the renderer's default
|
|
232
|
+
*/
|
|
233
|
+
export function chartConfigPresentation(raw, fieldCategoryColors) {
|
|
234
|
+
const config = isRecord(raw) ? raw : {};
|
|
235
|
+
const out = {};
|
|
236
|
+
const text = (v) => (typeof v === 'string' && v ? v : undefined);
|
|
237
|
+
if (typeof config.showLegend === 'boolean')
|
|
238
|
+
out.showLegend = config.showLegend;
|
|
239
|
+
if (typeof config.showDataLabels === 'boolean')
|
|
240
|
+
out.showDataLabels = config.showDataLabels;
|
|
241
|
+
const title = text(config.title);
|
|
242
|
+
if (title)
|
|
243
|
+
out.title = title;
|
|
244
|
+
const subtitle = text(config.subtitle);
|
|
245
|
+
if (subtitle)
|
|
246
|
+
out.subtitle = subtitle;
|
|
247
|
+
const description = text(config.description);
|
|
248
|
+
if (description)
|
|
249
|
+
out.description = description;
|
|
250
|
+
// A non-positive height would collapse the plot; the container default is the
|
|
251
|
+
// more honest answer than an invisible chart.
|
|
252
|
+
if (typeof config.height === 'number' && Number.isFinite(config.height) && config.height > 0) {
|
|
253
|
+
out.height = config.height;
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(config.annotations) && config.annotations.length > 0)
|
|
256
|
+
out.annotations = config.annotations;
|
|
257
|
+
if (config.interaction && typeof config.interaction === 'object' && !Array.isArray(config.interaction)) {
|
|
258
|
+
out.interaction = config.interaction;
|
|
259
|
+
}
|
|
260
|
+
// `colors` is overloaded kanban-style — and the two arms reach the renderer
|
|
261
|
+
// through two DIFFERENT props, so the split has to happen here (the react
|
|
262
|
+
// tier's ObjectChart splits it the same way): a `string[]` is the positional
|
|
263
|
+
// palette (`colors`), a `{ value: color }` record is an explicit per-category
|
|
264
|
+
// map (`categoryColors`). The author's map is merged OVER the dimension
|
|
265
|
+
// field's own option colours, which is the precedence the spec field comment
|
|
266
|
+
// states ("a value→color map — and a select/lookup dimension's option colors
|
|
267
|
+
// — take precedence over the positional palette per category").
|
|
268
|
+
const palette = Array.isArray(config.colors)
|
|
269
|
+
? config.colors.filter((c) => typeof c === 'string' && !!c)
|
|
270
|
+
: undefined;
|
|
271
|
+
if (palette?.length)
|
|
272
|
+
out.colors = palette;
|
|
273
|
+
const authorCategoryColors = config.colors && typeof config.colors === 'object' && !Array.isArray(config.colors)
|
|
274
|
+
? config.colors
|
|
275
|
+
: undefined;
|
|
276
|
+
if (fieldCategoryColors || authorCategoryColors) {
|
|
277
|
+
out.categoryColors = { ...(fieldCategoryColors ?? {}), ...(authorCategoryColors ?? {}) };
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
@@ -5,27 +5,6 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* chart-series — shape a semantic-layer `queryDataset` result into the
|
|
10
|
-
* `{ data, xAxisKey, series }` triple a chart renderer consumes (ADR-0021, #1759).
|
|
11
|
-
*
|
|
12
|
-
* Shared by `ObjectChart` (plugin-charts) and `DatasetWidget` (plugin-dashboard)
|
|
13
|
-
* so multi-dimension charts visualise identically across surfaces.
|
|
14
|
-
*
|
|
15
|
-
* Rules:
|
|
16
|
-
* - **2+ dimensions, single measure** → pivot the SECOND dimension into one
|
|
17
|
-
* series per distinct value (grouped/coloured bars). `xAxisKey` = first
|
|
18
|
-
* dimension; each output row is one first-dimension bucket with a column per
|
|
19
|
-
* second-dimension value holding the measure. This makes the second dimension
|
|
20
|
-
* visible instead of just repeating the x-axis label.
|
|
21
|
-
* - **otherwise** (single dimension, or multiple measures) → first dimension is
|
|
22
|
-
* the x-axis and each measure is its own series (long format passes through).
|
|
23
|
-
*
|
|
24
|
-
* BOTH branches map a NULL first-dimension VALUE to an explicit bucket label
|
|
25
|
-
* ({@link NULL_CATEGORY_LABEL}) so the group renders instead of vanishing —
|
|
26
|
-
* objectui#4466 for the single-dimension branch, objectui#4497 for the pivot.
|
|
27
|
-
* One doctrine, one predicate (`isNullCategory`), two call sites.
|
|
28
|
-
*/
|
|
29
8
|
export interface ChartResultField {
|
|
30
9
|
name: string;
|
|
31
10
|
label?: string;
|
|
@@ -69,6 +48,57 @@ export interface ChartSeriesResult {
|
|
|
69
48
|
* clicked bucket bar resolve back to its (raw null) dataset row.
|
|
70
49
|
*/
|
|
71
50
|
export declare const NULL_CATEGORY_LABEL = "(None)";
|
|
51
|
+
/**
|
|
52
|
+
* The IDENTITY of the chart bucket a first-dimension raw value falls in —
|
|
53
|
+
* distinct from the string that bucket DISPLAYS (objectui#4508).
|
|
54
|
+
*
|
|
55
|
+
* Deliberately the SAME encoder the pivot TABLE already keys its buckets with
|
|
56
|
+
* (`pivotBucketId` over a `pivotDimensionValue`-normalized value), because the
|
|
57
|
+
* two surfaces answer the same question about the same dataset rows and were
|
|
58
|
+
* answering it differently: `buildPivot` gives a null dimension value a bucket
|
|
59
|
+
* id no string can spell, while the chart used the display label as its own
|
|
60
|
+
* key. That gap is the whole of objectui#4508 — see `dataset-pivot.ts` for why
|
|
61
|
+
* the encoding is JSON rather than a delimiter or a placeholder character.
|
|
62
|
+
*
|
|
63
|
+
* Two collisions follow from a label-as-key, and one encoder closes both:
|
|
64
|
+
* - `null` and `''` are different groups. `String(x ?? '')` made them one
|
|
65
|
+
* bucket, so two groups drew ONE bar and one of them lost its drill.
|
|
66
|
+
* `[null]` and `[""]` are different ids.
|
|
67
|
+
* - a stored value that literally spells the bucket label (`'(None)'`, or any
|
|
68
|
+
* localized `nullCategoryLabel` — `'(未指定)'` and the other nine packs) is
|
|
69
|
+
* not the null group. `["(None)"]` and `[null]` are different ids, so the
|
|
70
|
+
* click resolves to the rows its own bar was drawn from.
|
|
71
|
+
*/
|
|
72
|
+
export declare function chartBucketId(rawValue: unknown): string;
|
|
73
|
+
/**
|
|
74
|
+
* The key an emitted chart row carries its {@link chartBucketId} under, when it
|
|
75
|
+
* carries one — the "alongside the display label" half of objectui#4508.
|
|
76
|
+
*
|
|
77
|
+
* **Written exactly when the display string does not name one bucket**, i.e.
|
|
78
|
+
* when two or more DISTINCT buckets render the same axis text. That is not a
|
|
79
|
+
* cost-saving heuristic, it is the complete condition: with the reader's
|
|
80
|
+
* matching made exact (see {@link findChartSeriesRow}), a display string that
|
|
81
|
+
* one bucket alone produces already identifies it, and the rows a chart is
|
|
82
|
+
* drawn from are also an authoring-visible surface (`data` on a `chart`
|
|
83
|
+
* schema) that renderer-internal metadata should not appear in for every
|
|
84
|
+
* ordinary chart. Rows that do not carry the category key at all never get one
|
|
85
|
+
* — they have no bucket, and that shape belongs to `hasNoCategoryKey`
|
|
86
|
+
* (framework#4033), not here.
|
|
87
|
+
*
|
|
88
|
+
* Read it with {@link chartRowBucketId} rather than the literal key.
|
|
89
|
+
*/
|
|
90
|
+
export declare const CHART_BUCKET_ID_KEY = "__bucketId";
|
|
91
|
+
/**
|
|
92
|
+
* Read the bucket identity off a row a chart was drawn from, or `undefined`
|
|
93
|
+
* when the row carries none (an unambiguous bucket, or rows a caller built
|
|
94
|
+
* itself rather than through {@link buildChartSeries}).
|
|
95
|
+
*
|
|
96
|
+
* Takes `unknown` because its callers hold a click payload, not a typed row:
|
|
97
|
+
* recharts hands a pie/funnel click a SPREAD COPY of the data item, which is
|
|
98
|
+
* why the identity is an ordinary enumerable property and not a symbol or a
|
|
99
|
+
* non-enumerable one — either of those would silently not survive the copy.
|
|
100
|
+
*/
|
|
101
|
+
export declare function chartRowBucketId(row: unknown): string | undefined;
|
|
72
102
|
/** Per-call knobs shared by {@link buildChartSeries} / {@link findChartSeriesRow}. */
|
|
73
103
|
export interface ChartSeriesOptions {
|
|
74
104
|
/**
|
|
@@ -81,6 +111,69 @@ export interface ChartSeriesOptions {
|
|
|
81
111
|
*/
|
|
82
112
|
nullCategoryLabel?: string;
|
|
83
113
|
}
|
|
114
|
+
/** Per-CLICK knobs for {@link findChartSeriesRow}, on top of the shared ones. */
|
|
115
|
+
export interface ChartDrillLookupOptions extends ChartSeriesOptions {
|
|
116
|
+
/**
|
|
117
|
+
* The clicked bucket's {@link chartBucketId}, when the renderer could read one
|
|
118
|
+
* off the row the segment was drawn from ({@link chartRowBucketId} over
|
|
119
|
+
* {@link CHART_BUCKET_ID_KEY}) — objectui#4508.
|
|
120
|
+
*
|
|
121
|
+
* Supplied ⇒ AUTHORITATIVE: the lookup matches on identity and ignores
|
|
122
|
+
* `category` entirely, which is what lets two bars painting the same axis
|
|
123
|
+
* text drill to their own rows instead of both resolving to the first.
|
|
124
|
+
* Absent ⇒ the bucket's display string identified it on its own (that is
|
|
125
|
+
* exactly when `buildChartSeries` writes no id), or the rows were not built
|
|
126
|
+
* by `buildChartSeries` at all, and the display match answers.
|
|
127
|
+
*/
|
|
128
|
+
bucketId?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* What a chart renderer hands a drill handler when a segment is clicked.
|
|
132
|
+
*
|
|
133
|
+
* Declared HERE, beside the two helpers that write and read a bucket, rather
|
|
134
|
+
* than re-spelled inline in each of the three packages that pass it along
|
|
135
|
+
* (`AdvancedChartImpl` builds it, `ChartRenderer` and `ObjectChart` forward it,
|
|
136
|
+
* `DatasetWidget` consumes it). It was three inline literals when
|
|
137
|
+
* {@link ChartDrillLookupOptions.bucketId} needed a carrier, and a field added
|
|
138
|
+
* to one of three copies reaches the consumer as `undefined` with nothing red.
|
|
139
|
+
*/
|
|
140
|
+
export interface ChartSegmentClickEvent {
|
|
141
|
+
/** The clicked bucket's axis TEXT — what the user sees on the tick. */
|
|
142
|
+
category?: string;
|
|
143
|
+
/**
|
|
144
|
+
* The clicked bucket's IDENTITY, when its row carried one (objectui#4508).
|
|
145
|
+
* Feed it straight to {@link ChartDrillLookupOptions.bucketId}; it is the
|
|
146
|
+
* only field that survives two buckets painting the same `category`.
|
|
147
|
+
*/
|
|
148
|
+
categoryId?: string;
|
|
149
|
+
/** The clicked series' key — a measure, or a pivoted second-dimension value. */
|
|
150
|
+
series?: string;
|
|
151
|
+
/**
|
|
152
|
+
* The clicked series' DISPLAY LABEL, when the renderer knew one
|
|
153
|
+
* (objectui#4682) — `series[].label` as {@link buildChartSeries} assigned it.
|
|
154
|
+
*
|
|
155
|
+
* {@link ChartSegmentClickEvent.series} stays the LOOKUP key and this never
|
|
156
|
+
* substitutes for it: {@link findChartSeriesRow} resolves a key through the
|
|
157
|
+
* same assignment `buildChartSeries` made, and a label cannot be resolved
|
|
158
|
+
* that way — it is not unique (that is precisely why the colliding groups key
|
|
159
|
+
* by identity instead). This field exists for what a key cannot do: be SHOWN.
|
|
160
|
+
*
|
|
161
|
+
* The two strings are equal for every ordinary group, which is why reading
|
|
162
|
+
* the key as a title went unnoticed. They part company when a group's label
|
|
163
|
+
* cannot name it — objectui#4508's collision on the series axis — and the key
|
|
164
|
+
* becomes an opaque `chartBucketId`. A drawer titled `Backlog / [null]` over
|
|
165
|
+
* a segment the user saw labelled `(None)` reads as broken DATA rather than
|
|
166
|
+
* as a broken title, which is why the label travels with the click rather
|
|
167
|
+
* than being re-derived by the consumer.
|
|
168
|
+
*
|
|
169
|
+
* Absent when the renderer resolved no series, or when the series carries no
|
|
170
|
+
* label distinct from its key; a consumer titles itself from
|
|
171
|
+
* `seriesLabel ?? series` and so is unchanged wherever it is absent.
|
|
172
|
+
*/
|
|
173
|
+
seriesLabel?: string;
|
|
174
|
+
/** The measure value at the click point. */
|
|
175
|
+
value?: number;
|
|
176
|
+
}
|
|
84
177
|
export declare function buildChartSeries(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, fields?: ChartResultField[] | null, options?: ChartSeriesOptions): ChartSeriesResult;
|
|
85
178
|
/**
|
|
86
179
|
* Resolve select/enum dimension VALUES to display LABELS in chart rows.
|
|
@@ -117,16 +210,37 @@ export declare function relabelDimensions(rows: Array<Record<string, unknown>> |
|
|
|
117
210
|
* BOTH the x-axis dimension (`category`) and the series dimension (`seriesKey`).
|
|
118
211
|
* - **otherwise** → match the x-axis (first) dimension only.
|
|
119
212
|
*
|
|
120
|
-
*
|
|
121
|
-
* surfaces as `category`
|
|
213
|
+
* The x-axis comparison is string-wise on the rows' display values (which is
|
|
214
|
+
* what the chart surfaces as `category`), unless the click carried a bucket
|
|
215
|
+
* IDENTITY — {@link ChartDrillLookupOptions.bucketId} — in which case that is
|
|
216
|
+
* authoritative and the display string is not consulted at all. Returns `-1`
|
|
217
|
+
* when nothing matches.
|
|
218
|
+
*
|
|
219
|
+
* The SERIES comparison is not string-wise at all as of objectui#4673: a series
|
|
220
|
+
* key is a `dataKey` {@link buildChartSeries} assigned, so it is resolved back
|
|
221
|
+
* through the same {@link pivotSeriesBuckets} assignment (same rows, same
|
|
222
|
+
* `nullCategoryLabel`) to the group IDENTITY it names, and rows are matched on
|
|
223
|
+
* that. A null-valued second dimension had no series at all before, so its
|
|
224
|
+
* segment could not be clicked; now it can, and `String(r[gDim] ?? '')` would
|
|
225
|
+
* have answered that click with an empty-string group's rows — the same
|
|
226
|
+
* conflation objectui#4508 removed on the x-axis.
|
|
122
227
|
*
|
|
123
228
|
* The NULL-category bucket {@link buildChartSeries} renders (objectui#4466) is
|
|
124
229
|
* matched back to its row here, and it has to be: the caller passes the rows it
|
|
125
230
|
* charted FROM (still carrying the raw null) while the click event carries the
|
|
126
231
|
* bucket LABEL, so without this the one bar the fix made visible would resolve
|
|
127
232
|
* to `-1` and its drill-through would silently no-op. Pass the same
|
|
128
|
-
* `nullCategoryLabel` both helpers were given.
|
|
129
|
-
*
|
|
233
|
+
* `nullCategoryLabel` both helpers were given.
|
|
234
|
+
*
|
|
235
|
+
* **A null category reads as its bucket LABEL and nothing else** (objectui#4508).
|
|
236
|
+
* It used to read as the label OR the empty string, on the stated grounds that
|
|
237
|
+
* `''` is the drill layer's own spelling of "no group value" — but no producer
|
|
238
|
+
* of this function's `category` argument writes that spelling (the one
|
|
239
|
+
* production caller feeds it a chart click's category, and `computeDrillFilter`
|
|
240
|
+
* is downstream of the drill, not upstream of this lookup), while `''` IS what
|
|
241
|
+
* a genuine empty-string group paints. So the tolerance cost that group its own
|
|
242
|
+
* drill and gave it a null group's records instead — a WRONG click, not a dead
|
|
243
|
+
* one. `''` now means the empty-string group, which is what it says.
|
|
130
244
|
*
|
|
131
245
|
* **`xOf` covers BOTH arms, which is what let objectui#4497 bucket the pivot
|
|
132
246
|
* branch without touching this function.** Worth stating because the reverse
|
|
@@ -140,7 +254,7 @@ export declare function relabelDimensions(rows: Array<Record<string, unknown>> |
|
|
|
140
254
|
* searches still carry their null either way, and `xOf` reads the label back to
|
|
141
255
|
* them in the multi-dimension arm exactly as in the single-dimension one.
|
|
142
256
|
*/
|
|
143
|
-
export declare function findChartSeriesRow(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, category: string | undefined, seriesKey?: string, options?:
|
|
257
|
+
export declare function findChartSeriesRow(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, category: string | undefined, seriesKey?: string, options?: ChartDrillLookupOptions): number;
|
|
144
258
|
/**
|
|
145
259
|
* Build a per-category colour map from a select/lookup field's `options`.
|
|
146
260
|
*
|