@rokkit/chart 1.3.14 → 1.3.15

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.
@@ -202,7 +202,10 @@
202
202
  })
203
203
 
204
204
  onDestroy(() => {
205
- cancelAnimationFrame(rafId)
205
+ // onDestroy is the one lifecycle hook that also runs during SSR, where
206
+ // cancelAnimationFrame is undefined — guard it so the component can be
207
+ // server-rendered / prerendered (rafId is only ever set in the browser).
208
+ if (typeof cancelAnimationFrame !== 'undefined') cancelAnimationFrame(rafId)
206
209
  clearInterval(reducedInterval)
207
210
  })
208
211
 
package/dist/Plot.svelte CHANGED
@@ -58,6 +58,13 @@
58
58
  onselect?: (detail: unknown) => void
59
59
  selectable?: boolean
60
60
  selected?: Row[]
61
+ /** Data-coordinate crossing point of the axes: `[xCross, yCross]`. Drives
62
+ * quadrant mode — where the origin sits relative to the domain makes a chart
63
+ * 1- / 2- / 4-quadrant. Omit a component to leave that axis auto (crosses at 0
64
+ * when the domain spans it, else edge-pinned). Also settable via `spec.axisOrigin`. */
65
+ axisOrigin?: [number | undefined, number | undefined]
66
+ /** Nudge an edge-pinned axis outward by N px (Q1 only). Also via `spec.axisOffset`. */
67
+ axisOffset?: number
61
68
  /** Animate marks on data/flip changes (opt-out for e.g. AnimatedPlot, which
62
69
  * tweens its own frames). Enabled one frame after the width settles so the
63
70
  * initial layout paints un-animated. Default `true`. */
@@ -94,6 +101,8 @@
94
101
  onselect = undefined,
95
102
  selectable = false,
96
103
  selected = $bindable([]),
104
+ axisOrigin = undefined,
105
+ axisOffset = undefined,
97
106
  animate = true,
98
107
  children
99
108
  }: Props = $props()
@@ -142,6 +151,8 @@
142
151
  colorScheme: spec?.colorScheme,
143
152
  colorMidpoint: spec?.colorMidpoint,
144
153
  orientation: orientation ?? spec?.orientation,
154
+ axisOrigin: spec?.axisOrigin ?? axisOrigin,
155
+ axisOffset: spec?.axisOffset ?? axisOffset,
145
156
  sort: spec?.sort,
146
157
  continuousCategory: spec?.continuousCategory,
147
158
  chartPreset,
@@ -41,6 +41,13 @@ type Props = {
41
41
  onselect?: (detail: unknown) => void;
42
42
  selectable?: boolean;
43
43
  selected?: Row[];
44
+ /** Data-coordinate crossing point of the axes: `[xCross, yCross]`. Drives
45
+ * quadrant mode — where the origin sits relative to the domain makes a chart
46
+ * 1- / 2- / 4-quadrant. Omit a component to leave that axis auto (crosses at 0
47
+ * when the domain spans it, else edge-pinned). Also settable via `spec.axisOrigin`. */
48
+ axisOrigin?: [number | undefined, number | undefined];
49
+ /** Nudge an edge-pinned axis outward by N px (Q1 only). Also via `spec.axisOffset`. */
50
+ axisOffset?: number;
44
51
  /** Animate marks on data/flip changes (opt-out for e.g. AnimatedPlot, which
45
52
  * tweens its own frames). Enabled one frame after the width settles so the
46
53
  * initial layout paints un-animated. Default `true`. */
@@ -425,6 +425,7 @@ export class PlotState {
425
425
  this.#selectable = config.selectable ?? false
426
426
  if (config.selected) this.#selected = new SvelteSet(config.selected)
427
427
  this.#axisOffset = config.axisOffset ?? 0
428
+ this.axisOrigin = config.axisOrigin ?? [undefined, undefined]
428
429
  this.#marginOverride = config.margin ?? undefined
429
430
  this.#orientationOverride = config.orientation ?? undefined
430
431
  this.#continuousCategory = config.continuousCategory ?? false
@@ -453,6 +454,7 @@ export class PlotState {
453
454
  if (config.onselect !== undefined) this.#onselect = config.onselect
454
455
  if (config.selectable !== undefined) this.#selectable = config.selectable
455
456
  if (config.axisOffset !== undefined) this.#axisOffset = config.axisOffset
457
+ this.axisOrigin = config.axisOrigin ?? [undefined, undefined]
456
458
  this.#marginOverride = config.margin ?? undefined
457
459
  this.#orientationOverride = config.orientation ?? undefined
458
460
  this.#continuousCategory = config.continuousCategory ?? false
@@ -142,15 +142,40 @@
142
142
  return gen(values)
143
143
  })
144
144
 
145
+ // Area fill anchors at the baseline when one is in effect (so a mixed-sign
146
+ // area fills up above / down below the crossing), else at the pixel bottom.
147
+ const baselineY = $derived(effectiveBaseline !== undefined ? yScale(effectiveBaseline) : null)
148
+
145
149
  const areaPath = $derived.by(() => {
146
150
  const gen = d3area<number>()
147
151
  .x((_, i) => xScale(i))
148
- .y0(height)
152
+ .y0(baselineY ?? height)
149
153
  .y1((v) => yScale(v))
150
154
  if (curve === 'smooth') gen.curve(curveCatmullRom)
151
155
  return gen(values)
152
156
  })
153
157
 
158
+ // When a baseline is in effect, split the fill into above/below regions (each
159
+ // clamped at the baseline) so themes can colour positive vs negative fills.
160
+ const areaAbovePath = $derived.by(() => {
161
+ if (baselineY === null) return null
162
+ const gen = d3area<number>()
163
+ .x((_, i) => xScale(i))
164
+ .y0(baselineY)
165
+ .y1((v) => Math.min(yScale(v), baselineY))
166
+ if (curve === 'smooth') gen.curve(curveCatmullRom)
167
+ return gen(values)
168
+ })
169
+ const areaBelowPath = $derived.by(() => {
170
+ if (baselineY === null) return null
171
+ const gen = d3area<number>()
172
+ .x((_, i) => xScale(i))
173
+ .y0(baselineY)
174
+ .y1((v) => Math.max(yScale(v), baselineY))
175
+ if (curve === 'smooth') gen.curve(curveCatmullRom)
176
+ return gen(values)
177
+ })
178
+
154
179
  const barWidth = $derived(Math.max(1, width / values.length - 1))
155
180
 
156
181
  const strokeColor = $derived(`rgb(var(--color-${color}-500, 100,116,139))`)
@@ -160,7 +185,7 @@
160
185
  const patternMarks = $derived(pattern ? (PATTERNS[pattern] ?? null) : null)
161
186
  </script>
162
187
 
163
- <svg {width} {height} style="overflow: visible; display: block;">
188
+ <svg {width} {height} style="overflow: visible; display: block; --spark-area-fill: {fillColor};">
164
189
  {#if patternMarks}
165
190
  <defs>
166
191
  <PatternDef id={patternId} marks={patternMarks} stroke={strokeColor} />
@@ -177,7 +202,22 @@
177
202
  stroke-linecap="round"
178
203
  />
179
204
  {:else if type === 'area'}
180
- <path d={areaPath} fill={patternMarks ? `url(#${patternId})` : fillColor} stroke="none" />
205
+ {#if baselineY !== null}
206
+ <path
207
+ d={areaAbovePath}
208
+ data-plot-area
209
+ data-plot-area-sign="above"
210
+ fill={patternMarks ? `url(#${patternId})` : undefined}
211
+ />
212
+ <path
213
+ d={areaBelowPath}
214
+ data-plot-area
215
+ data-plot-area-sign="below"
216
+ fill={patternMarks ? `url(#${patternId})` : undefined}
217
+ />
218
+ {:else}
219
+ <path d={areaPath} data-plot-area fill={patternMarks ? `url(#${patternId})` : undefined} />
220
+ {/if}
181
221
  <path
182
222
  d={linePath}
183
223
  fill="none"
@@ -233,6 +273,19 @@
233
273
  </svg>
234
274
 
235
275
  <style>
276
+ [data-plot-area] {
277
+ fill: var(--spark-area-fill, rgba(var(--color-primary-300), 0.25));
278
+ stroke: none;
279
+ }
280
+ /* Positive vs negative regions of a baseline-anchored area — default to the
281
+ same fill; a theme can distinguish them via these vars. */
282
+ [data-plot-area][data-plot-area-sign='above'] {
283
+ fill: var(--chart-area-above-color, var(--spark-area-fill));
284
+ }
285
+ [data-plot-area][data-plot-area-sign='below'] {
286
+ fill: var(--chart-area-below-color, var(--spark-area-fill));
287
+ }
288
+
236
289
  [data-plot-baseline] {
237
290
  stroke: var(--chart-baseline-color, currentColor);
238
291
  stroke-width: var(--chart-baseline-width, 1);
@@ -33,6 +33,7 @@ export type PlotSpec = {
33
33
  xLabel?: string | undefined;
34
34
  yLabel?: string | undefined;
35
35
  axisOrigin?: [number, number] | undefined;
36
+ axisOffset?: number | undefined;
36
37
  colorScale?: "categorical" | "sequential" | "diverging" | undefined;
37
38
  colorScheme?: string | undefined;
38
39
  colorMidpoint?: number | undefined;
@@ -33,6 +33,7 @@
33
33
  * @property {string} [xLabel]
34
34
  * @property {string} [yLabel]
35
35
  * @property {[number, number]} [axisOrigin]
36
+ * @property {number} [axisOffset]
36
37
  * @property {'categorical'|'sequential'|'diverging'} [colorScale]
37
38
  * @property {string} [colorScheme]
38
39
  * @property {number} [colorMidpoint]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/chart",
3
- "version": "1.3.14",
3
+ "version": "1.3.15",
4
4
  "type": "module",
5
5
  "description": "Data-driven chart components",
6
6
  "repository": {