@exegia/corpora-ui 2.0.0 → 3.0.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.
Files changed (55) hide show
  1. package/dist-lib/components/composed/chat/chart/chart-atom.d.ts +30 -0
  2. package/dist-lib/components/composed/chat/chart/chart-context.d.ts +2 -0
  3. package/dist-lib/components/composed/chat/chart/chart-legend.d.ts +4 -0
  4. package/dist-lib/components/composed/chat/chart/chart-plot.d.ts +3 -0
  5. package/dist-lib/components/composed/chat/chart/chart-root.d.ts +3 -0
  6. package/dist-lib/components/composed/chat/chart/chart-tooltip.d.ts +4 -0
  7. package/dist-lib/components/composed/chat/chart/constants.d.ts +3 -0
  8. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-area-chart.d.ts +93 -0
  9. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-bar-chart.d.ts +86 -0
  10. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-brush.d.ts +65 -0
  11. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-chart.d.ts +44 -0
  12. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-dot.d.ts +16 -0
  13. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-legend.d.ts +24 -0
  14. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-line-chart.d.ts +90 -0
  15. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-pie-chart.d.ts +73 -0
  16. package/dist-lib/components/composed/chat/chart/evilcharts/echarts-tooltip.d.ts +29 -0
  17. package/dist-lib/components/composed/chat/chart/index.d.ts +6 -0
  18. package/dist-lib/components/composed/chat/chart/type.d.ts +80 -0
  19. package/dist-lib/components/composed/chat/chart/use-chart-state.d.ts +3 -0
  20. package/dist-lib/components/composed/chat/chart/use-chart.d.ts +6 -0
  21. package/dist-lib/components/composed/chat/chart/utils.d.ts +5 -0
  22. package/dist-lib/components/composed/chat/chart.d.ts +1 -44
  23. package/dist-lib/components/composed/chat/index.d.ts +1 -1
  24. package/dist-lib/components/stories/chart.story.d.ts +39 -2
  25. package/dist-lib/components/stories/insight-cards.story.d.ts +30 -1
  26. package/dist-lib/index.js +10020 -5549
  27. package/dist-lib/index.js.map +1 -1
  28. package/package.json +2 -1
  29. package/src/components/composed/chat/__tests__/chart.test.tsx +255 -1
  30. package/src/components/composed/chat/chart/chart-atom.ts +55 -0
  31. package/src/components/composed/chat/chart/chart-context.ts +9 -0
  32. package/src/components/composed/chat/chart/chart-legend.tsx +75 -0
  33. package/src/components/composed/chat/chart/chart-plot.tsx +233 -0
  34. package/src/components/composed/chat/chart/chart-root.tsx +229 -0
  35. package/src/components/composed/chat/chart/chart-tooltip.tsx +74 -0
  36. package/src/components/composed/chat/chart/constants.ts +8 -0
  37. package/src/components/composed/chat/chart/evilcharts/LICENSE +21 -0
  38. package/src/components/composed/chat/chart/evilcharts/README.md +8 -0
  39. package/src/components/composed/chat/chart/evilcharts/echarts-area-chart.tsx +2358 -0
  40. package/src/components/composed/chat/chart/evilcharts/echarts-bar-chart.tsx +2261 -0
  41. package/src/components/composed/chat/chart/evilcharts/echarts-brush.tsx +233 -0
  42. package/src/components/composed/chat/chart/evilcharts/echarts-chart.tsx +214 -0
  43. package/src/components/composed/chat/chart/evilcharts/echarts-dot.tsx +111 -0
  44. package/src/components/composed/chat/chart/evilcharts/echarts-legend.tsx +131 -0
  45. package/src/components/composed/chat/chart/evilcharts/echarts-line-chart.tsx +2112 -0
  46. package/src/components/composed/chat/chart/evilcharts/echarts-pie-chart.tsx +1235 -0
  47. package/src/components/composed/chat/chart/evilcharts/echarts-tooltip.tsx +125 -0
  48. package/src/components/composed/chat/chart/index.ts +13 -0
  49. package/src/components/composed/chat/chart/type.ts +88 -0
  50. package/src/components/composed/chat/chart/use-chart-state.ts +22 -0
  51. package/src/components/composed/chat/chart/use-chart.ts +70 -0
  52. package/src/components/composed/chat/chart/utils.ts +47 -0
  53. package/src/components/composed/chat/chart.tsx +2 -400
  54. package/src/components/composed/chat/index.ts +1 -7
  55. package/src/components/stories/chart.story.tsx +16 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exegia/corpora-ui",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "shadcn-ready React UI library for the corpora manuscript-research apps.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -60,6 +60,7 @@
60
60
  "clsx": "^2.1.1",
61
61
  "cn": "^0.2.6",
62
62
  "cuelume": "^0.2.2",
63
+ "echarts": "^6.1.0",
63
64
  "frimousse": "^0.3.0",
64
65
  "fumadocs-core": "^16.15.10",
65
66
  "fumadocs-mdx": "^15.4.0",
@@ -1,7 +1,31 @@
1
1
  import { describe, expect, mock, test } from "bun:test"
2
2
  import { fireEvent, render, screen } from "@testing-library/react"
3
3
 
4
- import { Chart } from "../chart"
4
+ import {
5
+ Chart,
6
+ useChartActions,
7
+ useChartState,
8
+ removeChartInstance,
9
+ } from "../chart"
10
+ import { StrictMode } from "react"
11
+ import { Provider, createStore } from "jotai"
12
+ import { chartSelectedKeyAtom } from "../chart/chart-atom"
13
+ import { buildChartConfig } from "../chart/utils"
14
+ import { createChartTooltip } from "../chart/chart-tooltip"
15
+
16
+ function Controls({ id }: { id: string }) {
17
+ const actions = useChartActions(id)
18
+ const { selectedKey } = useChartState(id)
19
+ return (
20
+ <>
21
+ <output data-testid={`${id}-selection`}>{selectedKey ?? "none"}</output>
22
+ <button onClick={() => actions.select("v")}>Select {id}</button>
23
+ <button onClick={actions.clearSelection}>Clear {id}</button>
24
+ <button onClick={actions.reset}>Reset {id}</button>
25
+ <button onClick={() => actions.select("missing")}>Invalid {id}</button>
26
+ </>
27
+ )
28
+ }
5
29
 
6
30
  const data = [
7
31
  { label: "A", v: 1 },
@@ -60,3 +84,233 @@ describe("Chart", () => {
60
84
  expect(onClick).toHaveBeenCalledTimes(1)
61
85
  })
62
86
  })
87
+
88
+ describe("Chart selection", () => {
89
+ test("named instances stay isolated and external actions update legend selection", () => {
90
+ const onChange = mock(() => {})
91
+ render(
92
+ <Provider>
93
+ <Chart
94
+ chartId="one"
95
+ type="bar"
96
+ data={data}
97
+ series={series}
98
+ interactive
99
+ onSelectionChange={onChange}
100
+ />
101
+ <Chart
102
+ chartId="two"
103
+ type="line"
104
+ data={data}
105
+ series={series}
106
+ interactive
107
+ />
108
+ <Controls id="one" />
109
+ <Controls id="two" />
110
+ </Provider>
111
+ )
112
+ fireEvent.click(screen.getByText("Select one"))
113
+ expect(screen.getByTestId("one-selection").textContent).toBe("v")
114
+ expect(screen.getByTestId("two-selection").textContent).toBe("none")
115
+ expect(
116
+ screen
117
+ .getAllByRole("button", { name: "Value" })[0]
118
+ .getAttribute("aria-pressed")
119
+ ).toBe("true")
120
+ expect(onChange).toHaveBeenCalledTimes(1)
121
+ fireEvent.click(screen.getByText("Invalid one"))
122
+ expect(onChange).toHaveBeenCalledTimes(1)
123
+ fireEvent.click(screen.getByText("Clear one"))
124
+ expect(screen.getByTestId("one-selection").textContent).toBe("none")
125
+ })
126
+ test("controlled props remain authoritative until parent accepts selection", () => {
127
+ const onChange = mock(() => {})
128
+ const { rerender } = render(
129
+ <Chart
130
+ type="area"
131
+ data={data}
132
+ series={series}
133
+ interactive
134
+ selectedKey={null}
135
+ onSelectionChange={onChange}
136
+ />
137
+ )
138
+ fireEvent.click(screen.getByRole("button", { name: "Value" }))
139
+ expect(onChange).toHaveBeenCalledWith("v")
140
+ expect(
141
+ screen.getByRole("button", { name: "Value" }).getAttribute("aria-pressed")
142
+ ).toBe("false")
143
+ rerender(
144
+ <Chart
145
+ type="area"
146
+ data={data}
147
+ series={series}
148
+ interactive
149
+ selectedKey="v"
150
+ onSelectionChange={onChange}
151
+ />
152
+ )
153
+ expect(
154
+ screen.getByRole("button", { name: "Value" }).getAttribute("aria-pressed")
155
+ ).toBe("true")
156
+ })
157
+ test("reset restores the default and removed series clear selection", () => {
158
+ const store = createStore()
159
+ const { rerender } = render(
160
+ <Provider store={store}>
161
+ <Chart
162
+ chartId="reset"
163
+ type="bar"
164
+ data={data}
165
+ series={series}
166
+ defaultSelectedKey="v"
167
+ />
168
+ <Controls id="reset" />
169
+ </Provider>
170
+ )
171
+ fireEvent.click(screen.getByText("Clear reset"))
172
+ fireEvent.click(screen.getByText("Reset reset"))
173
+ expect(store.get(chartSelectedKeyAtom("reset"))).toBe("v")
174
+ rerender(
175
+ <Provider store={store}>
176
+ <Chart
177
+ chartId="reset"
178
+ type="bar"
179
+ data={data}
180
+ series={[]}
181
+ defaultSelectedKey="v"
182
+ />
183
+ </Provider>
184
+ )
185
+ expect(store.get(chartSelectedKeyAtom("reset"))).toBeNull()
186
+ })
187
+ test("generated identity survives StrictMode effect replay", async () => {
188
+ render(
189
+ <StrictMode>
190
+ <Chart type="line" data={data} series={series} interactive />
191
+ </StrictMode>
192
+ )
193
+ await Promise.resolve()
194
+ fireEvent.click(screen.getByRole("button", { name: "Value" }))
195
+ expect(
196
+ screen.getByRole("button", { name: "Value" }).getAttribute("aria-pressed")
197
+ ).toBe("true")
198
+ })
199
+ test("pie selection uses category labels", () => {
200
+ const callback = mock(() => {})
201
+ render(
202
+ <Chart
203
+ type="pie"
204
+ data={data}
205
+ series={series}
206
+ interactive
207
+ onSelectionChange={callback}
208
+ />
209
+ )
210
+ fireEvent.click(screen.getByRole("button", { name: "A 1" }))
211
+ expect(callback).toHaveBeenCalledWith("A")
212
+ })
213
+ test("instance removal releases the atom family", () => {
214
+ const original = chartSelectedKeyAtom("removed")
215
+ removeChartInstance("removed")
216
+ expect(chartSelectedKeyAtom("removed")).not.toBe(original)
217
+ })
218
+ })
219
+ describe("Chart adapters", () => {
220
+ test("config preserves CSS tokens and custom series colors", () => {
221
+ expect(
222
+ buildChartConfig("bar", data, [{ ...series[0], color: "var(--primary)" }])
223
+ .v.colors?.light
224
+ ).toEqual(["var(--primary)"])
225
+ expect(Object.keys(buildChartConfig("pie", data, series))).toEqual([
226
+ "A",
227
+ "B",
228
+ ])
229
+ })
230
+ test("tooltip escapes data and preserves React formatters", () => {
231
+ const tooltip = createChartTooltip("bar", data, [
232
+ {
233
+ key: "v",
234
+ label: "<script>",
235
+ format: (value) => <strong>{value}%</strong>,
236
+ },
237
+ ])
238
+ const html = tooltip({ seriesIndex: 0, dataIndex: 1 })
239
+ expect(html).toContain("&lt;script&gt;")
240
+ expect(html).toContain("<strong>2%</strong>")
241
+ })
242
+ })
243
+
244
+ test("tooltip defaults to frosted glass with one surface shadow", () => {
245
+ const params = { seriesIndex: 0, dataIndex: 1 }
246
+ const html = createChartTooltip("bar", data, series)(params)
247
+ expect(html).toContain("bg-background/50")
248
+ expect(html).toContain("backdrop-blur-md")
249
+ expect(html).toContain("box-shadow:0 4px 12px rgb(0 0 0 / 0.12)")
250
+ const solid = createChartTooltip("bar", data, series, { variant: "default" })(
251
+ params
252
+ )
253
+ expect(solid).not.toContain("backdrop-blur-md")
254
+ })
255
+
256
+ describe("Chart consumer layout", () => {
257
+ test("accepts CSS dimensions and replaces the framed root", () => {
258
+ const { container } = render(
259
+ <Chart
260
+ type="bar"
261
+ data={[]}
262
+ series={series}
263
+ width="100%"
264
+ height={360}
265
+ render={<section aria-label="Research chart" />}
266
+ />
267
+ )
268
+ const root = screen.getByRole("region", { name: "Research chart" })
269
+ expect(root.getAttribute("data-slot")).toBe("chart")
270
+ expect(root.style.width).toBe("100%")
271
+ expect(root.style.height).toBe("360px")
272
+ expect(
273
+ (container.querySelector('[role="group"]') as HTMLElement).style.height
274
+ ).toBe("100%")
275
+ })
276
+ test("explicit plot height and consumer styles take precedence", () => {
277
+ const { container } = render(
278
+ <Chart
279
+ type="bar"
280
+ data={[]}
281
+ series={series}
282
+ width={320}
283
+ height={360}
284
+ plotHeight="12rem"
285
+ style={{ width: "90%" }}
286
+ />
287
+ )
288
+ expect(
289
+ (container.querySelector('[data-slot="chart"]') as HTMLElement).style
290
+ .width
291
+ ).toBe("90%")
292
+ expect(
293
+ (container.querySelector('[role="group"]') as HTMLElement).style.height
294
+ ).toBe("12rem")
295
+ })
296
+ test("custom components receive props and children in headerless mode", () => {
297
+ function Surface(props: React.ComponentProps<"section">) {
298
+ return <section {...props} data-surface="custom" />
299
+ }
300
+ render(
301
+ <Chart
302
+ type="pie"
303
+ data={[]}
304
+ series={series}
305
+ headerless
306
+ render={<Surface aria-label="Bare chart" />}
307
+ width={480}
308
+ height={240}
309
+ />
310
+ )
311
+ const root = screen.getByRole("region", { name: "Bare chart" })
312
+ expect(root.getAttribute("data-surface")).toBe("custom")
313
+ expect(root.style.width).toBe("480px")
314
+ expect(screen.getByRole("status").textContent).toBe("No data")
315
+ })
316
+ })
@@ -0,0 +1,55 @@
1
+ import { atom } from "jotai"
2
+ import type { IChartState, TChartInstanceId } from "./type"
3
+
4
+ function createChartAtoms(id: TChartInstanceId) {
5
+ const selected = atom<string | null>(null)
6
+ selected.debugLabel = `chart/${id}/selectedKey`
7
+ const config = atom<{
8
+ controlled: boolean
9
+ initial: string | null
10
+ keys: readonly string[]
11
+ onChange?: (key: string | null) => void
12
+ }>({ controlled: false, initial: null, keys: [] })
13
+ const select = atom(null, (get, set, key: string | null) => {
14
+ const settings = get(config)
15
+ if (key !== null && !settings.keys.includes(key)) return
16
+ if (get(selected) === key) return
17
+ if (!settings.controlled) set(selected, key)
18
+ settings.onChange?.(key)
19
+ })
20
+ const toggle = atom(null, (get, set, key: string) =>
21
+ set(select, get(selected) === key ? null : key)
22
+ )
23
+ const reset = atom(null, (get, set) => {
24
+ const { initial, keys } = get(config)
25
+ set(select, initial !== null && keys.includes(initial) ? initial : null)
26
+ })
27
+ const state = atom<IChartState>((get) => ({ selectedKey: get(selected) }))
28
+ return { selected, config, select, toggle, reset, state }
29
+ }
30
+ const instances = new Map<
31
+ TChartInstanceId,
32
+ ReturnType<typeof createChartAtoms>
33
+ >()
34
+ function chartAtoms(id: TChartInstanceId) {
35
+ let atoms = instances.get(id)
36
+ if (!atoms) {
37
+ atoms = createChartAtoms(id)
38
+ instances.set(id, atoms)
39
+ }
40
+ return atoms
41
+ }
42
+ export const chartSelectedKeyAtom = (id: TChartInstanceId) =>
43
+ chartAtoms(id).selected
44
+ export const chartStateAtom = (id: TChartInstanceId) => chartAtoms(id).state
45
+ export const selectChartKeyAtom = (id: TChartInstanceId) =>
46
+ chartAtoms(id).select
47
+ export const toggleChartKeyAtom = (id: TChartInstanceId) =>
48
+ chartAtoms(id).toggle
49
+ export const resetChartAtom = (id: TChartInstanceId) => chartAtoms(id).reset
50
+ /** @internal Mounted root projects configuration and controlled props. */
51
+ export const chartConfigAtom = (id: TChartInstanceId) => chartAtoms(id).config
52
+ /** Call only after all consumers of a named instance have unmounted. */
53
+ export function removeChartInstance(id: TChartInstanceId) {
54
+ instances.delete(id)
55
+ }
@@ -0,0 +1,9 @@
1
+ "use client"
2
+ import { createContext, useContext } from "react"
3
+ export const ChartContext = createContext<string | null>(null)
4
+ export function useChartContext() {
5
+ const id = useContext(ChartContext)
6
+ if (id === null)
7
+ throw new Error("useChartContext must be used within <Chart>")
8
+ return id
9
+ }
@@ -0,0 +1,75 @@
1
+ "use client"
2
+ import type { CSSProperties } from "react"
3
+ import { LegendItem, type TDotTone } from "@/components/ui/chat"
4
+ import { cn } from "@/lib/utils"
5
+ import { chartSeriesColor, formatChartValue } from "./utils"
6
+ import type { IChartProps } from "./type"
7
+ export function ChartLegend({
8
+ type,
9
+ data,
10
+ series,
11
+ selectedKey,
12
+ interactive,
13
+ onSelect,
14
+ }: Pick<
15
+ IChartProps,
16
+ "type" | "data" | "series" | "selectedKey" | "interactive"
17
+ > & { onSelect: (key: string) => void }) {
18
+ const entries =
19
+ type === "pie"
20
+ ? data.map((row, i) => ({
21
+ key: row.label,
22
+ label: row.label,
23
+ value: formatChartValue(series[0], row[series[0].key]),
24
+ color: `var(--chart-series-${(i % 5) + 1})`,
25
+ }))
26
+ : series.map((s, i) => ({
27
+ key: s.key,
28
+ label: s.label,
29
+ value: undefined,
30
+ color: chartSeriesColor(s, i),
31
+ }))
32
+ return (
33
+ <ul
34
+ data-slot="chart-legend"
35
+ className={cn(
36
+ "gap-3 flex flex-wrap",
37
+ type === "pie" && "min-w-24 flex-1 flex-col"
38
+ )}
39
+ >
40
+ {entries.map((entry, i) => (
41
+ <li
42
+ key={entry.key}
43
+ style={{ "--legend-color": entry.color } as CSSProperties}
44
+ className={cn(
45
+ "min-w-0 [&_[data-slot=dot]]:bg-(--legend-color)",
46
+ selectedKey && selectedKey !== entry.key && "opacity-40"
47
+ )}
48
+ >
49
+ {interactive ? (
50
+ <button
51
+ type="button"
52
+ aria-pressed={selectedKey === entry.key}
53
+ className="w-full rounded-sm text-left focus-visible:outline-2 focus-visible:outline-ring"
54
+ onClick={() => onSelect(entry.key)}
55
+ >
56
+ <LegendItem
57
+ tone={`series-${(i % 5) + 1}` as TDotTone}
58
+ value={entry.value}
59
+ >
60
+ {entry.label}
61
+ </LegendItem>
62
+ </button>
63
+ ) : (
64
+ <LegendItem
65
+ tone={`series-${(i % 5) + 1}` as TDotTone}
66
+ value={entry.value}
67
+ >
68
+ {entry.label}
69
+ </LegendItem>
70
+ )}
71
+ </li>
72
+ ))}
73
+ </ul>
74
+ )
75
+ }
@@ -0,0 +1,233 @@
1
+ "use client"
2
+ import { useMemo, useEffect, useRef, useState } from "react"
3
+ import { EChartsAreaChart as Area } from "./evilcharts/echarts-area-chart"
4
+ import { EChartsLineChart as Line } from "./evilcharts/echarts-line-chart"
5
+ import { EChartsBarChart as Bar } from "./evilcharts/echarts-bar-chart"
6
+ import { EChartsPieChart as Pie } from "./evilcharts/echarts-pie-chart"
7
+ import { resolveTooltipPosition } from "./evilcharts/echarts-tooltip"
8
+ import { buildChartConfig } from "./utils"
9
+ import { createChartTooltip } from "./chart-tooltip"
10
+ import type { IChartProps } from "./type"
11
+
12
+ /** Wait for layout before initializing ECharts (also supports hidden tabs and SSR). */
13
+ export function ChartPlot(props: IChartProps) {
14
+ const host = useRef<HTMLDivElement>(null)
15
+ const [ready, setReady] = useState(false)
16
+ useEffect(() => {
17
+ const node = host.current
18
+ if (!node) return
19
+ const observer = new ResizeObserver((entries) => {
20
+ if (
21
+ entries.some(
22
+ (entry) => entry.contentRect.width > 0 && entry.contentRect.height > 0
23
+ )
24
+ )
25
+ setReady(true)
26
+ })
27
+ observer.observe(node)
28
+ return () => observer.disconnect()
29
+ }, [])
30
+ return (
31
+ <div ref={host} className="h-full w-full">
32
+ {ready && <ChartRenderer {...props} />}
33
+ </div>
34
+ )
35
+ }
36
+
37
+ function ChartRenderer({
38
+ type,
39
+ data,
40
+ series,
41
+ variant = "default",
42
+ renderer = "canvas",
43
+ animation = true,
44
+ loading = false,
45
+ interactive = false,
46
+ selectedKey = null,
47
+ onSelectionChange,
48
+ curveType = "monotone",
49
+ grid = true,
50
+ xAxis = true,
51
+ yAxis = false,
52
+ tooltip = true,
53
+ brush = false,
54
+ pie,
55
+ chartOptions,
56
+ }: IChartProps) {
57
+ const config = useMemo(
58
+ () => buildChartConfig(type, data, series),
59
+ [type, data, series]
60
+ )
61
+ const options = useMemo(
62
+ () => ({
63
+ ...(tooltip
64
+ ? {
65
+ tooltip: {
66
+ show: !loading,
67
+ trigger: type === "pie" ? "item" : "axis",
68
+ confine: true,
69
+ padding: 0,
70
+ borderWidth: 0,
71
+ backgroundColor: "transparent",
72
+ // Only the inner tooltip owns a shadow; ECharts adds one otherwise.
73
+ extraCssText: "box-shadow:none;",
74
+ axisPointer: {
75
+ type:
76
+ typeof tooltip === "object" && tooltip.cursor === false
77
+ ? "none"
78
+ : "line",
79
+ },
80
+ position: resolveTooltipPosition(
81
+ typeof tooltip === "object"
82
+ ? (tooltip.position ?? "variable")
83
+ : "variable"
84
+ ),
85
+ formatter: createChartTooltip(
86
+ type,
87
+ data,
88
+ series,
89
+ typeof tooltip === "object" ? tooltip : {}
90
+ ),
91
+ },
92
+ }
93
+ : {}),
94
+ ...chartOptions,
95
+ }),
96
+ [tooltip, loading, type, data, series, chartOptions]
97
+ )
98
+ const common = {
99
+ data,
100
+ config,
101
+ renderer,
102
+ animation,
103
+ isLoading: loading,
104
+ chartOptions: options,
105
+ className: "h-full w-full",
106
+ }
107
+ const selection = { selectedDataKey: selectedKey, onSelectionChange }
108
+ const tooltipProps = typeof tooltip === "object" ? tooltip : {}
109
+ const xProps = typeof xAxis === "object" ? xAxis : {}
110
+ const yProps = typeof yAxis === "object" ? yAxis : {}
111
+ const brushProps = typeof brush === "object" ? brush : {}
112
+ if (type === "pie")
113
+ return (
114
+ <Pie
115
+ {...common}
116
+ dataKey={series[0]?.key ?? "value"}
117
+ nameKey="label"
118
+ selectedSector={selectedKey}
119
+ onSelectionChange={(value) =>
120
+ onSelectionChange?.(value?.dataKey ?? null)
121
+ }
122
+ >
123
+ <Pie.Pie
124
+ innerRadius={variant === "donut" || variant === "default" ? "66%" : 0}
125
+ outerRadius="95%"
126
+ paddingAngle={2}
127
+ cornerRadius={8}
128
+ isClickable={interactive}
129
+ {...pie}
130
+ />
131
+ {tooltip && <Pie.Tooltip {...tooltipProps} />}
132
+ </Pie>
133
+ )
134
+ if (type === "bar")
135
+ return (
136
+ <Bar
137
+ {...common}
138
+ {...selection}
139
+ xDataKey="label"
140
+ barRadius={8}
141
+ layout={variant === "horizontal" ? "horizontal" : "vertical"}
142
+ stackType={
143
+ variant === "percent"
144
+ ? "percent"
145
+ : variant === "stacked"
146
+ ? "stacked"
147
+ : "default"
148
+ }
149
+ >
150
+ {series.map((s) => (
151
+ <Bar.Bar
152
+ key={s.key}
153
+ dataKey={s.key}
154
+ isClickable={interactive}
155
+ {...s.bar}
156
+ />
157
+ ))}
158
+ {xAxis && <Bar.XAxis {...xProps} />}
159
+ {yAxis && (
160
+ <Bar.YAxis
161
+ {...yProps}
162
+ tickFormatter={
163
+ yProps.tickFormatter
164
+ ? (value, index) => yProps.tickFormatter!(Number(value), index)
165
+ : undefined
166
+ }
167
+ />
168
+ )}
169
+ {grid && <Bar.Grid />}
170
+ {tooltip && <Bar.Tooltip {...tooltipProps} />}
171
+ {brush && variant !== "horizontal" && <Bar.Brush {...brushProps} />}
172
+ </Bar>
173
+ )
174
+ if (type === "line")
175
+ return (
176
+ <Line
177
+ {...common}
178
+ {...selection}
179
+ xDataKey="label"
180
+ curveType={curveType}
181
+ enableHoverHighlight={interactive}
182
+ >
183
+ {series.map((s) => (
184
+ <Line.Line
185
+ key={s.key}
186
+ dataKey={s.key}
187
+ isClickable={interactive}
188
+ {...s.line}
189
+ >
190
+ <Line.Dot />
191
+ <Line.ActiveDot />
192
+ </Line.Line>
193
+ ))}
194
+ {xAxis && <Line.XAxis {...xProps} />}
195
+ {yAxis && <Line.YAxis {...yProps} />}
196
+ {grid && <Line.Grid />}
197
+ {tooltip && <Line.Tooltip {...tooltipProps} />}
198
+ {brush && <Line.Brush {...brushProps} />}
199
+ </Line>
200
+ )
201
+ return (
202
+ <Area
203
+ {...common}
204
+ {...selection}
205
+ xDataKey="label"
206
+ curveType={curveType}
207
+ enableHoverHighlight={interactive}
208
+ stackType={
209
+ variant === "percent"
210
+ ? "expanded"
211
+ : variant === "stacked"
212
+ ? "stacked"
213
+ : "default"
214
+ }
215
+ >
216
+ {series.map((s) => (
217
+ <Area.Area
218
+ key={s.key}
219
+ dataKey={s.key}
220
+ isClickable={interactive}
221
+ {...s.area}
222
+ >
223
+ <Area.ActiveDot />
224
+ </Area.Area>
225
+ ))}
226
+ {xAxis && <Area.XAxis {...xProps} />}
227
+ {yAxis && <Area.YAxis {...yProps} />}
228
+ {grid && <Area.Grid />}
229
+ {tooltip && <Area.Tooltip {...tooltipProps} />}
230
+ {brush && <Area.Brush {...brushProps} />}
231
+ </Area>
232
+ )
233
+ }