From 8a4d7f030b911870e7db5e9f6131c9ca004dde29 Mon Sep 17 00:00:00 2001 From: Oseer Williams <265368733+owilliams-tetrascience@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:46:02 -0400 Subject: [PATCH 1/2] fix(charts): stop chart title defaulting to component name (SW-1889) AreaGraph, BarGraph, LineGraph, and PieChart hard-defaulted their `title` prop to the literal component name ("Area Graph", etc.), so any consumer that omitted `title` got a 32px placeholder title plus reserved top margin leaking into production UIs. - Default `title` to `undefined` in all four charts. - Omit the Plotly title block (and the JSX title in PieChart) when no title is set, and shrink `margin.t` accordingly. Top margins extracted to TITLE_MARGIN_TOP / NO_TITLE_MARGIN_TOP constants. - Add a "No Title" Storybook story per chart asserting no title block renders while the chart still draws. - Six existing LineGraph stories relied on the old default; set their title explicitly so they keep asserting "Line Graph". Co-Authored-By: Claude Opus 4.8 --- .../charts/AreaGraph/AreaGraph.stories.tsx | 33 ++++++++++++ src/components/charts/AreaGraph/AreaGraph.tsx | 51 ++++++++++++------- .../charts/BarGraph/BarGraph.stories.tsx | 30 +++++++++++ src/components/charts/BarGraph/BarGraph.tsx | 34 +++++++++---- .../charts/LineGraph/LineGraph.stories.tsx | 42 +++++++++++++++ src/components/charts/LineGraph/LineGraph.tsx | 34 +++++++++---- .../charts/PieChart/PieChart.stories.tsx | 36 +++++++++++++ src/components/charts/PieChart/PieChart.tsx | 2 +- 8 files changed, 222 insertions(+), 40 deletions(-) diff --git a/src/components/charts/AreaGraph/AreaGraph.stories.tsx b/src/components/charts/AreaGraph/AreaGraph.stories.tsx index f548ee13..65b19c55 100644 --- a/src/components/charts/AreaGraph/AreaGraph.stories.tsx +++ b/src/components/charts/AreaGraph/AreaGraph.stories.tsx @@ -183,3 +183,36 @@ export const CustomRange: Story = { }); }, }; + +export const NoTitle: Story = { + name: "No Title", + parameters: { + // Auto-generated by sync-storybook-zephyr - do not add manually + zephyr: { testCaseId: "" }, + }, + args: { + dataSeries: sampleDataSeries, + xTitle: "Columns", + yTitle: "Rows", + width: 1000, + height: 600, + variant: "normal", + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("No title block is rendered", async () => { + expect(canvasElement.querySelector(".gtitle")).not.toBeInTheDocument(); + expect(canvas.queryByText("Area Graph")).not.toBeInTheDocument(); + }); + + await step("Chart container renders", async () => { + expect(canvasElement.querySelector(".js-plotly-plot")).toBeInTheDocument(); + }); + + await step("Three traces are rendered", async () => { + const traces = canvasElement.querySelectorAll(".scatterlayer .trace"); + expect(traces.length).toBe(3); + }); + }, +}; diff --git a/src/components/charts/AreaGraph/AreaGraph.tsx b/src/components/charts/AreaGraph/AreaGraph.tsx index d251ed11..82ec30f9 100644 --- a/src/components/charts/AreaGraph/AreaGraph.tsx +++ b/src/components/charts/AreaGraph/AreaGraph.tsx @@ -17,6 +17,10 @@ interface AreaDataSeries { type AreaGraphVariant = "normal" | "stacked"; +/** Top margin reserving room for the 32px title; reduced when no title is set */ +const TITLE_MARGIN_TOP = 80; +const NO_TITLE_MARGIN_TOP = 40; + interface AreaGraphProps { dataSeries: AreaDataSeries[]; width?: number; @@ -38,7 +42,7 @@ const AreaGraph: React.FC = ({ variant = "normal", xTitle = "Columns", yTitle = "Rows", - title = "Area Graph", + title, }) => { const plotRef = useRef(null); const theme = usePlotlyTheme(); @@ -146,21 +150,24 @@ const AreaGraph: React.FC = ({ ); const titleOptions = useMemo( - () => ({ - text: title, - x: 0.5, - y: 0.95, - xanchor: "center" as const, - yanchor: "top" as const, - font: { - size: 32, - weight: 600, - family: "Inter, sans-serif", - color: theme.textColor, - lineheight: 1.2, - standoff: 30, - }, - }), + () => + title + ? { + text: title, + x: 0.5, + y: 0.95, + xanchor: "center" as const, + yanchor: "top" as const, + font: { + size: 32, + weight: 600, + family: "Inter, sans-serif", + color: theme.textColor, + lineheight: 1.2, + standoff: 30, + }, + } + : undefined, [title, theme], ); @@ -235,8 +242,14 @@ const AreaGraph: React.FC = ({ const layout = { width, height: height, - title: titleOptions, - margin: { l: 80, r: 40, b: 80, t: 80, pad: 0 }, + ...(titleOptions ? { title: titleOptions } : {}), + margin: { + l: 80, + r: 40, + b: 80, + t: title ? TITLE_MARGIN_TOP : NO_TITLE_MARGIN_TOP, + pad: 0, + }, paper_bgcolor: theme.paperBg, plot_bgcolor: theme.plotBg, font: { @@ -316,7 +329,7 @@ const AreaGraph: React.FC = ({ Plotly.purge(plotElement); } }; - }, [dataSeries, width, height, xRange, yRange, effectiveXRange, effectiveYRange, variant, xTitle, yTitle, titleOptions, tickOptions, xTicks, yTicks, theme, bindTooltip]); + }, [dataSeries, width, height, xRange, yRange, effectiveXRange, effectiveYRange, variant, xTitle, yTitle, title, titleOptions, tickOptions, xTicks, yTicks, theme, bindTooltip]); return (
diff --git a/src/components/charts/BarGraph/BarGraph.stories.tsx b/src/components/charts/BarGraph/BarGraph.stories.tsx index 467a471d..0a0b54e1 100644 --- a/src/components/charts/BarGraph/BarGraph.stories.tsx +++ b/src/components/charts/BarGraph/BarGraph.stories.tsx @@ -135,6 +135,36 @@ export const GroupedBars: Story = { }, }; +export const NoTitle: Story = { + name: "No Title", + parameters: { + // Auto-generated by sync-storybook-zephyr - do not add manually + zephyr: { testCaseId: "" }, + }, + args: { + dataSeries: generateBasicData(), + width: 1000, + height: 600, + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("No title block is rendered", async () => { + expect(canvasElement.querySelector(".gtitle")).not.toBeInTheDocument(); + expect(canvas.queryByText("Bar Graph")).not.toBeInTheDocument(); + }); + + await step("Chart container renders", async () => { + expect(canvasElement.querySelector(".js-plotly-plot")).toBeInTheDocument(); + }); + + await step("One trace is rendered", async () => { + const traces = canvasElement.querySelectorAll(".barlayer .trace"); + expect(traces.length).toBe(1); + }); + }, +}; + export const StackedBars: Story = { name: "Stacked Bars", parameters: { diff --git a/src/components/charts/BarGraph/BarGraph.tsx b/src/components/charts/BarGraph/BarGraph.tsx index 54aec4cf..e6c3ad71 100644 --- a/src/components/charts/BarGraph/BarGraph.tsx +++ b/src/components/charts/BarGraph/BarGraph.tsx @@ -21,6 +21,10 @@ interface BarDataSeries { type BarGraphVariant = "group" | "stack" | "overlay"; +/** Top margin reserving room for the 32px title; reduced when no title is set */ +const TITLE_MARGIN_TOP = 60; +const NO_TITLE_MARGIN_TOP = 30; + interface BarGraphProps { dataSeries: BarDataSeries[]; width?: number; @@ -43,7 +47,7 @@ const BarGraph: React.FC = ({ variant = "group", xTitle = "Columns", yTitle = "Rows", - title = "Bar Graph", + title, barWidth = 24, }) => { const plotRef = useRef(null); @@ -153,17 +157,27 @@ const BarGraph: React.FC = ({ })); const layout = { - title: { - text: title, - font: { - size: 32, - family: "Inter, sans-serif", - color: theme.textColor, - }, - }, + ...(title + ? { + title: { + text: title, + font: { + size: 32, + family: "Inter, sans-serif", + color: theme.textColor, + }, + }, + } + : {}), width, height, - margin: { l: 80, r: 30, b: 80, t: 60, pad: 0 }, + margin: { + l: 80, + r: 30, + b: 80, + t: title ? TITLE_MARGIN_TOP : NO_TITLE_MARGIN_TOP, + pad: 0, + }, paper_bgcolor: theme.paperBg, plot_bgcolor: theme.plotBg, font: { diff --git a/src/components/charts/LineGraph/LineGraph.stories.tsx b/src/components/charts/LineGraph/LineGraph.stories.tsx index 8b927185..a640005e 100644 --- a/src/components/charts/LineGraph/LineGraph.stories.tsx +++ b/src/components/charts/LineGraph/LineGraph.stories.tsx @@ -512,6 +512,7 @@ export const AutoRangeLineGraph: Story = { height: 600, dataSeries: generateDemoData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -559,6 +560,7 @@ export const WideRangeAutoScaled: Story = { height: 600, dataSeries: generateWideRangeData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -605,6 +607,7 @@ export const NarrowRangeAutoScaled: Story = { height: 600, dataSeries: generateNarrowRangeData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -662,6 +665,7 @@ export const OnlyXRangeProvided: Story = { dataSeries: generateDemoData(), variant: "lines+markers", xRange: [150, 1050], + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -710,6 +714,7 @@ export const OnlyYRangeProvided: Story = { dataSeries: generateDemoData(), variant: "lines+markers", yRange: [50, 350], + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -747,6 +752,7 @@ export const LineGraphStartingFromZero: Story = { height: 600, dataSeries: generateDataStartingFromZero(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -785,3 +791,39 @@ export const LineGraphStartingFromZero: Story = { }, }, }; + +export const NoTitle: Story = { + name: "No Title", + parameters: { + // Auto-generated by sync-storybook-zephyr - do not add manually + zephyr: { testCaseId: "" }, + }, + args: { + width: 1000, + height: 600, + dataSeries: generateBasicDemoData(), + xTitle: "Columns", + yTitle: "Rows", + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("No title block is rendered", async () => { + expect(canvasElement.querySelector(".gtitle")).not.toBeInTheDocument(); + expect(canvas.queryByText("Line Graph")).not.toBeInTheDocument(); + }); + + await step("Chart container renders", async () => { + expect(canvasElement.querySelector(".js-plotly-plot")).toBeInTheDocument(); + }); + + await step("6 traces are rendered", async () => { + expect(canvasElement.querySelectorAll(".scatterlayer .trace").length).toBe(6); + }); + + await step("Axis titles are displayed", async () => { + expect(canvas.getByText("Columns")).toBeInTheDocument(); + expect(canvas.getByText("Rows")).toBeInTheDocument(); + }); + }, +}; diff --git a/src/components/charts/LineGraph/LineGraph.tsx b/src/components/charts/LineGraph/LineGraph.tsx index 390e8182..763e824b 100644 --- a/src/components/charts/LineGraph/LineGraph.tsx +++ b/src/components/charts/LineGraph/LineGraph.tsx @@ -170,6 +170,10 @@ interface LineDataSeries { type LineGraphVariant = "lines" | "lines+markers" | "lines+markers+error_bars"; +/** Top margin reserving room for the 32px title; reduced when no title is set */ +const TITLE_MARGIN_TOP = 60; +const NO_TITLE_MARGIN_TOP = 30; + type LineGraphProps = { dataSeries: LineDataSeries[]; width?: number; @@ -191,7 +195,7 @@ const LineGraph: React.FC = ({ variant = "lines", xTitle = "Columns", yTitle = "Rows", - title = "Line Graph", + title, }) => { const plotRef = useRef(null); const theme = usePlotlyTheme(); @@ -320,17 +324,27 @@ const LineGraph: React.FC = ({ }); const layout = { - title: { - text: title, - font: { - size: 32, - family: "Inter, sans-serif", - color: theme.textColor, - }, - }, + ...(title + ? { + title: { + text: title, + font: { + size: 32, + family: "Inter, sans-serif", + color: theme.textColor, + }, + }, + } + : {}), width, height, - margin: { l: 80, r: 30, b: 80, t: 60, pad: 10 }, + margin: { + l: 80, + r: 30, + b: 80, + t: title ? TITLE_MARGIN_TOP : NO_TITLE_MARGIN_TOP, + pad: 10, + }, paper_bgcolor: theme.paperBg, plot_bgcolor: theme.plotBg, font: { diff --git a/src/components/charts/PieChart/PieChart.stories.tsx b/src/components/charts/PieChart/PieChart.stories.tsx index 4e52710b..4f97d6a2 100644 --- a/src/components/charts/PieChart/PieChart.stories.tsx +++ b/src/components/charts/PieChart/PieChart.stories.tsx @@ -251,3 +251,39 @@ export const WithRotation: Story = { }); }, }; + +export const NoTitle: Story = { + name: "No Title", + parameters: { + // Auto-generated by sync-storybook-zephyr - do not add manually + zephyr: { testCaseId: "" }, + }, + args: { + dataSeries: { + labels: ["pH", "Temperature", "Dissolved Oxygen", "Cell Density", "Viability"], + values: [12, 23, 35, 18, 12], + name: "Bioreactor Parameters", + }, + width: 480, + height: 480, + textInfo: "percent", + hole: 0, + rotation: 0, + }, + play: async ({ canvasElement, step }) => { + await step("No title block is rendered", async () => { + expect(canvasElement.querySelector(".title-container")).not.toBeInTheDocument(); + expect(canvasElement.querySelector(".title")).not.toBeInTheDocument(); + }); + + await step("Chart container renders", async () => { + expect(canvasElement.querySelector(".js-plotly-plot")).toBeInTheDocument(); + }); + + await step("Pie layer and slices render", async () => { + expect(canvasElement.querySelector(".pielayer")).toBeInTheDocument(); + const slices = canvasElement.querySelectorAll(".pielayer .slice"); + expect(slices.length).toBe(5); + }); + }, +}; diff --git a/src/components/charts/PieChart/PieChart.tsx b/src/components/charts/PieChart/PieChart.tsx index 7042d480..342360af 100644 --- a/src/components/charts/PieChart/PieChart.tsx +++ b/src/components/charts/PieChart/PieChart.tsx @@ -39,7 +39,7 @@ const PieChart: React.FC = ({ dataSeries, width = 400, height = 400, - title = "Pie Chart", + title, textInfo = "percent", hole = 0, rotation = 0, From 03c72792de551b70ba5531e6a577c75e0c802174 Mon Sep 17 00:00:00 2001 From: owilliams-tetrascience Date: Fri, 26 Jun 2026 13:45:16 +0000 Subject: [PATCH 2/2] chore: add Zephyr test case IDs to stories --- src/components/charts/AreaGraph/AreaGraph.stories.tsx | 2 +- src/components/charts/BarGraph/BarGraph.stories.tsx | 2 +- .../InteractiveScatter/InteractiveScatter.stories.tsx | 9 +++++++++ src/components/charts/LineGraph/LineGraph.stories.tsx | 2 +- src/components/charts/PieChart/PieChart.stories.tsx | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/charts/AreaGraph/AreaGraph.stories.tsx b/src/components/charts/AreaGraph/AreaGraph.stories.tsx index 65b19c55..3a44f0cf 100644 --- a/src/components/charts/AreaGraph/AreaGraph.stories.tsx +++ b/src/components/charts/AreaGraph/AreaGraph.stories.tsx @@ -188,7 +188,7 @@ export const NoTitle: Story = { name: "No Title", parameters: { // Auto-generated by sync-storybook-zephyr - do not add manually - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5461" }, }, args: { dataSeries: sampleDataSeries, diff --git a/src/components/charts/BarGraph/BarGraph.stories.tsx b/src/components/charts/BarGraph/BarGraph.stories.tsx index 0a0b54e1..b418622d 100644 --- a/src/components/charts/BarGraph/BarGraph.stories.tsx +++ b/src/components/charts/BarGraph/BarGraph.stories.tsx @@ -139,7 +139,7 @@ export const NoTitle: Story = { name: "No Title", parameters: { // Auto-generated by sync-storybook-zephyr - do not add manually - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5462" }, }, args: { dataSeries: generateBasicData(), diff --git a/src/components/charts/InteractiveScatter/InteractiveScatter.stories.tsx b/src/components/charts/InteractiveScatter/InteractiveScatter.stories.tsx index 929c76af..7e840f8d 100644 --- a/src/components/charts/InteractiveScatter/InteractiveScatter.stories.tsx +++ b/src/components/charts/InteractiveScatter/InteractiveScatter.stories.tsx @@ -773,6 +773,9 @@ export const ContinuousColorMapping: Story = { }); }); }, + parameters: { + zephyr: { testCaseId: "SW-T5412" }, + }, }; const EVENT_DATA: ScatterPoint[] = Array.from({ length: 6 }, (_, i) => ({ @@ -858,6 +861,9 @@ export const SelectionEvents: Story = { }); }); }, + parameters: { + zephyr: { testCaseId: "SW-T5413" }, + }, }; /** @@ -917,4 +923,7 @@ export const ThemedTooltip: Story = { }); }); }, + parameters: { + zephyr: { testCaseId: "SW-T5414" }, + }, }; diff --git a/src/components/charts/LineGraph/LineGraph.stories.tsx b/src/components/charts/LineGraph/LineGraph.stories.tsx index a640005e..afc7b0f1 100644 --- a/src/components/charts/LineGraph/LineGraph.stories.tsx +++ b/src/components/charts/LineGraph/LineGraph.stories.tsx @@ -796,7 +796,7 @@ export const NoTitle: Story = { name: "No Title", parameters: { // Auto-generated by sync-storybook-zephyr - do not add manually - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5463" }, }, args: { width: 1000, diff --git a/src/components/charts/PieChart/PieChart.stories.tsx b/src/components/charts/PieChart/PieChart.stories.tsx index 4f97d6a2..c99f576f 100644 --- a/src/components/charts/PieChart/PieChart.stories.tsx +++ b/src/components/charts/PieChart/PieChart.stories.tsx @@ -256,7 +256,7 @@ export const NoTitle: Story = { name: "No Title", parameters: { // Auto-generated by sync-storybook-zephyr - do not add manually - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5464" }, }, args: { dataSeries: {