diff --git a/.changeset/silly-integers-relax.md b/.changeset/silly-integers-relax.md new file mode 100644 index 000000000..a08b83ee3 --- /dev/null +++ b/.changeset/silly-integers-relax.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/kumo": minor +--- + +Add an optional `yAxisMinInterval` prop to `TimeseriesChart` for discrete data. diff --git a/packages/kumo-docs-astro/src/components/demos/Chart/ChartDemo.tsx b/packages/kumo-docs-astro/src/components/demos/Chart/ChartDemo.tsx index d95db4647..b1865edf7 100644 --- a/packages/kumo-docs-astro/src/components/demos/Chart/ChartDemo.tsx +++ b/packages/kumo-docs-astro/src/components/demos/Chart/ChartDemo.tsx @@ -111,6 +111,40 @@ export function BasicLineChartDemo() { ); } +/** + * Discrete request-count chart with whole-number y-axis ticks. + */ +export function IntegerYAxisChartDemo() { + const isDarkMode = useIsDarkMode(); + + const data = useMemo(() => { + const end = Date.now(); + const values = [0, 1, 3, 2, 5, 4, 7, 3, 6, 2, 4, 1]; + + return [ + { + name: "Requests", + data: values.map((value, index): [number, number] => [ + end - (values.length - index - 1) * 60_000, + value, + ]), + color: ChartPalette.semantic("Neutral", isDarkMode), + }, + ]; + }, [isDarkMode]); + + return ( + + ); +} + export function ReferenceMarkersChartDemo() { const isDarkMode = useIsDarkMode(); diff --git a/packages/kumo-docs-astro/src/pages/charts/timeseries.mdx b/packages/kumo-docs-astro/src/pages/charts/timeseries.mdx index 62d6bfc11..be0595028 100644 --- a/packages/kumo-docs-astro/src/pages/charts/timeseries.mdx +++ b/packages/kumo-docs-astro/src/pages/charts/timeseries.mdx @@ -9,6 +9,7 @@ import ComponentSection from "~/components/docs/ComponentSection.astro"; import ComponentExample from "~/components/docs/ComponentExample.astro"; import { BasicLineChartDemo, + IntegerYAxisChartDemo, BarChartDemo, ReferenceMarkersChartDemo, ThresholdsChartDemo, @@ -43,6 +44,21 @@ import { +## Discrete Count Data + +

+ Set yAxisMinInterval={1} for discrete values such as request + counts. This prevents fractional y-axis tick marks while leaving the default + axis behavior unchanged for rates and other continuous values. +

+ + + + +
+ + + ## Reference Markers

diff --git a/packages/kumo/src/components/chart/TimeseriesChart.test.tsx b/packages/kumo/src/components/chart/TimeseriesChart.test.tsx index da7c87ca3..9fcf37498 100644 --- a/packages/kumo/src/components/chart/TimeseriesChart.test.tsx +++ b/packages/kumo/src/components/chart/TimeseriesChart.test.tsx @@ -24,6 +24,49 @@ const createMockEcharts = (mockChart = createMockChart()) => ({ }); describe("TimeseriesChart", () => { + it("leaves the y-axis interval unconstrained by default", async () => { + const mockChart = createMockChart(); + + render( + , + ); + + await waitFor(() => + expect(mockChart.setOption).toHaveBeenCalledWith( + expect.objectContaining({ + yAxis: expect.not.objectContaining({ + minInterval: expect.anything(), + }), + }), + expect.anything(), + ), + ); + }); + + it("passes yAxisMinInterval to ECharts", async () => { + const mockChart = createMockChart(); + + render( + , + ); + + await waitFor(() => + expect(mockChart.setOption).toHaveBeenCalledWith( + expect.objectContaining({ + yAxis: expect.objectContaining({ minInterval: 1 }), + }), + expect.anything(), + ), + ); + }); + it("does not reserve footer space for an empty string", () => { const { container } = render( Math.min(value.min, thresholdExtent.min), @@ -555,6 +564,7 @@ export const TimeseriesChart = forwardRef< yAxisTickFormat, yAxisName, yAxisTickCount, + yAxisMinInterval, incompleteBefore, incompleteAfter, type,