@fundar/data-chart-telling 0.0.38 → 0.0.40

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.
@@ -37,12 +37,21 @@ export function watchLabelOverflow(args) {
37
37
  catch {
38
38
  return;
39
39
  }
40
- const overflow = {
41
- left: Math.ceil(Math.max(0, bounds.left - box.x)),
42
- right: Math.ceil(Math.max(0, box.x + box.width - bounds.right)),
43
- top: Math.ceil(Math.max(0, bounds.top - box.y)),
44
- bottom: Math.ceil(Math.max(0, box.y + box.height - bounds.bottom)),
45
- };
40
+ // An empty group (no labels currently rendered, e.g. every series is
41
+ // scrubbed/filtered out of view) reports getBBox() as (0,0,0,0) the
42
+ // SVG origin, not "no content". Reading that literally as a box sitting
43
+ // at (0,0) would report overflow proportional to the *current* margins
44
+ // themselves, which grows them, which remounts this group against the
45
+ // new, larger bounds, which reports overflow again — an unbounded loop
46
+ // with nothing rendered to justify it.
47
+ const overflow = box.width === 0 && box.height === 0
48
+ ? NO_OVERFLOW
49
+ : {
50
+ left: Math.ceil(Math.max(0, bounds.left - box.x)),
51
+ right: Math.ceil(Math.max(0, box.x + box.width - bounds.right)),
52
+ top: Math.ceil(Math.max(0, bounds.top - box.y)),
53
+ bottom: Math.ceil(Math.max(0, box.y + box.height - bounds.bottom))
54
+ };
46
55
  attempt += 1;
47
56
  const stable = lastReading != null &&
48
57
  lastReading.left === overflow.left &&
@@ -78,15 +87,48 @@ export function watchLabelOverflow(args) {
78
87
  * "shrink margin → overflow reappears → grow margin → …". Each side is only
79
88
  * ever set when the caller's own `margins` prop hasn't already pinned it, so
80
89
  * an explicit caller override always wins over this auto-grown one.
90
+ *
91
+ * That monotonic growth is only sound for a *stable* dataset — it assumes
92
+ * every reading describes the same thing being measured, just possibly at an
93
+ * earlier, not-yet-settled frame. It stops being sound once the caller's own
94
+ * data changes identity underneath it (e.g. a timeline scrubber revealing a
95
+ * different slice of rows on every tick): an overflow measured against
96
+ * *that* frame's rendered labels doesn't describe the next frame's, so
97
+ * carrying it forward forever can leave a plot permanently — and wrongly —
98
+ * margin-inflated by whatever the single most cluttered frame it ever
99
+ * passed through needed, long after scrubbing away from it. `resetKey`, if
100
+ * given, is compared by identity on every call; a change drops `measured`
101
+ * back to zero so the next frame's overflow is measured fresh against only
102
+ * what's actually on screen now. Pass whatever reactive value identifies
103
+ * "what's currently being plotted" for the caller (e.g. its own resolved
104
+ * series list) — a plain reference/value comparison, not a deep one, so it
105
+ * only need change identity when the rendered content actually does.
81
106
  */
82
- export function createLabelMarginTracker(margins) {
107
+ export function createLabelMarginTracker(margins, resetKey) {
83
108
  let measured = $state(NO_OVERFLOW);
109
+ // Skips the reset on the very first run (there's nothing to reset yet,
110
+ // and `measured` already starts at `NO_OVERFLOW`) so only a later,
111
+ // genuine change to `resetKey` clears accumulated growth.
112
+ let resetKeyInitialized = false;
113
+ let lastResetKey;
114
+ $effect(() => {
115
+ const key = resetKey?.();
116
+ if (!resetKeyInitialized) {
117
+ resetKeyInitialized = true;
118
+ lastResetKey = key;
119
+ return;
120
+ }
121
+ if (key !== lastResetKey) {
122
+ lastResetKey = key;
123
+ measured = NO_OVERFLOW;
124
+ }
125
+ });
84
126
  function report(overflow) {
85
127
  const next = {
86
128
  left: Math.max(measured.left, overflow.left),
87
129
  right: Math.max(measured.right, overflow.right),
88
130
  top: Math.max(measured.top, overflow.top),
89
- bottom: Math.max(measured.bottom, overflow.bottom),
131
+ bottom: Math.max(measured.bottom, overflow.bottom)
90
132
  };
91
133
  if (next.left !== measured.left ||
92
134
  next.right !== measured.right ||
@@ -126,11 +168,15 @@ export function createLabelMarginTracker(margins) {
126
168
  // must track.
127
169
  const plotProps = $derived.by(() => ({
128
170
  margins: resolvedMargins,
129
- marginRemountKey: JSON.stringify(measured),
171
+ marginRemountKey: JSON.stringify(measured)
130
172
  }));
131
173
  return {
132
174
  report,
133
- get resolvedMargins() { return resolvedMargins; },
134
- get plotProps() { return plotProps; },
175
+ get resolvedMargins() {
176
+ return resolvedMargins;
177
+ },
178
+ get plotProps() {
179
+ return plotProps;
180
+ }
135
181
  };
136
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundar/data-chart-telling",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"