@funnest/charts-react 0.4.0 → 0.5.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.
@@ -1,6 +1,8 @@
1
1
  import type { ChartEvents } from '@funnest/charts';
2
2
  import { type TradingChart, type TradingChartOptions, type TradingChartSlots } from '@funnest/charts-ui';
3
3
  import { type CSSProperties, type ReactNode } from 'react';
4
+ /** Slots of the surrounding <FnChart> or <FnMultiChart>, for <FnChartSlot>. */
5
+ export declare const SlotsContext: import("react").Context<TradingChartSlots | null>;
4
6
  export interface FnChartProps extends TradingChartOptions {
5
7
  className?: string;
6
8
  /** The widget fills its container; give it a height (default 100%). */
@@ -21,7 +23,7 @@ export declare function useFnChart(): TradingChart | null;
21
23
  /** Subscribes to a chart event of the surrounding `<FnChart>` for the component's lifetime. */
22
24
  export declare function useChartEvent<K extends keyof ChartEvents>(event: K, handler: (payload: ChartEvents[K]) => void): void;
23
25
  /**
24
- * Renders React children into one of the widget's slots ('toolbar', 'belowToolbar', 'footer')
26
+ * Renders React children into one of the widget's slots ('toolbar', 'toolbarEnd', 'belowToolbar', 'bottom', 'footer')
25
27
  * — e.g. a market stats bar or account info — styled with the widget's `--fnc-*` variables.
26
28
  */
27
29
  export declare function FnChartSlot({ name, children }: {
@@ -0,0 +1,20 @@
1
+ import { type MultiChart, type MultiChartOptions } from '@funnest/charts-ui';
2
+ import { type CSSProperties, type ReactNode } from 'react';
3
+ export interface FnMultiChartProps extends MultiChartOptions {
4
+ className?: string;
5
+ /** The widget fills its container; give it a height (default 100%). */
6
+ style?: CSSProperties;
7
+ /** Called once per created widget (also after it's recreated for a new datafeed). */
8
+ onReady?: (widget: MultiChart) => void;
9
+ /** `<FnChartSlot>` elements and other children that use `useFnMultiChart()`. */
10
+ children?: ReactNode;
11
+ }
12
+ /**
13
+ * React wrapper around `createMultiChart`: split layout of 1–8 linked charts with one toolbar.
14
+ * `panes`, `layout` and `sync` are read on mount (the user changes them from the toolbar; use
15
+ * the ref's `setLayout`/`setSync`/`load` to drive them). `locale`, `theme` and `colorScheme`
16
+ * stay in sync with props; a new `datafeed` recreates the widget.
17
+ */
18
+ export declare const FnMultiChart: import("react").ForwardRefExoticComponent<FnMultiChartProps & import("react").RefAttributes<MultiChart | null>>;
19
+ /** The surrounding `<FnMultiChart>`'s widget (null until mounted). */
20
+ export declare function useFnMultiChart(): MultiChart | null;
package/dist/index.d.ts CHANGED
@@ -13,5 +13,7 @@
13
13
  */
14
14
  export { FnChart, FnChartSlot, useChartEvent, useFnChart } from './fn-chart';
15
15
  export type { FnChartProps } from './fn-chart';
16
- export type { TradingChart, TradingChartOptions, TradingChartSlots } from '@funnest/charts-ui';
16
+ export { FnMultiChart, useFnMultiChart } from './fn-multi-chart';
17
+ export type { FnMultiChartProps } from './fn-multi-chart';
18
+ export type { LayoutId, LayoutSync, MultiChart, MultiChartEvents, MultiChartOptions, MultiChartState, PaneSpec, TradingChart, TradingChartOptions, TradingChartSlots, } from '@funnest/charts-ui';
17
19
  export * from './viz';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  import { createPortal } from "react-dom";
13
13
  import { jsx } from "react/jsx-runtime";
14
14
  var WidgetContext = createContext(null);
15
+ var SlotsContext = createContext(null);
15
16
  var FnChart = forwardRef(function FnChart2(props, ref) {
16
17
  const { className, style, onReady, children, ...options } = props;
17
18
  const hostRef = useRef(null);
@@ -51,7 +52,7 @@ var FnChart = forwardRef(function FnChart2(props, ref) {
51
52
  useEffect(() => {
52
53
  if (widget && theme) widget.setTheme(theme);
53
54
  }, [widget, themeKey]);
54
- return /* @__PURE__ */ jsx("div", { ref: hostRef, className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ jsx(WidgetContext.Provider, { value: widget, children }) });
55
+ return /* @__PURE__ */ jsx("div", { ref: hostRef, className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ jsx(WidgetContext.Provider, { value: widget, children: /* @__PURE__ */ jsx(SlotsContext.Provider, { value: widget?.slots ?? null, children }) }) });
55
56
  });
56
57
  function useFnChart() {
57
58
  return useContext(WidgetContext);
@@ -63,8 +64,50 @@ function useChartEvent(event, handler) {
63
64
  useEffect(() => widget?.chart.on(event, (payload) => handlerRef.current(payload)), [widget, event]);
64
65
  }
65
66
  function FnChartSlot({ name, children }) {
66
- const widget = useFnChart();
67
- return widget ? createPortal(children, widget.slots[name]) : null;
67
+ const slots = useContext(SlotsContext);
68
+ return slots ? createPortal(children, slots[name]) : null;
69
+ }
70
+
71
+ // src/fn-multi-chart.tsx
72
+ import { createMultiChart } from "@funnest/charts-ui";
73
+ import { createContext as createContext2, forwardRef as forwardRef2, useContext as useContext2, useEffect as useEffect2, useImperativeHandle as useImperativeHandle2, useRef as useRef2, useState as useState2 } from "react";
74
+ import { jsx as jsx2 } from "react/jsx-runtime";
75
+ var MultiContext = createContext2(null);
76
+ var FnMultiChart = forwardRef2(function FnMultiChart2(props, ref) {
77
+ const { className, style, onReady, children, ...options } = props;
78
+ const hostRef = useRef2(null);
79
+ const [widget, setWidget] = useState2(null);
80
+ const latest = useRef2(options);
81
+ latest.current = options;
82
+ const readyRef = useRef2(onReady);
83
+ readyRef.current = onReady;
84
+ useImperativeHandle2(ref, () => widget, [widget]);
85
+ useEffect2(() => {
86
+ const host = hostRef.current;
87
+ if (!host) return;
88
+ const instance = createMultiChart(host, latest.current);
89
+ setWidget(instance);
90
+ readyRef.current?.(instance);
91
+ return () => {
92
+ setWidget(null);
93
+ instance.destroy();
94
+ };
95
+ }, [options.datafeed]);
96
+ const { locale, theme, colorScheme } = options;
97
+ useEffect2(() => {
98
+ if (widget && locale) widget.setLocale(locale);
99
+ }, [widget, locale]);
100
+ useEffect2(() => {
101
+ if (widget && colorScheme) widget.setColorScheme(colorScheme);
102
+ }, [widget, colorScheme]);
103
+ const themeKey = JSON.stringify(theme ?? null);
104
+ useEffect2(() => {
105
+ if (widget && theme) widget.setTheme(theme);
106
+ }, [widget, themeKey]);
107
+ return /* @__PURE__ */ jsx2("div", { ref: hostRef, className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ jsx2(MultiContext.Provider, { value: widget, children: /* @__PURE__ */ jsx2(SlotsContext.Provider, { value: widget?.slots ?? null, children }) }) });
108
+ });
109
+ function useFnMultiChart() {
110
+ return useContext2(MultiContext);
68
111
  }
69
112
 
70
113
  // src/viz/cartesian-chart.tsx
@@ -84,19 +127,19 @@ import {
84
127
  Children,
85
128
  isValidElement,
86
129
  useCallback,
87
- useEffect as useEffect2,
130
+ useEffect as useEffect3,
88
131
  useId,
89
132
  useMemo,
90
- useRef as useRef2,
91
- useState as useState2
133
+ useRef as useRef3,
134
+ useState as useState3
92
135
  } from "react";
93
136
 
94
137
  // src/viz/context.ts
95
- import { createContext as createContext2, useContext as useContext2 } from "react";
138
+ import { createContext as createContext3, useContext as useContext3 } from "react";
96
139
  var nearScrub = (ctx, x, within = 56) => ctx.scrubIndex !== null && Math.abs(ctx.x(ctx.scrubIndex) - x) < within;
97
- var CartesianContext = createContext2(null);
140
+ var CartesianContext = createContext3(null);
98
141
  function useCartesianChart() {
99
- const ctx = useContext2(CartesianContext);
142
+ const ctx = useContext3(CartesianContext);
100
143
  if (!ctx) throw new Error("Funnest chart components must be rendered inside <CartesianChart> (or LineChart, AreaChart, BarChart).");
101
144
  return ctx;
102
145
  }
@@ -126,7 +169,7 @@ function seriesY(ctx, s, i) {
126
169
  var scaleFor = (ctx, axisId) => (axisId ? ctx.yScales[axisId] : void 0) ?? ctx.y;
127
170
 
128
171
  // src/viz/legend.tsx
129
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
172
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
130
173
  function Legend({ items, onItemClick, onItemHover, shape = "circle", direction = "row", justify = "flex-start", className, style }) {
131
174
  const swatch = (color) => {
132
175
  switch (shape) {
@@ -142,7 +185,7 @@ function Legend({ items, onItemClick, onItemHover, shape = "circle", direction =
142
185
  return { width: 8, height: 8, borderRadius: "50%", background: color };
143
186
  }
144
187
  };
145
- return /* @__PURE__ */ jsx2(
188
+ return /* @__PURE__ */ jsx3(
146
189
  "div",
147
190
  {
148
191
  className,
@@ -150,13 +193,13 @@ function Legend({ items, onItemClick, onItemHover, shape = "circle", direction =
150
193
  style: { display: "flex", flexDirection: direction, flexWrap: direction === "row" ? "wrap" : "nowrap", justifyContent: justify, gap: direction === "row" ? "4px 14px" : 8, padding: "4px 0", fontSize: 12, color: VAR.text, fontFamily: VAR.font, ...style },
151
194
  children: items.map((item) => {
152
195
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
153
- /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", style: { flex: "none", ...swatch(item.color) } }),
154
- /* @__PURE__ */ jsx2("span", { children: item.label }),
155
- item.value !== void 0 && /* @__PURE__ */ jsx2("b", { style: { color: VAR.textStrong, fontWeight: 600, fontVariantNumeric: "tabular-nums" }, children: item.value })
196
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", style: { flex: "none", ...swatch(item.color) } }),
197
+ /* @__PURE__ */ jsx3("span", { children: item.label }),
198
+ item.value !== void 0 && /* @__PURE__ */ jsx3("b", { style: { color: VAR.textStrong, fontWeight: 600, fontVariantNumeric: "tabular-nums" }, children: item.value })
156
199
  ] });
157
200
  const itemStyle = { display: "inline-flex", alignItems: "center", gap: 6, opacity: item.inactive ? 0.4 : 1, transition: "opacity 0.15s" };
158
201
  const hover = onItemHover ? { onPointerEnter: () => onItemHover(item.id), onPointerLeave: () => onItemHover(null) } : {};
159
- return onItemClick ? /* @__PURE__ */ jsx2(
202
+ return onItemClick ? /* @__PURE__ */ jsx3(
160
203
  "button",
161
204
  {
162
205
  type: "button",
@@ -167,14 +210,14 @@ function Legend({ items, onItemClick, onItemHover, shape = "circle", direction =
167
210
  children: content
168
211
  },
169
212
  item.id
170
- ) : /* @__PURE__ */ jsx2("span", { role: "listitem", style: itemStyle, ...hover, children: content }, item.id);
213
+ ) : /* @__PURE__ */ jsx3("span", { role: "listitem", style: itemStyle, ...hover, children: content }, item.id);
171
214
  })
172
215
  }
173
216
  );
174
217
  }
175
218
 
176
219
  // src/viz/cartesian-chart.tsx
177
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
220
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
178
221
  var DEFAULT_HEIGHT = 240;
179
222
  var DEFAULT_AXIS = "default";
180
223
  function resolveInset(inset) {
@@ -195,9 +238,9 @@ function collectAxisSpace(children, acc) {
195
238
  });
196
239
  }
197
240
  function useElementWidth(fixed) {
198
- const ref = useRef2(null);
199
- const [width, setWidth] = useState2(fixed ?? 0);
200
- useEffect2(() => {
241
+ const ref = useRef3(null);
242
+ const [width, setWidth] = useState3(fixed ?? 0);
243
+ useEffect3(() => {
201
244
  if (fixed !== void 0) {
202
245
  setWidth(fixed);
203
246
  return;
@@ -240,13 +283,13 @@ function CartesianChart({
240
283
  }) {
241
284
  const id = `fnv${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
242
285
  const [wrapRef, width] = useElementWidth(fixedWidth);
243
- const [scrubIndex, setScrubIndex] = useState2(null);
244
- const [focused, setFocused] = useState2(false);
245
- const [motion, setMotion] = useState2(false);
246
- const [hidden, setHidden] = useState2([]);
247
- const [emphasis, setEmphasis] = useState2(null);
248
- const [dimFuture, setDimFuture] = useState2(false);
249
- useEffect2(() => setMotion(animate && !prefersReducedMotion()), [animate]);
286
+ const [scrubIndex, setScrubIndex] = useState3(null);
287
+ const [focused, setFocused] = useState3(false);
288
+ const [motion, setMotion] = useState3(false);
289
+ const [hidden, setHidden] = useState3([]);
290
+ const [emphasis, setEmphasis] = useState3(null);
291
+ const [dimFuture, setDimFuture] = useState3(false);
292
+ useEffect3(() => setMotion(animate && !prefersReducedMotion()), [animate]);
250
293
  const legendCfg = normalizeLegend(legend);
251
294
  const axes = useMemo(() => {
252
295
  const list = Array.isArray(yAxis) ? yAxis : [yAxis ?? {}];
@@ -332,7 +375,7 @@ function CartesianChart({
332
375
  setDimFuture
333
376
  };
334
377
  }, [id, width, height, inset, axisSpace, all, visible, axes, firstAxis, xAxis, stacked, scrubIndex, focused, motion, dimFuture]);
335
- const xsRef = useRef2([]);
378
+ const xsRef = useRef3([]);
336
379
  xsRef.current = useMemo(() => Array.from({ length: ctx.length }, (_, i) => ctx.x(i)), [ctx.length, ctx.x]);
337
380
  const updateScrub = useCallback(
338
381
  (next) => {
@@ -369,7 +412,7 @@ function CartesianChart({
369
412
  };
370
413
  const hasData = all.some((s) => s.data.some((v) => v !== null && Number.isFinite(v)));
371
414
  const a11yText = (i) => getScrubberAccessibilityLabel ? getScrubberAccessibilityLabel(i) : [ctx.xLabel(i), ...ctx.series.map((s) => `${s.label ?? s.id} ${s.data[i] === null || s.data[i] === void 0 ? "\u2014" : formatNumber(s.data[i])}`)].join(", ");
372
- const legendEl = legendCfg && /* @__PURE__ */ jsx3(
415
+ const legendEl = legendCfg && /* @__PURE__ */ jsx4(
373
416
  Legend,
374
417
  {
375
418
  direction: legendCfg.position === "left" || legendCfg.position === "right" ? "column" : "row",
@@ -421,11 +464,11 @@ function CartesianChart({
421
464
  children: [
422
465
  ctx.futureMask && // Fade everything right of the scrubber to 35% (luminance mask: works on any background).
423
466
  /* @__PURE__ */ jsxs2("mask", { id: `${id}-future`, maskUnits: "userSpaceOnUse", x: -width, y: -height, width: width * 3, height: height * 3, children: [
424
- /* @__PURE__ */ jsx3("rect", { x: -width, y: -height, width: width + ctx.x(scrubIndex), height: height * 3, fill: "#fff" }),
425
- /* @__PURE__ */ jsx3("rect", { x: ctx.x(scrubIndex), y: -height, width: width * 2, height: height * 3, fill: "#fff", fillOpacity: 0.35 })
467
+ /* @__PURE__ */ jsx4("rect", { x: -width, y: -height, width: width + ctx.x(scrubIndex), height: height * 3, fill: "#fff" }),
468
+ /* @__PURE__ */ jsx4("rect", { x: ctx.x(scrubIndex), y: -height, width: width * 2, height: height * 3, fill: "#fff", fillOpacity: 0.35 })
426
469
  ] }),
427
- width > 0 && hasData && /* @__PURE__ */ jsx3(CartesianContext.Provider, { value: ctx, children }),
428
- width > 0 && !hasData && /* @__PURE__ */ jsx3("text", { x: width / 2, y: height / 2, textAnchor: "middle", dominantBaseline: "central", fontSize: 13, style: { fill: VAR.text, fontFamily: VAR.font }, children: typeof emptyState === "string" || emptyState === void 0 ? emptyState ?? "" : null })
470
+ width > 0 && hasData && /* @__PURE__ */ jsx4(CartesianContext.Provider, { value: ctx, children }),
471
+ width > 0 && !hasData && /* @__PURE__ */ jsx4("text", { x: width / 2, y: height / 2, textAnchor: "middle", dominantBaseline: "central", fontSize: 13, style: { fill: VAR.text, fontFamily: VAR.font }, children: typeof emptyState === "string" || emptyState === void 0 ? emptyState ?? "" : null })
429
472
  ]
430
473
  }
431
474
  );
@@ -442,11 +485,11 @@ function CartesianChart({
442
485
  },
443
486
  children: [
444
487
  legendCfg?.position === "top" || legendCfg && !legendCfg.position ? legendEl : null,
445
- side ? /* @__PURE__ */ jsx3("div", { style: { flex: 1, minWidth: 0 }, children: svg }) : svg,
488
+ side ? /* @__PURE__ */ jsx4("div", { style: { flex: 1, minWidth: 0 }, children: svg }) : svg,
446
489
  side && legendEl,
447
490
  legendCfg?.position === "bottom" && legendEl,
448
- !hasData && emptyState !== void 0 && typeof emptyState !== "string" && /* @__PURE__ */ jsx3("div", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center" }, children: emptyState }),
449
- enableScrubbing && /* @__PURE__ */ jsx3("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && scrubIndex !== null ? a11yText(scrubIndex) : "" })
491
+ !hasData && emptyState !== void 0 && typeof emptyState !== "string" && /* @__PURE__ */ jsx4("div", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center" }, children: emptyState }),
492
+ enableScrubbing && /* @__PURE__ */ jsx4("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && scrubIndex !== null ? a11yText(scrubIndex) : "" })
450
493
  ]
451
494
  }
452
495
  );
@@ -454,7 +497,7 @@ function CartesianChart({
454
497
 
455
498
  // src/viz/axes.tsx
456
499
  import { logTicks, sampleIndices, ticks as niceTicks } from "@funnest/charts-viz";
457
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
500
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
458
501
  function XAxis({
459
502
  position = "bottom",
460
503
  height = 24,
@@ -472,15 +515,15 @@ function XAxis({
472
515
  const lineY = position === "bottom" ? ctx.height - height : height;
473
516
  const textY = position === "bottom" ? lineY + height / 2 + 4 : lineY - height / 2 + 4;
474
517
  return /* @__PURE__ */ jsxs3("g", { "aria-hidden": "true", style: { fontFamily: VAR.font }, children: [
475
- showGrid && indices.map((i) => /* @__PURE__ */ jsx4("line", { x1: ctx.x(i), x2: ctx.x(i), y1: plot.top, y2: plot.bottom, shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${i}`)),
476
- showLine && /* @__PURE__ */ jsx4("line", { x1: plot.left, x2: plot.right, y1: lineY, y2: lineY, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
518
+ showGrid && indices.map((i) => /* @__PURE__ */ jsx5("line", { x1: ctx.x(i), x2: ctx.x(i), y1: plot.top, y2: plot.bottom, shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${i}`)),
519
+ showLine && /* @__PURE__ */ jsx5("line", { x1: plot.left, x2: plot.right, y1: lineY, y2: lineY, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
477
520
  indices.map((i) => {
478
521
  const x = ctx.x(i);
479
522
  const label = tickLabelFormatter ? tickLabelFormatter(ctx.xData?.[i], i) : ctx.xLabel(i);
480
523
  const anchor = x - plot.left < 24 ? "start" : plot.right - x < 24 ? "end" : "middle";
481
524
  return /* @__PURE__ */ jsxs3("g", { children: [
482
- showTickMarks && /* @__PURE__ */ jsx4("line", { x1: x, x2: x, y1: lineY, y2: position === "bottom" ? lineY + 4 : lineY - 4, style: { stroke: VAR.line } }),
483
- /* @__PURE__ */ jsx4("text", { x, y: textY, textAnchor: anchor, fontSize: 11, style: { fill: VAR.text }, children: label })
525
+ showTickMarks && /* @__PURE__ */ jsx5("line", { x1: x, x2: x, y1: lineY, y2: position === "bottom" ? lineY + 4 : lineY - 4, style: { stroke: VAR.line } }),
526
+ /* @__PURE__ */ jsx5("text", { x, y: textY, textAnchor: anchor, fontSize: 11, style: { fill: VAR.text }, children: label })
484
527
  ] }, i);
485
528
  })
486
529
  ] });
@@ -508,16 +551,16 @@ function YAxis({
508
551
  const textX = right ? ctx.width - width + 8 : width - 8;
509
552
  const lineX = right ? ctx.width - width : width;
510
553
  return /* @__PURE__ */ jsxs3("g", { "aria-hidden": "true", style: { fontFamily: VAR.font }, children: [
511
- showGrid && values.map((v) => /* @__PURE__ */ jsx4("line", { x1: plot.left, x2: plot.right, y1: y(v), y2: y(v), shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${v}`)),
512
- showLine && /* @__PURE__ */ jsx4("line", { x1: lineX, x2: lineX, y1: plot.top, y2: plot.bottom, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
513
- values.map((v) => /* @__PURE__ */ jsx4("text", { x: textX, y: y(v) + 4, textAnchor: right ? "start" : "end", fontSize: 11, style: { fill: VAR.text }, children: tickLabelFormatter(v) }, v))
554
+ showGrid && values.map((v) => /* @__PURE__ */ jsx5("line", { x1: plot.left, x2: plot.right, y1: y(v), y2: y(v), shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${v}`)),
555
+ showLine && /* @__PURE__ */ jsx5("line", { x1: lineX, x2: lineX, y1: plot.top, y2: plot.bottom, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
556
+ values.map((v) => /* @__PURE__ */ jsx5("text", { x: textX, y: y(v) + 4, textAnchor: right ? "start" : "end", fontSize: 11, style: { fill: VAR.text }, children: tickLabelFormatter(v) }, v))
514
557
  ] });
515
558
  }
516
559
  YAxis.axisSpace = (p) => ({ side: p.position ?? "right", size: p.width ?? 48 });
517
560
 
518
561
  // src/viz/line.tsx
519
562
  import { areaPath, linePath, segments } from "@funnest/charts-viz";
520
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
563
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
521
564
  function seriesRuns(ctx, s, connectNulls = false) {
522
565
  const st = ctx.stacked?.get(s.id);
523
566
  const y = s.scale;
@@ -543,8 +586,8 @@ function SplitClips({ id, y, ctx }) {
543
586
  const { left, right, top, bottom } = ctx.plot;
544
587
  const pad = 20;
545
588
  return /* @__PURE__ */ jsxs4("defs", { children: [
546
- /* @__PURE__ */ jsx5("clipPath", { id: `${id}-above`, children: /* @__PURE__ */ jsx5("rect", { x: left - pad, y: top - pad, width: right - left + pad * 2, height: Math.max(0, y - top + pad) }) }),
547
- /* @__PURE__ */ jsx5("clipPath", { id: `${id}-below`, children: /* @__PURE__ */ jsx5("rect", { x: left - pad, y, width: right - left + pad * 2, height: Math.max(0, bottom - y + pad) }) })
589
+ /* @__PURE__ */ jsx6("clipPath", { id: `${id}-above`, children: /* @__PURE__ */ jsx6("rect", { x: left - pad, y: top - pad, width: right - left + pad * 2, height: Math.max(0, y - top + pad) }) }),
590
+ /* @__PURE__ */ jsx6("clipPath", { id: `${id}-below`, children: /* @__PURE__ */ jsx6("rect", { x: left - pad, y, width: right - left + pad * 2, height: Math.max(0, bottom - y + pad) }) })
548
591
  ] });
549
592
  }
550
593
  function Area({ seriesId, curve = "monotone", fill = "gradient", opacity, connectNulls, baseline, splitColors, gradientStops }) {
@@ -564,17 +607,17 @@ function Area({ seriesId, curve = "monotone", fill = "gradient", opacity, connec
564
607
  ];
565
608
  return /* @__PURE__ */ jsxs4("g", { clipPath: clip ? `url(#${clip})` : void 0, opacity: dim, children: [
566
609
  /* @__PURE__ */ jsxs4("defs", { children: [
567
- fill === "gradient" && /* @__PURE__ */ jsx5("linearGradient", { id: fillId, x1: "0", y1: from, x2: "0", y2: base, gradientUnits: "userSpaceOnUse", children: stops.map((st, i) => /* @__PURE__ */ jsx5("stop", { offset: st.offset, stopOpacity: st.opacity, style: { stopColor: st.color ?? color } }, i)) }),
568
- fill === "dotted" && /* @__PURE__ */ jsx5("pattern", { id: fillId, width: "6", height: "6", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx5("circle", { cx: "1.5", cy: "1.5", r: "1.1", style: { fill: color } }) })
610
+ fill === "gradient" && /* @__PURE__ */ jsx6("linearGradient", { id: fillId, x1: "0", y1: from, x2: "0", y2: base, gradientUnits: "userSpaceOnUse", children: stops.map((st, i) => /* @__PURE__ */ jsx6("stop", { offset: st.offset, stopOpacity: st.opacity, style: { stopColor: st.color ?? color } }, i)) }),
611
+ fill === "dotted" && /* @__PURE__ */ jsx6("pattern", { id: fillId, width: "6", height: "6", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx6("circle", { cx: "1.5", cy: "1.5", r: "1.1", style: { fill: color } }) })
569
612
  ] }),
570
- runs.map((run, k) => /* @__PURE__ */ jsx5(
613
+ runs.map((run, k) => /* @__PURE__ */ jsx6(
571
614
  "path",
572
615
  {
573
616
  d: areaPath(run.upper, run.lower ?? base, curve),
574
617
  fillOpacity: fill === "gradient" ? void 0 : alpha,
575
618
  stroke: "none",
576
619
  style: { fill: fill === "solid" ? color : `url(#${fillId})` },
577
- children: ctx.animate && /* @__PURE__ */ jsx5("animate", { attributeName: "opacity", from: "0", to: "1", dur: "0.6s", fill: "freeze" })
620
+ children: ctx.animate && /* @__PURE__ */ jsx6("animate", { attributeName: "opacity", from: "0", to: "1", dur: "0.6s", fill: "freeze" })
578
621
  },
579
622
  k
580
623
  ))
@@ -582,12 +625,12 @@ function Area({ seriesId, curve = "monotone", fill = "gradient", opacity, connec
582
625
  };
583
626
  if (splitColors && baseline !== void 0 && !run0IsStacked(ctx, s)) {
584
627
  return /* @__PURE__ */ jsxs4("g", { mask: ctx.futureMask, children: [
585
- /* @__PURE__ */ jsx5(SplitClips, { id: gid, y: base, ctx }),
628
+ /* @__PURE__ */ jsx6(SplitClips, { id: gid, y: base, ctx }),
586
629
  paint("above", splitColors.above, ctx.plot.top, `${gid}-above`),
587
630
  paint("below", splitColors.below, ctx.plot.bottom, `${gid}-below`)
588
631
  ] });
589
632
  }
590
- return /* @__PURE__ */ jsx5("g", { mask: ctx.futureMask, children: paint("fill", s.color, ctx.plot.top) });
633
+ return /* @__PURE__ */ jsx6("g", { mask: ctx.futureMask, children: paint("fill", s.color, ctx.plot.top) });
591
634
  }
592
635
  var run0IsStacked = (ctx, s) => !!ctx.stacked?.get(s.id);
593
636
  function Line({
@@ -615,7 +658,7 @@ function Line({
615
658
  const gid = `${ctx.id}-line-${s.index}`;
616
659
  const baseY = baselineY(ctx, s.scale, baseline);
617
660
  const strokeOpacity = (opacity ?? 1) * (s.dimmed ? DIM_OPACITY : 1);
618
- const strokes = (color, clip) => /* @__PURE__ */ jsx5("g", { clipPath: clip ? `url(#${clip})` : void 0, mask: ctx.futureMask, children: runs.map((run, k) => /* @__PURE__ */ jsx5(
661
+ const strokes = (color, clip) => /* @__PURE__ */ jsx6("g", { clipPath: clip ? `url(#${clip})` : void 0, mask: ctx.futureMask, children: runs.map((run, k) => /* @__PURE__ */ jsx6(
619
662
  "path",
620
663
  {
621
664
  d: linePath(run.upper, curve),
@@ -627,7 +670,7 @@ function Line({
627
670
  strokeOpacity,
628
671
  pathLength: drawIn ? 1 : void 0,
629
672
  style: { stroke: color },
630
- children: drawIn && /* @__PURE__ */ jsx5("animate", { attributeName: "stroke-dashoffset", from: "1", to: "0", dur: "0.8s", fill: "freeze", calcMode: "spline", keySplines: "0.3 0 0.2 1", keyTimes: "0;1" })
673
+ children: drawIn && /* @__PURE__ */ jsx6("animate", { attributeName: "stroke-dashoffset", from: "1", to: "0", dur: "0.8s", fill: "freeze", calcMode: "spline", keySplines: "0.3 0 0.2 1", keyTimes: "0;1" })
631
674
  },
632
675
  k
633
676
  )) });
@@ -645,8 +688,8 @@ function Line({
645
688
  const r = p.radius ?? Math.max(3, strokeWidth + 1);
646
689
  dots.push(
647
690
  /* @__PURE__ */ jsxs4("g", { opacity: s.dimmed ? DIM_OPACITY : 1, children: [
648
- /* @__PURE__ */ jsx5("circle", { cx: x, cy: y, r, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } }),
649
- p.label && /* @__PURE__ */ jsx5(
691
+ /* @__PURE__ */ jsx6("circle", { cx: x, cy: y, r, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } }),
692
+ p.label && /* @__PURE__ */ jsx6(
650
693
  "text",
651
694
  {
652
695
  opacity: nearScrub(ctx, x) ? 0 : 1,
@@ -667,7 +710,7 @@ function Line({
667
710
  });
668
711
  }
669
712
  return /* @__PURE__ */ jsxs4("g", { children: [
670
- area !== "none" && /* @__PURE__ */ jsx5(
713
+ area !== "none" && /* @__PURE__ */ jsx6(
671
714
  Area,
672
715
  {
673
716
  seriesId,
@@ -680,7 +723,7 @@ function Line({
680
723
  }
681
724
  ),
682
725
  split ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
683
- /* @__PURE__ */ jsx5(SplitClips, { id: gid, y: baseY, ctx }),
726
+ /* @__PURE__ */ jsx6(SplitClips, { id: gid, y: baseY, ctx }),
684
727
  strokes(splitColors.above, `${gid}-above`),
685
728
  strokes(splitColors.below, `${gid}-below`)
686
729
  ] }) : strokes(s.color),
@@ -690,7 +733,7 @@ function Line({
690
733
 
691
734
  // src/viz/bar.tsx
692
735
  import { barPath } from "@funnest/charts-viz";
693
- import { jsx as jsx6 } from "react/jsx-runtime";
736
+ import { jsx as jsx7 } from "react/jsx-runtime";
694
737
  function BarPlot({
695
738
  seriesIds,
696
739
  radius = 3,
@@ -712,7 +755,7 @@ function BarPlot({
712
755
  return scale(Math.min(Math.max(0, Math.min(d0, d1)), Math.max(d0, d1)));
713
756
  };
714
757
  const dim = highlightScrubbed && ctx.scrubIndex !== null;
715
- return /* @__PURE__ */ jsx6("g", { children: list.map((s, k) => {
758
+ return /* @__PURE__ */ jsx7("g", { children: list.map((s, k) => {
716
759
  const st = ctx.stacked?.get(s.id);
717
760
  const y = s.scale;
718
761
  const zero = zeroOf(y);
@@ -721,7 +764,7 @@ function BarPlot({
721
764
  const ov = o.data[i];
722
765
  return ov !== null && ov !== void 0 && ov !== 0 && Math.sign(ov) === Math.sign(v);
723
766
  });
724
- return /* @__PURE__ */ jsx6("g", { opacity: s.dimmed ? DIM_OPACITY : 1, children: s.data.map((v, i) => {
767
+ return /* @__PURE__ */ jsx7("g", { opacity: s.dimmed ? DIM_OPACITY : 1, children: s.data.map((v, i) => {
725
768
  if (v === null || v === void 0 || !Number.isFinite(v) || v === 0) return null;
726
769
  const x = band(i) + offset;
727
770
  const low = s.low?.[i];
@@ -737,13 +780,13 @@ function BarPlot({
737
780
  if (minSize > 0 && Math.abs(y1 - y0) < minSize) y1 = y0 + dir * minSize;
738
781
  const color = getBarColor?.(v, i, s.id) ?? s.color;
739
782
  const r = top ? radius : st && stackGap > 0 ? Math.min(radius, 2) : 0;
740
- return /* @__PURE__ */ jsx6(
783
+ return /* @__PURE__ */ jsx7(
741
784
  "path",
742
785
  {
743
786
  d: barPath(x, barW, y0, y1, s.low ? radius : r, roundBaseline && atBase || !!s.low),
744
787
  fillOpacity: dim && ctx.scrubIndex !== i ? 0.45 : 1,
745
788
  style: { fill: color },
746
- children: ctx.animate && /* @__PURE__ */ jsx6("animate", { attributeName: "opacity", from: "0", to: "1", dur: "0.5s", fill: "freeze" })
789
+ children: ctx.animate && /* @__PURE__ */ jsx7("animate", { attributeName: "opacity", from: "0", to: "1", dur: "0.5s", fill: "freeze" })
747
790
  },
748
791
  i
749
792
  );
@@ -753,8 +796,8 @@ function BarPlot({
753
796
 
754
797
  // src/viz/horizontal-bar.tsx
755
798
  import { bandScale as bandScale2, barPath as barPath2, extent as extent2, linearScale as linearScale2, seriesColor as seriesColor2, stack as stack2, stackedExtent as stackedExtent2, ticks as niceTicks2 } from "@funnest/charts-viz";
756
- import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
757
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
799
+ import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
800
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
758
801
  var textWidth = (t, size = 12) => [...t].reduce((w, ch) => w + (ch.charCodeAt(0) > 11904 ? size : size * 0.6), 0);
759
802
  function HorizontalBarChart({
760
803
  series,
@@ -777,11 +820,11 @@ function HorizontalBarChart({
777
820
  style,
778
821
  "aria-label": ariaLabel
779
822
  }) {
780
- const wrapRef = useRef3(null);
781
- const [measured, setMeasured] = useState3(fixedWidth ?? 0);
782
- const [hover, setHover] = useState3(null);
783
- const [focused, setFocused] = useState3(false);
784
- useEffect3(() => {
823
+ const wrapRef = useRef4(null);
824
+ const [measured, setMeasured] = useState4(fixedWidth ?? 0);
825
+ const [hover, setHover] = useState4(null);
826
+ const [focused, setFocused] = useState4(false);
827
+ useEffect4(() => {
785
828
  if (fixedWidth !== void 0) return setMeasured(fixedWidth);
786
829
  const el = wrapRef.current;
787
830
  if (!el || typeof ResizeObserver === "undefined") return;
@@ -845,14 +888,14 @@ function HorizontalBarChart({
845
888
  const rowY = hover === null ? 0 : band.center(hover);
846
889
  const tipY = Math.min(Math.max(top, rowY - tipH / 2), bottom - tipH);
847
890
  return /* @__PURE__ */ jsxs5("div", { ref: wrapRef, className, style: { position: "relative", width: fixedWidth ?? "100%", fontFamily: VAR.font, ...style }, children: [
848
- legend && /* @__PURE__ */ jsx7(
891
+ legend && /* @__PURE__ */ jsx8(
849
892
  Legend,
850
893
  {
851
894
  ...typeof legend === "object" && legend.shape ? { shape: legend.shape } : {},
852
895
  items: resolved.map((s) => ({ id: s.id, label: s.label ?? s.id, color: s.color }))
853
896
  }
854
897
  ),
855
- /* @__PURE__ */ jsx7(
898
+ /* @__PURE__ */ jsx8(
856
899
  "svg",
857
900
  {
858
901
  width: w || "100%",
@@ -870,12 +913,12 @@ function HorizontalBarChart({
870
913
  setHover(null);
871
914
  } : void 0,
872
915
  children: w > 0 && /* @__PURE__ */ jsxs5(Fragment3, { children: [
873
- tickValues.map((t) => /* @__PURE__ */ jsx7("line", { x1: xs(t), x2: xs(t), y1: top, y2: bottom, shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${t}`)),
874
- min < 0 && /* @__PURE__ */ jsx7("line", { x1: zero, x2: zero, y1: top, y2: bottom, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
875
- hover !== null && /* @__PURE__ */ jsx7("rect", { x: left - 4, y: band.center(hover) - band.step / 2, width: right - left + 8, height: band.step, rx: 6, style: { fill: VAR.grid } }),
916
+ tickValues.map((t) => /* @__PURE__ */ jsx8("line", { x1: xs(t), x2: xs(t), y1: top, y2: bottom, shapeRendering: "crispEdges", style: { stroke: VAR.grid } }, `g${t}`)),
917
+ min < 0 && /* @__PURE__ */ jsx8("line", { x1: zero, x2: zero, y1: top, y2: bottom, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
918
+ hover !== null && /* @__PURE__ */ jsx8("rect", { x: left - 4, y: band.center(hover) - band.step / 2, width: right - left + 8, height: band.step, rx: 6, style: { fill: VAR.grid } }),
876
919
  resolved.map((s, k) => {
877
920
  const st = stackedMap?.get(s.id);
878
- return /* @__PURE__ */ jsx7("g", { children: s.high.map((v, i) => {
921
+ return /* @__PURE__ */ jsx8("g", { children: s.high.map((v, i) => {
879
922
  if (v === null || v === void 0 || !Number.isFinite(v) || v === 0) return null;
880
923
  const yTop = band(i) + (stacked ? 0 : k * (barH + groupGap));
881
924
  const lo = s.low?.[i];
@@ -889,7 +932,7 @@ function HorizontalBarChart({
889
932
  }
890
933
  const color = getBarColor?.(v, i, s.id) ?? s.color;
891
934
  const d = barPath2(yTop, barH, x0, x1, isTop || s.low ? radius : 0, !!s.low);
892
- return /* @__PURE__ */ jsx7(
935
+ return /* @__PURE__ */ jsx8(
893
936
  "path",
894
937
  {
895
938
  d,
@@ -901,27 +944,27 @@ function HorizontalBarChart({
901
944
  );
902
945
  }) }, s.id);
903
946
  }),
904
- showCategoryAxis && (categories ?? []).map((c, i) => /* @__PURE__ */ jsx7("text", { x: labelW, y: band.center(i), textAnchor: "end", dominantBaseline: "central", fontSize: 12, style: { fill: hover === i ? VAR.textStrong : VAR.text, fontWeight: hover === i ? 700 : 500 }, children: c }, c + i)),
905
- showValueAxis && tickValues.map((t) => /* @__PURE__ */ jsx7("text", { x: xs(t), y: h - 6, textAnchor: "middle", fontSize: 11, style: { fill: VAR.text }, children: valueFormatter(t) }, t)),
947
+ showCategoryAxis && (categories ?? []).map((c, i) => /* @__PURE__ */ jsx8("text", { x: labelW, y: band.center(i), textAnchor: "end", dominantBaseline: "central", fontSize: 12, style: { fill: hover === i ? VAR.textStrong : VAR.text, fontWeight: hover === i ? 700 : 500 }, children: c }, c + i)),
948
+ showValueAxis && tickValues.map((t) => /* @__PURE__ */ jsx8("text", { x: xs(t), y: h - 6, textAnchor: "middle", fontSize: 11, style: { fill: VAR.text }, children: valueFormatter(t) }, t)),
906
949
  tipRows.length > 0 && /* @__PURE__ */ jsxs5("g", { transform: `translate(${Math.min(right - tipW, Math.max(left, xs(max) - tipW))},${tipY})`, pointerEvents: "none", children: [
907
- /* @__PURE__ */ jsx7("rect", { width: tipW, height: tipH, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
950
+ /* @__PURE__ */ jsx8("rect", { width: tipW, height: tipH, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
908
951
  tipRows.map((r, k) => /* @__PURE__ */ jsxs5("g", { transform: `translate(10,${7 + 9 + k * 18})`, children: [
909
- /* @__PURE__ */ jsx7("circle", { cx: 3, cy: 0, r: 3, style: { fill: r.color } }),
910
- /* @__PURE__ */ jsx7("text", { x: 12, dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: r.s.label ?? r.s.id }),
911
- /* @__PURE__ */ jsx7("text", { x: tipW - 20, textAnchor: "end", dominantBaseline: "central", fontSize: 12, fontWeight: 700, style: { fill: VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: r.text })
952
+ /* @__PURE__ */ jsx8("circle", { cx: 3, cy: 0, r: 3, style: { fill: r.color } }),
953
+ /* @__PURE__ */ jsx8("text", { x: 12, dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: r.s.label ?? r.s.id }),
954
+ /* @__PURE__ */ jsx8("text", { x: tipW - 20, textAnchor: "end", dominantBaseline: "central", fontSize: 12, fontWeight: 700, style: { fill: VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: r.text })
912
955
  ] }, r.s.id))
913
956
  ] })
914
957
  ] })
915
958
  }
916
959
  ),
917
- enableScrubbing && /* @__PURE__ */ jsx7("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hover !== null ? [categories?.[hover] ?? String(hover), ...tipRows.map((r) => `${r.s.label ?? r.s.id} ${r.text}`)].join(", ") : "" })
960
+ enableScrubbing && /* @__PURE__ */ jsx8("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hover !== null ? [categories?.[hover] ?? String(hover), ...tipRows.map((r) => `${r.s.label ?? r.s.id} ${r.text}`)].join(", ") : "" })
918
961
  ] });
919
962
  }
920
963
 
921
964
  // src/viz/donut.tsx
922
965
  import { arcPath, arcs, seriesColor as seriesColor3 } from "@funnest/charts-viz";
923
- import { useId as useId2, useState as useState4 } from "react";
924
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
966
+ import { useId as useId2, useState as useState5 } from "react";
967
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
925
968
  var pctText = (f) => `${formatNumber(Math.round(f * 1e3) / 10)}%`;
926
969
  var isCenter = (v) => !!v && typeof v === "object" && "value" in v && typeof v.value === "string";
927
970
  function DonutChart({
@@ -945,8 +988,8 @@ function DonutChart({
945
988
  "aria-label": ariaLabel
946
989
  }) {
947
990
  const uid = useId2();
948
- const [own, setOwn] = useState4(null);
949
- const [focused, setFocused] = useState4(false);
991
+ const [own, setOwn] = useState5(null);
992
+ const [focused, setFocused] = useState5(false);
950
993
  const active = activeId !== void 0 ? activeId : own;
951
994
  const setActive = (id) => {
952
995
  if (activeId === void 0) setOwn(id);
@@ -999,13 +1042,13 @@ function DonutChart({
999
1042
  },
1000
1043
  onPointerLeave: () => setActive(null),
1001
1044
  children: [
1002
- total <= 0 && /* @__PURE__ */ jsx8("circle", { cx: c, cy: c, r: (r + ir) / 2, fill: "none", strokeWidth: Math.max(1, r - ir), style: { stroke: VAR.grid } }),
1045
+ total <= 0 && /* @__PURE__ */ jsx9("circle", { cx: c, cy: c, r: (r + ir) / 2, fill: "none", strokeWidth: Math.max(1, r - ir), style: { stroke: VAR.grid } }),
1003
1046
  visible.map((i) => {
1004
1047
  const on = i.id === active;
1005
1048
  const mid = (i.seg.startAngle + i.seg.endAngle) / 2;
1006
1049
  const dx = on ? Math.sin(mid) * pop : 0;
1007
1050
  const dy = on ? -Math.cos(mid) * pop : 0;
1008
- return /* @__PURE__ */ jsx8(
1051
+ return /* @__PURE__ */ jsx9(
1009
1052
  "path",
1010
1053
  {
1011
1054
  d: arcPath(c, c, ir, r, i.seg.startAngle, i.seg.endAngle, cornerRadius),
@@ -1013,13 +1056,13 @@ function DonutChart({
1013
1056
  opacity: active && !on ? 0.35 : 1,
1014
1057
  onPointerEnter: () => setActive(i.id),
1015
1058
  style: { fill: i.color, transition: "transform 0.15s ease, opacity 0.15s", cursor: "default" },
1016
- children: /* @__PURE__ */ jsx8("title", { children: describe(i) })
1059
+ children: /* @__PURE__ */ jsx9("title", { children: describe(i) })
1017
1060
  },
1018
1061
  i.id
1019
1062
  );
1020
1063
  }),
1021
1064
  ir > 24 && centerContent !== null && (isCenter(centerContent) ? /* @__PURE__ */ jsxs6("g", { pointerEvents: "none", style: { fontFamily: VAR.font }, children: [
1022
- /* @__PURE__ */ jsx8(
1065
+ /* @__PURE__ */ jsx9(
1023
1066
  "text",
1024
1067
  {
1025
1068
  x: c,
@@ -1032,8 +1075,8 @@ function DonutChart({
1032
1075
  children: centerContent.value
1033
1076
  }
1034
1077
  ),
1035
- centerContent.label && /* @__PURE__ */ jsx8("text", { x: c, y: c + 13, textAnchor: "middle", dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: centerContent.label })
1036
- ] }) : /* @__PURE__ */ jsx8("foreignObject", { x: c - ir * 0.72, y: c - ir * 0.72, width: ir * 1.44, height: ir * 1.44, pointerEvents: "none", children: /* @__PURE__ */ jsx8("div", { style: { width: "100%", height: "100%", display: "grid", placeItems: "center", textAlign: "center" }, children: centerContent }) }))
1078
+ centerContent.label && /* @__PURE__ */ jsx9("text", { x: c, y: c + 13, textAnchor: "middle", dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: centerContent.label })
1079
+ ] }) : /* @__PURE__ */ jsx9("foreignObject", { x: c - ir * 0.72, y: c - ir * 0.72, width: ir * 1.44, height: ir * 1.44, pointerEvents: "none", children: /* @__PURE__ */ jsx9("div", { style: { width: "100%", height: "100%", display: "grid", placeItems: "center", textAlign: "center" }, children: centerContent }) }))
1037
1080
  ]
1038
1081
  }
1039
1082
  );
@@ -1052,7 +1095,7 @@ function DonutChart({
1052
1095
  },
1053
1096
  children: [
1054
1097
  svg,
1055
- showLegend && /* @__PURE__ */ jsx8(
1098
+ showLegend && /* @__PURE__ */ jsx9(
1056
1099
  Legend,
1057
1100
  {
1058
1101
  direction: side ? "column" : "row",
@@ -1062,7 +1105,7 @@ function DonutChart({
1062
1105
  onItemHover: setActive
1063
1106
  }
1064
1107
  ),
1065
- /* @__PURE__ */ jsx8("span", { id: `${uid}-live`, "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hit ? describe(hit) : "" })
1108
+ /* @__PURE__ */ jsx9("span", { id: `${uid}-live`, "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hit ? describe(hit) : "" })
1066
1109
  ]
1067
1110
  }
1068
1111
  );
@@ -1070,8 +1113,8 @@ function DonutChart({
1070
1113
 
1071
1114
  // src/viz/treemap.tsx
1072
1115
  import { seriesColor as seriesColor4, treemap } from "@funnest/charts-viz";
1073
- import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
1074
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1116
+ import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
1117
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1075
1118
  var hex = (c) => {
1076
1119
  const m = /^#([0-9a-f]{6})$/i.exec(c);
1077
1120
  if (!m) return null;
@@ -1107,11 +1150,11 @@ function TreemapChart({
1107
1150
  style,
1108
1151
  "aria-label": ariaLabel
1109
1152
  }) {
1110
- const wrapRef = useRef4(null);
1111
- const [measured, setMeasured] = useState5(fixedWidth ?? 0);
1112
- const [active, setActive] = useState5(null);
1113
- const [focused, setFocused] = useState5(false);
1114
- useEffect4(() => {
1153
+ const wrapRef = useRef5(null);
1154
+ const [measured, setMeasured] = useState6(fixedWidth ?? 0);
1155
+ const [active, setActive] = useState6(null);
1156
+ const [focused, setFocused] = useState6(false);
1157
+ useEffect5(() => {
1115
1158
  if (fixedWidth !== void 0) return setMeasured(fixedWidth);
1116
1159
  const el = wrapRef.current;
1117
1160
  if (!el || typeof ResizeObserver === "undefined") return;
@@ -1191,7 +1234,7 @@ function TreemapChart({
1191
1234
  const fitsSub = !!sub && fitsLabel && r.height >= fs * 2 + 14 && r.width >= textWidth2(sub, fs * 0.8) + 10;
1192
1235
  const cy = r.y + r.height / 2;
1193
1236
  return /* @__PURE__ */ jsxs7("g", { onPointerEnter: () => setActive(r.id), onClick: onItemClick ? () => onItemClick(d) : void 0, style: { cursor: onItemClick ? "pointer" : "default" }, children: [
1194
- /* @__PURE__ */ jsx9(
1237
+ /* @__PURE__ */ jsx10(
1195
1238
  "rect",
1196
1239
  {
1197
1240
  x: r.x,
@@ -1203,7 +1246,7 @@ function TreemapChart({
1203
1246
  opacity: active && !on ? 0.55 : 1
1204
1247
  }
1205
1248
  ),
1206
- on && /* @__PURE__ */ jsx9(
1249
+ on && /* @__PURE__ */ jsx10(
1207
1250
  "rect",
1208
1251
  {
1209
1252
  x: r.x + 1,
@@ -1216,7 +1259,7 @@ function TreemapChart({
1216
1259
  style: { stroke: "#fff" }
1217
1260
  }
1218
1261
  ),
1219
- fitsLabel && /* @__PURE__ */ jsx9(
1262
+ fitsLabel && /* @__PURE__ */ jsx10(
1220
1263
  "text",
1221
1264
  {
1222
1265
  x: r.x + r.width / 2,
@@ -1230,7 +1273,7 @@ function TreemapChart({
1230
1273
  children: d.label
1231
1274
  }
1232
1275
  ),
1233
- fitsSub && /* @__PURE__ */ jsx9(
1276
+ fitsSub && /* @__PURE__ */ jsx10(
1234
1277
  "text",
1235
1278
  {
1236
1279
  x: r.x + r.width / 2,
@@ -1247,10 +1290,10 @@ function TreemapChart({
1247
1290
  ] }, r.id);
1248
1291
  }),
1249
1292
  tip && /* @__PURE__ */ jsxs7("g", { transform: `translate(${tip.x},${tip.y})`, pointerEvents: "none", children: [
1250
- /* @__PURE__ */ jsx9("rect", { width: tip.w, height: tip.h, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
1293
+ /* @__PURE__ */ jsx10("rect", { width: tip.w, height: tip.h, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
1251
1294
  tip.rows.map(([a, b], k) => /* @__PURE__ */ jsxs7("g", { transform: `translate(10,${7 + 9 + k * 18})`, children: [
1252
- /* @__PURE__ */ jsx9("text", { dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: a }),
1253
- /* @__PURE__ */ jsx9(
1295
+ /* @__PURE__ */ jsx10("text", { dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: a }),
1296
+ /* @__PURE__ */ jsx10(
1254
1297
  "text",
1255
1298
  {
1256
1299
  x: tip.w - 20,
@@ -1267,12 +1310,12 @@ function TreemapChart({
1267
1310
  ]
1268
1311
  }
1269
1312
  ),
1270
- /* @__PURE__ */ jsx9("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hitItem ? describe(hitItem) : "" })
1313
+ /* @__PURE__ */ jsx10("span", { "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: focused && hitItem ? describe(hitItem) : "" })
1271
1314
  ] });
1272
1315
  }
1273
1316
 
1274
1317
  // src/viz/candles.tsx
1275
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1318
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1276
1319
  var KO = { open: "\uC2DC\uAC00", high: "\uACE0\uAC00", low: "\uC800\uAC00", close: "\uC885\uAC00", volume: "\uAC70\uB798\uB7C9" };
1277
1320
  var fade = (color, a) => {
1278
1321
  const m = /^#([0-9a-f]{6})$/i.exec(color);
@@ -1316,12 +1359,12 @@ function CandlePlot({
1316
1359
  const x = band.center(i);
1317
1360
  const right = x + 12 + tw <= ctx.plot.right;
1318
1361
  tip = /* @__PURE__ */ jsxs8("g", { pointerEvents: "none", children: [
1319
- /* @__PURE__ */ jsx10("line", { x1: x, x2: x, y1: ctx.plot.top, y2: ctx.plot.bottom, strokeWidth: 1, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
1362
+ /* @__PURE__ */ jsx11("line", { x1: x, x2: x, y1: ctx.plot.top, y2: ctx.plot.bottom, strokeWidth: 1, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
1320
1363
  /* @__PURE__ */ jsxs8("g", { transform: `translate(${right ? x + 12 : x - 12 - tw},${ctx.plot.top + 4})`, children: [
1321
- /* @__PURE__ */ jsx10("rect", { width: tw, height: th, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
1364
+ /* @__PURE__ */ jsx11("rect", { width: tw, height: th, rx: 8, strokeWidth: 1, style: { fill: VAR.bg, stroke: VAR.grid } }),
1322
1365
  rows.map(([a, b, color], k) => /* @__PURE__ */ jsxs8("g", { transform: `translate(10,${16 + k * 18})`, children: [
1323
- /* @__PURE__ */ jsx10("text", { dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: a }),
1324
- /* @__PURE__ */ jsx10("text", { x: tw - 20, textAnchor: "end", dominantBaseline: "central", fontSize: 12, fontWeight: 700, style: { fill: color ?? VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: b })
1366
+ /* @__PURE__ */ jsx11("text", { dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: a }),
1367
+ /* @__PURE__ */ jsx11("text", { x: tw - 20, textAnchor: "end", dominantBaseline: "central", fontSize: 12, fontWeight: 700, style: { fill: color ?? VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: b })
1325
1368
  ] }, a))
1326
1369
  ] })
1327
1370
  ] });
@@ -1337,8 +1380,8 @@ function CandlePlot({
1337
1380
  const h = Math.max(1, bottom - top);
1338
1381
  const hollow = hollowUp && up && bw >= 4;
1339
1382
  return /* @__PURE__ */ jsxs8("g", { opacity: i !== null && i !== k ? 0.55 : 1, children: [
1340
- /* @__PURE__ */ jsx10("line", { x1: cx, x2: cx, y1: y(d.high), y2: y(d.low), strokeWidth: 1, style: { stroke: color } }),
1341
- /* @__PURE__ */ jsx10(
1383
+ /* @__PURE__ */ jsx11("line", { x1: cx, x2: cx, y1: y(d.high), y2: y(d.low), strokeWidth: 1, style: { stroke: color } }),
1384
+ /* @__PURE__ */ jsx11(
1342
1385
  "rect",
1343
1386
  {
1344
1387
  x: cx - bw / 2,
@@ -1406,9 +1449,9 @@ function CandlestickChart({
1406
1449
  return d ? `${l.open} ${valueFormatter(d.open)}, ${l.high} ${valueFormatter(d.high)}, ${l.low} ${valueFormatter(d.low)}, ${l.close} ${valueFormatter(d.close)}` : "";
1407
1450
  }),
1408
1451
  children: [
1409
- yAx && /* @__PURE__ */ jsx10(YAxis, { tickLabelFormatter: valueFormatter, ...yAx, axisId: "price" }, "y"),
1410
- xAx && /* @__PURE__ */ jsx10(XAxis, { ...xAx }, "x"),
1411
- hasVolume && /* @__PURE__ */ jsx10(
1452
+ yAx && /* @__PURE__ */ jsx11(YAxis, { tickLabelFormatter: valueFormatter, ...yAx, axisId: "price" }, "y"),
1453
+ xAx && /* @__PURE__ */ jsx11(XAxis, { ...xAx }, "x"),
1454
+ hasVolume && /* @__PURE__ */ jsx11(
1412
1455
  BarPlot,
1413
1456
  {
1414
1457
  seriesIds: ["volume"],
@@ -1420,7 +1463,7 @@ function CandlestickChart({
1420
1463
  }
1421
1464
  }
1422
1465
  ),
1423
- /* @__PURE__ */ jsx10(
1466
+ /* @__PURE__ */ jsx11(
1424
1467
  CandlePlot,
1425
1468
  {
1426
1469
  data,
@@ -1441,8 +1484,8 @@ function CandlestickChart({
1441
1484
  }
1442
1485
 
1443
1486
  // src/viz/scrubber.tsx
1444
- import { useEffect as useEffect5 } from "react";
1445
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1487
+ import { useEffect as useEffect6 } from "react";
1488
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1446
1489
  var TIP_FONT = 12;
1447
1490
  var ROW = 18;
1448
1491
  var TIP_PAD = 7;
@@ -1462,7 +1505,7 @@ function Scrubber({
1462
1505
  const i = ctx.scrubIndex;
1463
1506
  const { setDimFuture } = ctx;
1464
1507
  const fade2 = dimFuture && !hideOverlay;
1465
- useEffect5(() => {
1508
+ useEffect6(() => {
1466
1509
  setDimFuture(fade2);
1467
1510
  return () => setDimFuture(false);
1468
1511
  }, [fade2, setDimFuture]);
@@ -1470,7 +1513,7 @@ function Scrubber({
1470
1513
  const colorOf = (s, v) => beaconColor?.(v, s.id) ?? s.color;
1471
1514
  if (i === null || i >= ctx.length) {
1472
1515
  if (!idlePulse || ctx.band) return null;
1473
- return /* @__PURE__ */ jsx11("g", { pointerEvents: "none", children: pick.map((s) => {
1516
+ return /* @__PURE__ */ jsx12("g", { pointerEvents: "none", children: pick.map((s) => {
1474
1517
  let last = s.data.length - 1;
1475
1518
  while (last >= 0 && (s.data[last] === null || s.data[last] === void 0)) last--;
1476
1519
  if (last < 0) return null;
@@ -1480,10 +1523,10 @@ function Scrubber({
1480
1523
  const color = colorOf(s, s.data[last]);
1481
1524
  return /* @__PURE__ */ jsxs9("g", { children: [
1482
1525
  /* @__PURE__ */ jsxs9("circle", { cx: x2, cy: y, r: 4, fillOpacity: 0.35, style: { fill: color }, children: [
1483
- /* @__PURE__ */ jsx11("animate", { attributeName: "r", values: "4;13", dur: "1.6s", repeatCount: "indefinite" }),
1484
- /* @__PURE__ */ jsx11("animate", { attributeName: "fill-opacity", values: "0.35;0", dur: "1.6s", repeatCount: "indefinite" })
1526
+ /* @__PURE__ */ jsx12("animate", { attributeName: "r", values: "4;13", dur: "1.6s", repeatCount: "indefinite" }),
1527
+ /* @__PURE__ */ jsx12("animate", { attributeName: "fill-opacity", values: "0.35;0", dur: "1.6s", repeatCount: "indefinite" })
1485
1528
  ] }),
1486
- /* @__PURE__ */ jsx11("circle", { cx: x2, cy: y, r: 4, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } })
1529
+ /* @__PURE__ */ jsx12("circle", { cx: x2, cy: y, r: 4, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } })
1487
1530
  ] }, s.id);
1488
1531
  }) });
1489
1532
  }
@@ -1505,8 +1548,8 @@ function Scrubber({
1505
1548
  const tipX = tipRight ? x + 12 : x - 12 - tipW;
1506
1549
  const tipY = plot.top + 4;
1507
1550
  return /* @__PURE__ */ jsxs9("g", { pointerEvents: "none", children: [
1508
- /* @__PURE__ */ jsx11("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth: 1, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
1509
- text && /* @__PURE__ */ jsx11("text", { x: textX, y: Math.max(11, plot.top - 2), textAnchor: "middle", fontSize: 11, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill: VAR.textStrong }, children: text }),
1551
+ /* @__PURE__ */ jsx12("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth: 1, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
1552
+ text && /* @__PURE__ */ jsx12("text", { x: textX, y: Math.max(11, plot.top - 2), textAnchor: "middle", fontSize: 11, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill: VAR.textStrong }, children: text }),
1510
1553
  !ctx.band && list.map((s) => {
1511
1554
  const y = seriesY(ctx, s, i);
1512
1555
  if (y === null) return null;
@@ -1515,9 +1558,9 @@ function Scrubber({
1515
1558
  const text2 = beaconLabels ? typeof beaconLabels === "function" ? beaconLabels(v, s.id) : valueFormatter(v, s.id) : "";
1516
1559
  const right = x + 12 + textWidth3(text2) <= plot.right;
1517
1560
  return /* @__PURE__ */ jsxs9("g", { children: [
1518
- /* @__PURE__ */ jsx11("circle", { cx: x, cy: y, r: 7, fillOpacity: 0.18, style: { fill: color } }),
1519
- /* @__PURE__ */ jsx11("circle", { cx: x, cy: y, r: 4, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } }),
1520
- text2 && /* @__PURE__ */ jsx11(
1561
+ /* @__PURE__ */ jsx12("circle", { cx: x, cy: y, r: 7, fillOpacity: 0.18, style: { fill: color } }),
1562
+ /* @__PURE__ */ jsx12("circle", { cx: x, cy: y, r: 4, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } }),
1563
+ text2 && /* @__PURE__ */ jsx12(
1521
1564
  "text",
1522
1565
  {
1523
1566
  x: right ? x + 10 : x - 10,
@@ -1536,31 +1579,31 @@ function Scrubber({
1536
1579
  ] }, s.id);
1537
1580
  }),
1538
1581
  rows.length > 0 && /* @__PURE__ */ jsxs9("g", { transform: `translate(${tipX},${tipY})`, children: [
1539
- /* @__PURE__ */ jsx11("rect", { width: tipW, height: tipH, rx: 8, style: { fill: VAR.bg, stroke: VAR.grid }, strokeWidth: 1 }),
1582
+ /* @__PURE__ */ jsx12("rect", { width: tipW, height: tipH, rx: 8, style: { fill: VAR.bg, stroke: VAR.grid }, strokeWidth: 1 }),
1540
1583
  rows.map((r, k) => /* @__PURE__ */ jsxs9("g", { transform: `translate(10,${TIP_PAD + ROW / 2 + k * ROW})`, children: [
1541
- /* @__PURE__ */ jsx11("circle", { cx: 3, cy: 0, r: 3, style: { fill: r.s.color } }),
1542
- (rows.length > 1 || r.s.label) && /* @__PURE__ */ jsx11("text", { x: 12, dominantBaseline: "central", fontSize: TIP_FONT, style: { fill: VAR.text }, children: r.s.label ?? r.s.id }),
1543
- /* @__PURE__ */ jsx11("text", { x: tipW - 20, textAnchor: "end", dominantBaseline: "central", fontSize: TIP_FONT, fontWeight: 700, style: { fill: VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: r.text })
1584
+ /* @__PURE__ */ jsx12("circle", { cx: 3, cy: 0, r: 3, style: { fill: r.s.color } }),
1585
+ (rows.length > 1 || r.s.label) && /* @__PURE__ */ jsx12("text", { x: 12, dominantBaseline: "central", fontSize: TIP_FONT, style: { fill: VAR.text }, children: r.s.label ?? r.s.id }),
1586
+ /* @__PURE__ */ jsx12("text", { x: tipW - 20, textAnchor: "end", dominantBaseline: "central", fontSize: TIP_FONT, fontWeight: 700, style: { fill: VAR.textStrong, fontVariantNumeric: "tabular-nums" }, children: r.text })
1544
1587
  ] }, r.s.id))
1545
1588
  ] })
1546
1589
  ] });
1547
1590
  }
1548
1591
 
1549
1592
  // src/viz/annotations.tsx
1550
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1593
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1551
1594
  var LABEL_FONT = 11;
1552
1595
  var labelWidth = (t) => [...t].reduce((w, ch) => w + (ch.charCodeAt(0) > 11904 ? LABEL_FONT : LABEL_FONT * 0.62), 0);
1553
1596
  function LineLabel(props) {
1554
1597
  const { x, y, text, anchor, color, pill } = props;
1555
1598
  const textColor = color === VAR.line ? VAR.text : color;
1556
1599
  if (!pill) {
1557
- return /* @__PURE__ */ jsx12("text", { x, y, textAnchor: anchor, dominantBaseline: "central", fontSize: LABEL_FONT, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill: textColor }, children: text });
1600
+ return /* @__PURE__ */ jsx13("text", { x, y, textAnchor: anchor, dominantBaseline: "central", fontSize: LABEL_FONT, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill: textColor }, children: text });
1558
1601
  }
1559
1602
  const w = labelWidth(text) + 14;
1560
1603
  const left = anchor === "start" ? x : anchor === "end" ? x - w : x - w / 2;
1561
1604
  return /* @__PURE__ */ jsxs10("g", { children: [
1562
- /* @__PURE__ */ jsx12("rect", { x: left, y: y - 10, width: w, height: 20, rx: 10, style: { fill: color === VAR.line ? VAR.textStrong : color } }),
1563
- /* @__PURE__ */ jsx12("text", { x: left + w / 2, y, textAnchor: "middle", dominantBaseline: "central", fontSize: LABEL_FONT, fontWeight: 700, style: { fill: VAR.bg }, children: text })
1605
+ /* @__PURE__ */ jsx13("rect", { x: left, y: y - 10, width: w, height: 20, rx: 10, style: { fill: color === VAR.line ? VAR.textStrong : color } }),
1606
+ /* @__PURE__ */ jsx13("text", { x: left + w / 2, y, textAnchor: "middle", dominantBaseline: "central", fontSize: LABEL_FONT, fontWeight: 700, style: { fill: VAR.bg }, children: text })
1564
1607
  ] });
1565
1608
  }
1566
1609
  function ReferenceLine({
@@ -1589,16 +1632,16 @@ function ReferenceLine({
1589
1632
  const off = pill ? 13 : 9;
1590
1633
  const ly = labelAlign === "center" ? y : labelAlign === "below" ? y + off : y - off;
1591
1634
  return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1592
- /* @__PURE__ */ jsx12("line", { x1: plot.left, x2: plot.right, y1: y, y2: y, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1593
- label !== void 0 && /* @__PURE__ */ jsx12(LineLabel, { x: tx + labelDx, y: ly + labelDy, text: label, anchor, color, pill })
1635
+ /* @__PURE__ */ jsx13("line", { x1: plot.left, x2: plot.right, y1: y, y2: y, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1636
+ label !== void 0 && /* @__PURE__ */ jsx13(LineLabel, { x: tx + labelDx, y: ly + labelDy, text: label, anchor, color, pill })
1594
1637
  ] });
1595
1638
  }
1596
1639
  if (dataX !== void 0) {
1597
1640
  const x = ctx.x(dataX);
1598
1641
  const pos = labelPosition ?? "top";
1599
1642
  return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1600
- /* @__PURE__ */ jsx12("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1601
- label !== void 0 && /* @__PURE__ */ jsx12(
1643
+ /* @__PURE__ */ jsx13("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1644
+ label !== void 0 && /* @__PURE__ */ jsx13(
1602
1645
  LineLabel,
1603
1646
  {
1604
1647
  x: x + (pill ? 0 : 4) + labelDx,
@@ -1626,24 +1669,24 @@ function Point({ dataX, dataY, seriesId, yAxisId, color, radius = 4, label, labe
1626
1669
  const [lx, ly, anchor] = labelPosition === "bottom" ? [x, y + gap + 8, "middle"] : labelPosition === "left" ? [x - gap, y + 4, "end"] : labelPosition === "right" ? [x + gap, y + 4, "start"] : [x, y - gap, "middle"];
1627
1670
  return /* @__PURE__ */ jsxs10("g", { style: onClick ? { cursor: "pointer" } : void 0, onClick, pointerEvents: onClick ? "auto" : "none", children: [
1628
1671
  pulse && /* @__PURE__ */ jsxs10("circle", { cx: x, cy: y, r: radius, fillOpacity: 0.35, style: { fill }, children: [
1629
- /* @__PURE__ */ jsx12("animate", { attributeName: "r", values: `${radius};${radius * 3.2}`, dur: "1.6s", repeatCount: "indefinite" }),
1630
- /* @__PURE__ */ jsx12("animate", { attributeName: "fill-opacity", values: "0.35;0", dur: "1.6s", repeatCount: "indefinite" })
1672
+ /* @__PURE__ */ jsx13("animate", { attributeName: "r", values: `${radius};${radius * 3.2}`, dur: "1.6s", repeatCount: "indefinite" }),
1673
+ /* @__PURE__ */ jsx13("animate", { attributeName: "fill-opacity", values: "0.35;0", dur: "1.6s", repeatCount: "indefinite" })
1631
1674
  ] }),
1632
- /* @__PURE__ */ jsx12("circle", { cx: x, cy: y, r: radius, strokeWidth: 2, style: { fill, stroke: VAR.bg } }),
1633
- label !== void 0 && /* @__PURE__ */ jsx12("text", { opacity: nearScrub(ctx, x) ? 0 : 1, x: lx, y: ly, textAnchor: anchor, fontSize: 11, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill }, children: label })
1675
+ /* @__PURE__ */ jsx13("circle", { cx: x, cy: y, r: radius, strokeWidth: 2, style: { fill, stroke: VAR.bg } }),
1676
+ label !== void 0 && /* @__PURE__ */ jsx13("text", { opacity: nearScrub(ctx, x) ? 0 : 1, x: lx, y: ly, textAnchor: anchor, fontSize: 11, fontWeight: 600, strokeWidth: 3, strokeLinejoin: "round", paintOrder: "stroke", style: { stroke: VAR.bg, fill }, children: label })
1634
1677
  ] });
1635
1678
  }
1636
1679
 
1637
1680
  // src/viz/period-selector.tsx
1638
- import { useLayoutEffect, useRef as useRef5, useState as useState6 } from "react";
1639
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1681
+ import { useLayoutEffect, useRef as useRef6, useState as useState7 } from "react";
1682
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1640
1683
  function LiveTabLabel({ label, color = "var(--fnv-live, #F04452)" }) {
1641
1684
  return /* @__PURE__ */ jsxs11("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
1642
1685
  /* @__PURE__ */ jsxs11("span", { "aria-hidden": "true", style: { position: "relative", width: 7, height: 7 }, children: [
1643
- /* @__PURE__ */ jsx13("span", { style: { position: "absolute", inset: 0, borderRadius: "50%", background: color } }),
1644
- /* @__PURE__ */ jsx13("svg", { width: "7", height: "7", viewBox: "0 0 7 7", style: { position: "absolute", inset: 0, overflow: "visible" }, children: /* @__PURE__ */ jsxs11("circle", { cx: "3.5", cy: "3.5", r: "3.5", style: { fill: color }, fillOpacity: 0.45, children: [
1645
- /* @__PURE__ */ jsx13("animate", { attributeName: "r", values: "3.5;8", dur: "1.6s", repeatCount: "indefinite" }),
1646
- /* @__PURE__ */ jsx13("animate", { attributeName: "fill-opacity", values: "0.45;0", dur: "1.6s", repeatCount: "indefinite" })
1686
+ /* @__PURE__ */ jsx14("span", { style: { position: "absolute", inset: 0, borderRadius: "50%", background: color } }),
1687
+ /* @__PURE__ */ jsx14("svg", { width: "7", height: "7", viewBox: "0 0 7 7", style: { position: "absolute", inset: 0, overflow: "visible" }, children: /* @__PURE__ */ jsxs11("circle", { cx: "3.5", cy: "3.5", r: "3.5", style: { fill: color }, fillOpacity: 0.45, children: [
1688
+ /* @__PURE__ */ jsx14("animate", { attributeName: "r", values: "3.5;8", dur: "1.6s", repeatCount: "indefinite" }),
1689
+ /* @__PURE__ */ jsx14("animate", { attributeName: "fill-opacity", values: "0.45;0", dur: "1.6s", repeatCount: "indefinite" })
1647
1690
  ] }) })
1648
1691
  ] }),
1649
1692
  label
@@ -1670,8 +1713,8 @@ function PeriodSelector({
1670
1713
  }) {
1671
1714
  const activeIsLive = periods.find((p) => p.id === value)?.live ?? false;
1672
1715
  const pillColor = activeIsLive ? liveColor : activeColor;
1673
- const listRef = useRef5(null);
1674
- const [pill, setPill] = useState6(null);
1716
+ const listRef = useRef6(null);
1717
+ const [pill, setPill] = useState7(null);
1675
1718
  useLayoutEffect(() => {
1676
1719
  const list = listRef.current;
1677
1720
  const active = list?.querySelector('[aria-selected="true"]');
@@ -1703,7 +1746,7 @@ function PeriodSelector({
1703
1746
  onKeyDown,
1704
1747
  style: { position: "relative", display: fullWidth ? "flex" : "inline-flex", gap: 2, fontFamily: VAR.font, ...style },
1705
1748
  children: [
1706
- pill && /* @__PURE__ */ jsx13(
1749
+ pill && /* @__PURE__ */ jsx14(
1707
1750
  "span",
1708
1751
  {
1709
1752
  "aria-hidden": "true",
@@ -1722,7 +1765,7 @@ function PeriodSelector({
1722
1765
  ),
1723
1766
  periods.map((p) => {
1724
1767
  const active = p.id === value;
1725
- return /* @__PURE__ */ jsx13(
1768
+ return /* @__PURE__ */ jsx14(
1726
1769
  "button",
1727
1770
  {
1728
1771
  type: "button",
@@ -1746,7 +1789,7 @@ function PeriodSelector({
1746
1789
  fontWeight: active ? 700 : 500,
1747
1790
  cursor: "pointer"
1748
1791
  },
1749
- children: p.live ? /* @__PURE__ */ jsx13(LiveTabLabel, { label: p.label, color: liveColor }) : p.label
1792
+ children: p.live ? /* @__PURE__ */ jsx14(LiveTabLabel, { label: p.label, color: liveColor }) : p.label
1750
1793
  },
1751
1794
  p.id
1752
1795
  );
@@ -1758,12 +1801,12 @@ function PeriodSelector({
1758
1801
 
1759
1802
  // src/viz/charts.tsx
1760
1803
  import { percentages, seriesColor as seriesColor5 } from "@funnest/charts-viz";
1761
- import { useState as useState7 } from "react";
1762
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1804
+ import { useState as useState8 } from "react";
1805
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1763
1806
  var axisProps2 = (v) => v ? v === true ? {} : v : null;
1764
1807
  var axisElements = (x, y) => [
1765
- y && /* @__PURE__ */ jsx14(YAxis, { ...y }, "y"),
1766
- x && /* @__PURE__ */ jsx14(XAxis, { ...x }, "x")
1808
+ y && /* @__PURE__ */ jsx15(YAxis, { ...y }, "y"),
1809
+ x && /* @__PURE__ */ jsx15(XAxis, { ...x }, "x")
1767
1810
  ];
1768
1811
  var defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
1769
1812
  var KR_SPLIT = { above: "#F04452", below: "#3182F6" };
@@ -1786,7 +1829,7 @@ function LineChart({
1786
1829
  const split = baseline !== void 0 ? splitColors ?? KR_SPLIT : void 0;
1787
1830
  return /* @__PURE__ */ jsxs12(CartesianChart, { enableScrubbing: !!sc, ...chart, children: [
1788
1831
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1789
- chart.series.map((s) => /* @__PURE__ */ jsx14(
1832
+ chart.series.map((s) => /* @__PURE__ */ jsx15(
1790
1833
  Line,
1791
1834
  {
1792
1835
  seriesId: s.id,
@@ -1796,7 +1839,7 @@ function LineChart({
1796
1839
  s.id
1797
1840
  )),
1798
1841
  children,
1799
- sc && /* @__PURE__ */ jsx14(
1842
+ sc && /* @__PURE__ */ jsx15(
1800
1843
  Scrubber,
1801
1844
  {
1802
1845
  ...split && baseline !== void 0 ? { beaconColor: (v) => v >= baseline ? split.above : split.below } : {},
@@ -1826,7 +1869,7 @@ function AreaChart({
1826
1869
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1827
1870
  chart.series.map((s) => {
1828
1871
  const own = s.areaStyle === "none" ? null : s.areaStyle ?? (chart.stacked && fill === "gradient" ? "solid" : fill);
1829
- return own ? /* @__PURE__ */ jsx14(
1872
+ return own ? /* @__PURE__ */ jsx15(
1830
1873
  Area,
1831
1874
  {
1832
1875
  seriesId: s.id,
@@ -1837,16 +1880,16 @@ function AreaChart({
1837
1880
  `a${s.id}`
1838
1881
  ) : null;
1839
1882
  }),
1840
- showLines && chart.series.map((s) => /* @__PURE__ */ jsx14(Line, { seriesId: s.id, curve, strokeWidth, ...defined({ lineStyle, baseline, splitColors }) }, `l${s.id}`)),
1883
+ showLines && chart.series.map((s) => /* @__PURE__ */ jsx15(Line, { seriesId: s.id, curve, strokeWidth, ...defined({ lineStyle, baseline, splitColors }) }, `l${s.id}`)),
1841
1884
  children,
1842
- sc && /* @__PURE__ */ jsx14(Scrubber, { ...sc })
1885
+ sc && /* @__PURE__ */ jsx15(Scrubber, { ...sc })
1843
1886
  ] });
1844
1887
  }
1845
1888
  function BarChart({ showXAxis, showYAxis, scrubber, radius, getBarColor, groupGap, stackGap, roundBaseline, barMinSize, layout = "vertical", xAxis, children, ...chart }) {
1846
1889
  const sc = axisProps2(scrubber);
1847
1890
  if (layout === "horizontal") {
1848
1891
  const yAx = axisProps2(showYAxis);
1849
- return /* @__PURE__ */ jsx14(
1892
+ return /* @__PURE__ */ jsx15(
1850
1893
  HorizontalBarChart,
1851
1894
  {
1852
1895
  series: chart.series,
@@ -1875,9 +1918,9 @@ function BarChart({ showXAxis, showYAxis, scrubber, radius, getBarColor, groupGa
1875
1918
  }
1876
1919
  return /* @__PURE__ */ jsxs12(CartesianChart, { enableScrubbing: !!sc, xAxis: { ...xAxis, scaleType: "band" }, ...chart, children: [
1877
1920
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1878
- /* @__PURE__ */ jsx14(BarPlot, { ...defined({ radius, getBarColor, groupGap, stackGap, roundBaseline, minSize: barMinSize }), highlightScrubbed: !!sc }),
1921
+ /* @__PURE__ */ jsx15(BarPlot, { ...defined({ radius, getBarColor, groupGap, stackGap, roundBaseline, minSize: barMinSize }), highlightScrubbed: !!sc }),
1879
1922
  children,
1880
- sc && /* @__PURE__ */ jsx14(Scrubber, { ...sc })
1923
+ sc && /* @__PURE__ */ jsx15(Scrubber, { ...sc })
1881
1924
  ] });
1882
1925
  }
1883
1926
  var pct = (_item, f) => `${formatNumber(Math.round(f * 1e3) / 10)}%`;
@@ -1913,8 +1956,8 @@ function PercentageBarChart({
1913
1956
  colorOf.set(d.id, d.color ?? seriesColor5(colorOf.size, palette));
1914
1957
  legendItems.push(d);
1915
1958
  }
1916
- const [hovered, setHovered] = useState7(null);
1917
- const [hoverRow, setHoverRow] = useState7(null);
1959
+ const [hovered, setHovered] = useState8(null);
1960
+ const [hoverRow, setHoverRow] = useState8(null);
1918
1961
  const rowSegs = allRows.map((r) => {
1919
1962
  const segs = percentages(r.data);
1920
1963
  return { row: r, items: r.data.map((d, i) => ({ ...d, color: colorOf.get(d.id), fraction: segs[i].fraction })) };
@@ -1922,7 +1965,7 @@ function PercentageBarChart({
1922
1965
  const renderBar = (items, rowId) => {
1923
1966
  const visible = items.filter((i) => i.fraction > 0);
1924
1967
  const ordered = vertical ? [...visible].reverse() : visible;
1925
- return /* @__PURE__ */ jsx14(
1968
+ return /* @__PURE__ */ jsx15(
1926
1969
  "div",
1927
1970
  {
1928
1971
  style: {
@@ -1934,7 +1977,7 @@ function PercentageBarChart({
1934
1977
  children: ordered.map((i, k) => {
1935
1978
  const a = k === 0 ? end : inner;
1936
1979
  const b = k === ordered.length - 1 ? end : inner;
1937
- return /* @__PURE__ */ jsx14(
1980
+ return /* @__PURE__ */ jsx15(
1938
1981
  "span",
1939
1982
  {
1940
1983
  title: `${i.label} ${formatLabel(i, i.fraction)}`,
@@ -1963,7 +2006,7 @@ function PercentageBarChart({
1963
2006
  );
1964
2007
  };
1965
2008
  const legendRow = multi ? rowSegs.find((r) => r.row.id === hoverRow) : rowSegs[0];
1966
- const legend = showLabels && /* @__PURE__ */ jsx14(
2009
+ const legend = showLabels && /* @__PURE__ */ jsx15(
1967
2010
  Legend,
1968
2011
  {
1969
2012
  style: legendAt === "top" ? { marginBottom: 10 } : legendAt === "bottom" ? { marginTop: 10 } : {},
@@ -1982,14 +2025,14 @@ function PercentageBarChart({
1982
2025
  onItemHover: setHovered
1983
2026
  }
1984
2027
  );
1985
- const label = (r) => multi && r.label ? /* @__PURE__ */ jsx14("span", { style: { fontSize: 12, color: hoverRow === r.id ? VAR.textStrong : VAR.text, fontWeight: hoverRow === r.id ? 700 : 500, whiteSpace: "nowrap" }, children: r.label }) : null;
1986
- const bars = vertical ? /* @__PURE__ */ jsx14("div", { style: { display: "flex", gap: 14, alignItems: "flex-end" }, children: rowSegs.map(({ row, items }) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }, children: [
2028
+ const label = (r) => multi && r.label ? /* @__PURE__ */ jsx15("span", { style: { fontSize: 12, color: hoverRow === r.id ? VAR.textStrong : VAR.text, fontWeight: hoverRow === r.id ? 700 : 500, whiteSpace: "nowrap" }, children: r.label }) : null;
2029
+ const bars = vertical ? /* @__PURE__ */ jsx15("div", { style: { display: "flex", gap: 14, alignItems: "flex-end" }, children: rowSegs.map(({ row, items }) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }, children: [
1987
2030
  renderBar(items, row.id),
1988
2031
  label(row)
1989
- ] }, row.id)) }) : multi ? /* @__PURE__ */ jsx14("div", { style: { display: "grid", gridTemplateColumns: "max-content 1fr", columnGap: 12, rowGap: 10, alignItems: "center", flex: legendAt === "right" ? 1 : void 0 }, children: rowSegs.map(({ row, items }) => /* @__PURE__ */ jsxs12("div", { style: { display: "contents" }, children: [
1990
- label(row) ?? /* @__PURE__ */ jsx14("span", {}),
2032
+ ] }, row.id)) }) : multi ? /* @__PURE__ */ jsx15("div", { style: { display: "grid", gridTemplateColumns: "max-content 1fr", columnGap: 12, rowGap: 10, alignItems: "center", flex: legendAt === "right" ? 1 : void 0 }, children: rowSegs.map(({ row, items }) => /* @__PURE__ */ jsxs12("div", { style: { display: "contents" }, children: [
2033
+ label(row) ?? /* @__PURE__ */ jsx15("span", {}),
1991
2034
  renderBar(items, row.id)
1992
- ] }, row.id)) }) : /* @__PURE__ */ jsx14("div", { style: { display: "flex", flex: legendAt === "right" ? 1 : void 0 }, children: renderBar(rowSegs[0].items, rowSegs[0].row.id) });
2035
+ ] }, row.id)) }) : /* @__PURE__ */ jsx15("div", { style: { display: "flex", flex: legendAt === "right" ? 1 : void 0 }, children: renderBar(rowSegs[0].items, rowSegs[0].row.id) });
1993
2036
  return /* @__PURE__ */ jsxs12(
1994
2037
  "div",
1995
2038
  {
@@ -2017,6 +2060,7 @@ export {
2017
2060
  DonutChart,
2018
2061
  FnChart,
2019
2062
  FnChartSlot,
2063
+ FnMultiChart,
2020
2064
  HorizontalBarChart,
2021
2065
  Legend,
2022
2066
  Line,
@@ -2033,5 +2077,6 @@ export {
2033
2077
  formatNumber,
2034
2078
  useCartesianChart,
2035
2079
  useChartEvent,
2036
- useFnChart
2080
+ useFnChart,
2081
+ useFnMultiChart
2037
2082
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnest/charts-react",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "React bindings for Funnest Charts: the trading widget plus SVG line, area and bar charts",
5
5
  "keywords": [
6
6
  "react",
@@ -46,9 +46,9 @@
46
46
  "NOTICE"
47
47
  ],
48
48
  "dependencies": {
49
- "@funnest/charts-ui": "0.4.0",
50
- "@funnest/charts": "0.4.0",
51
- "@funnest/charts-viz": "0.4.0"
49
+ "@funnest/charts-ui": "0.5.0",
50
+ "@funnest/charts": "0.5.0",
51
+ "@funnest/charts-viz": "0.5.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "react": ">=18",