@spatika/react 1.3.0 → 1.4.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.
- package/README.md +6 -2
- package/dist/composites/EventCalendar.d.ts +13 -2
- package/dist/composites/EventCalendar.d.ts.map +1 -1
- package/dist/composites/EventCalendar.js +10 -24
- package/dist/composites/cartesian-charts.d.ts +12 -2
- package/dist/composites/cartesian-charts.d.ts.map +1 -1
- package/dist/composites/cartesian-charts.js +27 -13
- package/dist/composites/chart-composition.d.ts +19 -1
- package/dist/composites/chart-composition.d.ts.map +1 -1
- package/dist/composites/chart-composition.js +124 -62
- package/dist/composites/chart-interaction.d.ts +81 -0
- package/dist/composites/chart-interaction.d.ts.map +1 -0
- package/dist/composites/chart-interaction.js +56 -0
- package/dist/composites/chart-ui.d.ts +8 -13
- package/dist/composites/chart-ui.d.ts.map +1 -1
- package/dist/composites/chart-ui.js +9 -6
- package/dist/composites/flow-charts.d.ts +6 -1
- package/dist/composites/flow-charts.d.ts.map +1 -1
- package/dist/composites/flow-charts.js +28 -14
- package/dist/composites/radial-charts.d.ts +11 -1
- package/dist/composites/radial-charts.d.ts.map +1 -1
- package/dist/composites/radial-charts.js +33 -14
- package/dist/composites/scheduler-ui.d.ts +14 -1
- package/dist/composites/scheduler-ui.d.ts.map +1 -1
- package/dist/composites/scheduler-ui.js +23 -2
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/lib/chips.d.ts +3 -0
- package/dist/lib/chips.d.ts.map +1 -1
- package/dist/lib/chips.js +10 -0
- package/dist/lib/scheduler.d.ts +12 -0
- package/dist/lib/scheduler.d.ts.map +1 -1
- package/dist/lib/scheduler.js +22 -0
- package/dist/primitives/NativeSelect.d.ts +10 -1
- package/dist/primitives/NativeSelect.d.ts.map +1 -1
- package/dist/primitives/NativeSelect.js +2 -2
- package/package.json +3 -3
- package/skills/spatika-ui/SKILL.md +4 -0
- package/src/composites/EventCalendar.tsx +35 -24
- package/src/composites/cartesian-charts.test.tsx +86 -2
- package/src/composites/cartesian-charts.tsx +84 -16
- package/src/composites/chart-composition.test.tsx +15 -0
- package/src/composites/chart-composition.tsx +229 -88
- package/src/composites/chart-interaction.ts +136 -0
- package/src/composites/chart-ui.tsx +35 -27
- package/src/composites/flow-charts.test.tsx +21 -2
- package/src/composites/flow-charts.tsx +59 -25
- package/src/composites/radial-charts.test.tsx +17 -2
- package/src/composites/radial-charts.tsx +38 -12
- package/src/composites/scheduler-ui.tsx +128 -26
- package/src/composites/scheduler.test.tsx +39 -0
- package/src/index.test.ts +4 -0
- package/src/index.ts +18 -1
- package/src/lib/chips.ts +16 -0
- package/src/lib/scheduler.test.ts +16 -0
- package/src/lib/scheduler.ts +34 -0
- package/src/primitives/NativeSelect.test.tsx +28 -0
- package/src/primitives/NativeSelect.tsx +14 -4
|
@@ -54,6 +54,10 @@ export function App() {
|
|
|
54
54
|
| Chart | `BarChart` / `LineChart` / `ChartContainer` |
|
|
55
55
|
| Calendar | `EventCalendar` |
|
|
56
56
|
|
|
57
|
+
Charts: `onItemClick` for mark navigation, `itemColors` for per-bar tints, `renderTooltip` / `renderMark` for host chrome, `referenceLines` for capacity guides, `fillHeight` when the parent sets height. Pie and Heatmap accept the same click and tooltip slots.
|
|
58
|
+
|
|
59
|
+
Prefer `toolbarDensity="compact"` and `showDateJump` for a dense product toolbar. `NativeSelect size="sm"` is the compact pill. `onVisibleRangeChange` is the fetch window (`end` exclusive). Use `showEventEditor={false}` when the host owns create/edit.
|
|
60
|
+
|
|
57
61
|
## App shell
|
|
58
62
|
|
|
59
63
|
```tsx
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
useEffect,
|
|
2
3
|
useMemo,
|
|
3
4
|
useRef,
|
|
4
5
|
useState,
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
addDays,
|
|
14
15
|
addMonths,
|
|
15
16
|
agendaDays,
|
|
17
|
+
calendarVisibleRange,
|
|
18
|
+
CALENDAR_AGENDA_DAYS,
|
|
16
19
|
DEFAULT_SCHEDULER_PREFERENCES,
|
|
17
20
|
eventCanDrag,
|
|
18
21
|
eventCanResize,
|
|
@@ -44,6 +47,7 @@ import {
|
|
|
44
47
|
weekNumber,
|
|
45
48
|
weekSpanLanesForDays,
|
|
46
49
|
type CalendarView,
|
|
50
|
+
type CalendarVisibleRange,
|
|
47
51
|
type NormalizedEvent,
|
|
48
52
|
type SchedulerEvent,
|
|
49
53
|
type SchedulerPreferences,
|
|
@@ -54,9 +58,11 @@ import {
|
|
|
54
58
|
CALENDAR_VIEW_LABEL,
|
|
55
59
|
EventResizeHandle,
|
|
56
60
|
ResourceLegend,
|
|
61
|
+
SchedulerDateJump,
|
|
57
62
|
SchedulerPreferencesMenu,
|
|
58
63
|
SchedulerShell,
|
|
59
64
|
SchedulerToolbar,
|
|
65
|
+
type SchedulerToolbarDensity,
|
|
60
66
|
} from "./scheduler-ui";
|
|
61
67
|
import { DropdownMenuItem } from "../primitives/DropdownMenu";
|
|
62
68
|
|
|
@@ -125,6 +131,14 @@ export type EventCalendarProps = {
|
|
|
125
131
|
showPreferences?: boolean;
|
|
126
132
|
/** Extra controls after the view menu. Ignored when `renderToolbar` is set. */
|
|
127
133
|
toolbarTrailing?: ReactNode;
|
|
134
|
+
/** Default toolbar layout. Compact is a single desktop row. Ignored when `renderToolbar` is set. */
|
|
135
|
+
toolbarDensity?: SchedulerToolbarDensity;
|
|
136
|
+
/** Month/year native selects on the default toolbar. Ignored when `renderToolbar` is set. */
|
|
137
|
+
showDateJump?: boolean;
|
|
138
|
+
/** Years in the date-jump select. Defaults to a window around the current year. */
|
|
139
|
+
yearOptions?: number[];
|
|
140
|
+
/** Fires when the painted date window changes. `end` is exclusive. */
|
|
141
|
+
onVisibleRangeChange?: (range: CalendarVisibleRange & { view: CalendarView }) => void;
|
|
128
142
|
/** Replace event chip contents. The kit still wraps with the click/drag trigger. */
|
|
129
143
|
renderEvent?: (
|
|
130
144
|
event: NormalizedEvent,
|
|
@@ -136,7 +150,6 @@ export type EventCalendarProps = {
|
|
|
136
150
|
};
|
|
137
151
|
|
|
138
152
|
const MAX_TIMED_IN_CELL = 3;
|
|
139
|
-
const AGENDA_LENGTH = 14;
|
|
140
153
|
|
|
141
154
|
function chunkWeeks<T>(items: T[]): T[][] {
|
|
142
155
|
const weeks: T[][] = [];
|
|
@@ -153,27 +166,6 @@ function startOfToday(): Date {
|
|
|
153
166
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
function visibleRange(
|
|
157
|
-
view: CalendarView,
|
|
158
|
-
date: Date,
|
|
159
|
-
weekStartsOn: number,
|
|
160
|
-
): { start: Date; end: Date } {
|
|
161
|
-
if (view === "month") {
|
|
162
|
-
const grid = getMonthGrid(date, weekStartsOn);
|
|
163
|
-
return { start: grid[0], end: addDays(grid[grid.length - 1], 1) };
|
|
164
|
-
}
|
|
165
|
-
if (view === "week") {
|
|
166
|
-
const days = getWeekDays(date, weekStartsOn);
|
|
167
|
-
return { start: days[0], end: addDays(days[6], 1) };
|
|
168
|
-
}
|
|
169
|
-
if (view === "agenda") {
|
|
170
|
-
const days = agendaDays(date, AGENDA_LENGTH);
|
|
171
|
-
return { start: days[0], end: addDays(days[days.length - 1], 1) };
|
|
172
|
-
}
|
|
173
|
-
const start = startOfDay(date);
|
|
174
|
-
return { start, end: addDays(start, 1) };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
169
|
function draftFromSource(source: SchedulerEvent): EventEditorDraft {
|
|
178
170
|
return {
|
|
179
171
|
id: source.id,
|
|
@@ -217,6 +209,10 @@ export function EventCalendar({
|
|
|
217
209
|
showEventEditor = true,
|
|
218
210
|
showPreferences = true,
|
|
219
211
|
toolbarTrailing,
|
|
212
|
+
toolbarDensity = "default",
|
|
213
|
+
showDateJump = false,
|
|
214
|
+
yearOptions,
|
|
215
|
+
onVisibleRangeChange,
|
|
220
216
|
renderEvent,
|
|
221
217
|
renderToolbar,
|
|
222
218
|
className,
|
|
@@ -253,10 +249,14 @@ export function EventCalendar({
|
|
|
253
249
|
const editorLocked = readOnly || Boolean(draft?.id && sourceEvent(events, draft.id)?.readOnly);
|
|
254
250
|
|
|
255
251
|
const range = useMemo(
|
|
256
|
-
() =>
|
|
252
|
+
() => calendarVisibleRange(resolvedView, resolvedDate, startOn),
|
|
257
253
|
[resolvedView, resolvedDate, startOn],
|
|
258
254
|
);
|
|
259
255
|
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
onVisibleRangeChange?.({ start: range.start, end: range.end, view: resolvedView });
|
|
258
|
+
}, [range.start, range.end, resolvedView, onVisibleRangeChange]);
|
|
259
|
+
|
|
260
260
|
const normalized = useMemo(() => {
|
|
261
261
|
const visible = visibleEvents(normalizeEvents(events, resources), hiddenIds);
|
|
262
262
|
return expandVisibleEvents(visible, range.start, range.end, events);
|
|
@@ -473,6 +473,7 @@ export function EventCalendar({
|
|
|
473
473
|
) : (
|
|
474
474
|
<SchedulerToolbar
|
|
475
475
|
title={title}
|
|
476
|
+
density={toolbarDensity}
|
|
476
477
|
onPrev={() => go(-1)}
|
|
477
478
|
onNext={() => go(1)}
|
|
478
479
|
onToday={() => setDate(new Date())}
|
|
@@ -482,6 +483,16 @@ export function EventCalendar({
|
|
|
482
483
|
{CALENDAR_VIEW_LABEL[item]}
|
|
483
484
|
</DropdownMenuItem>
|
|
484
485
|
))}
|
|
486
|
+
dateJump={
|
|
487
|
+
showDateJump ? (
|
|
488
|
+
<SchedulerDateJump
|
|
489
|
+
date={resolvedDate}
|
|
490
|
+
locale={locale}
|
|
491
|
+
yearOptions={yearOptions}
|
|
492
|
+
onDateChange={(next) => setDate(next)}
|
|
493
|
+
/>
|
|
494
|
+
) : null
|
|
495
|
+
}
|
|
485
496
|
trailing={
|
|
486
497
|
<>
|
|
487
498
|
{toolbarTrailing}
|
|
@@ -964,7 +975,7 @@ function AgendaList({
|
|
|
964
975
|
options?: { resize?: "ns"; variant: EventCalendarEventVariant },
|
|
965
976
|
) => ReactNode;
|
|
966
977
|
}) {
|
|
967
|
-
const days = filterWeekendDays(agendaDays(date,
|
|
978
|
+
const days = filterWeekendDays(agendaDays(date, CALENDAR_AGENDA_DAYS), showWeekends);
|
|
968
979
|
|
|
969
980
|
return (
|
|
970
981
|
<div className="divide-y divide-border/50">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { AreaChart, BarChart, LineChart } from "./cartesian-charts";
|
|
4
4
|
|
|
5
5
|
const months = ["Jan", "Feb", "Mar"];
|
|
@@ -49,4 +49,88 @@ describe("cartesian-charts", () => {
|
|
|
49
49
|
expect(container.querySelector('[data-slot="area-plot"]')).toBeTruthy();
|
|
50
50
|
expect(container.querySelectorAll('[data-slot="area-plot"] path').length).toBeGreaterThanOrEqual(2);
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
it("fires onItemClick with the bar category and value", () => {
|
|
54
|
+
const onItemClick = vi.fn();
|
|
55
|
+
const { container } = render(
|
|
56
|
+
<BarChart
|
|
57
|
+
width={360}
|
|
58
|
+
height={220}
|
|
59
|
+
hideLegend
|
|
60
|
+
xAxis={[{ data: months }]}
|
|
61
|
+
series={[{ label: "Views", data: [4, 3, 5] }]}
|
|
62
|
+
onItemClick={onItemClick}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
const mark = container.querySelector(".spk-chart-mark--interactive");
|
|
66
|
+
expect(mark).toBeTruthy();
|
|
67
|
+
fireEvent.click(mark!);
|
|
68
|
+
expect(onItemClick).toHaveBeenCalledWith(
|
|
69
|
+
expect.objectContaining({ seriesLabel: "Views", dataIndex: 0, category: "Jan", value: 4 }),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("colors individual bars from itemColors", () => {
|
|
74
|
+
const { container } = render(
|
|
75
|
+
<BarChart
|
|
76
|
+
width={360}
|
|
77
|
+
height={220}
|
|
78
|
+
hideLegend
|
|
79
|
+
xAxis={[{ data: months }]}
|
|
80
|
+
series={[{ label: "Growth", data: [2, -1, 3], itemColors: ["var(--chart-3)", "var(--chart-4)"] }]}
|
|
81
|
+
/>,
|
|
82
|
+
);
|
|
83
|
+
const marks = container.querySelectorAll('[data-slot="bar-plot"] .spk-chart-mark');
|
|
84
|
+
expect(marks[0]?.getAttribute("fill")).toBe("var(--chart-3)");
|
|
85
|
+
expect(marks[1]?.getAttribute("fill")).toBe("var(--chart-4)");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("renders a reference line and end labels on bars", () => {
|
|
89
|
+
const { container } = render(
|
|
90
|
+
<BarChart
|
|
91
|
+
width={360}
|
|
92
|
+
height={220}
|
|
93
|
+
hideLegend
|
|
94
|
+
showLabels
|
|
95
|
+
xAxis={[{ data: months }]}
|
|
96
|
+
series={[{ label: "Views", data: [4, 3, 5] }]}
|
|
97
|
+
referenceLines={[{ y: 4, label: "Target" }]}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
expect(container.querySelector('[data-slot="chart-reference-lines"]')).toBeTruthy();
|
|
101
|
+
expect(screen.getByText("Target")).toBeInTheDocument();
|
|
102
|
+
expect(container.querySelector('[data-slot="bar-labels"]')).toBeTruthy();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("fires onItemClick from horizontal bars", () => {
|
|
106
|
+
const onItemClick = vi.fn();
|
|
107
|
+
const { container } = render(
|
|
108
|
+
<BarChart
|
|
109
|
+
width={360}
|
|
110
|
+
height={220}
|
|
111
|
+
hideLegend
|
|
112
|
+
layout="horizontal"
|
|
113
|
+
xAxis={[{ data: months }]}
|
|
114
|
+
series={[{ label: "Views", data: [4, 3, 5] }]}
|
|
115
|
+
onItemClick={onItemClick}
|
|
116
|
+
/>,
|
|
117
|
+
);
|
|
118
|
+
fireEvent.click(container.querySelector(".spk-chart-mark--interactive")!);
|
|
119
|
+
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ category: "Jan", value: 4 }));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("renders a custom tooltip from hover", () => {
|
|
123
|
+
const { container } = render(
|
|
124
|
+
<BarChart
|
|
125
|
+
width={360}
|
|
126
|
+
height={220}
|
|
127
|
+
hideLegend
|
|
128
|
+
xAxis={[{ data: months }]}
|
|
129
|
+
series={[{ label: "Views", data: [4, 3, 5] }]}
|
|
130
|
+
renderTooltip={(hover) => <p>{hover.category} custom</p>}
|
|
131
|
+
/>,
|
|
132
|
+
);
|
|
133
|
+
fireEvent.mouseEnter(container.querySelector(".spk-chart-mark")!);
|
|
134
|
+
expect(screen.getByText("Jan custom")).toBeInTheDocument();
|
|
135
|
+
});
|
|
52
136
|
});
|
|
@@ -33,6 +33,14 @@ import {
|
|
|
33
33
|
type ChartHover,
|
|
34
34
|
} from "./chart-ui";
|
|
35
35
|
import { ChartContainer } from "./chart-composition";
|
|
36
|
+
import {
|
|
37
|
+
bindChartMark,
|
|
38
|
+
seriesItemColor,
|
|
39
|
+
type ChartItemEvent,
|
|
40
|
+
type ChartMarkRenderContext,
|
|
41
|
+
type ChartReferenceLine,
|
|
42
|
+
type ChartTooltipRenderer,
|
|
43
|
+
} from "./chart-interaction";
|
|
36
44
|
import { ScatterWebGLLayer } from "./ScatterWebGL";
|
|
37
45
|
import {
|
|
38
46
|
nearestScatterHit,
|
|
@@ -49,6 +57,8 @@ export type NumericSeries = {
|
|
|
49
57
|
data?: Array<number | null>;
|
|
50
58
|
dataKey?: string;
|
|
51
59
|
color?: string;
|
|
60
|
+
/** Per-category colors; each entry falls back to `color`. */
|
|
61
|
+
itemColors?: Array<string | null | undefined>;
|
|
52
62
|
stack?: string;
|
|
53
63
|
area?: boolean;
|
|
54
64
|
curve?: ChartCurve;
|
|
@@ -75,6 +85,12 @@ export type CartesianChartProps = {
|
|
|
75
85
|
highlightScope?: import("../lib/charts").HighlightScope;
|
|
76
86
|
animated?: boolean;
|
|
77
87
|
locale?: string;
|
|
88
|
+
fillHeight?: boolean;
|
|
89
|
+
onItemClick?: (event: ChartItemEvent) => void;
|
|
90
|
+
renderTooltip?: ChartTooltipRenderer;
|
|
91
|
+
renderMark?: (ctx: ChartMarkRenderContext) => ReactNode;
|
|
92
|
+
referenceLines?: ChartReferenceLine[];
|
|
93
|
+
showLabels?: boolean;
|
|
78
94
|
};
|
|
79
95
|
|
|
80
96
|
export type BarChartProps = CartesianChartProps & {
|
|
@@ -268,6 +284,12 @@ export function BarChart({
|
|
|
268
284
|
highlightScope,
|
|
269
285
|
animated,
|
|
270
286
|
locale,
|
|
287
|
+
fillHeight,
|
|
288
|
+
onItemClick,
|
|
289
|
+
renderTooltip,
|
|
290
|
+
renderMark,
|
|
291
|
+
referenceLines,
|
|
292
|
+
showLabels,
|
|
271
293
|
}: BarChartProps) {
|
|
272
294
|
const meta = seriesMeta(series, colors);
|
|
273
295
|
|
|
@@ -287,6 +309,10 @@ export function BarChart({
|
|
|
287
309
|
margin={margin}
|
|
288
310
|
className={className}
|
|
289
311
|
barGapRatio={barGapRatio}
|
|
312
|
+
fillHeight={fillHeight}
|
|
313
|
+
onItemClick={onItemClick}
|
|
314
|
+
renderTooltip={renderTooltip}
|
|
315
|
+
showLabels={showLabels}
|
|
290
316
|
/>
|
|
291
317
|
);
|
|
292
318
|
}
|
|
@@ -315,6 +341,12 @@ export function BarChart({
|
|
|
315
341
|
highlightScope={highlightScope}
|
|
316
342
|
animated={animated}
|
|
317
343
|
locale={locale}
|
|
344
|
+
fillHeight={fillHeight}
|
|
345
|
+
onItemClick={onItemClick}
|
|
346
|
+
renderTooltip={renderTooltip}
|
|
347
|
+
renderMark={renderMark}
|
|
348
|
+
referenceLines={referenceLines}
|
|
349
|
+
showLabels={showLabels}
|
|
318
350
|
/>
|
|
319
351
|
);
|
|
320
352
|
}
|
|
@@ -333,6 +365,10 @@ function HorizontalBarChart({
|
|
|
333
365
|
margin,
|
|
334
366
|
className,
|
|
335
367
|
barGapRatio = 0.15,
|
|
368
|
+
fillHeight,
|
|
369
|
+
onItemClick,
|
|
370
|
+
renderTooltip,
|
|
371
|
+
showLabels,
|
|
336
372
|
}: BarChartProps) {
|
|
337
373
|
const categories = categoryLabels(xAxis?.[0], series[0]?.data?.length ?? 0);
|
|
338
374
|
const domain = numericDomain(series, Boolean(stacked), yAxis?.[0]);
|
|
@@ -349,11 +385,13 @@ function HorizontalBarChart({
|
|
|
349
385
|
className={className}
|
|
350
386
|
width={width}
|
|
351
387
|
height={height}
|
|
352
|
-
|
|
388
|
+
fillHeight={fillHeight}
|
|
389
|
+
margin={margin ?? { top: 16, right: showLabels ? 48 : 16, bottom: 28, left: 72 }}
|
|
353
390
|
legend={hideLegend ? undefined : meta}
|
|
354
391
|
hiddenIds={hidden}
|
|
355
392
|
onToggleSeries={toggle}
|
|
356
393
|
hover={hover}
|
|
394
|
+
renderTooltip={renderTooltip}
|
|
357
395
|
aria-label={`Bar chart of ${meta.map((s) => s.label).join(", ")}`}
|
|
358
396
|
>
|
|
359
397
|
{({ width: w, height: h, m }) => {
|
|
@@ -423,22 +461,42 @@ function HorizontalBarChart({
|
|
|
423
461
|
}
|
|
424
462
|
const x = Math.min(x1, x2);
|
|
425
463
|
const barW = Math.max(1, Math.abs(x2 - x1));
|
|
464
|
+
const color = seriesItemColor(info.color, item.itemColors, i);
|
|
465
|
+
const event: ChartItemEvent = {
|
|
466
|
+
seriesId: info.id,
|
|
467
|
+
seriesLabel: info.label,
|
|
468
|
+
dataIndex: i,
|
|
469
|
+
category: categories[i] ?? i,
|
|
470
|
+
value,
|
|
471
|
+
color,
|
|
472
|
+
};
|
|
426
473
|
return (
|
|
427
|
-
<
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
474
|
+
<g key={`${info.id}-${i}`}>
|
|
475
|
+
<path
|
|
476
|
+
d={roundedRectPath(x, y, barW, stacked ? yScale.bandwidth : barH, borderRadius)}
|
|
477
|
+
fill={color}
|
|
478
|
+
{...bindChartMark({
|
|
479
|
+
event,
|
|
480
|
+
hover: {
|
|
481
|
+
title: String(categories[i]),
|
|
482
|
+
items: [{ color, label: info.label, value: formatSeriesValue(value) }],
|
|
483
|
+
},
|
|
484
|
+
onItemClick,
|
|
485
|
+
setHover,
|
|
486
|
+
clearHover: clear,
|
|
487
|
+
})}
|
|
488
|
+
/>
|
|
489
|
+
{showLabels ? (
|
|
490
|
+
<text
|
|
491
|
+
className="spk-chart-label"
|
|
492
|
+
x={Math.max(x1, x2) + 6}
|
|
493
|
+
y={y + (stacked ? yScale.bandwidth : barH) / 2}
|
|
494
|
+
dominantBaseline="middle"
|
|
495
|
+
>
|
|
496
|
+
{formatSeriesValue(value)}
|
|
497
|
+
</text>
|
|
498
|
+
) : null}
|
|
499
|
+
</g>
|
|
442
500
|
);
|
|
443
501
|
});
|
|
444
502
|
})}
|
|
@@ -471,6 +529,11 @@ export function LineChart({
|
|
|
471
529
|
highlightScope,
|
|
472
530
|
animated,
|
|
473
531
|
locale,
|
|
532
|
+
fillHeight,
|
|
533
|
+
onItemClick,
|
|
534
|
+
renderTooltip,
|
|
535
|
+
renderMark,
|
|
536
|
+
referenceLines,
|
|
474
537
|
}: LineChartProps) {
|
|
475
538
|
const meta = seriesMeta(series, colors);
|
|
476
539
|
const stackedAreas = stacked && (area || series.some((item) => item.area || item.type === "area"));
|
|
@@ -501,6 +564,11 @@ export function LineChart({
|
|
|
501
564
|
highlightScope={highlightScope}
|
|
502
565
|
animated={animated}
|
|
503
566
|
locale={locale}
|
|
567
|
+
fillHeight={fillHeight}
|
|
568
|
+
onItemClick={onItemClick}
|
|
569
|
+
renderTooltip={renderTooltip}
|
|
570
|
+
renderMark={renderMark}
|
|
571
|
+
referenceLines={referenceLines}
|
|
504
572
|
/>
|
|
505
573
|
);
|
|
506
574
|
}
|
|
@@ -50,4 +50,19 @@ describe("chart-composition", () => {
|
|
|
50
50
|
expect(container.querySelector('[data-slot="area-plot"]')).toBeTruthy();
|
|
51
51
|
expect(container.querySelectorAll('[data-slot="area-plot"] path').length).toBeGreaterThanOrEqual(2);
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
it("draws reference lines from ChartContainer", () => {
|
|
55
|
+
const { container } = render(
|
|
56
|
+
<ChartContainer
|
|
57
|
+
width={360}
|
|
58
|
+
height={220}
|
|
59
|
+
hideLegend
|
|
60
|
+
xAxis={[{ data: ["Jan", "Feb"] }]}
|
|
61
|
+
series={[{ type: "line", label: "Gap", data: [3, 5] }]}
|
|
62
|
+
referenceLines={[{ y: 4, label: "Capacity" }]}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
expect(container.querySelector('[data-slot="chart-reference-lines"]')).toBeTruthy();
|
|
66
|
+
expect(screen.getByText("Capacity")).toBeInTheDocument();
|
|
67
|
+
});
|
|
53
68
|
});
|