diff --git a/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx b/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
index f655c4a058a793e32f345dd36e75e8c89ec61fb1..cdad0444138750f853f0a49d1101895c255da085 100644
--- a/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
+++ b/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
@@ -14,11 +14,14 @@ const GRID_ASPECT_RATIO = 4 / 3;
 const PADDING = 14;
 
 const DEFAULT_GRID_SIZE = 100;
+export const HIDE_HORIZONTAL_LEGEND_THRESHOLD = 180;
+export const HIDE_SECONDARY_INFO_THRESHOLD = 260;
 
 class ChartWithLegend extends Component {
   static defaultProps = {
     aspectRatio: 1,
     style: {},
+    showLegend: true,
   };
 
   render() {
@@ -57,16 +60,13 @@ class ChartWithLegend extends Component {
     let type;
     let LegendComponent;
     const isHorizontal = gridSize.width > gridSize.height / GRID_ASPECT_RATIO;
-    if (showLegend === false) {
+    if (!showLegend) {
       type = "small";
-    } else if (
-      !gridSize ||
-      (isHorizontal &&
-        (showLegend || gridSize.width > 4 || gridSize.height > 4))
-    ) {
+    } else if (isHorizontal && width > HIDE_HORIZONTAL_LEGEND_THRESHOLD) {
       type = "horizontal";
       LegendComponent = LegendVertical;
-      if (gridSize && gridSize.width < 6) {
+
+      if (width < HIDE_SECONDARY_INFO_THRESHOLD) {
         legendTitles = legendTitles.map(title =>
           Array.isArray(title) ? title.slice(0, 1) : title,
         );
@@ -78,18 +78,14 @@ class ChartWithLegend extends Component {
         chartWidth = desiredWidth;
       }
       chartHeight = height;
-    } else if (
-      !isHorizontal &&
-      (showLegend || (gridSize.height > 3 && gridSize.width > 2))
-    ) {
+    } else if (!isHorizontal && gridSize.height > 3 && gridSize.width > 2) {
       type = "vertical";
       LegendComponent = LegendHorizontal;
       legendTitles = legendTitles.map(title =>
-        Array.isArray(title) ? title.join(" – ") : title,
+        Array.isArray(title) ? title.join(" ") : title,
       );
       const desiredHeight = width * (1 / aspectRatio);
       if (desiredHeight > height * (3 / 4)) {
-        // chartHeight = height * (3 / 4);
         flexChart = true;
       } else {
         chartHeight = desiredHeight;
@@ -99,6 +95,8 @@ class ChartWithLegend extends Component {
       type = "small";
     }
 
+    const hasDimensions = width > 0 && height > 0;
+
     const legend = LegendComponent ? (
       <LegendComponent
         className={styles.Legend}
@@ -138,7 +136,7 @@ class ChartWithLegend extends Component {
           className={cx(styles.Chart)}
           style={{ width: chartWidth, height: chartHeight }}
         >
-          {children}
+          {hasDimensions ? children : null}
         </div>
         {/* spacer div to balance legend */}
         {legend && (
diff --git a/frontend/src/metabase/visualizations/components/ChartWithLegend.unit.spec.jsx b/frontend/src/metabase/visualizations/components/ChartWithLegend.unit.spec.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..527d5e5cefb282f2c78100c4cc96538ab513d996
--- /dev/null
+++ b/frontend/src/metabase/visualizations/components/ChartWithLegend.unit.spec.jsx
@@ -0,0 +1,75 @@
+import { render, screen } from "@testing-library/react";
+
+import ChartWithLegend, {
+  HIDE_HORIZONTAL_LEGEND_THRESHOLD,
+  HIDE_SECONDARY_INFO_THRESHOLD,
+} from "./ChartWithLegend";
+
+const defaultProps = {
+  legendTitles: [
+    ["Series 1", "50%"],
+    ["Series 2", "50%"],
+  ],
+  legendColors: ["Red", "Green"],
+  showLegend: true,
+  width: 800,
+  height: 600,
+  gridSize: { width: 8, height: 6 },
+  children: <div>Chart stub</div>,
+};
+
+const setup = (props = {}) => {
+  render(<ChartWithLegend {...defaultProps} {...props} />);
+};
+
+describe("ChartWithLegend", () => {
+  it("should show full legend titles when width is above secondary info threshold", () => {
+    setup();
+
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Series 1");
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Series 2");
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("50%");
+  });
+
+  it("should hide secondary info when width is below threshold", () => {
+    setup({ width: HIDE_SECONDARY_INFO_THRESHOLD - 1 });
+
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Series 1");
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Series 2");
+    expect(screen.getByTestId("chart-legend")).not.toHaveTextContent("50%");
+  });
+
+  it("should not render legend when width is below horizontal legend threshold", () => {
+    setup({ width: HIDE_HORIZONTAL_LEGEND_THRESHOLD - 1 });
+
+    expect(screen.queryByTestId("chart-legend")).not.toBeInTheDocument();
+  });
+
+  it("should not render chart content when dimensions are zero", () => {
+    setup({ width: 0, height: 0 });
+
+    expect(screen.queryByText("Chart stub")).not.toBeInTheDocument();
+  });
+
+  it("should render chart content when dimensions are valid", () => {
+    setup();
+
+    expect(screen.getByText("Chart stub")).toBeInTheDocument();
+  });
+
+  it("should join legend titles with space in vertical layout", () => {
+    setup({
+      width: 400,
+      height: 600,
+      gridSize: { width: 3, height: 6 },
+      aspectRatio: 0.5,
+      legendTitles: [
+        ["Foo", "10%"],
+        ["Bar", "90%"],
+      ],
+    });
+
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Foo 10%");
+    expect(screen.getByTestId("chart-legend")).toHaveTextContent("Bar 90%");
+  });
+});
diff --git a/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx b/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx
index ead78bc1336da41803ecf68c37a25c1e48699180..a0bfdddc267e05c53f9bb2633e5ba74d02d84d96 100644
--- a/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx
+++ b/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx
@@ -1,6 +1,9 @@
 import styled from "@emotion/styled";
 
 export const EChartsRendererRoot = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
   /* HACK: zrender adds user-select: none to the root svg element which prevents users from selecting text on charts */
   & svg {
     user-select: auto !important;
diff --git a/frontend/src/metabase/visualizations/components/LegendVertical.jsx b/frontend/src/metabase/visualizations/components/LegendVertical.jsx
index 26665261b268ea374701c678780765fd2b79bf59..2b14c4937e3a735e43a836dd168ef6d9ff6c3109 100644
--- a/frontend/src/metabase/visualizations/components/LegendVertical.jsx
+++ b/frontend/src/metabase/visualizations/components/LegendVertical.jsx
@@ -18,16 +18,10 @@ export default class LegendVertical extends Component {
   state = {
     overflowCount: 0,
     size: null,
-    listWidth: null,
   };
 
   listRef = createRef();
 
-  componentDidMount() {
-    const listWidth = this.listRef.current.getBoundingClientRect().width;
-    this.setState({ listWidth });
-  }
-
   componentDidUpdate(prevProps, prevState) {
     // Get the bounding rectangle of the chart widget to determine if
     // legend items will overflow the widget area
@@ -55,20 +49,6 @@ export default class LegendVertical extends Component {
         this.setState({ overflowCount, size });
       }
     }
-
-    const [previousSampleTitle] = prevProps.titles || [];
-    const [sampleTitle] = this.props.titles || [];
-
-    if (
-      sampleTitle &&
-      previousSampleTitle &&
-      previousSampleTitle.length !== sampleTitle.length
-    ) {
-      this.setState({ listWidth: null }, () => {
-        const listWidth = this.listRef.current.getBoundingClientRect().width;
-        this.setState({ listWidth });
-      });
-    }
   }
 
   render() {
@@ -95,7 +75,6 @@ export default class LegendVertical extends Component {
     return (
       <ol
         className={cx(className, LegendS.Legend, LegendS.vertical)}
-        style={{ width: this.state.listWidth }}
         ref={this.listRef}
       >
         {items.map((title, index) => {
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx b/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
index 64a81e545074fd1fd912ef338a27b50979969cb5..7fbb183a84b056ff108a79b503589f48c1d4bb6f 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
@@ -117,27 +117,34 @@ export function PieChart(props: VisualizationProps) {
 
   const eventHandlers = useChartEvents(props, chartRef, chartModel);
 
-  const slices = getArrayFromMapValues(chartModel.sliceTree);
-  const legendTitles = slices
-    .filter(s => s.includeInLegend)
-    .map(s => {
-      const label = s.name;
-
-      // Hidden slices don't have a percentage
-      const sliceHidden = s.normalizedPercentage === 0;
-      const percentDisabled =
-        settings["pie.percent_visibility"] !== "legend" &&
-        settings["pie.percent_visibility"] !== "both";
-
-      if (sliceHidden || percentDisabled) {
-        return [label];
-      }
-
-      return [
-        label,
-        formatters.formatPercent(s.normalizedPercentage, "legend"),
-      ];
-    });
+  const slices = useMemo(
+    () => getArrayFromMapValues(chartModel.sliceTree),
+    [chartModel.sliceTree],
+  );
+  const legendTitles = useMemo(
+    () =>
+      slices
+        .filter(s => s.includeInLegend)
+        .map(s => {
+          const label = s.name;
+
+          // Hidden slices don't have a percentage
+          const sliceHidden = s.normalizedPercentage === 0;
+          const percentDisabled =
+            settings["pie.percent_visibility"] !== "legend" &&
+            settings["pie.percent_visibility"] !== "both";
+
+          if (sliceHidden || percentDisabled) {
+            return [label];
+          }
+
+          return [
+            label,
+            formatters.formatPercent(s.normalizedPercentage, "legend"),
+          ];
+        }),
+    [formatters, settings, slices],
+  );
 
   const hiddenSlicesLegendIndices = slices
     .filter(s => s.includeInLegend)
@@ -157,7 +164,7 @@ export function PieChart(props: VisualizationProps) {
     );
 
   const handleToggleSeriesVisibility = (
-    event: MouseEvent,
+    _event: MouseEvent,
     sliceIndex: number,
   ) => {
     const slice = slices[sliceIndex];