diff --git a/src/components/charts/AreaGraph/AreaGraph.stories.tsx b/src/components/charts/AreaGraph/AreaGraph.stories.tsx index 5e071b75..e0c3d340 100644 --- a/src/components/charts/AreaGraph/AreaGraph.stories.tsx +++ b/src/components/charts/AreaGraph/AreaGraph.stories.tsx @@ -242,3 +242,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: "SW-T5461" }, + }, + 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 f5d32df4..3ef7cb48 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; @@ -45,7 +49,7 @@ const AreaGraph: React.FC = ({ variant = "normal", xTitle = "Columns", yTitle = "Rows", - title = "Area Graph", + title, xTickText, }) => { const plotRef = useRef(null); @@ -166,21 +170,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], ); @@ -255,8 +262,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: { @@ -337,7 +350,7 @@ const AreaGraph: React.FC = ({ Plotly.purge(plotElement); } }; - }, [dataSeries, width, height, xRange, yRange, effectiveXRange, effectiveYRange, variant, xTitle, yTitle, titleOptions, tickOptions, xTicks, yTicks, xDataValues, useCategoricalX, xTickText, theme, bindTooltip]); + }, [dataSeries, width, height, xRange, yRange, effectiveXRange, effectiveYRange, variant, xTitle, yTitle, title, titleOptions, tickOptions, xTicks, yTicks, xDataValues, useCategoricalX, xTickText, theme, bindTooltip]); return (
diff --git a/src/components/charts/BarGraph/BarGraph.stories.tsx b/src/components/charts/BarGraph/BarGraph.stories.tsx index 94c141b0..be0cc455 100644 --- a/src/components/charts/BarGraph/BarGraph.stories.tsx +++ b/src/components/charts/BarGraph/BarGraph.stories.tsx @@ -155,6 +155,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: "SW-T5462" }, + }, + 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 8786f74e..5cf77d50 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; @@ -50,7 +54,7 @@ const BarGraph: React.FC = ({ variant = "group", xTitle = "Columns", yTitle = "Rows", - title = "Bar Graph", + title, barWidth = 24, xTickText, }) => { @@ -165,17 +169,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 0efc8a54..c3a251e6 100644 --- a/src/components/charts/LineGraph/LineGraph.stories.tsx +++ b/src/components/charts/LineGraph/LineGraph.stories.tsx @@ -585,6 +585,7 @@ export const AutoRangeLineGraph: Story = { height: 600, dataSeries: generateDemoData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -632,6 +633,7 @@ export const WideRangeAutoScaled: Story = { height: 600, dataSeries: generateWideRangeData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -678,6 +680,7 @@ export const NarrowRangeAutoScaled: Story = { height: 600, dataSeries: generateNarrowRangeData(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -735,6 +738,7 @@ export const OnlyXRangeProvided: Story = { dataSeries: generateDemoData(), variant: "lines+markers", xRange: [150, 1050], + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -783,6 +787,7 @@ export const OnlyYRangeProvided: Story = { dataSeries: generateDemoData(), variant: "lines+markers", yRange: [50, 350], + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -820,6 +825,7 @@ export const LineGraphStartingFromZero: Story = { height: 600, dataSeries: generateDataStartingFromZero(), variant: "lines+markers", + title: "Line Graph", xTitle: "Columns", yTitle: "Rows", }, @@ -858,3 +864,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: "SW-T5463" }, + }, + 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 4fe06556..d3858217 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; @@ -198,7 +202,7 @@ const LineGraph: React.FC = ({ variant = "lines", xTitle = "Columns", yTitle = "Rows", - title = "Line Graph", + title, xTickText, }) => { const plotRef = useRef(null); @@ -332,17 +336,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 67a11b15..41020fda 100644 --- a/src/components/charts/PieChart/PieChart.stories.tsx +++ b/src/components/charts/PieChart/PieChart.stories.tsx @@ -263,3 +263,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: "SW-T5464" }, + }, + 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 a5c7beab..1d199bab 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,