@object-ui/plugin-charts 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 CHANGED
@@ -1,5 +1,308 @@
1
1
  # @object-ui/plugin-charts
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9ce096f: Give a chart bucket an identity distinct from its display label
8
+
9
+ objectui#4508. `buildChartSeries` used the bucket's DISPLAY string as the
10
+ bucket's own key, so two pairs of genuinely different groups were conflated —
11
+ and the segment click that drills a bar back to its records inherited both
12
+ conflations. The maintainer ruling (2026-08-14) approved the sentinel-identity
13
+ direction, aligning the chart branch with the distinct-bucket-id form the pivot
14
+ TABLE (`buildPivot`) already uses over the same dataset rows.
15
+
16
+ Two collisions, one cause:
17
+
18
+ - **A null group and an empty-string group drew ONE bar.** The pivot branch
19
+ keyed buckets by `String(xRaw ?? '')`, which spells `null` and `''`
20
+ identically. The bar took its label from whichever row created the bucket, and
21
+ the other group's segment then resolved to no row at all — a visible bar whose
22
+ click did nothing.
23
+ - **A record whose stored value spells the bucket label stole the null bucket's
24
+ drill.** A row storing the literal text `(None)` (or any localized
25
+ `chart.nullCategory` — `(未指定)` and the other nine packs) kept its own
26
+ bucket, so two bars carried the same axis text and BOTH resolved to the first.
27
+ That one is a wrong drill, not a dead one: clicking the null bucket's bar
28
+ opened the drawer on another group's records.
29
+
30
+ What changed:
31
+
32
+ - **`chartBucketId`** (`@object-ui/core`) is the bucket identity — the SAME
33
+ encoder `buildPivot` keys its buckets with (`pivotBucketId` over
34
+ `pivotDimensionValue`), so the two surfaces stop answering one question two
35
+ ways. The pivot branch now buckets by it, which is what makes null and `''`
36
+ two groups again.
37
+ - **`CHART_BUCKET_ID_KEY`** carries that identity on an emitted row, written
38
+ exactly where two DISTINCT buckets paint the same axis text — the complete set
39
+ of cases where the display string cannot name what was clicked. An ordinary
40
+ chart's rows are returned untouched (by identity), so no renderer-internal key
41
+ reaches an authoring surface.
42
+ - **`findChartSeriesRow`** takes that identity back as `options.bucketId` and
43
+ treats it as authoritative. The renderers forward it: the drill event gains
44
+ `categoryId` (`ChartSegmentClickEvent`, now declared once in
45
+ `@object-ui/core` instead of inline in three packages), `AdvancedChartImpl`
46
+ reads it off the clicked row on the cartesian, pie and funnel paths, and
47
+ `DatasetWidget.handleChartDrill` hands it to the lookup.
48
+
49
+ Behaviour change worth noting: an empty-string category no longer resolves to a
50
+ null-valued row. That tolerance was justified as the drill layer's own spelling
51
+ of "no group value", but no producer of this lookup's `category` writes it,
52
+ while `''` IS the axis text a genuine empty-string group paints — so the
53
+ tolerance was giving that group's bar a different group's records. A host that
54
+ forwards no `categoryId` keeps its existing drill unchanged.
55
+
56
+ ### Patch Changes
57
+
58
+ - e132433: A grouped chart whose SECOND dimension was never projected says so instead of
59
+ drawing an empty frame.
60
+
61
+ "Cannot know refuses loudly" was answered on the first dimension only.
62
+ `AdvancedChartImpl`'s `hasNoCategoryKey` (framework#4033) names an unprojected
63
+ x-axis dimension rather than drawing a bare axis; the series axis had no
64
+ counterpart, so a pivot whose second dimension was absent from the result rows
65
+ produced `series: []` and rendered axes, grid, tooltip and legend around zero
66
+ marks — indistinguishable, to the author, from "no data matched".
67
+
68
+ Such a chart now renders the same explanatory placeholder
69
+ (`data-chart-error="no-plottable-series"`) and logs the same diagnostic pair the
70
+ category-axis guard logs: the axis it did plot, and the keys its rows actually
71
+ carry.
72
+
73
+ The three-way distinction is unchanged and pinned: null / empty-string group
74
+ values still DRAW (they are real groups with real buckets), a partially
75
+ projected group key still draws what projects — mirroring the category axis,
76
+ which refuses only when NOT ONE row carries the key — and an ordinary pivot
77
+ renders unchanged. The refusal is limited to the families whose marks come from
78
+ `series` and nothing else (bar, horizontal-bar, line, area, combo); pie, donut,
79
+ funnel, radar and scatter draw from a `value` column with no series declared, so
80
+ they are untouched. A caller that computed no series binding at all
81
+ (`series === undefined`) is also untouched.
82
+ - f95434b: Combo charts drill from their marks
83
+
84
+ objectui#4692, ruled Option B. `AdvancedChartImpl` built `cartesianClickProps` once and
85
+ applied it to exactly one element — the final cartesian `ChartComponent`. The `combo`
86
+ branch returns earlier, from its own `ComposedChart`, which was rendered with `data` and
87
+ no click props at all, so a combo chart fired `onChartClick` never: not on a mark, not on
88
+ the axis. Its marks are the same `Bar` / `Line` / `Area` components the drillable branch
89
+ renders.
90
+
91
+ The trap that made this worth fixing rather than documenting is that the family is
92
+ **derived**, not only authored: `effectiveChartFamily` resolves a chart to `combo`
93
+ whenever its series declare different families (objectui#2945), so adding `type: 'line'`
94
+ to one series of a drillable bar chart silently turned that chart's drill-through off —
95
+ nothing in the authored spec said drill had been touched, and nothing errored.
96
+
97
+ A combo's `Bar` / `Line` / `Area` marks now emit `{ category, categoryId, series, value }`
98
+ with the same semantics the plain cartesian branch gives, reusing the item-level
99
+ series-identity machinery from objectui#4672 / objectui#4682: the mark handler records the
100
+ series it was rendered with, the chart-level handler composes the one event, so a gesture
101
+ still produces exactly one `onChartClick`. Retyping one series now changes that series'
102
+ mark and nothing else.
103
+
104
+ **Only the marks drill.** A click on a combo's plot surface or axis stays silent, where
105
+ the plain cartesian branch falls back to its axis-level answer. A combo plots several
106
+ measures on one plot, so a surface click there has no single series to report and the
107
+ fallback would have to invent one — the same reasoning objectui#4672's ruling gave the
108
+ pivoted case. Combo also carries no chart-wide pointer cursor for that reason; the
109
+ affordance sits on the marks that answer.
110
+
111
+ Radar is now the one cartesian-adjacent family with no click wiring. The `onChartClick`
112
+ doc comment, corrected in objectui#4705 to say combo was a no-op, states the new rule and
113
+ its one deliberate exception.
114
+ - e05db88: A clicked cartesian mark names its own series, and the drill title reads its label
115
+
116
+ objectui#4672, objectui#4682.
117
+
118
+ **The dead pivoted drill.** objectui#4680 fixed what a cartesian click could
119
+ read out of recharts 3's `MouseHandlerDataParam`, and measured the wall it could
120
+ not get past: a chart-level click is an AXIS interaction, and recharts
121
+ dispatches those with `activeDataKey` hard-coded `undefined`, because the shared
122
+ cursor spans every series at that tick. A pivoted dataset chart — 2 dimensions,
123
+ 1 measure, the shape ADR-0021 introduced — needs the series to resolve its drill
124
+ row, so every segment of every such dashboard chart stayed a dead click. The
125
+ series was left unresolved rather than guessed, and the card carried the rest.
126
+
127
+ The answer is the mark itself. This renderer draws the `Bar` / `Line` / `Area`,
128
+ so an item-level `onClick` closes over the very `dataKey` it was rendered with —
129
+ the series is statically known, not inferred from tooltip state.
130
+
131
+ Both handlers fire for one gesture (measured: item first, chart second, sharing
132
+ one `nativeEvent` object), so the item handler does not emit. It RECORDS its
133
+ series, stamped with that gesture, and the chart-level handler composes the one
134
+ event. That is the double-fire answer and the additive property together:
135
+
136
+ - **one click, one drill event**, because there is one emit site — not a second
137
+ event suppressed after the fact;
138
+ - **a click that lands on no mark is untouched**: it records nothing and falls
139
+ through to the objectui#4680 axis answer exactly as shipped — category, bucket
140
+ identity, and the series only where one series is plotted. Empty plot area
141
+ stays category-only, and "drill the whole category" was rejected as a
142
+ different product question. Nothing that resolved before stops resolving; a
143
+ line's `dot={false}` stroke simply GAINS the exact series where it is hit;
144
+ - pairing on the shared DOM event rather than on a flag means a record left by
145
+ one gesture can never be adopted by a later click.
146
+
147
+ The clicked key is forwarded exactly as rendered, `''` included: the
148
+ empty-string second-dimension group draws its own bar since objectui#4673, and
149
+ `''` is falsy, so a truthiness test on the way out would send no series at all
150
+ and leave that bar's drill standing on the reader's coercion instead of on what
151
+ was clicked.
152
+
153
+ **The opaque drill title.** `ChartSegmentClickEvent` gains `seriesLabel`, and
154
+ `DatasetWidget`'s drill drawer titles itself from `seriesLabel ?? series`.
155
+ `ev.series` stays the LOOKUP key — `findChartSeriesRow` resolves it through the
156
+ same assignment `buildChartSeries` made — and only the title reads the label.
157
+
158
+ The two strings are equal for every ordinary group, which is why reading the key
159
+ as a title went unnoticed. They part company when a group's label cannot name
160
+ it: the null bucket beside a record whose stored value literally spells
161
+ `(None)`, which is objectui#4508's collision on the series axis, reachable since
162
+ objectui#4673. Both groups then key by `chartBucketId`, and the drawer opened on
163
+ the right records under the title `Backlog / [null]`. An internal id where a
164
+ label belongs reads as broken DATA rather than as a broken title.
165
+
166
+ Neither string can do the other's job, which is why this is a second field
167
+ rather than a change to the first: the label is not resolvable (it is exactly
168
+ what the colliding groups share) and the key is not showable. `seriesLabel` is
169
+ optional and absent wherever a renderer resolved no label, so every other
170
+ chart's title is byte-identical.
171
+ - d298be8: Cartesian chart clicks report the clicked series and value again
172
+
173
+ objectui#4672. `AdvancedChartImpl`'s chart-level click handler built its drill
174
+ event from `payload.activePayload[0]` — a **recharts 2** field. This package is
175
+ on recharts 3, which hands a chart-level `onClick` a `MouseHandlerDataParam`:
176
+ `{ activeCoordinate, activeDataKey, activeIndex, activeLabel, activeTooltipIndex,
177
+ isTooltipActive }`, and nothing else. `activePayload` appears nowhere in the
178
+ shipped library, so the read was `undefined` on **every** cartesian click and
179
+ every bar / line / area drill event carried `series: undefined, value: undefined`.
180
+ Nothing went red: the payload is typed `any` at the call site, and every existing
181
+ drill test either calls the pure lookup directly or stubs the chart.
182
+
183
+ The handler now works from the payload recharts 3 actually sends:
184
+
185
+ - **The value** is read off the clicked row — `data[activeTooltipIndex]`, the
186
+ array this component was given — for the resolved measure, the same way the
187
+ bucket identity has been read since objectui#4508.
188
+ - **The series** comes from `activeDataKey` when the payload carries one, and
189
+ otherwise from the chart's own series list when it plots exactly one series,
190
+ where the clicked column can belong to nothing else.
191
+ - **A click with no active tick** (the plot margins, an axis label) resolves to
192
+ no row instead of to bucket zero. recharts reports a **null** index there, not
193
+ an absent one, and `Number(null)` is `0` — so such a click used to drill the
194
+ first bucket's records. That is a wrong drill, not a dead one.
195
+
196
+ A drill on a single-measure chart therefore names its measure and carries its
197
+ value again — `resolveDrillTitle` composes the drawer title from them, and an
198
+ authored drill filter can reference `${event.value}`.
199
+
200
+ **Still open, deliberately:** a chart plotting several series under the default
201
+ SHARED cursor. Measured against recharts 3.10.1, an axis interaction is
202
+ dispatched with `activeDataKey` hard-coded `undefined` (bar, line and area
203
+ alike, on the mark and on empty plot area), so the payload names no series at
204
+ all — and a pivoted dataset chart's drill lookup requires one. The series is
205
+ left unresolved rather than guessed: naming a series the user did not click
206
+ drills to another group's records, which is worse than the dead click. Resolving
207
+ it needs the clicked mark rather than this payload; objectui#4672 carries that
208
+ half.
209
+ - Updated dependencies [88085e3]
210
+ - Updated dependencies [69251bf]
211
+ - Updated dependencies [57e668f]
212
+ - Updated dependencies [516663d]
213
+ - Updated dependencies [41ac1b7]
214
+ - Updated dependencies [1eaf0a1]
215
+ - Updated dependencies [460c4d0]
216
+ - Updated dependencies [0ae27f7]
217
+ - Updated dependencies [2533ec5]
218
+ - Updated dependencies [78c0f9a]
219
+ - Updated dependencies [bbe8b86]
220
+ - Updated dependencies [8477be5]
221
+ - Updated dependencies [279fb13]
222
+ - Updated dependencies [2e82ab2]
223
+ - Updated dependencies [ad07b65]
224
+ - Updated dependencies [41f498b]
225
+ - Updated dependencies [ef0d150]
226
+ - Updated dependencies [f34226e]
227
+ - Updated dependencies [564b605]
228
+ - Updated dependencies [e1d4251]
229
+ - Updated dependencies [40d3a33]
230
+ - Updated dependencies [8b9dc62]
231
+ - Updated dependencies [1184192]
232
+ - Updated dependencies [a2a9747]
233
+ - Updated dependencies [a1609a6]
234
+ - Updated dependencies [53f23bc]
235
+ - Updated dependencies [c4533dc]
236
+ - Updated dependencies [be60815]
237
+ - Updated dependencies [37f6844]
238
+ - Updated dependencies [93de4f6]
239
+ - Updated dependencies [2b50261]
240
+ - Updated dependencies [384f30d]
241
+ - Updated dependencies [ac600e5]
242
+ - Updated dependencies [97fba31]
243
+ - Updated dependencies [232f61a]
244
+ - Updated dependencies [d374caf]
245
+ - Updated dependencies [5673576]
246
+ - Updated dependencies [c1ef923]
247
+ - Updated dependencies [911ceaa]
248
+ - Updated dependencies [98eab36]
249
+ - Updated dependencies [af5e292]
250
+ - Updated dependencies [3fbbea1]
251
+ - Updated dependencies [7f96b10]
252
+ - Updated dependencies [167ec42]
253
+ - Updated dependencies [616a2a5]
254
+ - Updated dependencies [0046d8f]
255
+ - Updated dependencies [f1d4748]
256
+ - Updated dependencies [bea374e]
257
+ - Updated dependencies [b1119ec]
258
+ - Updated dependencies [9f23d2b]
259
+ - Updated dependencies [578e025]
260
+ - Updated dependencies [af025ee]
261
+ - Updated dependencies [d109a4d]
262
+ - Updated dependencies [598c89a]
263
+ - Updated dependencies [4a0bd17]
264
+ - Updated dependencies [b8b9af4]
265
+ - Updated dependencies [31676be]
266
+ - Updated dependencies [8c0d52e]
267
+ - Updated dependencies [aff10e2]
268
+ - Updated dependencies [70a774b]
269
+ - Updated dependencies [9ce096f]
270
+ - Updated dependencies [e05db88]
271
+ - Updated dependencies [7458a41]
272
+ - Updated dependencies [ad13d63]
273
+ - Updated dependencies [5ffcc14]
274
+ - Updated dependencies [d971e51]
275
+ - Updated dependencies [97abb24]
276
+ - Updated dependencies [deb157a]
277
+ - Updated dependencies [9c60144]
278
+ - Updated dependencies [d2ce342]
279
+ - Updated dependencies [9695da7]
280
+ - Updated dependencies [75444e3]
281
+ - Updated dependencies [58b8346]
282
+ - Updated dependencies [2d0bd16]
283
+ - Updated dependencies [a9e17b4]
284
+ - Updated dependencies [b8ce7dc]
285
+ - Updated dependencies [dad51e5]
286
+ - Updated dependencies [1c9c342]
287
+ - Updated dependencies [787c738]
288
+ - Updated dependencies [8396656]
289
+ - Updated dependencies [dbbd38a]
290
+ - Updated dependencies [8871c14]
291
+ - Updated dependencies [93fe362]
292
+ - Updated dependencies [dfc6975]
293
+ - Updated dependencies [3cf4de0]
294
+ - Updated dependencies [c9dc811]
295
+ - Updated dependencies [144ef9b]
296
+ - Updated dependencies [138ab04]
297
+ - Updated dependencies [a0b9e91]
298
+ - Updated dependencies [99bd015]
299
+ - Updated dependencies [21e4585]
300
+ - @object-ui/types@17.6.0
301
+ - @object-ui/i18n@17.6.0
302
+ - @object-ui/react@17.6.0
303
+ - @object-ui/components@17.6.0
304
+ - @object-ui/core@17.6.0
305
+
3
306
  ## 17.5.0
4
307
 
5
308
  ### Minor Changes
@@ -1,5 +1,6 @@
1
1
  import { ChartContainerConfig } from './ChartContainerImpl';
2
2
  import { NormalizedAxis, NormalizedSeries } from './normalizeChartSchema';
3
+ import { ChartSegmentClickEvent } from '@object-ui/core';
3
4
  /**
4
5
  * ObjectUI
5
6
  * Copyright (c) 2024-present ObjectStack Inc.
@@ -52,14 +53,34 @@ export interface AdvancedChartImplProps {
52
53
  categoryOrder?: string[];
53
54
  /**
54
55
  * Optional drill-down click handler. Fires when a chart segment is clicked
55
- * with `{ category, series, value }`. Wired for bar/horizontal-bar/line/
56
- * area/pie/donut. Other chart types are no-ops in L1.
56
+ * with `{ category, categoryId, series, value }`. Wired for
57
+ * bar/horizontal-bar/line/area/pie/donut/funnel/scatter/treemap/sankey —
58
+ * each has its own click handler attached to its mark(s) below — and for
59
+ * `combo`, whose `Bar`/`Line`/`Area` marks are the same components the plain
60
+ * cartesian branch renders and now emit the same event with the same
61
+ * semantics (ruled on objectui#4692).
62
+ *
63
+ * `combo` differs from the plain cartesian branch in exactly ONE way, and
64
+ * deliberately: **only its marks drill.** A click on the plot surface or an
65
+ * axis stays silent there, where the plain branch would fall back to its
66
+ * axis-level answer. A combo plots several measures on one plot, so a
67
+ * surface click has no single series answer to give — the same reasoning
68
+ * objectui#4672's ruling used for the pivoted case. The plain branch keeps
69
+ * that fallback because its own marks share one measure, so its axis answer
70
+ * is either unambiguous or explicitly named by recharts.
71
+ *
72
+ * That combo drills at all is what keeps its DERIVATION from costing an
73
+ * interaction. The branch is reachable without authoring
74
+ * `chartType: 'combo'`: `effectiveChartFamily` derives it whenever a series'
75
+ * own family disagrees with the chart's own (see its doc comment), so giving
76
+ * one series of a drillable chart a different `type` used to turn that
77
+ * chart's drill off with nothing in the authored spec saying drill was
78
+ * touched. It now changes the mark and nothing else.
79
+ *
80
+ * Radar is the one remaining no-op in L1 — its branch attaches no click
81
+ * handler anywhere.
57
82
  */
58
- onChartClick?: (event: {
59
- category?: string;
60
- series?: string;
61
- value?: number;
62
- }) => void;
83
+ onChartClick?: (event: ChartSegmentClickEvent) => void;
63
84
  /**
64
85
  * Spec `ChartAxis` presentation for the category axis — `format` (tick
65
86
  * formatter), `title`, `showGridLines`. Its `field` already arrived as
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { ChartSegmentClickEvent } from '@object-ui/core';
2
3
  export interface ChartBarRendererProps {
3
4
  schema: {
4
5
  type: string;
@@ -88,11 +89,7 @@ export interface ChartRendererProps {
88
89
  isAnimationActive?: boolean;
89
90
  };
90
91
  /** Drill-down click handler — wired by ObjectChart when drillDown is enabled. */
91
- onChartClick?: (event: {
92
- category?: string;
93
- series?: string;
94
- value?: number;
95
- }) => void;
92
+ onChartClick?: (event: ChartSegmentClickEvent) => void;
96
93
  }
97
94
  /**
98
95
  * ChartRenderer - The public API for the advanced chart component
@@ -1,9 +1,19 @@
1
1
  import { default as React } from 'react';
2
+ import { humanizeLabel } from '@object-ui/core';
2
3
  /**
3
4
  * Humanize a snake_case or kebab-case string into Title Case.
4
- * Local implementation to avoid a dependency on @object-ui/fields.
5
+ *
6
+ * Was a local implementation here "to avoid a dependency on
7
+ * `@object-ui/fields`" — byte-identical to that package's export, which
8
+ * `plugin-grid` / `plugin-gantt` / `plugin-detail` read, so the two could drift
9
+ * into a live disagreement (one dashboard can hold a chart and a grid over the
10
+ * same stored value). objectui#5444 moved the single implementation to
11
+ * `@object-ui/core`, which this package already depends on: the dependency the
12
+ * copy existed to avoid is still avoided, and there is now one function rather
13
+ * than two. Re-exported at module scope because that is the surface this file
14
+ * has always offered.
5
15
  */
6
- export declare function humanizeLabel(value: string): string;
16
+ export { humanizeLabel };
7
17
  /**
8
18
  * The result column an object-bound `aggregate` projects its value under
9
19
  * (framework#3701, `chartAggregateValueKey` in `@objectstack/spec/ui`).