@funnest/charts-react 0.3.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,13 +1310,19 @@ 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" };
1320
+ var fade = (color, a) => {
1321
+ const m = /^#([0-9a-f]{6})$/i.exec(color);
1322
+ if (!m) return `color-mix(in srgb, ${color} ${Math.round(a * 100)}%, transparent)`;
1323
+ const n = parseInt(m[1], 16);
1324
+ return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
1325
+ };
1277
1326
  function CandlePlot({
1278
1327
  data,
1279
1328
  yAxisId,
@@ -1310,12 +1359,12 @@ function CandlePlot({
1310
1359
  const x = band.center(i);
1311
1360
  const right = x + 12 + tw <= ctx.plot.right;
1312
1361
  tip = /* @__PURE__ */ jsxs8("g", { pointerEvents: "none", children: [
1313
- /* @__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 } }),
1314
1363
  /* @__PURE__ */ jsxs8("g", { transform: `translate(${right ? x + 12 : x - 12 - tw},${ctx.plot.top + 4})`, children: [
1315
- /* @__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 } }),
1316
1365
  rows.map(([a, b, color], k) => /* @__PURE__ */ jsxs8("g", { transform: `translate(10,${16 + k * 18})`, children: [
1317
- /* @__PURE__ */ jsx10("text", { dominantBaseline: "central", fontSize: 12, style: { fill: VAR.text }, children: a }),
1318
- /* @__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 })
1319
1368
  ] }, a))
1320
1369
  ] })
1321
1370
  ] });
@@ -1331,8 +1380,8 @@ function CandlePlot({
1331
1380
  const h = Math.max(1, bottom - top);
1332
1381
  const hollow = hollowUp && up && bw >= 4;
1333
1382
  return /* @__PURE__ */ jsxs8("g", { opacity: i !== null && i !== k ? 0.55 : 1, children: [
1334
- /* @__PURE__ */ jsx10("line", { x1: cx, x2: cx, y1: y(d.high), y2: y(d.low), strokeWidth: 1, style: { stroke: color } }),
1335
- /* @__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(
1336
1385
  "rect",
1337
1386
  {
1338
1387
  x: cx - bw / 2,
@@ -1400,9 +1449,9 @@ function CandlestickChart({
1400
1449
  return d ? `${l.open} ${valueFormatter(d.open)}, ${l.high} ${valueFormatter(d.high)}, ${l.low} ${valueFormatter(d.low)}, ${l.close} ${valueFormatter(d.close)}` : "";
1401
1450
  }),
1402
1451
  children: [
1403
- yAx && /* @__PURE__ */ jsx10(YAxis, { tickLabelFormatter: valueFormatter, ...yAx, axisId: "price" }, "y"),
1404
- xAx && /* @__PURE__ */ jsx10(XAxis, { ...xAx }, "x"),
1405
- 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(
1406
1455
  BarPlot,
1407
1456
  {
1408
1457
  seriesIds: ["volume"],
@@ -1410,11 +1459,11 @@ function CandlestickChart({
1410
1459
  highlightScrubbed: false,
1411
1460
  getBarColor: (_v, i) => {
1412
1461
  const d = data[i];
1413
- return d ? `color-mix(in srgb, ${d.close >= d.open ? upColor : downColor} 30%, transparent)` : void 0;
1462
+ return d ? fade(d.close >= d.open ? upColor : downColor, 0.3) : void 0;
1414
1463
  }
1415
1464
  }
1416
1465
  ),
1417
- /* @__PURE__ */ jsx10(
1466
+ /* @__PURE__ */ jsx11(
1418
1467
  CandlePlot,
1419
1468
  {
1420
1469
  data,
@@ -1435,8 +1484,8 @@ function CandlestickChart({
1435
1484
  }
1436
1485
 
1437
1486
  // src/viz/scrubber.tsx
1438
- import { useEffect as useEffect5 } from "react";
1439
- 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";
1440
1489
  var TIP_FONT = 12;
1441
1490
  var ROW = 18;
1442
1491
  var TIP_PAD = 7;
@@ -1455,16 +1504,16 @@ function Scrubber({
1455
1504
  const ctx = useCartesianChart();
1456
1505
  const i = ctx.scrubIndex;
1457
1506
  const { setDimFuture } = ctx;
1458
- const fade = dimFuture && !hideOverlay;
1459
- useEffect5(() => {
1460
- setDimFuture(fade);
1507
+ const fade2 = dimFuture && !hideOverlay;
1508
+ useEffect6(() => {
1509
+ setDimFuture(fade2);
1461
1510
  return () => setDimFuture(false);
1462
- }, [fade, setDimFuture]);
1511
+ }, [fade2, setDimFuture]);
1463
1512
  const pick = seriesIds ? ctx.series.filter((s) => seriesIds.includes(s.id)) : ctx.series;
1464
1513
  const colorOf = (s, v) => beaconColor?.(v, s.id) ?? s.color;
1465
1514
  if (i === null || i >= ctx.length) {
1466
1515
  if (!idlePulse || ctx.band) return null;
1467
- return /* @__PURE__ */ jsx11("g", { pointerEvents: "none", children: pick.map((s) => {
1516
+ return /* @__PURE__ */ jsx12("g", { pointerEvents: "none", children: pick.map((s) => {
1468
1517
  let last = s.data.length - 1;
1469
1518
  while (last >= 0 && (s.data[last] === null || s.data[last] === void 0)) last--;
1470
1519
  if (last < 0) return null;
@@ -1474,10 +1523,10 @@ function Scrubber({
1474
1523
  const color = colorOf(s, s.data[last]);
1475
1524
  return /* @__PURE__ */ jsxs9("g", { children: [
1476
1525
  /* @__PURE__ */ jsxs9("circle", { cx: x2, cy: y, r: 4, fillOpacity: 0.35, style: { fill: color }, children: [
1477
- /* @__PURE__ */ jsx11("animate", { attributeName: "r", values: "4;13", dur: "1.6s", repeatCount: "indefinite" }),
1478
- /* @__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" })
1479
1528
  ] }),
1480
- /* @__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 } })
1481
1530
  ] }, s.id);
1482
1531
  }) });
1483
1532
  }
@@ -1499,8 +1548,8 @@ function Scrubber({
1499
1548
  const tipX = tipRight ? x + 12 : x - 12 - tipW;
1500
1549
  const tipY = plot.top + 4;
1501
1550
  return /* @__PURE__ */ jsxs9("g", { pointerEvents: "none", children: [
1502
- /* @__PURE__ */ jsx11("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth: 1, shapeRendering: "crispEdges", style: { stroke: VAR.line } }),
1503
- 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 }),
1504
1553
  !ctx.band && list.map((s) => {
1505
1554
  const y = seriesY(ctx, s, i);
1506
1555
  if (y === null) return null;
@@ -1509,9 +1558,9 @@ function Scrubber({
1509
1558
  const text2 = beaconLabels ? typeof beaconLabels === "function" ? beaconLabels(v, s.id) : valueFormatter(v, s.id) : "";
1510
1559
  const right = x + 12 + textWidth3(text2) <= plot.right;
1511
1560
  return /* @__PURE__ */ jsxs9("g", { children: [
1512
- /* @__PURE__ */ jsx11("circle", { cx: x, cy: y, r: 7, fillOpacity: 0.18, style: { fill: color } }),
1513
- /* @__PURE__ */ jsx11("circle", { cx: x, cy: y, r: 4, strokeWidth: 2, style: { fill: color, stroke: VAR.bg } }),
1514
- 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(
1515
1564
  "text",
1516
1565
  {
1517
1566
  x: right ? x + 10 : x - 10,
@@ -1530,31 +1579,31 @@ function Scrubber({
1530
1579
  ] }, s.id);
1531
1580
  }),
1532
1581
  rows.length > 0 && /* @__PURE__ */ jsxs9("g", { transform: `translate(${tipX},${tipY})`, children: [
1533
- /* @__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 }),
1534
1583
  rows.map((r, k) => /* @__PURE__ */ jsxs9("g", { transform: `translate(10,${TIP_PAD + ROW / 2 + k * ROW})`, children: [
1535
- /* @__PURE__ */ jsx11("circle", { cx: 3, cy: 0, r: 3, style: { fill: r.s.color } }),
1536
- (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 }),
1537
- /* @__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 })
1538
1587
  ] }, r.s.id))
1539
1588
  ] })
1540
1589
  ] });
1541
1590
  }
1542
1591
 
1543
1592
  // src/viz/annotations.tsx
1544
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1593
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1545
1594
  var LABEL_FONT = 11;
1546
1595
  var labelWidth = (t) => [...t].reduce((w, ch) => w + (ch.charCodeAt(0) > 11904 ? LABEL_FONT : LABEL_FONT * 0.62), 0);
1547
1596
  function LineLabel(props) {
1548
1597
  const { x, y, text, anchor, color, pill } = props;
1549
1598
  const textColor = color === VAR.line ? VAR.text : color;
1550
1599
  if (!pill) {
1551
- 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 });
1552
1601
  }
1553
1602
  const w = labelWidth(text) + 14;
1554
1603
  const left = anchor === "start" ? x : anchor === "end" ? x - w : x - w / 2;
1555
1604
  return /* @__PURE__ */ jsxs10("g", { children: [
1556
- /* @__PURE__ */ jsx12("rect", { x: left, y: y - 10, width: w, height: 20, rx: 10, style: { fill: color === VAR.line ? VAR.textStrong : color } }),
1557
- /* @__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 })
1558
1607
  ] });
1559
1608
  }
1560
1609
  function ReferenceLine({
@@ -1583,16 +1632,16 @@ function ReferenceLine({
1583
1632
  const off = pill ? 13 : 9;
1584
1633
  const ly = labelAlign === "center" ? y : labelAlign === "below" ? y + off : y - off;
1585
1634
  return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1586
- /* @__PURE__ */ jsx12("line", { x1: plot.left, x2: plot.right, y1: y, y2: y, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1587
- 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 })
1588
1637
  ] });
1589
1638
  }
1590
1639
  if (dataX !== void 0) {
1591
1640
  const x = ctx.x(dataX);
1592
1641
  const pos = labelPosition ?? "top";
1593
1642
  return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1594
- /* @__PURE__ */ jsx12("line", { x1: x, x2: x, y1: plot.top, y2: plot.bottom, strokeWidth, strokeDasharray: dash || void 0, shapeRendering: "crispEdges", style: { stroke: color } }),
1595
- 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(
1596
1645
  LineLabel,
1597
1646
  {
1598
1647
  x: x + (pill ? 0 : 4) + labelDx,
@@ -1620,24 +1669,24 @@ function Point({ dataX, dataY, seriesId, yAxisId, color, radius = 4, label, labe
1620
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"];
1621
1670
  return /* @__PURE__ */ jsxs10("g", { style: onClick ? { cursor: "pointer" } : void 0, onClick, pointerEvents: onClick ? "auto" : "none", children: [
1622
1671
  pulse && /* @__PURE__ */ jsxs10("circle", { cx: x, cy: y, r: radius, fillOpacity: 0.35, style: { fill }, children: [
1623
- /* @__PURE__ */ jsx12("animate", { attributeName: "r", values: `${radius};${radius * 3.2}`, dur: "1.6s", repeatCount: "indefinite" }),
1624
- /* @__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" })
1625
1674
  ] }),
1626
- /* @__PURE__ */ jsx12("circle", { cx: x, cy: y, r: radius, strokeWidth: 2, style: { fill, stroke: VAR.bg } }),
1627
- 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 })
1628
1677
  ] });
1629
1678
  }
1630
1679
 
1631
1680
  // src/viz/period-selector.tsx
1632
- import { useLayoutEffect, useRef as useRef5, useState as useState6 } from "react";
1633
- 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";
1634
1683
  function LiveTabLabel({ label, color = "var(--fnv-live, #F04452)" }) {
1635
1684
  return /* @__PURE__ */ jsxs11("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
1636
1685
  /* @__PURE__ */ jsxs11("span", { "aria-hidden": "true", style: { position: "relative", width: 7, height: 7 }, children: [
1637
- /* @__PURE__ */ jsx13("span", { style: { position: "absolute", inset: 0, borderRadius: "50%", background: color } }),
1638
- /* @__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: [
1639
- /* @__PURE__ */ jsx13("animate", { attributeName: "r", values: "3.5;8", dur: "1.6s", repeatCount: "indefinite" }),
1640
- /* @__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" })
1641
1690
  ] }) })
1642
1691
  ] }),
1643
1692
  label
@@ -1664,8 +1713,8 @@ function PeriodSelector({
1664
1713
  }) {
1665
1714
  const activeIsLive = periods.find((p) => p.id === value)?.live ?? false;
1666
1715
  const pillColor = activeIsLive ? liveColor : activeColor;
1667
- const listRef = useRef5(null);
1668
- const [pill, setPill] = useState6(null);
1716
+ const listRef = useRef6(null);
1717
+ const [pill, setPill] = useState7(null);
1669
1718
  useLayoutEffect(() => {
1670
1719
  const list = listRef.current;
1671
1720
  const active = list?.querySelector('[aria-selected="true"]');
@@ -1697,7 +1746,7 @@ function PeriodSelector({
1697
1746
  onKeyDown,
1698
1747
  style: { position: "relative", display: fullWidth ? "flex" : "inline-flex", gap: 2, fontFamily: VAR.font, ...style },
1699
1748
  children: [
1700
- pill && /* @__PURE__ */ jsx13(
1749
+ pill && /* @__PURE__ */ jsx14(
1701
1750
  "span",
1702
1751
  {
1703
1752
  "aria-hidden": "true",
@@ -1716,7 +1765,7 @@ function PeriodSelector({
1716
1765
  ),
1717
1766
  periods.map((p) => {
1718
1767
  const active = p.id === value;
1719
- return /* @__PURE__ */ jsx13(
1768
+ return /* @__PURE__ */ jsx14(
1720
1769
  "button",
1721
1770
  {
1722
1771
  type: "button",
@@ -1740,7 +1789,7 @@ function PeriodSelector({
1740
1789
  fontWeight: active ? 700 : 500,
1741
1790
  cursor: "pointer"
1742
1791
  },
1743
- 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
1744
1793
  },
1745
1794
  p.id
1746
1795
  );
@@ -1752,12 +1801,12 @@ function PeriodSelector({
1752
1801
 
1753
1802
  // src/viz/charts.tsx
1754
1803
  import { percentages, seriesColor as seriesColor5 } from "@funnest/charts-viz";
1755
- import { useState as useState7 } from "react";
1756
- 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";
1757
1806
  var axisProps2 = (v) => v ? v === true ? {} : v : null;
1758
1807
  var axisElements = (x, y) => [
1759
- y && /* @__PURE__ */ jsx14(YAxis, { ...y }, "y"),
1760
- x && /* @__PURE__ */ jsx14(XAxis, { ...x }, "x")
1808
+ y && /* @__PURE__ */ jsx15(YAxis, { ...y }, "y"),
1809
+ x && /* @__PURE__ */ jsx15(XAxis, { ...x }, "x")
1761
1810
  ];
1762
1811
  var defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
1763
1812
  var KR_SPLIT = { above: "#F04452", below: "#3182F6" };
@@ -1780,7 +1829,7 @@ function LineChart({
1780
1829
  const split = baseline !== void 0 ? splitColors ?? KR_SPLIT : void 0;
1781
1830
  return /* @__PURE__ */ jsxs12(CartesianChart, { enableScrubbing: !!sc, ...chart, children: [
1782
1831
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1783
- chart.series.map((s) => /* @__PURE__ */ jsx14(
1832
+ chart.series.map((s) => /* @__PURE__ */ jsx15(
1784
1833
  Line,
1785
1834
  {
1786
1835
  seriesId: s.id,
@@ -1790,7 +1839,7 @@ function LineChart({
1790
1839
  s.id
1791
1840
  )),
1792
1841
  children,
1793
- sc && /* @__PURE__ */ jsx14(
1842
+ sc && /* @__PURE__ */ jsx15(
1794
1843
  Scrubber,
1795
1844
  {
1796
1845
  ...split && baseline !== void 0 ? { beaconColor: (v) => v >= baseline ? split.above : split.below } : {},
@@ -1820,7 +1869,7 @@ function AreaChart({
1820
1869
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1821
1870
  chart.series.map((s) => {
1822
1871
  const own = s.areaStyle === "none" ? null : s.areaStyle ?? (chart.stacked && fill === "gradient" ? "solid" : fill);
1823
- return own ? /* @__PURE__ */ jsx14(
1872
+ return own ? /* @__PURE__ */ jsx15(
1824
1873
  Area,
1825
1874
  {
1826
1875
  seriesId: s.id,
@@ -1831,16 +1880,16 @@ function AreaChart({
1831
1880
  `a${s.id}`
1832
1881
  ) : null;
1833
1882
  }),
1834
- 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}`)),
1835
1884
  children,
1836
- sc && /* @__PURE__ */ jsx14(Scrubber, { ...sc })
1885
+ sc && /* @__PURE__ */ jsx15(Scrubber, { ...sc })
1837
1886
  ] });
1838
1887
  }
1839
1888
  function BarChart({ showXAxis, showYAxis, scrubber, radius, getBarColor, groupGap, stackGap, roundBaseline, barMinSize, layout = "vertical", xAxis, children, ...chart }) {
1840
1889
  const sc = axisProps2(scrubber);
1841
1890
  if (layout === "horizontal") {
1842
1891
  const yAx = axisProps2(showYAxis);
1843
- return /* @__PURE__ */ jsx14(
1892
+ return /* @__PURE__ */ jsx15(
1844
1893
  HorizontalBarChart,
1845
1894
  {
1846
1895
  series: chart.series,
@@ -1869,9 +1918,9 @@ function BarChart({ showXAxis, showYAxis, scrubber, radius, getBarColor, groupGa
1869
1918
  }
1870
1919
  return /* @__PURE__ */ jsxs12(CartesianChart, { enableScrubbing: !!sc, xAxis: { ...xAxis, scaleType: "band" }, ...chart, children: [
1871
1920
  axisElements(axisProps2(showXAxis), axisProps2(showYAxis)),
1872
- /* @__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 }),
1873
1922
  children,
1874
- sc && /* @__PURE__ */ jsx14(Scrubber, { ...sc })
1923
+ sc && /* @__PURE__ */ jsx15(Scrubber, { ...sc })
1875
1924
  ] });
1876
1925
  }
1877
1926
  var pct = (_item, f) => `${formatNumber(Math.round(f * 1e3) / 10)}%`;
@@ -1907,8 +1956,8 @@ function PercentageBarChart({
1907
1956
  colorOf.set(d.id, d.color ?? seriesColor5(colorOf.size, palette));
1908
1957
  legendItems.push(d);
1909
1958
  }
1910
- const [hovered, setHovered] = useState7(null);
1911
- const [hoverRow, setHoverRow] = useState7(null);
1959
+ const [hovered, setHovered] = useState8(null);
1960
+ const [hoverRow, setHoverRow] = useState8(null);
1912
1961
  const rowSegs = allRows.map((r) => {
1913
1962
  const segs = percentages(r.data);
1914
1963
  return { row: r, items: r.data.map((d, i) => ({ ...d, color: colorOf.get(d.id), fraction: segs[i].fraction })) };
@@ -1916,7 +1965,7 @@ function PercentageBarChart({
1916
1965
  const renderBar = (items, rowId) => {
1917
1966
  const visible = items.filter((i) => i.fraction > 0);
1918
1967
  const ordered = vertical ? [...visible].reverse() : visible;
1919
- return /* @__PURE__ */ jsx14(
1968
+ return /* @__PURE__ */ jsx15(
1920
1969
  "div",
1921
1970
  {
1922
1971
  style: {
@@ -1928,7 +1977,7 @@ function PercentageBarChart({
1928
1977
  children: ordered.map((i, k) => {
1929
1978
  const a = k === 0 ? end : inner;
1930
1979
  const b = k === ordered.length - 1 ? end : inner;
1931
- return /* @__PURE__ */ jsx14(
1980
+ return /* @__PURE__ */ jsx15(
1932
1981
  "span",
1933
1982
  {
1934
1983
  title: `${i.label} ${formatLabel(i, i.fraction)}`,
@@ -1957,7 +2006,7 @@ function PercentageBarChart({
1957
2006
  );
1958
2007
  };
1959
2008
  const legendRow = multi ? rowSegs.find((r) => r.row.id === hoverRow) : rowSegs[0];
1960
- const legend = showLabels && /* @__PURE__ */ jsx14(
2009
+ const legend = showLabels && /* @__PURE__ */ jsx15(
1961
2010
  Legend,
1962
2011
  {
1963
2012
  style: legendAt === "top" ? { marginBottom: 10 } : legendAt === "bottom" ? { marginTop: 10 } : {},
@@ -1976,14 +2025,14 @@ function PercentageBarChart({
1976
2025
  onItemHover: setHovered
1977
2026
  }
1978
2027
  );
1979
- 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;
1980
- 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: [
1981
2030
  renderBar(items, row.id),
1982
2031
  label(row)
1983
- ] }, 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: [
1984
- 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", {}),
1985
2034
  renderBar(items, row.id)
1986
- ] }, 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) });
1987
2036
  return /* @__PURE__ */ jsxs12(
1988
2037
  "div",
1989
2038
  {
@@ -2011,6 +2060,7 @@ export {
2011
2060
  DonutChart,
2012
2061
  FnChart,
2013
2062
  FnChartSlot,
2063
+ FnMultiChart,
2014
2064
  HorizontalBarChart,
2015
2065
  Legend,
2016
2066
  Line,
@@ -2027,5 +2077,6 @@ export {
2027
2077
  formatNumber,
2028
2078
  useCartesianChart,
2029
2079
  useChartEvent,
2030
- useFnChart
2080
+ useFnChart,
2081
+ useFnMultiChart
2031
2082
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnest/charts-react",
3
- "version": "0.3.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": "0.3.0",
50
- "@funnest/charts-ui": "0.3.0",
51
- "@funnest/charts-viz": "0.3.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",