diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_All_Negative.png b/.loki/reference/chrome_laptop_static_viz_PieChart_All_Negative.png
index 1b4c21eb7e319abe939e118a6ca4a60b2a97d8a0..4b30ff2502363cce7b8e4d24ea7a15aa41b7ce82 100644
Binary files a/.loki/reference/chrome_laptop_static_viz_PieChart_All_Negative.png and b/.loki/reference/chrome_laptop_static_viz_PieChart_All_Negative.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_On_Chart.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_On_Chart.png
new file mode 100644
index 0000000000000000000000000000000000000000..c36e5f5bc4398902ea4d57e7805301f7bb61ffce
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_On_Chart.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_With_Percent.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_With_Percent.png
new file mode 100644
index 0000000000000000000000000000000000000000..d19eced54933cd8a20a0d9e38ad8e497008b01c1
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Labels_With_Percent.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings.png
new file mode 100644
index 0000000000000000000000000000000000000000..abfafe0d42894fdeec4ced1e2b8033d7a86e07f7
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_No_Labels.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_No_Labels.png
new file mode 100644
index 0000000000000000000000000000000000000000..92f375f508a94cd881d83ad9900117895969b49b
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_No_Labels.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_Other_Slices.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_Other_Slices.png
new file mode 100644
index 0000000000000000000000000000000000000000..980ac6b5949d4674110a322f07d495580073f220
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Three_Rings_Other_Slices.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png
index 98b50c8db0672b1762b544d4c58042cf872c8e76..df7fac840d8abce3c417ad60a85bc701b5844610 100644
Binary files a/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png and b/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Two_Rings.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Two_Rings.png
new file mode 100644
index 0000000000000000000000000000000000000000..8a1f853ed81875e77415992bb59c5644db383474
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Two_Rings.png differ
diff --git a/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js b/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js
index dd337f324d3aac2419d1cdba37559e1ad9b1c5ec..b7c4d176ff3cf31f092014869933a8b42f45e21b 100644
--- a/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js
+++ b/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js
@@ -1,7 +1,9 @@
 import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import {
+  assertEChartsTooltip,
   chartPathWithFillColor,
+  echartsContainer,
   getDraggableElements,
   getNotebookStep,
   leftSidebar,
@@ -15,7 +17,7 @@ import {
   visualize,
 } from "e2e/support/helpers";
 
-const { PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE;
+const { PRODUCTS, PRODUCTS_ID, ORDERS_ID, ORDERS, PEOPLE } = SAMPLE_DATABASE;
 
 const testQuery = {
   type: "query",
@@ -27,6 +29,53 @@ const testQuery = {
   database: SAMPLE_DB_ID,
 };
 
+const twoRingQuery = {
+  database: 1,
+  type: "query",
+  query: {
+    "source-table": ORDERS_ID,
+    aggregation: [["count"]],
+    breakout: [
+      [
+        "field",
+        ORDERS.CREATED_AT,
+        { "base-type": "type/DateTime", "temporal-unit": "day-of-week" },
+      ],
+      [
+        "field",
+        PRODUCTS.CATEGORY,
+        { "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID },
+      ],
+    ],
+  },
+};
+
+const threeRingQuery = {
+  database: 1,
+  type: "query",
+  query: {
+    "source-table": ORDERS_ID,
+    aggregation: [["count"]],
+    breakout: [
+      [
+        "field",
+        ORDERS.CREATED_AT,
+        { "base-type": "type/DateTime", "temporal-unit": "year" },
+      ],
+      [
+        "field",
+        PEOPLE.SOURCE,
+        { "base-type": "type/Text", "source-field": ORDERS.USER_ID },
+      ],
+      [
+        "field",
+        PRODUCTS.CATEGORY,
+        { "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID },
+      ],
+    ],
+  },
+};
+
 describe("scenarios > visualizations > pie chart", () => {
   beforeEach(() => {
     restore();
@@ -39,7 +88,12 @@ describe("scenarios > visualizations > pie chart", () => {
       display: "pie",
     });
 
-    ensurePieChartRendered(["Doohickey", "Gadget", "Gizmo", "Widget"], 200);
+    ensurePieChartRendered(
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+      null,
+      null,
+      200,
+    );
 
     // chart should be centered (#48123)
     cy.findByTestId("chart-legend").then(([legend]) => {
@@ -230,9 +284,215 @@ describe("scenarios > visualizations > pie chart", () => {
       cy.get("li").eq(3).contains("Woooget");
     });
   });
+
+  it("should automatically map dimension columns in query to rings", () => {
+    visitQuestionAdhoc({
+      dataset_query: twoRingQuery,
+      display: "pie",
+    });
+
+    ensurePieChartRendered(
+      [
+        "Sunday",
+        "Monday",
+        "Tuesday",
+        "Wednesday",
+        "Thursday",
+        "Friday",
+        "Saturday",
+      ],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+    );
+  });
+
+  it("should allow the user to edit rings", () => {
+    visitQuestionAdhoc({
+      dataset_query: threeRingQuery,
+      display: "pie",
+      visualization_settings: {
+        "pie.slice_threshold": 0,
+      },
+    });
+
+    ensurePieChartRendered(
+      ["2022", "2023", "2024", "2025", "2026"],
+      ["Affiliate", "Facebook", "Google", "Organic", "Twitter"],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+    );
+
+    cy.findByTestId("viz-settings-button").click();
+
+    cy.findAllByTestId("chartsettings-field-picker")
+      .last()
+      .within(() => {
+        cy.icon("close").click();
+      });
+
+    ensurePieChartRendered(
+      ["2022", "2023", "2024", "2025", "2026"],
+      ["Affiliate", "Facebook", "Google", "Organic", "Twitter"],
+    );
+
+    cy.findAllByTestId("chartsettings-field-picker")
+      .last()
+      .within(() => {
+        cy.icon("chevrondown").click();
+      });
+
+    cy.get("[data-element-id=list-section]").last().click();
+
+    ensurePieChartRendered(
+      ["2022", "2023", "2024", "2025", "2026"],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+    );
+
+    leftSidebar().within(() => {
+      cy.findByText("Add Ring").click();
+    });
+
+    cy.get("[data-element-id=list-section]").last().click();
+
+    ensurePieChartRendered(
+      ["2022", "2023", "2024", "2025", "2026"],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+      ["Affiliate", "Facebook", "Google", "Organic", "Twitter"],
+    );
+  });
+
+  it("should handle hover and click actions correctly", () => {
+    visitQuestionAdhoc({
+      dataset_query: twoRingQuery,
+      display: "pie",
+      visualization_settings: {
+        "pie.slice_threshold": 0,
+      },
+    });
+
+    ensurePieChartRendered(
+      [
+        "Sunday",
+        "Monday",
+        "Tuesday",
+        "Wednesday",
+        "Thursday",
+        "Friday",
+        "Saturday",
+      ],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+    );
+
+    echartsContainer().within(() => {
+      cy.findByText("Saturday").as("saturdaySlice").trigger("mousemove");
+    });
+
+    assertEChartsTooltip({
+      header: "Created At",
+      rows: [
+        {
+          color: "#51528D",
+          name: "Saturday",
+          value: "2,747",
+        },
+        {
+          color: "#ED8535",
+          name: "Thursday",
+          value: "2,698",
+        },
+        {
+          color: "#E75454",
+          name: "Tuesday",
+          value: "2,695",
+        },
+        {
+          color: "#689636",
+          name: "Sunday",
+          value: "2,671",
+        },
+        {
+          color: "#8A5EB0",
+          name: "Monday",
+          value: "2,664",
+        },
+        {
+          color: "#69C8C8",
+          name: "Friday",
+          value: "2,662",
+        },
+        {
+          color: "#F7C41F",
+          name: "Wednesday",
+          value: "2,623",
+        },
+      ],
+    });
+
+    cy.get("@saturdaySlice").click({ force: true });
+
+    popover().within(() => {
+      cy.findByText("=").click();
+    });
+
+    cy.findByTestId("qb-filters-panel").within(() => {
+      cy.findByText("Count is equal to 2747").should("be.visible");
+    });
+
+    cy.go("back");
+
+    ensurePieChartRendered(
+      [
+        "Sunday",
+        "Monday",
+        "Tuesday",
+        "Wednesday",
+        "Thursday",
+        "Friday",
+        "Saturday",
+      ],
+      ["Doohickey", "Gadget", "Gizmo", "Widget"],
+    );
+
+    echartsContainer().within(() => {
+      cy.findAllByText("Doohickey")
+        .first()
+        .as("doohickeySlice")
+        .trigger("mousemove");
+    });
+
+    assertEChartsTooltip({
+      header: "Saturday",
+      rows: [
+        {
+          name: "Doohickey",
+          value: "606",
+        },
+        {
+          name: "Gadget",
+          value: "740",
+        },
+        {
+          name: "Gizmo",
+          value: "640",
+        },
+        {
+          name: "Widget",
+          value: "761",
+        },
+      ],
+    });
+
+    cy.get("@doohickeySlice").click({ force: true });
+
+    popover().within(() => {
+      cy.findByText("=").click();
+    });
+
+    cy.findByTestId("qb-filters-panel").within(() => {
+      cy.findByText("Count is equal to 606").should("be.visible");
+    });
+  });
 });
 
-function ensurePieChartRendered(rows, totalValue) {
+function ensurePieChartRendered(rows, middleRows, outerRows, totalValue) {
   cy.findByTestId("query-visualization-root").within(() => {
     // detail
     if (totalValue != null) {
@@ -241,7 +501,17 @@ function ensurePieChartRendered(rows, totalValue) {
     }
 
     // slices
-    pieSlices().should("have.length", rows.length);
+    let rowCount = rows.length;
+    const hasMiddleRows = middleRows != null && middleRows.length > 0;
+    const hasOuterRows = outerRows != null && outerRows.length > 0;
+
+    if (hasMiddleRows) {
+      rowCount += rows.length * middleRows.length;
+    }
+    if (hasMiddleRows && hasOuterRows) {
+      rowCount += rows.length * middleRows.length * outerRows.length;
+    }
+    pieSlices().should("have.length", rowCount);
 
     // legend
     rows.forEach((name, i) => {
diff --git a/frontend/src/metabase-types/api/card.ts b/frontend/src/metabase-types/api/card.ts
index dca7425e13fb98ad0d9ac6587ca76095869faccb..f0f646bf3c08375f7f5533fb115fe0b0d1f9efae 100644
--- a/frontend/src/metabase-types/api/card.ts
+++ b/frontend/src/metabase-types/api/card.ts
@@ -214,12 +214,15 @@ export type VisualizationSettings = {
   "scalar.compact_primary_number"?: boolean;
 
   // Pie Settings
-  "pie.dimension"?: string;
+  "pie.dimension"?: string | string[];
+  "pie.middle_dimension"?: string;
+  "pie.outer_dimension"?: string;
   "pie.rows"?: PieRow[];
   "pie.metric"?: string;
   "pie.sort_rows"?: boolean;
   "pie.show_legend"?: boolean;
   "pie.show_total"?: boolean;
+  "pie.show_labels"?: boolean;
   "pie.percent_visibility"?: "off" | "legend" | "inside" | "both";
   "pie.decimal_places"?: number;
   "pie.slice_threshold"?: number;
diff --git a/frontend/src/metabase/core/components/Sortable/SortableList.tsx b/frontend/src/metabase/core/components/Sortable/SortableList.tsx
index 949fa96cf350a9a354c14ec9e6d6c271e05446a3..eb69a9e1416c0ab8dac616deb64c8dae8712c554 100644
--- a/frontend/src/metabase/core/components/Sortable/SortableList.tsx
+++ b/frontend/src/metabase/core/components/Sortable/SortableList.tsx
@@ -24,7 +24,7 @@ export type RenderItemProps<T> = {
   id: ItemId;
   isDragOverlay?: boolean;
 };
-type useSortableListProps<T> = {
+type SortableListProps<T> = {
   items: T[];
   getId: (item: T) => ItemId;
   renderItem: ({
@@ -48,7 +48,7 @@ export const SortableList = <T,>({
   sensors = [],
   modifiers = [],
   useDragOverlay = true,
-}: useSortableListProps<T>) => {
+}: SortableListProps<T>) => {
   const [itemIds, setItemIds] = useState<ItemId[]>([]);
   const [indexedItems, setIndexedItems] = useState<Partial<Record<ItemId, T>>>(
     {},
diff --git a/frontend/src/metabase/lib/colors/palette.ts b/frontend/src/metabase/lib/colors/palette.ts
index 0570fd81441835507b3ffe43ab0b31234b8dcaf5..50ad67c13f88c5c27496e003c829246b91daadf8 100644
--- a/frontend/src/metabase/lib/colors/palette.ts
+++ b/frontend/src/metabase/lib/colors/palette.ts
@@ -55,7 +55,7 @@ export const colors = {
 
 export const originalColors = { ...colors };
 
-const aliases: Record<string, (palette: ColorPalette) => string> = {
+export const aliases: Record<string, (palette: ColorPalette) => string> = {
   dashboard: palette => color("brand", palette),
   nav: palette => color("bg-white", palette),
   content: palette => color("bg-light", palette),
diff --git a/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx b/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx
index 3719b3b3f0944b97a57ca3217ee3e963cda0bf44..1397357a23cbf7f7603b3af4bd820cb5a99f0238 100644
--- a/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx
+++ b/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx
@@ -405,3 +405,39 @@ export const MissingLabelLargeSlice38424 = {
     renderingContext,
   },
 };
+
+export const TwoRings = Template.bind({});
+TwoRings.args = {
+  rawSeries: data.twoRings as any,
+  renderingContext,
+};
+
+export const ThreeRings = Template.bind({});
+ThreeRings.args = {
+  rawSeries: data.threeRings as any,
+  renderingContext,
+};
+
+export const ThreeRingsNoLabels = Template.bind({});
+ThreeRingsNoLabels.args = {
+  rawSeries: data.threeRingsNoLabels as any,
+  renderingContext,
+};
+
+export const ThreeRingsOtherSlices = Template.bind({});
+ThreeRingsOtherSlices.args = {
+  rawSeries: data.threeRingsOtherSlices as any,
+  renderingContext,
+};
+
+export const LabelsWithPercent = Template.bind({});
+LabelsWithPercent.args = {
+  rawSeries: data.labelsWithPercent as any,
+  renderingContext,
+};
+
+export const LabelsOnChart = Template.bind({});
+LabelsOnChart.args = {
+  rawSeries: data.labelsOnChart as any,
+  renderingContext,
+};
diff --git a/frontend/src/metabase/static-viz/components/PieChart/legend.tsx b/frontend/src/metabase/static-viz/components/PieChart/legend.tsx
index 06b39fe24dbcbe4b1ed7c3782c87b08e964ee831..36a200a2dcd7b285f3abd90e74f0f690bd9eac78 100644
--- a/frontend/src/metabase/static-viz/components/PieChart/legend.tsx
+++ b/frontend/src/metabase/static-viz/components/PieChart/legend.tsx
@@ -1,6 +1,7 @@
 import { DIMENSIONS } from "metabase/visualizations/echarts/pie/constants";
 import type { PieChartFormatters } from "metabase/visualizations/echarts/pie/format";
 import type { PieChartModel } from "metabase/visualizations/echarts/pie/model/types";
+import { getArrayFromMapValues } from "metabase/visualizations/echarts/pie/util";
 import type { ComputedVisualizationSettings } from "metabase/visualizations/types";
 
 import { Legend } from "../Legend";
@@ -17,18 +18,20 @@ export function getPieChartLegend(
     width: legendWidth,
     items,
   } = calculateLegendRowsWithColumns({
-    items: chartModel.slices
-      .filter(s => s.data.includeInLegend)
-      .map(s => ({
-        name: s.data.name,
-        percent:
-          settings["pie.percent_visibility"] === "legend" ||
-          settings["pie.percent_visibility"] === "both"
-            ? formatters.formatPercent(s.data.normalizedPercentage, "legend")
-            : undefined,
-        color: s.data.color,
-        key: String(s.data.key),
-      })),
+    items: getArrayFromMapValues(chartModel.sliceTree)
+      .filter(s => s.includeInLegend)
+      .map(s => {
+        return {
+          name: s.name,
+          percent:
+            settings["pie.percent_visibility"] === "legend" ||
+            settings["pie.percent_visibility"] === "both"
+              ? formatters.formatPercent(s.normalizedPercentage, "legend")
+              : undefined,
+          color: s.color,
+          key: String(s.key),
+        };
+      }),
     width: DIMENSIONS.maxSideLength,
     horizontalPadding: DIMENSIONS.padding.side,
   });
diff --git a/frontend/src/metabase/static-viz/components/PieChart/settings.ts b/frontend/src/metabase/static-viz/components/PieChart/settings.ts
index 36fbac7761a3a6869cfe020dd1c19a32336dfd73..72178c1d32433281699493950029575183099530 100644
--- a/frontend/src/metabase/static-viz/components/PieChart/settings.ts
+++ b/frontend/src/metabase/static-viz/components/PieChart/settings.ts
@@ -3,13 +3,12 @@ import {
   fillWithDefaultValue,
   getCommonStaticVizSettings,
 } from "metabase/static-viz/lib/settings";
-import {
-  columnsAreValid,
-  getDefaultDimensionAndMetric,
-} from "metabase/visualizations/lib/utils";
+import { columnsAreValid } from "metabase/visualizations/lib/utils";
 import {
   getColors,
   getDefaultPercentVisibility,
+  getDefaultPieColumns,
+  getDefaultShowLabels,
   getDefaultShowLegend,
   getDefaultShowTotal,
   getDefaultSliceThreshold,
@@ -23,25 +22,20 @@ export function computeStaticPieChartSettings(
   rawSeries: RawSeries,
 ): ComputedVisualizationSettings {
   const settings = getCommonStaticVizSettings(rawSeries);
-  const { dimension: defaultDimension, metric: defaultMetric } =
-    getDefaultDimensionAndMetric(rawSeries);
-
-  const dimensionIsValid = columnsAreValid(
-    settings["pie.dimension"],
-    rawSeries[0].data,
-  );
-  const metricIsValid = columnsAreValid(
-    settings["pie.metric"],
-    rawSeries[0].data,
+  const defaultColumns = getDefaultPieColumns(rawSeries);
+  fillWithDefaultValue(
+    settings,
+    "pie.dimension",
+    defaultColumns.dimension,
+    columnsAreValid(settings["pie.dimension"], rawSeries[0].data),
   );
 
   fillWithDefaultValue(
     settings,
-    "pie.dimension",
-    defaultDimension,
-    dimensionIsValid,
+    "pie.metric",
+    defaultColumns.metric,
+    columnsAreValid(settings["pie.metric"], rawSeries[0].data),
   );
-  fillWithDefaultValue(settings, "pie.metric", defaultMetric, metricIsValid);
 
   fillWithDefaultValue(settings, "pie.sort_rows", getDefaultSortRows);
 
@@ -59,6 +53,11 @@ export function computeStaticPieChartSettings(
 
   fillWithDefaultValue(settings, "pie.show_legend", getDefaultShowLegend());
   fillWithDefaultValue(settings, "pie.show_total", getDefaultShowTotal());
+  fillWithDefaultValue(
+    settings,
+    "pie.show_labels",
+    getDefaultShowLabels(settings),
+  );
   fillWithDefaultValue(
     settings,
     "pie.percent_visibility",
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts b/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts
index 685977a0545fedcd6bf1ed9713e6d236bb14f5e2..6a9e9a93591a0cbaf1321def922c57f7a21c5441 100644
--- a/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts
@@ -12,6 +12,8 @@ import defaultSettings from "./default-settings.json";
 import hideLegend from "./hide-legend.json";
 import hideTotal from "./hide-total.json";
 import invalidDimensionSetting44085 from "./invalid-dimension-setting-44085.json";
+import labelsOnChart from "./labels-on-chart.json";
+import labelsWithPercent from "./labels-with-percent.json";
 import largeMinimumSlicePercentage from "./large-min-slice-percentage.json";
 import longDimensionName from "./long-dimension-name.json";
 import missingColors44087 from "./missing-colors-44087.json";
@@ -35,8 +37,12 @@ import showPercentagesOnChartDense from "./show-percentages-on-chart-dense.json"
 import showPercentagesOnChart from "./show-percentages-on-chart.json";
 import singleDimension from "./single-dimension.json";
 import smallMinimumSlicePercentage from "./small-min-slice-percentage.json";
+import threeRingsNoLabels from "./three-rings-no-labels.json";
+import threeRingsOtherSlices from "./three-rings-other-slices.json";
+import threeRings from "./three-rings.json";
 import tinySlicesDisappear43766 from "./tiny-slices-disappear-43766.json";
 import truncatedTotal from "./truncated-total.json";
+import twoRings from "./two-rings.json";
 import unaggregatedDimension from "./unaggregated-dimension.json";
 import zeroMinimumSlicePercentage from "./zero-min-slice-percentage.json";
 
@@ -82,4 +88,10 @@ export const data = {
   noSingleColumnLegend45149,
   numericSQLColumnCrashes28568,
   missingLabelLargeSlice38424,
+  twoRings,
+  threeRings,
+  threeRingsNoLabels,
+  threeRingsOtherSlices,
+  labelsWithPercent,
+  labelsOnChart,
 };
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-on-chart.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-on-chart.json
new file mode 100644
index 0000000000000000000000000000000000000000..e0e27c0cbfb778dcfb060111e3bdec64076314b1
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-on-chart.json
@@ -0,0 +1,368 @@
+[
+  {
+    "card": {
+      "original_card_id": 445,
+      "table_name": null,
+      "initial_sync_status": null,
+      "scores": [
+        {
+          "weight": 2,
+          "score": 0,
+          "name": "pinned"
+        },
+        {
+          "weight": 2,
+          "score": 0,
+          "name": "bookmarked"
+        },
+        {
+          "weight": 1.5,
+          "score": 1,
+          "name": "recency"
+        },
+        {
+          "weight": 1,
+          "score": 0.02,
+          "name": "dashboard"
+        },
+        {
+          "weight": 0.5,
+          "score": 0.5,
+          "name": "model"
+        },
+        {
+          "score": 1,
+          "name": "text-exact-match",
+          "weight": 4.444444444444444,
+          "match": "Pie - Labels  on Chart - Poke Count by Type 1",
+          "match-context-thunk": "metabase.search.scoring$text_scores_with$iter__134420__134426$fn__134427$iter__134422__134428$fn__134429$fn__134430$fn__134433@736f3ed3",
+          "column": "name"
+        },
+        {
+          "score": 1,
+          "name": "text-consecutivity",
+          "weight": 2.222222222222222,
+          "match": "Pie - Labels  on Chart - Poke Count by Type 1",
+          "match-context-thunk": "metabase.search.scoring$text_scores_with$iter__134420__134426$fn__134427$iter__134422__134428$fn__134429$fn__134430$fn__134433@7750b466",
+          "column": "name"
+        },
+        {
+          "score": 1,
+          "name": "text-total-occurrences",
+          "weight": 2.222222222222222,
+          "match": "Pie - Labels  on Chart - Poke Count by Type 1",
+          "match-context-thunk": "metabase.search.scoring$text_scores_with$iter__134420__134426$fn__134427$iter__134422__134428$fn__134429$fn__134430$fn__134433@325234e8",
+          "column": "name"
+        },
+        {
+          "score": 0.2727272727272727,
+          "name": "text-fullness",
+          "weight": 1.111111111111111,
+          "match": "Pie - Labels  on Chart - Poke Count by Type 1",
+          "match-context-thunk": "metabase.search.scoring$text_scores_with$iter__134420__134426$fn__134427$iter__134422__134428$fn__134429$fn__134430$fn__134433@9b90ddb",
+          "column": "name"
+        }
+      ],
+      "context": null,
+      "dashboardcard_count": 1,
+      "table_description": null,
+      "last_edited_at": "2024-09-13T17:43:10.364621Z",
+      "model_name": null,
+      "last_editor_id": 1,
+      "effective_location": null,
+      "model_id": null,
+      "model_index_id": null,
+      "collection_authority_level": null,
+      "creator_common_name": "Emmad Usmani",
+      "table_schema": null,
+      "pk_ref": null,
+      "database_name": null,
+      "last_editor_common_name": "Emmad Usmani",
+      "bookmark": false,
+      "model": "card",
+      "location": null,
+      "moderated_status": null,
+      "fully_parameterized": true,
+      "can_delete": false,
+      "public_uuid": null,
+      "parameter_usage_count": 0,
+      "created_at": "2024-09-13T17:43:10.100482Z",
+      "parameters": [],
+      "metabase_version": "v0.1.33-SNAPSHOT (8755117)",
+      "collection": {
+        "id": 23,
+        "name": "Pie",
+        "authority_level": null,
+        "type": null
+      },
+      "visualization_settings": {
+        "pie.show_labels": true
+      },
+      "last-edit-info": {
+        "id": 1,
+        "last_name": "Usmani",
+        "first_name": "Emmad",
+        "email": "emmad@metabase.com",
+        "timestamp": "2024-09-13T17:43:10.364621Z"
+      },
+      "collection_preview": true,
+      "entity_id": "TN-g0zMacqF7YzocQPJWr",
+      "archived_directly": false,
+      "display": "pie",
+      "parameter_mappings": [],
+      "id": 445,
+      "dataset_query": {
+        "database": 2,
+        "type": "query",
+        "query": {
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ]
+          ],
+          "source-table": "card__104"
+        }
+      },
+      "cache_ttl": null,
+      "embedding_params": null,
+      "made_public_by_id": null,
+      "updated_at": "2024-09-13T17:43:10.100482Z",
+      "moderation_reviews": [],
+      "can_restore": false,
+      "creator_id": 1,
+      "average_query_time": null,
+      "type": "question",
+      "last_used_at": "2024-09-13T17:43:10.100482Z",
+      "dashboard_count": 0,
+      "last_query_start": null,
+      "name": "Pie - Labels  on Chart - Poke Count by Type 1",
+      "query_type": "query",
+      "collection_id": 23,
+      "enable_embedding": false,
+      "database_id": null,
+      "trashed_from_collection_id": null,
+      "can_write": true,
+      "initially_published_at": null,
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "result_metadata": [
+        {
+          "semantic_type": "type/Category",
+          "name": "type_1",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 16,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 8,
+                "q1": 38,
+                "q3": 76,
+                "max": 134,
+                "sd": 32.210500459776796,
+                "avg": 57.111111111111114
+              }
+            }
+          }
+        }
+      ],
+      "can_run_adhoc_query": true,
+      "table_id": null,
+      "source_card_id": 104,
+      "collection_position": null,
+      "view_count": 0,
+      "archived": false,
+      "description": null,
+      "cache_invalidated_at": null,
+      "displayIsLocked": true
+    },
+    "data": {
+      "rows": [
+        ["Bug", 81],
+        ["Dark", 44],
+        ["Dragon", 40],
+        ["Electric", 61],
+        ["Fairy", 22],
+        ["Fighting", 38],
+        ["Fire", 65],
+        ["Flying", 8],
+        ["Ghost", 41],
+        ["Grass", 91],
+        ["Ground", 41],
+        ["Ice", 36],
+        ["Normal", 115],
+        ["Poison", 39],
+        ["Psychic", 76],
+        ["Rock", 60],
+        ["Steel", 36],
+        ["Water", 134]
+      ],
+      "cols": [
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_1",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "position": 9,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"source\".\"type_1\" AS \"type_1\", COUNT(*) AS \"count\" FROM (SELECT \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"_mb_row_id\" AS \"_mb_row_id\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"pokedex_number\" AS \"pokedex_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"name\" AS \"name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"german_name\" AS \"german_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"japanese_name\" AS \"japanese_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"generation\" AS \"generation\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"status\" AS \"status\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"species\" AS \"species\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_number\" AS \"type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_1\" AS \"type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_2\" AS \"type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"height_m\" AS \"height_m\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"weight_kg\" AS \"weight_kg\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"abilities_number\" AS \"abilities_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_1\" AS \"ability_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_2\" AS \"ability_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_hidden\" AS \"ability_hidden\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"total_points\" AS \"total_points\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"hp\" AS \"hp\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"attack\" AS \"attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"defense\" AS \"defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_attack\" AS \"sp_attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_defense\" AS \"sp_defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"speed\" AS \"speed\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"catch_rate\" AS \"catch_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_friendship\" AS \"base_friendship\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_experience\" AS \"base_experience\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"growth_rate\" AS \"growth_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_number\" AS \"egg_type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_1\" AS \"egg_type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_2\" AS \"egg_type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"percentage_male\" AS \"percentage_male\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_cycles\" AS \"egg_cycles\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_normal\" AS \"against_normal\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fire\" AS \"against_fire\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_water\" AS \"against_water\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_electric\" AS \"against_electric\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_grass\" AS \"against_grass\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ice\" AS \"against_ice\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fight\" AS \"against_fight\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_poison\" AS \"against_poison\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ground\" AS \"against_ground\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_flying\" AS \"against_flying\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_psychic\" AS \"against_psychic\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_bug\" AS \"against_bug\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_rock\" AS \"against_rock\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ghost\" AS \"against_ghost\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dragon\" AS \"against_dragon\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dark\" AS \"against_dark\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_steel\" AS \"against_steel\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fairy\" AS \"against_fairy\" FROM \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\") AS \"source\" GROUP BY \"source\".\"type_1\" ORDER BY \"source\".\"type_1\" ASC",
+        "params": null
+      },
+      "dataset": true,
+      "model": true,
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "semantic_type": "type/Category",
+            "name": "type_1",
+            "field_ref": [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1475,
+            "visibility_type": "normal",
+            "display_name": "Type 1",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 18,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 5.281128404669261
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 16,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 8,
+                  "q1": 38,
+                  "q3": 76,
+                  "max": 134,
+                  "sd": 32.210500459776796,
+                  "avg": 57.111111111111114
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-with-percent.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-with-percent.json
new file mode 100644
index 0000000000000000000000000000000000000000..afcfd7aac5f24b40e3a124a585492e2843841b92
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/labels-with-percent.json
@@ -0,0 +1,525 @@
+[
+  {
+    "card": {
+      "original_card_id": 444,
+      "can_delete": false,
+      "public_uuid": null,
+      "parameter_usage_count": 0,
+      "created_at": "2024-09-13T17:32:13.380475Z",
+      "parameters": [],
+      "metabase_version": "v0.1.33-SNAPSHOT (8755117)",
+      "collection": {
+        "authority_level": null,
+        "description": null,
+        "archived": false,
+        "trashed_from_location": null,
+        "slug": "sunburst",
+        "archive_operation_id": null,
+        "name": "Sunburst",
+        "personal_owner_id": null,
+        "type": null,
+        "is_sample": false,
+        "id": 32,
+        "archived_directly": null,
+        "entity_id": "yivDsLk9XUmdCWr0lt9GJ",
+        "location": "/5/23/",
+        "namespace": null,
+        "is_personal": false,
+        "created_at": "2024-08-25T18:16:32.499168Z"
+      },
+      "visualization_settings": {
+        "pie.dimension": ["CREATED_AT", "CATEGORY"],
+        "pie.middle_dimension": "CREATED_AT",
+        "pie.metric": "count",
+        "pie.rows": [
+          {
+            "key": "2",
+            "name": "Mon",
+            "originalName": "Monday",
+            "color": "#227FD2",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "3",
+            "name": "Tue",
+            "originalName": "Tuesday",
+            "color": "#689636",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "4",
+            "name": "Wed",
+            "originalName": "Wednesday",
+            "color": "#8A5EB0",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "5",
+            "name": "Thur",
+            "originalName": "Thursday",
+            "color": "#F7C41F",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "6",
+            "name": "Fri",
+            "originalName": "Friday",
+            "color": "#69C8C8",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "isOther": false,
+            "hidden": false,
+            "enabled": false,
+            "defaultColor": true,
+            "color": "#7172AD",
+            "originalName": "Saturday",
+            "name": "Saturday",
+            "key": "7"
+          },
+          {
+            "isOther": false,
+            "hidden": false,
+            "enabled": false,
+            "defaultColor": true,
+            "color": "#88BF4D",
+            "originalName": "Sunday",
+            "name": "Sunday",
+            "key": "1"
+          }
+        ],
+        "pie.sort_rows": false,
+        "pie.percent_visibility": "both",
+        "column_settings": {
+          "[\"name\",\"CREATED_AT\"]": {
+            "date_abbreviate": false
+          }
+        }
+      },
+      "last-edit-info": {
+        "id": 1,
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_name": "Usmani",
+        "timestamp": "2024-09-13T17:32:13.665731Z"
+      },
+      "collection_preview": true,
+      "entity_id": "Td3JZQD_ovbIlirGTI4QK",
+      "archived_directly": false,
+      "display": "pie",
+      "parameter_mappings": [],
+      "id": 444,
+      "dataset_query": {
+        "database": 1,
+        "type": "query",
+        "query": {
+          "source-table": 2,
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              58,
+              {
+                "base-type": "type/Text",
+                "source-field": 40
+              }
+            ],
+            [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "day-of-week"
+              }
+            ]
+          ]
+        }
+      },
+      "cache_ttl": null,
+      "embedding_params": null,
+      "made_public_by_id": null,
+      "updated_at": "2024-09-13T17:32:13.380475Z",
+      "moderation_reviews": [],
+      "can_restore": false,
+      "creator_id": 1,
+      "average_query_time": null,
+      "type": "question",
+      "last_used_at": "2024-09-13T17:32:13.380475Z",
+      "dashboard_count": 0,
+      "last_query_start": null,
+      "name": "Sunburst - Labels with Percent - orders weekdays, category",
+      "query_type": "query",
+      "collection_id": 32,
+      "enable_embedding": false,
+      "database_id": 1,
+      "trashed_from_collection_id": null,
+      "can_write": true,
+      "initially_published_at": null,
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "result_metadata": [
+        {
+          "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+          "semantic_type": "type/Category",
+          "coercion_strategy": null,
+          "name": "CATEGORY",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            58,
+            {
+              "base-type": "type/Text",
+              "source-field": 40
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 58,
+          "visibility_type": "normal",
+          "display_name": "Product → Category",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 4,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 6.375
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "semantic_type": "type/CreationTimestamp",
+          "coercion_strategy": null,
+          "unit": "day-of-week",
+          "name": "CREATED_AT",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "day-of-week"
+            }
+          ],
+          "effective_type": "type/Integer",
+          "id": 41,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/Integer"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 27,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 537,
+                "q1": 622,
+                "q3": 717.5,
+                "max": 763,
+                "sd": 67.4245806369908,
+                "avg": 670
+              }
+            }
+          }
+        }
+      ],
+      "can_run_adhoc_query": true,
+      "table_id": 2,
+      "source_card_id": null,
+      "collection_position": null,
+      "view_count": 0,
+      "archived": false,
+      "description": null,
+      "cache_invalidated_at": null,
+      "displayIsLocked": true
+    },
+    "data": {
+      "rows": [
+        ["Doohickey", 1, 566],
+        ["Doohickey", 2, 537],
+        ["Doohickey", 3, 553],
+        ["Doohickey", 4, 563],
+        ["Doohickey", 5, 569],
+        ["Doohickey", 6, 582],
+        ["Doohickey", 7, 606],
+        ["Gadget", 1, 672],
+        ["Gadget", 2, 693],
+        ["Gadget", 3, 763],
+        ["Gadget", 4, 638],
+        ["Gadget", 5, 724],
+        ["Gadget", 6, 709],
+        ["Gadget", 7, 740],
+        ["Gizmo", 1, 717],
+        ["Gizmo", 2, 696],
+        ["Gizmo", 3, 680],
+        ["Gizmo", 4, 704],
+        ["Gizmo", 5, 685],
+        ["Gizmo", 6, 662],
+        ["Gizmo", 7, 640],
+        ["Widget", 1, 716],
+        ["Widget", 2, 738],
+        ["Widget", 3, 699],
+        ["Widget", 4, 718],
+        ["Widget", 5, 720],
+        ["Widget", 6, 709],
+        ["Widget", 7, 761]
+      ],
+      "cols": [
+        {
+          "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+          "database_type": "CHARACTER VARYING",
+          "semantic_type": "type/Category",
+          "table_id": 1,
+          "coercion_strategy": null,
+          "name": "CATEGORY",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 40,
+          "field_ref": [
+            "field",
+            58,
+            {
+              "base-type": "type/Text",
+              "source-field": 40
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 58,
+          "position": 3,
+          "visibility_type": "normal",
+          "display_name": "Product → Category",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 4,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 6.375
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PRODUCTS__via__PRODUCT_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "INTEGER",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 2,
+          "coercion_strategy": null,
+          "unit": "day-of-week",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "day-of-week"
+            }
+          ],
+          "effective_type": "type/Integer",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/Integer"
+        },
+        {
+          "database_type": "BIGINT",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\" AS \"PRODUCTS__via__PRODUCT_ID__CATEGORY\", COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) AS \"CREATED_AT\", COUNT(*) AS \"count\" FROM \"PUBLIC\".\"ORDERS\" LEFT JOIN \"PUBLIC\".\"PRODUCTS\" AS \"PRODUCTS__via__PRODUCT_ID\" ON \"PUBLIC\".\"ORDERS\".\"PRODUCT_ID\" = \"PRODUCTS__via__PRODUCT_ID\".\"ID\" GROUP BY \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\", COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) ORDER BY \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\" ASC, COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) ASC",
+        "params": null
+      },
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+            "semantic_type": "type/Category",
+            "coercion_strategy": null,
+            "name": "CATEGORY",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              58,
+              {
+                "base-type": "type/Text",
+                "source-field": 40
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 58,
+            "visibility_type": "normal",
+            "display_name": "Product → Category",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 4,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 6.375
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "description": "The date and time an order was submitted.",
+            "semantic_type": "type/CreationTimestamp",
+            "coercion_strategy": null,
+            "unit": "day-of-week",
+            "name": "CREATED_AT",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "day-of-week"
+              }
+            ],
+            "effective_type": "type/Integer",
+            "id": 41,
+            "visibility_type": "normal",
+            "display_name": "Created At",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 10001,
+                "nil%": 0
+              },
+              "type": {
+                "type/DateTime": {
+                  "earliest": "2022-04-30T18:56:13.352Z",
+                  "latest": "2026-04-19T14:07:15.657Z"
+                }
+              }
+            },
+            "base_type": "type/Integer"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 27,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 537,
+                  "q1": 622,
+                  "q3": 717.5,
+                  "max": 763,
+                  "sd": 67.4245806369908,
+                  "avg": 670
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-no-labels.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-no-labels.json
new file mode 100644
index 0000000000000000000000000000000000000000..d9fdf84a23bc01b367530eb286db8a1e08c6f707
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-no-labels.json
@@ -0,0 +1,603 @@
+[
+  {
+    "card": {
+      "original_card_id": 437,
+      "can_delete": false,
+      "public_uuid": null,
+      "parameter_usage_count": 0,
+      "created_at": "2024-09-11T15:27:33.439408Z",
+      "parameters": [],
+      "metabase_version": "v0.1.33-SNAPSHOT (8755117)",
+      "collection": {
+        "authority_level": null,
+        "description": null,
+        "archived": false,
+        "trashed_from_location": null,
+        "slug": "sunburst",
+        "archive_operation_id": null,
+        "name": "Sunburst",
+        "personal_owner_id": null,
+        "type": null,
+        "is_sample": false,
+        "id": 32,
+        "archived_directly": null,
+        "entity_id": "yivDsLk9XUmdCWr0lt9GJ",
+        "location": "/5/23/",
+        "namespace": null,
+        "is_personal": false,
+        "created_at": "2024-08-25T18:16:32.499168Z"
+      },
+      "visualization_settings": {
+        "table.pivot_column": "generation",
+        "table.cell_column": "count",
+        "pie.dimension": ["generation", "type_1", "type_2"],
+        "pie.metric": "count",
+        "pie.slice_threshold": 0,
+        "pie.show_labels": false
+      },
+      "last-edit-info": {
+        "id": 1,
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_name": "Usmani",
+        "timestamp": "2024-09-12T20:57:42.021468Z"
+      },
+      "collection_preview": true,
+      "entity_id": "ue-evUeQ07b-xZEr48VAk",
+      "archived_directly": false,
+      "display": "pie",
+      "parameter_mappings": [],
+      "id": 437,
+      "dataset_query": {
+        "database": 2,
+        "type": "query",
+        "query": {
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ]
+          ],
+          "order-by": [["desc", ["aggregation", 0]]],
+          "source-table": "card__104",
+          "filter": [
+            "and",
+            [
+              "=",
+              [
+                "field",
+                "type_1",
+                {
+                  "base-type": "type/Text"
+                }
+              ],
+              "Water",
+              "Grass",
+              "Fire"
+            ],
+            [
+              "not-empty",
+              [
+                "field",
+                "type_2",
+                {
+                  "base-type": "type/Text"
+                }
+              ]
+            ]
+          ]
+        }
+      },
+      "cache_ttl": null,
+      "embedding_params": null,
+      "made_public_by_id": null,
+      "updated_at": "2024-09-12T20:57:41.785432Z",
+      "moderation_reviews": [],
+      "can_restore": false,
+      "creator_id": 1,
+      "average_query_time": 487.7894736842105,
+      "type": "question",
+      "last_used_at": "2024-09-13T17:05:33.584358Z",
+      "dashboard_count": 1,
+      "last_query_start": "2024-09-13T17:05:33.238198Z",
+      "name": "Sunburst - Poke Count by Gen, Type 1, Type 2, 0 MSP",
+      "query_type": "query",
+      "collection_id": 32,
+      "enable_embedding": false,
+      "database_id": 2,
+      "trashed_from_collection_id": null,
+      "can_write": true,
+      "initially_published_at": null,
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "result_metadata": [
+        {
+          "semantic_type": "type/Category",
+          "name": "generation",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_1",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_2",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 6,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 1,
+                "q3": 2.2964690541943424,
+                "max": 10,
+                "sd": 1.2898977338673652,
+                "avg": 1.8076923076923077
+              }
+            }
+          }
+        }
+      ],
+      "can_run_adhoc_query": true,
+      "table_id": 156,
+      "source_card_id": 104,
+      "collection_position": null,
+      "view_count": 35,
+      "archived": false,
+      "description": null,
+      "cache_invalidated_at": null,
+      "displayIsLocked": true
+    },
+    "data": {
+      "rows": [
+        [1, "Grass", "Poison", 10],
+        [3, "Water", "Ground", 5],
+        [1, "Water", "Psychic", 4],
+        [3, "Water", "Dark", 4],
+        [1, "Fire", "Flying", 3],
+        [1, "Water", "Ice", 3],
+        [2, "Grass", "Flying", 3],
+        [3, "Fire", "Fighting", 3],
+        [3, "Fire", "Ground", 3],
+        [3, "Grass", "Dark", 3],
+        [3, "Water", "Grass", 3],
+        [4, "Grass", "Ice", 3],
+        [7, "Grass", "Fairy", 3],
+        [8, "Grass", "Dragon", 3],
+        [1, "Grass", "Psychic", 2],
+        [1, "Water", "Poison", 2],
+        [2, "Water", "Electric", 2],
+        [2, "Water", "Fairy", 2],
+        [2, "Water", "Ground", 2],
+        [3, "Water", "Flying", 2],
+        [4, "Fire", "Fighting", 2],
+        [4, "Grass", "Poison", 2],
+        [5, "Fire", "Fighting", 2],
+        [5, "Grass", "Fairy", 2],
+        [5, "Grass", "Poison", 2],
+        [5, "Grass", "Steel", 2],
+        [5, "Water", "Fighting", 2],
+        [5, "Water", "Flying", 2],
+        [5, "Water", "Ghost", 2],
+        [5, "Water", "Ground", 2],
+        [5, "Water", "Rock", 2],
+        [6, "Fire", "Flying", 2],
+        [6, "Fire", "Normal", 2],
+        [6, "Water", "Dark", 2],
+        [7, "Grass", "Flying", 2],
+        [7, "Water", "Bug", 2],
+        [7, "Water", "Fairy", 2],
+        [8, "Fire", "Bug", 2],
+        [1, "Fire", "Dragon", 1],
+        [1, "Fire", "Ghost", 1],
+        [1, "Grass", "Dragon", 1],
+        [1, "Water", "Dark", 1],
+        [1, "Water", "Fighting", 1],
+        [1, "Water", "Flying", 1],
+        [2, "Fire", "Flying", 1],
+        [2, "Fire", "Rock", 1],
+        [2, "Water", "Dragon", 1],
+        [2, "Water", "Flying", 1],
+        [2, "Water", "Poison", 1],
+        [2, "Water", "Psychic", 1],
+        [2, "Water", "Rock", 1],
+        [3, "Grass", "Dragon", 1],
+        [3, "Grass", "Fighting", 1],
+        [3, "Grass", "Flying", 1],
+        [3, "Grass", "Poison", 1],
+        [3, "Water", "Rock", 1],
+        [4, "Fire", "Steel", 1],
+        [4, "Grass", "Flying", 1],
+        [4, "Grass", "Ground", 1],
+        [4, "Water", "Dragon", 1],
+        [4, "Water", "Flying", 1],
+        [4, "Water", "Ground", 1],
+        [4, "Water", "Steel", 1],
+        [5, "Fire", "Psychic", 1],
+        [5, "Grass", "Fighting", 1],
+        [6, "Fire", "Psychic", 1],
+        [6, "Fire", "Water", 1],
+        [6, "Grass", "Fighting", 1],
+        [7, "Fire", "Dark", 1],
+        [7, "Fire", "Dragon", 1],
+        [7, "Fire", "Flying", 1],
+        [7, "Fire", "Ghost", 1],
+        [7, "Grass", "Ghost", 1],
+        [7, "Grass", "Steel", 1],
+        [7, "Water", "Psychic", 1],
+        [8, "Water", "Dragon", 1],
+        [8, "Water", "Ice", 1],
+        [8, "Water", "Rock", 1]
+      ],
+      "cols": [
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "generation",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "position": 5,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_1",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "position": 9,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_2",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "position": 10,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"source\".\"generation\" AS \"generation\", \"source\".\"type_1\" AS \"type_1\", \"source\".\"type_2\" AS \"type_2\", COUNT(*) AS \"count\" FROM (SELECT \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"_mb_row_id\" AS \"_mb_row_id\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"pokedex_number\" AS \"pokedex_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"name\" AS \"name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"german_name\" AS \"german_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"japanese_name\" AS \"japanese_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"generation\" AS \"generation\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"status\" AS \"status\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"species\" AS \"species\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_number\" AS \"type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_1\" AS \"type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_2\" AS \"type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"height_m\" AS \"height_m\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"weight_kg\" AS \"weight_kg\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"abilities_number\" AS \"abilities_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_1\" AS \"ability_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_2\" AS \"ability_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_hidden\" AS \"ability_hidden\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"total_points\" AS \"total_points\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"hp\" AS \"hp\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"attack\" AS \"attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"defense\" AS \"defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_attack\" AS \"sp_attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_defense\" AS \"sp_defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"speed\" AS \"speed\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"catch_rate\" AS \"catch_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_friendship\" AS \"base_friendship\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_experience\" AS \"base_experience\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"growth_rate\" AS \"growth_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_number\" AS \"egg_type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_1\" AS \"egg_type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_2\" AS \"egg_type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"percentage_male\" AS \"percentage_male\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_cycles\" AS \"egg_cycles\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_normal\" AS \"against_normal\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fire\" AS \"against_fire\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_water\" AS \"against_water\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_electric\" AS \"against_electric\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_grass\" AS \"against_grass\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ice\" AS \"against_ice\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fight\" AS \"against_fight\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_poison\" AS \"against_poison\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ground\" AS \"against_ground\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_flying\" AS \"against_flying\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_psychic\" AS \"against_psychic\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_bug\" AS \"against_bug\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_rock\" AS \"against_rock\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ghost\" AS \"against_ghost\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dragon\" AS \"against_dragon\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dark\" AS \"against_dark\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_steel\" AS \"against_steel\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fairy\" AS \"against_fairy\" FROM \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\") AS \"source\" WHERE ((\"source\".\"type_1\" = 'Water') OR (\"source\".\"type_1\" = 'Grass') OR (\"source\".\"type_1\" = 'Fire')) AND (\"source\".\"type_2\" IS NOT NULL) AND ((\"source\".\"type_2\" <> '') OR (\"source\".\"type_2\" IS NULL)) GROUP BY \"source\".\"generation\", \"source\".\"type_1\", \"source\".\"type_2\" ORDER BY \"count\" DESC, \"source\".\"generation\" ASC, \"source\".\"type_1\" ASC, \"source\".\"type_2\" ASC",
+        "params": null
+      },
+      "dataset": true,
+      "model": true,
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "semantic_type": "type/Category",
+            "name": "generation",
+            "field_ref": [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            "effective_type": "type/BigInteger",
+            "id": 1503,
+            "visibility_type": "normal",
+            "display_name": "Generation",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 8,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 2.1045160333142734,
+                  "q3": 5.716989271844637,
+                  "max": 8,
+                  "sd": 2.234937326790694,
+                  "avg": 4.034046692607004
+                }
+              }
+            },
+            "base_type": "type/BigInteger"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_1",
+            "field_ref": [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1475,
+            "visibility_type": "normal",
+            "display_name": "Type 1",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 18,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 5.281128404669261
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_2",
+            "field_ref": [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1499,
+            "visibility_type": "normal",
+            "display_name": "Type 2",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 19,
+                "nil%": 0.4727626459143969
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 2.9474708171206228
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 6,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 1,
+                  "q3": 2.2964690541943424,
+                  "max": 10,
+                  "sd": 1.2898977338673652,
+                  "avg": 1.8076923076923077
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-other-slices.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-other-slices.json
new file mode 100644
index 0000000000000000000000000000000000000000..ed84018a607977742581dec22493d1094bc21e25
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings-other-slices.json
@@ -0,0 +1,604 @@
+[
+  {
+    "card": {
+      "original_card_id": 435,
+      "can_delete": false,
+      "public_uuid": null,
+      "parameter_usage_count": 0,
+      "created_at": "2024-09-05T21:37:00.110724Z",
+      "parameters": [],
+      "metabase_version": "v0.1.33-SNAPSHOT (8755117)",
+      "collection": {
+        "authority_level": null,
+        "description": null,
+        "archived": false,
+        "trashed_from_location": null,
+        "slug": "sunburst",
+        "archive_operation_id": null,
+        "name": "Sunburst",
+        "personal_owner_id": null,
+        "type": null,
+        "is_sample": false,
+        "id": 32,
+        "archived_directly": null,
+        "entity_id": "yivDsLk9XUmdCWr0lt9GJ",
+        "location": "/5/23/",
+        "namespace": null,
+        "is_personal": false,
+        "created_at": "2024-08-25T18:16:32.499168Z"
+      },
+      "visualization_settings": {
+        "table.pivot_column": "generation",
+        "table.cell_column": "count",
+        "pie.dimension": ["generation", "type_1", "type_2"],
+        "pie.middle_dimension": "type_1",
+        "pie.outer_dimension": "type_2",
+        "pie.metric": "count",
+        "pie.slice_threshold": 11
+      },
+      "last-edit-info": {
+        "id": 1,
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_name": "Usmani",
+        "timestamp": "2024-09-12T20:55:39.917649Z"
+      },
+      "collection_preview": true,
+      "entity_id": "qrGpX7x2lkmsuiuF8YJw5",
+      "archived_directly": false,
+      "display": "pie",
+      "parameter_mappings": [],
+      "id": 435,
+      "dataset_query": {
+        "database": 2,
+        "type": "query",
+        "query": {
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ]
+          ],
+          "order-by": [["desc", ["aggregation", 0]]],
+          "source-table": "card__104",
+          "filter": [
+            "and",
+            [
+              "=",
+              [
+                "field",
+                "type_1",
+                {
+                  "base-type": "type/Text"
+                }
+              ],
+              "Water",
+              "Grass",
+              "Fire"
+            ],
+            [
+              "not-empty",
+              [
+                "field",
+                "type_2",
+                {
+                  "base-type": "type/Text"
+                }
+              ]
+            ]
+          ]
+        }
+      },
+      "cache_ttl": null,
+      "embedding_params": null,
+      "made_public_by_id": null,
+      "updated_at": "2024-09-12T20:55:39.607203Z",
+      "moderation_reviews": [],
+      "can_restore": false,
+      "creator_id": 1,
+      "average_query_time": 483.0125,
+      "type": "question",
+      "last_used_at": "2024-09-13T17:32:18.893089Z",
+      "dashboard_count": 1,
+      "last_query_start": "2024-09-13T17:32:18.623548Z",
+      "name": "Sunburst - Poke Count by Gen, Type 1, Type 2, high MSP",
+      "query_type": "query",
+      "collection_id": 32,
+      "enable_embedding": false,
+      "database_id": 2,
+      "trashed_from_collection_id": null,
+      "can_write": true,
+      "initially_published_at": null,
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "result_metadata": [
+        {
+          "semantic_type": "type/Category",
+          "name": "generation",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_1",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_2",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 6,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 1,
+                "q3": 2.2964690541943424,
+                "max": 10,
+                "sd": 1.2898977338673652,
+                "avg": 1.8076923076923077
+              }
+            }
+          }
+        }
+      ],
+      "can_run_adhoc_query": true,
+      "table_id": 156,
+      "source_card_id": 104,
+      "collection_position": null,
+      "view_count": 59,
+      "archived": false,
+      "description": null,
+      "cache_invalidated_at": null,
+      "displayIsLocked": true
+    },
+    "data": {
+      "rows": [
+        [1, "Grass", "Poison", 10],
+        [3, "Water", "Ground", 5],
+        [1, "Water", "Psychic", 4],
+        [3, "Water", "Dark", 4],
+        [1, "Fire", "Flying", 3],
+        [1, "Water", "Ice", 3],
+        [2, "Grass", "Flying", 3],
+        [3, "Fire", "Fighting", 3],
+        [3, "Fire", "Ground", 3],
+        [3, "Grass", "Dark", 3],
+        [3, "Water", "Grass", 3],
+        [4, "Grass", "Ice", 3],
+        [7, "Grass", "Fairy", 3],
+        [8, "Grass", "Dragon", 3],
+        [1, "Grass", "Psychic", 2],
+        [1, "Water", "Poison", 2],
+        [2, "Water", "Electric", 2],
+        [2, "Water", "Fairy", 2],
+        [2, "Water", "Ground", 2],
+        [3, "Water", "Flying", 2],
+        [4, "Fire", "Fighting", 2],
+        [4, "Grass", "Poison", 2],
+        [5, "Fire", "Fighting", 2],
+        [5, "Grass", "Fairy", 2],
+        [5, "Grass", "Poison", 2],
+        [5, "Grass", "Steel", 2],
+        [5, "Water", "Fighting", 2],
+        [5, "Water", "Flying", 2],
+        [5, "Water", "Ghost", 2],
+        [5, "Water", "Ground", 2],
+        [5, "Water", "Rock", 2],
+        [6, "Fire", "Flying", 2],
+        [6, "Fire", "Normal", 2],
+        [6, "Water", "Dark", 2],
+        [7, "Grass", "Flying", 2],
+        [7, "Water", "Bug", 2],
+        [7, "Water", "Fairy", 2],
+        [8, "Fire", "Bug", 2],
+        [1, "Fire", "Dragon", 1],
+        [1, "Fire", "Ghost", 1],
+        [1, "Grass", "Dragon", 1],
+        [1, "Water", "Dark", 1],
+        [1, "Water", "Fighting", 1],
+        [1, "Water", "Flying", 1],
+        [2, "Fire", "Flying", 1],
+        [2, "Fire", "Rock", 1],
+        [2, "Water", "Dragon", 1],
+        [2, "Water", "Flying", 1],
+        [2, "Water", "Poison", 1],
+        [2, "Water", "Psychic", 1],
+        [2, "Water", "Rock", 1],
+        [3, "Grass", "Dragon", 1],
+        [3, "Grass", "Fighting", 1],
+        [3, "Grass", "Flying", 1],
+        [3, "Grass", "Poison", 1],
+        [3, "Water", "Rock", 1],
+        [4, "Fire", "Steel", 1],
+        [4, "Grass", "Flying", 1],
+        [4, "Grass", "Ground", 1],
+        [4, "Water", "Dragon", 1],
+        [4, "Water", "Flying", 1],
+        [4, "Water", "Ground", 1],
+        [4, "Water", "Steel", 1],
+        [5, "Fire", "Psychic", 1],
+        [5, "Grass", "Fighting", 1],
+        [6, "Fire", "Psychic", 1],
+        [6, "Fire", "Water", 1],
+        [6, "Grass", "Fighting", 1],
+        [7, "Fire", "Dark", 1],
+        [7, "Fire", "Dragon", 1],
+        [7, "Fire", "Flying", 1],
+        [7, "Fire", "Ghost", 1],
+        [7, "Grass", "Ghost", 1],
+        [7, "Grass", "Steel", 1],
+        [7, "Water", "Psychic", 1],
+        [8, "Water", "Dragon", 1],
+        [8, "Water", "Ice", 1],
+        [8, "Water", "Rock", 1]
+      ],
+      "cols": [
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "generation",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "position": 5,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_1",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "position": 9,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_2",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "position": 10,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"source\".\"generation\" AS \"generation\", \"source\".\"type_1\" AS \"type_1\", \"source\".\"type_2\" AS \"type_2\", COUNT(*) AS \"count\" FROM (SELECT \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"_mb_row_id\" AS \"_mb_row_id\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"pokedex_number\" AS \"pokedex_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"name\" AS \"name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"german_name\" AS \"german_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"japanese_name\" AS \"japanese_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"generation\" AS \"generation\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"status\" AS \"status\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"species\" AS \"species\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_number\" AS \"type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_1\" AS \"type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_2\" AS \"type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"height_m\" AS \"height_m\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"weight_kg\" AS \"weight_kg\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"abilities_number\" AS \"abilities_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_1\" AS \"ability_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_2\" AS \"ability_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_hidden\" AS \"ability_hidden\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"total_points\" AS \"total_points\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"hp\" AS \"hp\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"attack\" AS \"attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"defense\" AS \"defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_attack\" AS \"sp_attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_defense\" AS \"sp_defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"speed\" AS \"speed\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"catch_rate\" AS \"catch_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_friendship\" AS \"base_friendship\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_experience\" AS \"base_experience\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"growth_rate\" AS \"growth_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_number\" AS \"egg_type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_1\" AS \"egg_type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_2\" AS \"egg_type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"percentage_male\" AS \"percentage_male\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_cycles\" AS \"egg_cycles\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_normal\" AS \"against_normal\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fire\" AS \"against_fire\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_water\" AS \"against_water\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_electric\" AS \"against_electric\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_grass\" AS \"against_grass\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ice\" AS \"against_ice\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fight\" AS \"against_fight\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_poison\" AS \"against_poison\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ground\" AS \"against_ground\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_flying\" AS \"against_flying\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_psychic\" AS \"against_psychic\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_bug\" AS \"against_bug\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_rock\" AS \"against_rock\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ghost\" AS \"against_ghost\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dragon\" AS \"against_dragon\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dark\" AS \"against_dark\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_steel\" AS \"against_steel\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fairy\" AS \"against_fairy\" FROM \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\") AS \"source\" WHERE ((\"source\".\"type_1\" = 'Water') OR (\"source\".\"type_1\" = 'Grass') OR (\"source\".\"type_1\" = 'Fire')) AND (\"source\".\"type_2\" IS NOT NULL) AND ((\"source\".\"type_2\" <> '') OR (\"source\".\"type_2\" IS NULL)) GROUP BY \"source\".\"generation\", \"source\".\"type_1\", \"source\".\"type_2\" ORDER BY \"count\" DESC, \"source\".\"generation\" ASC, \"source\".\"type_1\" ASC, \"source\".\"type_2\" ASC",
+        "params": null
+      },
+      "dataset": true,
+      "model": true,
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "semantic_type": "type/Category",
+            "name": "generation",
+            "field_ref": [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            "effective_type": "type/BigInteger",
+            "id": 1503,
+            "visibility_type": "normal",
+            "display_name": "Generation",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 8,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 2.1045160333142734,
+                  "q3": 5.716989271844637,
+                  "max": 8,
+                  "sd": 2.234937326790694,
+                  "avg": 4.034046692607004
+                }
+              }
+            },
+            "base_type": "type/BigInteger"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_1",
+            "field_ref": [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1475,
+            "visibility_type": "normal",
+            "display_name": "Type 1",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 18,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 5.281128404669261
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_2",
+            "field_ref": [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1499,
+            "visibility_type": "normal",
+            "display_name": "Type 2",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 19,
+                "nil%": 0.4727626459143969
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 2.9474708171206228
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 6,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 1,
+                  "q3": 2.2964690541943424,
+                  "max": 10,
+                  "sd": 1.2898977338673652,
+                  "avg": 1.8076923076923077
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings.json
new file mode 100644
index 0000000000000000000000000000000000000000..454fb279b3962939a2958526b8761af2990a2791
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/three-rings.json
@@ -0,0 +1,602 @@
+[
+  {
+    "card": {
+      "original_card_id": 437,
+      "can_delete": false,
+      "public_uuid": null,
+      "parameter_usage_count": 0,
+      "created_at": "2024-09-11T15:27:33.439408Z",
+      "parameters": [],
+      "metabase_version": "v0.1.33-SNAPSHOT (8755117)",
+      "collection": {
+        "authority_level": null,
+        "description": null,
+        "archived": false,
+        "trashed_from_location": null,
+        "slug": "sunburst",
+        "archive_operation_id": null,
+        "name": "Sunburst",
+        "personal_owner_id": null,
+        "type": null,
+        "is_sample": false,
+        "id": 32,
+        "archived_directly": null,
+        "entity_id": "yivDsLk9XUmdCWr0lt9GJ",
+        "location": "/5/23/",
+        "namespace": null,
+        "is_personal": false,
+        "created_at": "2024-08-25T18:16:32.499168Z"
+      },
+      "visualization_settings": {
+        "table.pivot_column": "generation",
+        "table.cell_column": "count",
+        "pie.dimension": ["generation", "type_1", "type_2"],
+        "pie.metric": "count",
+        "pie.slice_threshold": 0
+      },
+      "last-edit-info": {
+        "id": 1,
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_name": "Usmani",
+        "timestamp": "2024-09-12T20:57:42.021468Z"
+      },
+      "collection_preview": true,
+      "entity_id": "ue-evUeQ07b-xZEr48VAk",
+      "archived_directly": false,
+      "display": "pie",
+      "parameter_mappings": [],
+      "id": 437,
+      "dataset_query": {
+        "database": 2,
+        "type": "query",
+        "query": {
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ]
+          ],
+          "order-by": [["desc", ["aggregation", 0]]],
+          "source-table": "card__104",
+          "filter": [
+            "and",
+            [
+              "=",
+              [
+                "field",
+                "type_1",
+                {
+                  "base-type": "type/Text"
+                }
+              ],
+              "Water",
+              "Grass",
+              "Fire"
+            ],
+            [
+              "not-empty",
+              [
+                "field",
+                "type_2",
+                {
+                  "base-type": "type/Text"
+                }
+              ]
+            ]
+          ]
+        }
+      },
+      "cache_ttl": null,
+      "embedding_params": null,
+      "made_public_by_id": null,
+      "updated_at": "2024-09-12T20:57:41.785432Z",
+      "moderation_reviews": [],
+      "can_restore": false,
+      "creator_id": 1,
+      "average_query_time": 487.7894736842105,
+      "type": "question",
+      "last_used_at": "2024-09-13T17:05:33.584358Z",
+      "dashboard_count": 1,
+      "last_query_start": "2024-09-13T17:05:33.238198Z",
+      "name": "Sunburst - Poke Count by Gen, Type 1, Type 2, 0 MSP",
+      "query_type": "query",
+      "collection_id": 32,
+      "enable_embedding": false,
+      "database_id": 2,
+      "trashed_from_collection_id": null,
+      "can_write": true,
+      "initially_published_at": null,
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "result_metadata": [
+        {
+          "semantic_type": "type/Category",
+          "name": "generation",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_1",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "semantic_type": "type/Category",
+          "name": "type_2",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 6,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 1,
+                "q3": 2.2964690541943424,
+                "max": 10,
+                "sd": 1.2898977338673652,
+                "avg": 1.8076923076923077
+              }
+            }
+          }
+        }
+      ],
+      "can_run_adhoc_query": true,
+      "table_id": 156,
+      "source_card_id": 104,
+      "collection_position": null,
+      "view_count": 35,
+      "archived": false,
+      "description": null,
+      "cache_invalidated_at": null,
+      "displayIsLocked": true
+    },
+    "data": {
+      "rows": [
+        [1, "Grass", "Poison", 10],
+        [3, "Water", "Ground", 5],
+        [1, "Water", "Psychic", 4],
+        [3, "Water", "Dark", 4],
+        [1, "Fire", "Flying", 3],
+        [1, "Water", "Ice", 3],
+        [2, "Grass", "Flying", 3],
+        [3, "Fire", "Fighting", 3],
+        [3, "Fire", "Ground", 3],
+        [3, "Grass", "Dark", 3],
+        [3, "Water", "Grass", 3],
+        [4, "Grass", "Ice", 3],
+        [7, "Grass", "Fairy", 3],
+        [8, "Grass", "Dragon", 3],
+        [1, "Grass", "Psychic", 2],
+        [1, "Water", "Poison", 2],
+        [2, "Water", "Electric", 2],
+        [2, "Water", "Fairy", 2],
+        [2, "Water", "Ground", 2],
+        [3, "Water", "Flying", 2],
+        [4, "Fire", "Fighting", 2],
+        [4, "Grass", "Poison", 2],
+        [5, "Fire", "Fighting", 2],
+        [5, "Grass", "Fairy", 2],
+        [5, "Grass", "Poison", 2],
+        [5, "Grass", "Steel", 2],
+        [5, "Water", "Fighting", 2],
+        [5, "Water", "Flying", 2],
+        [5, "Water", "Ghost", 2],
+        [5, "Water", "Ground", 2],
+        [5, "Water", "Rock", 2],
+        [6, "Fire", "Flying", 2],
+        [6, "Fire", "Normal", 2],
+        [6, "Water", "Dark", 2],
+        [7, "Grass", "Flying", 2],
+        [7, "Water", "Bug", 2],
+        [7, "Water", "Fairy", 2],
+        [8, "Fire", "Bug", 2],
+        [1, "Fire", "Dragon", 1],
+        [1, "Fire", "Ghost", 1],
+        [1, "Grass", "Dragon", 1],
+        [1, "Water", "Dark", 1],
+        [1, "Water", "Fighting", 1],
+        [1, "Water", "Flying", 1],
+        [2, "Fire", "Flying", 1],
+        [2, "Fire", "Rock", 1],
+        [2, "Water", "Dragon", 1],
+        [2, "Water", "Flying", 1],
+        [2, "Water", "Poison", 1],
+        [2, "Water", "Psychic", 1],
+        [2, "Water", "Rock", 1],
+        [3, "Grass", "Dragon", 1],
+        [3, "Grass", "Fighting", 1],
+        [3, "Grass", "Flying", 1],
+        [3, "Grass", "Poison", 1],
+        [3, "Water", "Rock", 1],
+        [4, "Fire", "Steel", 1],
+        [4, "Grass", "Flying", 1],
+        [4, "Grass", "Ground", 1],
+        [4, "Water", "Dragon", 1],
+        [4, "Water", "Flying", 1],
+        [4, "Water", "Ground", 1],
+        [4, "Water", "Steel", 1],
+        [5, "Fire", "Psychic", 1],
+        [5, "Grass", "Fighting", 1],
+        [6, "Fire", "Psychic", 1],
+        [6, "Fire", "Water", 1],
+        [6, "Grass", "Fighting", 1],
+        [7, "Fire", "Dark", 1],
+        [7, "Fire", "Dragon", 1],
+        [7, "Fire", "Flying", 1],
+        [7, "Fire", "Ghost", 1],
+        [7, "Grass", "Ghost", 1],
+        [7, "Grass", "Steel", 1],
+        [7, "Water", "Psychic", 1],
+        [8, "Water", "Dragon", 1],
+        [8, "Water", "Ice", 1],
+        [8, "Water", "Rock", 1]
+      ],
+      "cols": [
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "generation",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "generation",
+            {
+              "base-type": "type/BigInteger"
+            }
+          ],
+          "effective_type": "type/BigInteger",
+          "id": 1503,
+          "position": 5,
+          "visibility_type": "normal",
+          "display_name": "Generation",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 8,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 2.1045160333142734,
+                "q3": 5.716989271844637,
+                "max": 8,
+                "sd": 2.234937326790694,
+                "avg": 4.034046692607004
+              }
+            }
+          },
+          "base_type": "type/BigInteger"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_1",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_1",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1475,
+          "position": 9,
+          "visibility_type": "normal",
+          "display_name": "Type 1",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 18,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 5.281128404669261
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "varchar",
+          "semantic_type": "type/Category",
+          "table_id": 156,
+          "name": "type_2",
+          "source": "breakout",
+          "field_ref": [
+            "field",
+            "type_2",
+            {
+              "base-type": "type/Text"
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 1499,
+          "position": 10,
+          "visibility_type": "normal",
+          "display_name": "Type 2",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 19,
+              "nil%": 0.4727626459143969
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 2.9474708171206228
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "database_type": "int8",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"source\".\"generation\" AS \"generation\", \"source\".\"type_1\" AS \"type_1\", \"source\".\"type_2\" AS \"type_2\", COUNT(*) AS \"count\" FROM (SELECT \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"_mb_row_id\" AS \"_mb_row_id\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"pokedex_number\" AS \"pokedex_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"name\" AS \"name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"german_name\" AS \"german_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"japanese_name\" AS \"japanese_name\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"generation\" AS \"generation\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"status\" AS \"status\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"species\" AS \"species\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_number\" AS \"type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_1\" AS \"type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"type_2\" AS \"type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"height_m\" AS \"height_m\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"weight_kg\" AS \"weight_kg\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"abilities_number\" AS \"abilities_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_1\" AS \"ability_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_2\" AS \"ability_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"ability_hidden\" AS \"ability_hidden\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"total_points\" AS \"total_points\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"hp\" AS \"hp\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"attack\" AS \"attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"defense\" AS \"defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_attack\" AS \"sp_attack\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"sp_defense\" AS \"sp_defense\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"speed\" AS \"speed\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"catch_rate\" AS \"catch_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_friendship\" AS \"base_friendship\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"base_experience\" AS \"base_experience\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"growth_rate\" AS \"growth_rate\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_number\" AS \"egg_type_number\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_1\" AS \"egg_type_1\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_type_2\" AS \"egg_type_2\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"percentage_male\" AS \"percentage_male\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"egg_cycles\" AS \"egg_cycles\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_normal\" AS \"against_normal\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fire\" AS \"against_fire\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_water\" AS \"against_water\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_electric\" AS \"against_electric\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_grass\" AS \"against_grass\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ice\" AS \"against_ice\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fight\" AS \"against_fight\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_poison\" AS \"against_poison\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ground\" AS \"against_ground\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_flying\" AS \"against_flying\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_psychic\" AS \"against_psychic\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_bug\" AS \"against_bug\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_rock\" AS \"against_rock\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_ghost\" AS \"against_ghost\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dragon\" AS \"against_dragon\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_dark\" AS \"against_dark\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_steel\" AS \"against_steel\", \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\".\"against_fairy\" AS \"against_fairy\" FROM \"csv_upload_data\".\"csv_upload_pokedex_20231202112932\") AS \"source\" WHERE ((\"source\".\"type_1\" = 'Water') OR (\"source\".\"type_1\" = 'Grass') OR (\"source\".\"type_1\" = 'Fire')) AND (\"source\".\"type_2\" IS NOT NULL) AND ((\"source\".\"type_2\" <> '') OR (\"source\".\"type_2\" IS NULL)) GROUP BY \"source\".\"generation\", \"source\".\"type_1\", \"source\".\"type_2\" ORDER BY \"count\" DESC, \"source\".\"generation\" ASC, \"source\".\"type_1\" ASC, \"source\".\"type_2\" ASC",
+        "params": null
+      },
+      "dataset": true,
+      "model": true,
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "semantic_type": "type/Category",
+            "name": "generation",
+            "field_ref": [
+              "field",
+              "generation",
+              {
+                "base-type": "type/BigInteger"
+              }
+            ],
+            "effective_type": "type/BigInteger",
+            "id": 1503,
+            "visibility_type": "normal",
+            "display_name": "Generation",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 8,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 2.1045160333142734,
+                  "q3": 5.716989271844637,
+                  "max": 8,
+                  "sd": 2.234937326790694,
+                  "avg": 4.034046692607004
+                }
+              }
+            },
+            "base_type": "type/BigInteger"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_1",
+            "field_ref": [
+              "field",
+              "type_1",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1475,
+            "visibility_type": "normal",
+            "display_name": "Type 1",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 18,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 5.281128404669261
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "semantic_type": "type/Category",
+            "name": "type_2",
+            "field_ref": [
+              "field",
+              "type_2",
+              {
+                "base-type": "type/Text"
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 1499,
+            "visibility_type": "normal",
+            "display_name": "Type 2",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 19,
+                "nil%": 0.4727626459143969
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 2.9474708171206228
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 6,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1,
+                  "q1": 1,
+                  "q3": 2.2964690541943424,
+                  "max": 10,
+                  "sd": 1.2898977338673652,
+                  "avg": 1.8076923076923077
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/two-rings.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/two-rings.json
new file mode 100644
index 0000000000000000000000000000000000000000..c11adf12c4d6db34f83fcd748754c4bbfbb21c29
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/two-rings.json
@@ -0,0 +1,521 @@
+[
+  {
+    "card": {
+      "cache_invalidated_at": null,
+      "description": null,
+      "archived": false,
+      "view_count": 63,
+      "collection_position": null,
+      "source_card_id": null,
+      "table_id": 2,
+      "can_run_adhoc_query": true,
+      "result_metadata": [
+        {
+          "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+          "semantic_type": "type/Category",
+          "coercion_strategy": null,
+          "name": "CATEGORY",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            58,
+            {
+              "base-type": "type/Text",
+              "source-field": 40
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 58,
+          "visibility_type": "normal",
+          "display_name": "Product → Category",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 4,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 6.375
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "semantic_type": "type/CreationTimestamp",
+          "coercion_strategy": null,
+          "unit": "day-of-week",
+          "name": "CREATED_AT",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "day-of-week"
+            }
+          ],
+          "effective_type": "type/Integer",
+          "id": 41,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/Integer"
+        },
+        {
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "field_ref": ["aggregation", 0],
+          "base_type": "type/BigInteger",
+          "effective_type": "type/BigInteger",
+          "name": "count",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 27,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 537,
+                "q1": 622,
+                "q3": 717.5,
+                "max": 763,
+                "sd": 67.4245806369908,
+                "avg": 670
+              }
+            }
+          }
+        }
+      ],
+      "creator": {
+        "email": "emmad@metabase.com",
+        "first_name": "Emmad",
+        "last_login": "2024-09-13T17:05:26.388255Z",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Usmani",
+        "date_joined": "2023-11-21T21:25:41.062104Z",
+        "common_name": "Emmad Usmani"
+      },
+      "initially_published_at": null,
+      "can_write": true,
+      "trashed_from_collection_id": null,
+      "database_id": 1,
+      "enable_embedding": false,
+      "collection_id": 32,
+      "query_type": "query",
+      "name": "Sunburst - count of orders by product category, created at day of week",
+      "last_query_start": "2024-09-13T17:05:37.409414Z",
+      "dashboard_count": 1,
+      "last_used_at": "2024-09-13T17:05:37.527665Z",
+      "type": "question",
+      "average_query_time": 334.13698630136986,
+      "creator_id": 1,
+      "can_restore": false,
+      "moderation_reviews": [],
+      "updated_at": "2024-09-13T17:08:01.767581Z",
+      "made_public_by_id": null,
+      "embedding_params": null,
+      "cache_ttl": null,
+      "dataset_query": {
+        "database": 1,
+        "type": "query",
+        "query": {
+          "source-table": 2,
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              58,
+              {
+                "base-type": "type/Text",
+                "source-field": 40
+              }
+            ],
+            [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "day-of-week"
+              }
+            ]
+          ]
+        }
+      },
+      "id": 428,
+      "parameter_mappings": [],
+      "display": "pie",
+      "archived_directly": false,
+      "entity_id": "NST44N53kq64YzhQhPZO5",
+      "collection_preview": true,
+      "last-edit-info": {
+        "timestamp": "2024-09-13T17:08:02.559Z",
+        "id": 1,
+        "first_name": "Emmad",
+        "last_name": "Usmani",
+        "email": "emmad@metabase.com"
+      },
+      "visualization_settings": {
+        "pie.dimension": ["CREATED_AT", "CATEGORY"],
+        "pie.metric": "count",
+        "pie.rows": [
+          {
+            "key": "2",
+            "name": "Mon",
+            "originalName": "Monday",
+            "color": "#227FD2",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "3",
+            "name": "Tue",
+            "originalName": "Tuesday",
+            "color": "#689636",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "4",
+            "name": "Wed",
+            "originalName": "Wednesday",
+            "color": "#8A5EB0",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "5",
+            "name": "Thur",
+            "originalName": "Thursday",
+            "color": "#F7C41F",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "key": "6",
+            "name": "Fri",
+            "originalName": "Friday",
+            "color": "#69C8C8",
+            "defaultColor": false,
+            "enabled": true,
+            "hidden": false,
+            "isOther": false
+          },
+          {
+            "isOther": false,
+            "hidden": false,
+            "enabled": false,
+            "defaultColor": true,
+            "color": "#7172AD",
+            "originalName": "Saturday",
+            "name": "Saturday",
+            "key": "7"
+          },
+          {
+            "isOther": false,
+            "hidden": false,
+            "enabled": false,
+            "defaultColor": true,
+            "color": "#88BF4D",
+            "originalName": "Sunday",
+            "name": "Sunday",
+            "key": "1"
+          }
+        ],
+        "pie.sort_rows": false,
+        "column_settings": {
+          "[\"name\",\"CREATED_AT\"]": {
+            "date_abbreviate": false
+          }
+        }
+      },
+      "collection": {
+        "authority_level": null,
+        "description": null,
+        "archived": false,
+        "trashed_from_location": null,
+        "slug": "sunburst",
+        "archive_operation_id": null,
+        "name": "Sunburst",
+        "personal_owner_id": null,
+        "type": null,
+        "is_sample": false,
+        "id": 32,
+        "archived_directly": null,
+        "entity_id": "yivDsLk9XUmdCWr0lt9GJ",
+        "location": "/5/23/",
+        "namespace": null,
+        "is_personal": false,
+        "created_at": "2024-08-25T18:16:32.499168Z"
+      },
+      "metabase_version": "v0.1.33-SNAPSHOT (7222517)",
+      "parameters": [],
+      "created_at": "2024-08-30T03:45:00.550597Z",
+      "parameter_usage_count": 0,
+      "public_uuid": null,
+      "can_delete": false
+    },
+    "data": {
+      "rows": [
+        ["Doohickey", 1, 566],
+        ["Doohickey", 2, 537],
+        ["Doohickey", 3, 553],
+        ["Doohickey", 4, 563],
+        ["Doohickey", 5, 569],
+        ["Doohickey", 6, 582],
+        ["Doohickey", 7, 606],
+        ["Gadget", 1, 672],
+        ["Gadget", 2, 693],
+        ["Gadget", 3, 763],
+        ["Gadget", 4, 638],
+        ["Gadget", 5, 724],
+        ["Gadget", 6, 709],
+        ["Gadget", 7, 740],
+        ["Gizmo", 1, 717],
+        ["Gizmo", 2, 696],
+        ["Gizmo", 3, 680],
+        ["Gizmo", 4, 704],
+        ["Gizmo", 5, 685],
+        ["Gizmo", 6, 662],
+        ["Gizmo", 7, 640],
+        ["Widget", 1, 716],
+        ["Widget", 2, 738],
+        ["Widget", 3, 699],
+        ["Widget", 4, 718],
+        ["Widget", 5, 720],
+        ["Widget", 6, 709],
+        ["Widget", 7, 761]
+      ],
+      "cols": [
+        {
+          "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+          "database_type": "CHARACTER VARYING",
+          "semantic_type": "type/Category",
+          "table_id": 1,
+          "coercion_strategy": null,
+          "name": "CATEGORY",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 40,
+          "field_ref": [
+            "field",
+            58,
+            {
+              "base-type": "type/Text",
+              "source-field": 40
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 58,
+          "position": 3,
+          "visibility_type": "normal",
+          "display_name": "Product → Category",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 4,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 0,
+                "average-length": 6.375
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PRODUCTS__via__PRODUCT_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "INTEGER",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 2,
+          "coercion_strategy": null,
+          "unit": "day-of-week",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "day-of-week"
+            }
+          ],
+          "effective_type": "type/Integer",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/Integer"
+        },
+        {
+          "database_type": "BIGINT",
+          "semantic_type": "type/Quantity",
+          "name": "count",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "effective_type": "type/BigInteger",
+          "aggregation_index": 0,
+          "display_name": "Count",
+          "base_type": "type/BigInteger"
+        }
+      ],
+      "native_form": {
+        "query": "SELECT \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\" AS \"PRODUCTS__via__PRODUCT_ID__CATEGORY\", COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) AS \"CREATED_AT\", COUNT(*) AS \"count\" FROM \"PUBLIC\".\"ORDERS\" LEFT JOIN \"PUBLIC\".\"PRODUCTS\" AS \"PRODUCTS__via__PRODUCT_ID\" ON \"PUBLIC\".\"ORDERS\".\"PRODUCT_ID\" = \"PRODUCTS__via__PRODUCT_ID\".\"ID\" GROUP BY \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\", COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) ORDER BY \"PRODUCTS__via__PRODUCT_ID\".\"CATEGORY\" ASC, COALESCE(NULLIF((extract(iso_day_of_week from \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") + 1) % 7, 0), 7) ASC",
+        "params": null
+      },
+      "format-rows?": true,
+      "results_timezone": "America/Los_Angeles",
+      "results_metadata": {
+        "columns": [
+          {
+            "description": "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget",
+            "semantic_type": "type/Category",
+            "coercion_strategy": null,
+            "name": "CATEGORY",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              58,
+              {
+                "base-type": "type/Text",
+                "source-field": 40
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 58,
+            "visibility_type": "normal",
+            "display_name": "Product → Category",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 4,
+                "nil%": 0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0,
+                  "percent-url": 0,
+                  "percent-email": 0,
+                  "percent-state": 0,
+                  "average-length": 6.375
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "description": "The date and time an order was submitted.",
+            "semantic_type": "type/CreationTimestamp",
+            "coercion_strategy": null,
+            "unit": "day-of-week",
+            "name": "CREATED_AT",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "day-of-week"
+              }
+            ],
+            "effective_type": "type/Integer",
+            "id": 41,
+            "visibility_type": "normal",
+            "display_name": "Created At",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 10001,
+                "nil%": 0
+              },
+              "type": {
+                "type/DateTime": {
+                  "earliest": "2022-04-30T18:56:13.352Z",
+                  "latest": "2026-04-19T14:07:15.657Z"
+                }
+              }
+            },
+            "base_type": "type/Integer"
+          },
+          {
+            "display_name": "Count",
+            "semantic_type": "type/Quantity",
+            "field_ref": ["aggregation", 0],
+            "base_type": "type/BigInteger",
+            "effective_type": "type/BigInteger",
+            "name": "count",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 27,
+                "nil%": 0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 537,
+                  "q1": 622,
+                  "q3": 717.5,
+                  "max": 763,
+                  "sd": 67.4245806369908,
+                  "avg": 670
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": null
+    }
+  }
+]
diff --git a/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx b/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
index 2154c1b651d756f55f555b463609083a144e7d4a..a6ba65c316c539b44deef5f30184743f9951f179 100644
--- a/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
+++ b/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
@@ -13,6 +13,7 @@ export interface EChartsTooltipRow {
   isFocused?: boolean;
   isSecondary?: boolean;
   values: React.ReactNode[];
+  key?: string;
 }
 
 export interface EChartsTooltipFooter {
@@ -66,11 +67,11 @@ export const EChartsTooltip = ({
         })}
       >
         <tbody>
-          {paddedRows.map((row, index) => {
+          {paddedRows.map(row => {
             return !row.isSecondary ? (
-              <TooltipRow key={index} {...row} />
+              <TooltipRow {...row} />
             ) : (
-              <SecondaryRow key={index} {...row} />
+              <SecondaryRow {...row} />
             );
           })}
         </tbody>
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
index 9f2edb02974d32f9ac397b418df962d4f7c35707..69811949a1b9b689e42f6729f7f2878f91490275 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
@@ -4,6 +4,7 @@ import type { PillSize } from "metabase/core/components/ColorPill";
 import ColorSelector from "metabase/core/components/ColorSelector";
 import CS from "metabase/css/core/index.css";
 import { getAccentColors } from "metabase/lib/colors/groups";
+import type { AccentColorOptions } from "metabase/lib/colors/types";
 
 interface ChartSettingColorPickerProps {
   className?: string;
@@ -11,6 +12,7 @@ interface ChartSettingColorPickerProps {
   title?: string;
   pillSize?: PillSize;
   onChange?: (newValue: string) => void;
+  accentColorOptions?: AccentColorOptions;
 }
 
 export const ChartSettingColorPicker = ({
@@ -19,12 +21,13 @@ export const ChartSettingColorPicker = ({
   title,
   pillSize,
   onChange,
+  accentColorOptions = { main: true, light: true, dark: true, harmony: false },
 }: ChartSettingColorPickerProps) => {
   return (
     <div className={cx(CS.flex, CS.alignCenter, CS.mb1, className)}>
       <ColorSelector
         value={value}
-        colors={getAccentColors()}
+        colors={getAccentColors(accentColorOptions)}
         onChange={onChange}
         pillSize={pillSize}
       />
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
index fecb0215dcfc58474fd235930a1a7cc056c042b9..6869dd3b4d018956fbcc0cdeb1e5de421e493836 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
@@ -3,11 +3,12 @@ import { useCallback } from "react";
 
 import type { DragEndEvent } from "metabase/core/components/Sortable";
 import { Sortable, SortableList } from "metabase/core/components/Sortable";
+import type { AccentColorOptions } from "metabase/lib/colors/types";
 import type { IconProps } from "metabase/ui";
 
 import { ColumnItem } from "../ColumnItem";
 
-interface SortableItem {
+export interface SortableItem {
   enabled: boolean;
   color?: string;
   icon?: IconProps["name"];
@@ -29,6 +30,8 @@ interface ChartSettingOrderedItemsProps<T extends SortableItem>
   items: T[];
   getId: (item: T) => string | number;
   removeIcon?: IconProps["name"];
+  accentColorOptions?: AccentColorOptions;
+  getItemColor?: (item: SortableItem) => string | undefined;
 }
 
 export function ChartSettingOrderedItems<T extends SortableItem>({
@@ -43,6 +46,8 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
   onColorChange,
   getId,
   removeIcon,
+  accentColorOptions,
+  getItemColor = item => item.color,
 }: ChartSettingOrderedItemsProps<T>) {
   const isDragDisabled = items.length < 1;
   const pointerSensor = useSensor(PointerSensor, {
@@ -78,11 +83,12 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
                 ? (color: string) => onColorChange(item, color)
                 : undefined
             }
-            color={item.color}
+            color={getItemColor(item)}
             draggable={!isDragDisabled}
             icon={item.icon}
             removeIcon={removeIcon}
             role="listitem"
+            accentColorOptions={accentColorOptions}
           />
         </Sortable>
       ) : null,
@@ -96,6 +102,8 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
       onAdd,
       onEnable,
       onColorChange,
+      accentColorOptions,
+      getItemColor,
     ],
   );
 
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
index 9e8e0357d4296a77bf7b75c5f7a002413d5063a3..863d2f5e3024301bea12429d35e6c8d2268d7209 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
@@ -5,18 +5,22 @@ import { t } from "ttag";
 import _ from "underscore";
 
 import type { DragEndEvent } from "metabase/core/components/Sortable";
+import type { AccentColorOptions } from "metabase/lib/colors/types";
 import { NULL_DISPLAY_VALUE } from "metabase/lib/constants";
 import { isEmpty } from "metabase/lib/validate";
 import { Button, Select } from "metabase/ui";
 import type { Series } from "metabase-types/api";
 
-import { ChartSettingOrderedItems } from "./ChartSettingOrderedItems";
+import {
+  ChartSettingOrderedItems,
+  type SortableItem as SortableChartSettingOrderedItem,
+} from "./ChartSettingOrderedItems";
 import {
   ChartSettingMessage,
   ChartSettingOrderedSimpleRoot,
 } from "./ChartSettingOrderedSimple.styled";
 
-interface SortableItem {
+export interface SortableItem {
   key: string;
   enabled: boolean;
   name: string;
@@ -27,8 +31,6 @@ interface SortableItem {
 interface ChartSettingSeriesOrderProps {
   onChange: (rows: SortableItem[]) => void;
   value: SortableItem[];
-  addButtonLabel: string;
-  searchPickerPlaceholder: string;
   onShowWidget: (
     widget: { props: { seriesKey: string } },
     ref: HTMLElement | undefined,
@@ -37,6 +39,10 @@ interface ChartSettingSeriesOrderProps {
   hasEditSettings: boolean;
   onChangeSeriesColor: (seriesKey: string, color: string) => void;
   onSortEnd: (newItems: SortableItem[]) => void;
+  accentColorOptions?: AccentColorOptions;
+  getItemColor?: (item: SortableChartSettingOrderedItem) => string | undefined;
+  addButtonLabel?: string;
+  searchPickerPlaceholder?: string;
 }
 
 export const ChartSettingSeriesOrder = ({
@@ -48,6 +54,8 @@ export const ChartSettingSeriesOrder = ({
   hasEditSettings = true,
   onChangeSeriesColor,
   onSortEnd,
+  getItemColor,
+  accentColorOptions,
 }: ChartSettingSeriesOrderProps) => {
   const [isSeriesPickerVisible, setSeriesPickerVisible] = useState(false);
 
@@ -137,6 +145,8 @@ export const ChartSettingSeriesOrder = ({
             onColorChange={handleColorChange}
             getId={getId}
             removeIcon="close"
+            accentColorOptions={accentColorOptions}
+            getItemColor={getItemColor}
           />
           {canAddSeries && !isSeriesPickerVisible && (
             <Button
diff --git a/frontend/src/metabase/visualizations/components/settings/ColumnItem/ColumnItem.tsx b/frontend/src/metabase/visualizations/components/settings/ColumnItem/ColumnItem.tsx
index 515a9f2601ad9d60e3045c4f8fd2c935132724b3..f1fd4adfc8ab029866540de9db969f56eef1eb9d 100644
--- a/frontend/src/metabase/visualizations/components/settings/ColumnItem/ColumnItem.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ColumnItem/ColumnItem.tsx
@@ -1,3 +1,4 @@
+import type { AccentColorOptions } from "metabase/lib/colors/types";
 import type { IconProps } from "metabase/ui";
 import { Icon } from "metabase/ui";
 
@@ -25,6 +26,7 @@ interface ColumnItemProps {
   onEdit?: (target: HTMLElement) => void;
   onEnable?: (target: HTMLElement) => void;
   onColorChange?: (newColor: string) => void;
+  accentColorOptions?: AccentColorOptions;
 }
 
 const BaseColumnItem = ({
@@ -41,6 +43,7 @@ const BaseColumnItem = ({
   onEdit,
   onEnable,
   onColorChange,
+  accentColorOptions,
 }: ColumnItemProps) => {
   return (
     <ColumnItemRoot
@@ -59,6 +62,7 @@ const BaseColumnItem = ({
             value={color}
             onChange={onColorChange}
             pillSize="small"
+            accentColorOptions={accentColorOptions}
           />
         )}
         <ColumnItemContent>
diff --git a/frontend/src/metabase/visualizations/echarts/pie/constants.ts b/frontend/src/metabase/visualizations/echarts/pie/constants.ts
index 9317c7793b9388015ce987b77a44c5c0ad59d14e..ff7065164ca967036aee32e67772e406ce250e10 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/constants.ts
+++ b/frontend/src/metabase/visualizations/echarts/pie/constants.ts
@@ -1,5 +1,7 @@
 import { t } from "ttag";
 
+import { NULL_CHAR } from "../cartesian/constants/dataset";
+
 export const DIMENSIONS = {
   maxSideLength: 550,
   padding: {
@@ -8,16 +10,20 @@ export const DIMENSIONS = {
   },
   slice: {
     innerRadiusRatio: 3 / 5,
+    twoRingInnerRadiusRatio: 2 / 5,
+    threeRingInnerRadiusRatio: 1 / 4,
     borderProportion: 360, // 1 degree
+    twoRingBorderWidth: 1,
+    threeRingBorderWidth: 0.3,
     maxFontSize: 20,
     minFontSize: 14,
+    multiRingFontSize: 12,
     label: {
       fontWeight: 700,
       padding: 4,
     },
   },
   total: {
-    minWidth: 120,
     valueFontSize: 22,
     labelFontSize: 14,
     fontWeight: 700,
@@ -28,6 +34,10 @@ export const SLICE_THRESHOLD = 0.025; // approx 1 degree in percentage
 
 export const OTHER_SLICE_MIN_PERCENTAGE = 0.005;
 
-export const OTHER_SLICE_KEY = "___OTHER___";
+export const OTHER_SLICE_KEY = `${NULL_CHAR}___OTHER___`;
+
+export const OTHER_SLICE_NAME = t`Other`;
 
 export const TOTAL_TEXT = t`Total`.toUpperCase();
+
+export const OPTION_NAME_SEPERATOR = `–${NULL_CHAR}–`;
diff --git a/frontend/src/metabase/visualizations/echarts/pie/format.ts b/frontend/src/metabase/visualizations/echarts/pie/format.ts
index c959541d274f4463b28de9f9dbf470f047a13142..57a05c6142edebf83096d647fde9bf5602bc1960 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/format.ts
+++ b/frontend/src/metabase/visualizations/echarts/pie/format.ts
@@ -1,16 +1,37 @@
+import { NULL_DISPLAY_VALUE } from "metabase/lib/constants";
 import { computeMaxDecimalsForValues } from "metabase/visualizations/lib/utils";
 import type {
   ComputedVisualizationSettings,
+  Formatter,
+  RemappingHydratedDatasetColumn,
   RenderingContext,
 } from "metabase/visualizations/types";
+import type { RowValue } from "metabase-types/api";
 
-import type { PieChartModel } from "./model/types";
+import type { PieChartModel, SliceTree, SliceTreeNode } from "./model/types";
+import { getArrayFromMapValues } from "./util";
 
 export interface PieChartFormatters {
   formatMetric: (value: unknown, isCompact?: boolean) => string;
   formatPercent: (value: unknown, location: "legend" | "chart") => string;
 }
 
+function getAllSlicePercentages(sliceTree: SliceTree) {
+  const percentages: number[] = [];
+
+  function getPercentages(node: SliceTreeNode) {
+    percentages.push(node.normalizedPercentage);
+    if (node.isOther) {
+      return;
+    }
+
+    node.children.forEach(c => getPercentages(c));
+  }
+  sliceTree.forEach(node => getPercentages(node));
+
+  return percentages;
+}
+
 export function getPieChartFormatters(
   chartModel: PieChartModel,
   settings: ComputedVisualizationSettings,
@@ -34,13 +55,17 @@ export function getPieChartFormatters(
   const formatPercent = (value: unknown, location: "legend" | "chart") => {
     let decimals = settings["pie.decimal_places"];
     if (decimals == null) {
-      decimals = computeMaxDecimalsForValues(
-        chartModel.slices.map(s => s.data.normalizedPercentage),
-        {
-          style: "percent",
-          maximumSignificantDigits: location === "legend" ? 3 : 2,
-        },
-      );
+      const percentages =
+        location === "chart"
+          ? getAllSlicePercentages(chartModel.sliceTree)
+          : getArrayFromMapValues(chartModel.sliceTree).map(
+              s => s.normalizedPercentage,
+            );
+
+      decimals = computeMaxDecimalsForValues(percentages, {
+        style: "percent",
+        maximumSignificantDigits: location === "legend" ? 3 : 2,
+      });
     }
 
     return renderingContext.formatValue(value, {
@@ -53,3 +78,24 @@ export function getPieChartFormatters(
 
   return { formatMetric, formatPercent };
 }
+
+export function getDimensionFormatter(
+  settings: ComputedVisualizationSettings,
+  dimensionColumn: RemappingHydratedDatasetColumn,
+  formatter: Formatter,
+) {
+  const getColumnSettings = settings["column"];
+  if (!getColumnSettings) {
+    throw Error("`settings.column` is undefined");
+  }
+
+  const dimensionColSettings = getColumnSettings(dimensionColumn);
+
+  return (value: RowValue) => {
+    if (value == null) {
+      return NULL_DISPLAY_VALUE;
+    }
+
+    return formatter(value, dimensionColSettings);
+  };
+}
diff --git a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts
index 2a852bf71ca15de4a87b47dc60700a00a5509b8c..e7947bd4f5b2d0deeddf50e1c373438c518b6c97 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts
+++ b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts
@@ -1,28 +1,40 @@
 import { pie } from "d3";
-import { t } from "ttag";
 import _ from "underscore";
 
 import { findWithIndex } from "metabase/lib/arrays";
 import { checkNotNull } from "metabase/lib/types";
+import type { ColumnDescriptor } from "metabase/visualizations/lib/graph/columns";
 import { getNumberOr } from "metabase/visualizations/lib/settings/row-values";
-import { pieNegativesWarning } from "metabase/visualizations/lib/warnings";
+import {
+  pieNegativesWarning,
+  unaggregatedDataWarningPie,
+} from "metabase/visualizations/lib/warnings";
 import {
   getAggregatedRows,
   getKeyFromDimensionValue,
+  getPieDimensions,
 } from "metabase/visualizations/shared/settings/pie";
 import type {
   ComputedVisualizationSettings,
   RenderingContext,
 } from "metabase/visualizations/types";
-import type { RawSeries } from "metabase-types/api";
+import type { RawSeries, RowValue } from "metabase-types/api";
 
 import type { ShowWarning } from "../../types";
-import { OTHER_SLICE_KEY, OTHER_SLICE_MIN_PERCENTAGE } from "../constants";
+import {
+  OTHER_SLICE_KEY,
+  OTHER_SLICE_MIN_PERCENTAGE,
+  OTHER_SLICE_NAME,
+} from "../constants";
+import { getDimensionFormatter } from "../format";
+import { getArrayFromMapValues } from "../util";
+import { getColorForRing } from "../util/colors";
 
 import type {
   PieChartModel,
   PieColumnDescriptors,
-  PieSliceData,
+  SliceTree,
+  SliceTreeNode,
 } from "./types";
 
 export function getPieColumns(
@@ -35,19 +47,18 @@ export function getPieColumns(
     },
   ] = rawSeries;
 
-  const dimension = findWithIndex(
-    cols,
-    c => c.name === settings["pie.dimension"],
-  );
   const metric = findWithIndex(cols, c => c.name === settings["pie.metric"]);
 
+  const dimensionColNames = getPieDimensions(settings);
+  const dimension = findWithIndex(cols, c => c.name === dimensionColNames[0]);
+
   if (!dimension.item || !metric.item) {
     throw new Error(
       `Could not find columns based on "pie.dimension" (${settings["pie.dimension"]}) and "pie.metric" (${settings["pie.metric"]}) settings.`,
     );
   }
 
-  return {
+  const colDescs: PieColumnDescriptors = {
     dimensionDesc: {
       index: dimension.index,
       column: dimension.item,
@@ -57,6 +68,177 @@ export function getPieColumns(
       column: metric.item,
     },
   };
+
+  if (dimensionColNames.length > 1) {
+    const middleDimension = findWithIndex(
+      cols,
+      c => c.name === dimensionColNames[1],
+    );
+    if (!middleDimension.item) {
+      throw new Error(
+        `Could not find column based on "pie.dimension" (${settings["pie.dimension"]})`,
+      );
+    }
+
+    colDescs.middleDimensionDesc = {
+      index: middleDimension.index,
+      column: middleDimension.item,
+    };
+  }
+
+  if (dimensionColNames.length > 2) {
+    const outerDimension = findWithIndex(
+      cols,
+      c => c.name === dimensionColNames[2],
+    );
+    if (!outerDimension.item) {
+      throw new Error(
+        `Could not find column based on "pie.dimension" (${settings["pie.dimension"]})`,
+      );
+    }
+
+    colDescs.outerDimensionDesc = {
+      index: outerDimension.index,
+      column: outerDimension.item,
+    };
+  }
+
+  return colDescs;
+}
+
+function createOrUpdateNode(
+  metricValue: number,
+  dimensionValue: RowValue,
+  colDesc: ColumnDescriptor,
+  formatter: (rowValue: RowValue) => string,
+  parentNode: SliceTreeNode,
+  color: string,
+  rowIndex: number,
+  total: number,
+  showWarning?: ShowWarning,
+) {
+  const dimensionKey = getKeyFromDimensionValue(dimensionValue);
+  let dimensionNode = parentNode.children.get(String(dimensionKey));
+
+  if (dimensionNode == null) {
+    // If there is no node for this dimension value in the tree
+    // create it.
+    dimensionNode = {
+      key: dimensionKey,
+      name: formatter(dimensionValue),
+      value: metricValue,
+      displayValue: metricValue,
+      normalizedPercentage: metricValue / total,
+      color,
+      visible: true,
+      column: colDesc.column,
+      rowIndex,
+      isOther: false,
+      children: new Map(),
+      startAngle: 0,
+      endAngle: 0,
+    };
+    parentNode.children.set(dimensionKey, dimensionNode);
+  } else {
+    // If the node already exists, add the metric value from the current row
+    // to it.
+    dimensionNode.value += metricValue;
+    dimensionNode.displayValue += metricValue;
+    dimensionNode.normalizedPercentage = dimensionNode.value / total;
+
+    showWarning?.(unaggregatedDataWarningPie(colDesc.column).text);
+  }
+
+  return dimensionNode;
+}
+
+function markOtherNodes(
+  node: SliceTreeNode,
+  parent: SliceTreeNode,
+  settings: ComputedVisualizationSettings,
+) {
+  node.isOther =
+    node.displayValue / parent.displayValue <
+    (settings["pie.slice_threshold"] ?? 0) / 100;
+
+  node.children.forEach(child => markOtherNodes(child, node, settings));
+}
+
+function aggregateSlices(
+  node: SliceTreeNode,
+  total: number,
+  renderingContext: RenderingContext,
+) {
+  const children = getArrayFromMapValues(node.children);
+  const others = children.filter(s => s.isOther);
+  const otherTotal = others.reduce((currTotal, o) => currTotal + o.value, 0);
+
+  if (others.length > 1 && otherTotal > 0) {
+    const otherSliceChildren: SliceTree = new Map();
+    others.forEach(o => {
+      otherSliceChildren.set(String(o.key), { ...o, color: "" });
+      node.children.delete(String(o.key));
+    });
+
+    node.children.set(OTHER_SLICE_KEY, {
+      key: OTHER_SLICE_KEY,
+      name: OTHER_SLICE_NAME,
+      value: otherTotal,
+      displayValue: otherTotal,
+      normalizedPercentage: otherTotal / total,
+      color: renderingContext.getColor("text-light"),
+      children: otherSliceChildren,
+      visible: true,
+      isOther: true,
+      startAngle: 0,
+      endAngle: 0,
+    });
+  } else if (others.length === 1) {
+    others[0].isOther = false;
+  }
+
+  children.forEach(child => aggregateSlices(child, total, renderingContext));
+}
+
+function computeSliceAngles(
+  slices: SliceTreeNode[],
+  startAngle?: number,
+  endAngle?: number,
+) {
+  const d3Pie = pie<SliceTreeNode>()
+    .sort(null)
+    // 1 degree in radians
+    .padAngle((Math.PI / 180) * 1)
+    .startAngle(startAngle ?? 0)
+    .endAngle(endAngle ?? 2 * Math.PI)
+    .value(s => s.value);
+
+  const d3Slices = d3Pie(slices, { startAngle, endAngle });
+  d3Slices.forEach((d3Slice, index) => {
+    slices[index].startAngle = d3Slice.startAngle;
+    slices[index].endAngle = d3Slice.endAngle;
+  });
+
+  slices.forEach(slice =>
+    computeSliceAngles(
+      getArrayFromMapValues(slice.children),
+      slice.startAngle,
+      slice.endAngle,
+    ),
+  );
+}
+
+function countNumRings(node: SliceTreeNode, numRings = 0): number {
+  if (node.isOther) {
+    return numRings + 1;
+  }
+
+  return Math.max(
+    ...getArrayFromMapValues(node.children).map(node =>
+      countNumRings(node, numRings + 1),
+    ),
+    numRings + 1,
+  );
 }
 
 export function getPieChartModel(
@@ -87,7 +269,7 @@ export function getPieChartModel(
     dataRows,
     colDescs.dimensionDesc.index,
     colDescs.metricDesc.index,
-    showWarning,
+    colDescs.middleDimensionDesc == null ? showWarning : undefined,
     colDescs.dimensionDesc.column,
   );
 
@@ -123,15 +305,8 @@ export function getPieChartModel(
       : !hiddenSlices.includes(row.key),
   );
 
-  // We allow negative values if every single metric value is negative or 0
-  // (`isNonPositive` = true). If the values are mixed between positives and
-  // negatives, we'll simply ignore the negatives in all calculations.
-  const isNonPositive =
-    visiblePieRows.every(row => row.value <= 0) &&
-    !visiblePieRows.every(row => row.value === 0);
-
   const total = visiblePieRows.reduce((currTotal, { value }) => {
-    if (!isNonPositive && value < 0) {
+    if (value < 0) {
       showWarning?.(pieNegativesWarning().text);
       return currTotal;
     }
@@ -139,26 +314,38 @@ export function getPieChartModel(
     return currTotal + value;
   }, 0);
 
-  const [slices, others] = _.chain(pieRowsWithValues)
-    .map(({ value, color, key, name, isOther }): PieSliceData => {
+  // Create sliceTree, fill out the innermost slice ring
+  const sliceTree: SliceTree = new Map();
+  const [sliceTreeNodes, others] = _.chain(pieRowsWithValues)
+    .map(({ value, color, key, name, isOther }, index) => {
       const visible = isOther
         ? !hiddenSlices.includes(OTHER_SLICE_KEY)
         : !hiddenSlices.includes(key);
+
       return {
         key,
         name,
-        value: isNonPositive ? -1 * value : value,
+        value,
         displayValue: value,
         normalizedPercentage: visible ? value / total : 0, // slice percentage values are normalized to 0-1 scale
-        rowIndex: rowIndiciesByKey.get(key),
-        color,
+        color: getColorForRing(
+          color,
+          "inner",
+          colDescs.middleDimensionDesc != null,
+          renderingContext,
+        ),
         visible,
+        children: new Map(),
+        column: colDescs.dimensionDesc.column,
+        rowIndex: checkNotNull(rowIndiciesByKey.get(key)),
+        legendHoverIndex: index,
         isOther,
-        noHover: false,
         includeInLegend: true,
+        startAngle: 0, // placeholders
+        endAngle: 0,
       };
     })
-    .filter(slice => isNonPositive || slice.value > 0)
+    .filter(slice => slice.value > 0)
     .partition(slice => slice != null && !slice.isOther)
     .value();
 
@@ -166,65 +353,166 @@ export function getPieChartModel(
   // group into it
   if (others.length === 1) {
     const singleOtherSlice = others.pop();
-    slices.push(checkNotNull(singleOtherSlice));
+    sliceTreeNodes.push(checkNotNull(singleOtherSlice));
+  }
+
+  sliceTreeNodes.forEach(node => {
+    // Map key needs to be string, because we use it for lookup with values from
+    // echarts, and echarts casts numbers to strings
+    sliceTree.set(String(node.key), node);
+  });
+
+  // Iterate through non-aggregated rows from query result to build layers for
+  // the middle and outer ring slices.
+  if (colDescs.middleDimensionDesc != null) {
+    const formatMiddleDimensionValue = getDimensionFormatter(
+      settings,
+      colDescs.middleDimensionDesc.column,
+      renderingContext.formatValue,
+    );
+
+    const formatOuterDimensionValue =
+      colDescs.outerDimensionDesc?.column != null
+        ? getDimensionFormatter(
+            settings,
+            colDescs.outerDimensionDesc.column,
+            renderingContext.formatValue,
+          )
+        : undefined;
+
+    dataRows.forEach((row, index) => {
+      // Needed to tell typescript it's defined
+      if (colDescs.middleDimensionDesc == null) {
+        throw new Error(`Missing middleDimensionDesc`);
+      }
+
+      const dimensionNode = sliceTree.get(
+        getKeyFromDimensionValue(row[colDescs.dimensionDesc.index]),
+      );
+      const dimensionIsOther = dimensionNode == null;
+      if (dimensionIsOther) {
+        return;
+      }
+      const metricValue = getNumberOr(row[colDescs.metricDesc.index], 0);
+      if (metricValue < 0) {
+        return;
+      }
+
+      // Create or update node for middle dimension
+      const middleDimensionNode = createOrUpdateNode(
+        metricValue,
+        row[colDescs.middleDimensionDesc.index],
+        colDescs.middleDimensionDesc,
+        formatMiddleDimensionValue,
+        dimensionNode,
+        getColorForRing(dimensionNode.color, "middle", true, renderingContext),
+        index,
+        total,
+        colDescs.outerDimensionDesc == null ? showWarning : undefined,
+      );
+
+      if (
+        colDescs.outerDimensionDesc == null ||
+        formatOuterDimensionValue == null
+      ) {
+        return;
+      }
+
+      // Create or update node for outer dimension
+      createOrUpdateNode(
+        metricValue,
+        row[colDescs.outerDimensionDesc.index],
+        colDescs.outerDimensionDesc,
+        formatOuterDimensionValue,
+        middleDimensionNode,
+        getColorForRing(dimensionNode.color, "outer", true, renderingContext),
+        index,
+        total,
+        showWarning,
+      );
+    });
   }
 
+  sliceTree.forEach(node =>
+    node.children.forEach(child => markOtherNodes(child, node, settings)),
+  );
+
   // Only add "other" slice if there are slices below threshold with non-zero total
   const otherTotal = others.reduce((currTotal, o) => currTotal + o.value, 0);
   if (otherTotal > 0) {
+    const children: SliceTree = new Map();
+    others.forEach(node => {
+      children.set(String(node.key), {
+        ...node,
+        color: "",
+      });
+    });
     const visible = !hiddenSlices.includes(OTHER_SLICE_KEY);
-    slices.push({
+
+    sliceTree.set(OTHER_SLICE_KEY, {
       key: OTHER_SLICE_KEY,
-      name: t`Other`,
+      name: OTHER_SLICE_NAME,
       value: otherTotal,
       displayValue: otherTotal,
       normalizedPercentage: visible ? otherTotal / total : 0,
       color: renderingContext.getColor("text-light"),
+      column: colDescs.dimensionDesc.column,
       visible,
-      isOther: true,
-      noHover: false,
+      children,
+      legendHoverIndex: sliceTree.size,
       includeInLegend: true,
+      isOther: true,
+      startAngle: 0,
+      endAngle: 0,
     });
   }
 
-  slices.forEach(slice => {
-    // We increase the size of small slices, otherwise they will not be visible
-    // in echarts due to the border rendering over the tiny slice
-    if (
-      slice.visible &&
-      slice.normalizedPercentage < OTHER_SLICE_MIN_PERCENTAGE
-    ) {
+  // Aggregate slices in middle and outer ring into "other" slices
+  sliceTreeNodes.forEach(node =>
+    aggregateSlices(node, total, renderingContext),
+  );
+
+  // We increase the size of small slices, but only for the first ring, because
+  // if we do this for the outer rings, it can lead to overlapping slices.
+  sliceTree.forEach(slice => {
+    if (slice.normalizedPercentage < OTHER_SLICE_MIN_PERCENTAGE) {
       slice.value = total * OTHER_SLICE_MIN_PERCENTAGE;
     }
   });
 
+  // We need start and end angles for the label formatter, to determine if we
+  // should the percent label on the chart for a specific slice. To get these we
+  // need to use d3.
+  computeSliceAngles(getArrayFromMapValues(sliceTree));
+
   // If there are no non-zero slices, we'll display a single "other" slice
-  if (slices.length === 0) {
-    slices.push({
+  if (sliceTree.size === 0) {
+    sliceTree.set(OTHER_SLICE_KEY, {
       key: OTHER_SLICE_KEY,
-      name: t`Other`,
+      name: OTHER_SLICE_NAME,
       value: 1,
       displayValue: 0,
       normalizedPercentage: 0,
       color: renderingContext.getColor("text-light"),
       visible: true,
+      column: colDescs.dimensionDesc.column,
+      children: new Map(),
+      legendHoverIndex: 0,
       isOther: true,
       noHover: true,
       includeInLegend: false,
+      startAngle: 0,
+      endAngle: 2 * Math.PI,
     });
   }
 
-  // We need d3 slices for the label formatter, to determine if we should the
-  // percent label on the chart for a specific slice
-  const d3Pie = pie<PieSliceData>()
-    .sort(null)
-    // 1 degree in radians
-    .padAngle((Math.PI / 180) * 1)
-    .value(s => s.value);
+  const numRings = Math.max(
+    ...getArrayFromMapValues(sliceTree).map(node => countNumRings(node)),
+  );
 
   return {
-    slices: d3Pie(slices),
-    otherSlices: d3Pie(others),
+    sliceTree,
+    numRings,
     total,
     colDescs,
   };
diff --git a/frontend/src/metabase/visualizations/echarts/pie/model/types.ts b/frontend/src/metabase/visualizations/echarts/pie/model/types.ts
index bea9cfc6a6a906a1d73c7f5a43f64f0d288d68ab..a110c1f275dd36668fcb5ff43a72e2be86639967 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/model/types.ts
+++ b/frontend/src/metabase/visualizations/echarts/pie/model/types.ts
@@ -1,9 +1,8 @@
-import type { PieArcDatum } from "d3";
-
 import type { ColumnDescriptor } from "metabase/visualizations/lib/graph/columns";
+import type { RemappingHydratedDatasetColumn } from "metabase/visualizations/types";
 
 export interface PieRow {
-  key: string | number;
+  key: string;
   name: string;
   originalName: string;
   color: string;
@@ -16,27 +15,34 @@ export interface PieRow {
 export interface PieColumnDescriptors {
   metricDesc: ColumnDescriptor;
   dimensionDesc: ColumnDescriptor;
+  middleDimensionDesc?: ColumnDescriptor;
+  outerDimensionDesc?: ColumnDescriptor;
 }
 
-export interface PieSliceData {
-  key: string | number; // dimension value, used to lookup slices
+export type SliceTreeNode = {
+  key: string;
   name: string; // display name, already formatted
   value: number; // size of the slice used for rendering
   displayValue: number; // real metric value of the slice displayed in tooltip or total graphic
   normalizedPercentage: number;
   visible: boolean;
   color: string;
-  isOther: boolean;
-  noHover: boolean;
-  includeInLegend: boolean;
+  startAngle: number;
+  endAngle: number;
+  children: SliceTree;
+  column?: RemappingHydratedDatasetColumn;
   rowIndex?: number;
-}
+  legendHoverIndex?: number;
+  isOther?: boolean;
+  noHover?: boolean;
+  includeInLegend?: boolean;
+};
 
-export type PieSlice = PieArcDatum<PieSliceData>;
+export type SliceTree = Map<string, SliceTreeNode>;
 
 export interface PieChartModel {
-  slices: PieSlice[];
-  otherSlices: PieSlice[];
+  sliceTree: SliceTree;
   total: number;
+  numRings: number;
   colDescs: PieColumnDescriptors;
 }
diff --git a/frontend/src/metabase/visualizations/echarts/pie/option.ts b/frontend/src/metabase/visualizations/echarts/pie/option.ts
index 4a29b93df6b1658a30ce4f2c210d9c436f6a845d..a516e70cae505f0793a050811f1195432b0ff684 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/option.ts
+++ b/frontend/src/metabase/visualizations/echarts/pie/option.ts
@@ -1,29 +1,18 @@
 import Color from "color";
-import type { EChartsOption } from "echarts";
+import type { EChartsOption, SunburstSeriesOption } from "echarts";
 
 import { getTextColorForBackground } from "metabase/lib/colors";
+import { checkNotNull } from "metabase/lib/types";
 import { truncateText } from "metabase/visualizations/lib/text";
 import type {
   ComputedVisualizationSettings,
   RenderingContext,
 } from "metabase/visualizations/types";
 
-import { DIMENSIONS, TOTAL_TEXT } from "./constants";
+import { DIMENSIONS, OPTION_NAME_SEPERATOR, TOTAL_TEXT } from "./constants";
 import type { PieChartFormatters } from "./format";
-import type { PieChartModel, PieSlice, PieSliceData } from "./model/types";
-
-function getSliceByKey(key: PieSliceData["key"], slices: PieSlice[]) {
-  const slice = slices.find(s => s.data.key === key);
-  if (!slice) {
-    throw Error(
-      `Could not find slice with key ${key} in slices: ${JSON.stringify(
-        slices,
-      )}`,
-    );
-  }
-
-  return slice;
-}
+import type { PieChartModel, SliceTreeNode } from "./model/types";
+import { getArrayFromMapValues, getSliceTreeNodesFromPath } from "./util";
 
 function getTotalGraphicOption(
   settings: ComputedVisualizationSettings,
@@ -31,49 +20,80 @@ function getTotalGraphicOption(
   formatters: PieChartFormatters,
   renderingContext: RenderingContext,
   hoveredIndex: number | undefined,
+  hoveredSliceKeyPath: string[] | undefined,
   outerRadius: number,
+  innerRadius: number,
 ) {
+  // The font size is technically incorrect for the label text since it uses a
+  // smaller font than the value, however using the value font size for
+  // measurements makes up for the inaccuracy of our heuristic and provided a
+  // good end result.
+  const fontStyle = {
+    size: DIMENSIONS.total.valueFontSize,
+    weight: DIMENSIONS.total.fontWeight,
+    family: renderingContext.fontFamily,
+  };
+
   let valueText = "";
   let labelText = "";
 
-  // Don't display any text if there isn't enough width
-  const hasSufficientWidth = outerRadius * 2 >= DIMENSIONS.total.minWidth;
+  const defaultLabelWillOverflow =
+    renderingContext.measureText(TOTAL_TEXT, fontStyle) >= innerRadius * 2;
+
+  if (settings["pie.show_total"] && !defaultLabelWillOverflow) {
+    let sliceValueOrTotal = 0;
+
+    // chart hovered
+    if (hoveredSliceKeyPath != null) {
+      const { sliceTreeNode } = getSliceTreeNodesFromPath(
+        chartModel.sliceTree,
+        hoveredSliceKeyPath,
+      );
+
+      sliceValueOrTotal = checkNotNull(sliceTreeNode).displayValue;
+      labelText = checkNotNull(sliceTreeNode?.name);
+
+      // legend hovered
+    } else if (hoveredIndex != null) {
+      const slice = getArrayFromMapValues(chartModel.sliceTree)[hoveredIndex];
 
-  if (hasSufficientWidth && settings["pie.show_total"]) {
-    const sliceValueOrTotal =
-      hoveredIndex != null
-        ? chartModel.slices[hoveredIndex].data.displayValue
-        : chartModel.total;
+      sliceValueOrTotal = slice.displayValue;
+      labelText = slice.name.toUpperCase();
+    } else {
+      sliceValueOrTotal = chartModel.total;
+      labelText = TOTAL_TEXT;
+    }
 
     const valueWillOverflow =
-      renderingContext.measureText(formatters.formatMetric(sliceValueOrTotal), {
-        size: DIMENSIONS.total.valueFontSize,
-        family: renderingContext.fontFamily,
-        weight: DIMENSIONS.total.fontWeight,
-      }) > outerRadius; // innerRadius technically makes more sense, but looks too narrow in practice
-
-    const fontStyle = {
-      size: DIMENSIONS.total.valueFontSize,
-      weight: DIMENSIONS.total.fontWeight,
-      family: renderingContext.fontFamily,
-    };
+      renderingContext.measureText(
+        formatters.formatMetric(sliceValueOrTotal),
+        fontStyle,
+      ) > outerRadius; // innerRadius technically makes more sense, but looks too narrow in practice      ;
 
     valueText = truncateText(
       formatters.formatMetric(sliceValueOrTotal, valueWillOverflow),
-      outerRadius,
+      innerRadius * 2,
       renderingContext.measureText,
       fontStyle,
     );
     labelText = truncateText(
-      hoveredIndex != null
-        ? chartModel.slices[hoveredIndex].data.name.toUpperCase()
-        : TOTAL_TEXT,
-      outerRadius,
+      labelText,
+      innerRadius * 2,
       renderingContext.measureText,
       fontStyle,
     );
   }
 
+  const valueTextWidth = renderingContext.measureText(valueText, fontStyle);
+  const labelTextWidth = renderingContext.measureText(labelText, fontStyle);
+  const totalWidth = Math.max(valueTextWidth, labelTextWidth);
+
+  const hasSufficientWidth = innerRadius * 2 >= totalWidth;
+  if (!hasSufficientWidth) {
+    valueText = "";
+    labelText = "";
+  }
+
   return {
     type: "group",
     top: "center",
@@ -110,20 +130,53 @@ function getTotalGraphicOption(
   };
 }
 
-function getRadiusOption(sideLength: number) {
+function getRadiusOption(sideLength: number, chartModel: PieChartModel) {
+  let innerRadiusRatio = DIMENSIONS.slice.innerRadiusRatio;
+  if (chartModel.numRings === 2) {
+    innerRadiusRatio = DIMENSIONS.slice.twoRingInnerRadiusRatio;
+  } else if (chartModel.numRings === 3) {
+    innerRadiusRatio = DIMENSIONS.slice.threeRingInnerRadiusRatio;
+  }
+
   const outerRadius = sideLength / 2;
-  const innerRadius = outerRadius * DIMENSIONS.slice.innerRadiusRatio;
+  const innerRadius = outerRadius * innerRadiusRatio;
 
   return { outerRadius, innerRadius };
 }
 
+function getSliceLabel(
+  slice: SliceTreeNode,
+  settings: ComputedVisualizationSettings,
+  formatters: PieChartFormatters,
+) {
+  const name = settings["pie.show_labels"] ? slice.name : undefined;
+  const percent =
+    settings["pie.percent_visibility"] === "inside" ||
+    settings["pie.percent_visibility"] === "both"
+      ? formatters.formatPercent(slice.normalizedPercentage, "chart")
+      : undefined;
+
+  if (name != null && percent != null) {
+    return `${name}: ${percent}`;
+  }
+  if (name != null) {
+    return name;
+  }
+  if (percent != null) {
+    return percent;
+  }
+  return " ";
+}
+
 function getIsLabelVisible(
   label: string,
-  slice: PieSlice,
+  slice: SliceTreeNode,
   innerRadius: number,
   outerRadius: number,
   fontSize: number,
   renderingContext: RenderingContext,
+  ring: number,
+  numRings: number,
 ) {
   // We use the law of cosines to determine the length of the chord with the
   // same endpoints as the arc. The label should be shorter than this chord, and
@@ -134,11 +187,13 @@ function getIsLabelVisible(
   let arcAngle = slice.startAngle - slice.endAngle;
   arcAngle = Math.min(Math.abs(arcAngle), Math.PI - 0.001);
 
+  const donutWidth = (outerRadius - innerRadius) / numRings;
+  const ringInnerRadius = innerRadius + donutWidth * (ring - 1);
+
   const innerCircleChordLength = Math.sqrt(
-    2 * innerRadius * innerRadius -
-      2 * innerRadius * innerRadius * Math.cos(arcAngle),
+    2 * ringInnerRadius * ringInnerRadius -
+      2 * ringInnerRadius * ringInnerRadius * Math.cos(arcAngle),
   );
-  const donutWidth = outerRadius - innerRadius;
   const maxLabelDimension = Math.min(innerCircleChordLength, donutWidth);
 
   const fontStyle = {
@@ -149,70 +204,52 @@ function getIsLabelVisible(
   const labelWidth = renderingContext.measureText(label, fontStyle);
   const labelHeight = renderingContext.measureTextHeight(label, fontStyle);
 
+  if (ring === 1) {
+    return (
+      labelWidth + DIMENSIONS.slice.label.padding <= maxLabelDimension &&
+      labelHeight + DIMENSIONS.slice.label.padding <= maxLabelDimension
+    );
+  }
+
   return (
-    labelWidth + DIMENSIONS.slice.label.padding <= maxLabelDimension &&
-    labelHeight + DIMENSIONS.slice.label.padding <= maxLabelDimension
+    labelWidth + DIMENSIONS.slice.label.padding <= donutWidth &&
+    labelHeight + DIMENSIONS.slice.label.padding <= innerCircleChordLength
   );
 }
 
-export function getPieChartOption(
+function getSeriesDataFromSlices(
   chartModel: PieChartModel,
-  formatters: PieChartFormatters,
   settings: ComputedVisualizationSettings,
+  formatters: PieChartFormatters,
   renderingContext: RenderingContext,
-  sideLength: number,
-  hoveredIndex?: number,
-): EChartsOption {
-  // Sizing
-  const innerSideLength = Math.min(
-    sideLength - DIMENSIONS.padding.side * 2,
-    DIMENSIONS.maxSideLength,
-  );
-  const { outerRadius, innerRadius } = getRadiusOption(innerSideLength);
-
-  const borderWidth =
-    (Math.PI * innerSideLength) / DIMENSIONS.slice.borderProportion; // arc length formula: s = 2πr(θ/360°), we want border to be 1 degree
-
-  const fontSize = Math.max(
-    DIMENSIONS.slice.maxFontSize * (innerSideLength / DIMENSIONS.maxSideLength),
-    DIMENSIONS.slice.minFontSize,
-  );
-
-  // "Show total" setting
-  const graphicOption = getTotalGraphicOption(
-    settings,
-    chartModel,
-    formatters,
-    renderingContext,
-    hoveredIndex,
-    outerRadius,
-  );
-
-  // "Show percentages: On the chart" setting
-  const formatSlicePercent = (key: PieSliceData["key"]) => {
-    if (
-      settings["pie.percent_visibility"] == null ||
-      settings["pie.percent_visibility"] === "off" ||
-      settings["pie.percent_visibility"] === "legend"
-    ) {
-      return " ";
+  borderWidth: number,
+  innerRadius: number,
+  outerRadius: number,
+  fontSize: number,
+): SunburstSeriesOption["data"] {
+  function getSeriesData(
+    slices: SliceTreeNode[],
+    ring = 1,
+    parentName: string | null = null,
+  ): SunburstSeriesOption["data"] {
+    if (slices.length === 0) {
+      return [];
     }
 
-    return formatters.formatPercent(
-      getSliceByKey(key, chartModel.slices).data.normalizedPercentage,
-      "chart",
-    );
-  };
+    let ringBorderWidth = borderWidth;
+    if (ring === 2) {
+      ringBorderWidth = DIMENSIONS.slice.twoRingBorderWidth;
+    }
+    if (ring === 3) {
+      ringBorderWidth = DIMENSIONS.slice.threeRingBorderWidth;
+    }
 
-  // Series data
-  const data = chartModel.slices
-    .filter(s => s.data.visible)
-    .map(s => {
+    return slices.map(s => {
       const labelColor = getTextColorForBackground(
-        s.data.color,
+        s.color,
         renderingContext.getColor,
       );
-      const label = formatSlicePercent(s.data.key);
+      const label = getSliceLabel(s, settings, formatters);
       const isLabelVisible = getIsLabelVisible(
         label,
         s,
@@ -220,19 +257,30 @@ export function getPieChartOption(
         outerRadius,
         fontSize,
         renderingContext,
+        ring,
+        chartModel.numRings,
       );
 
+      const name =
+        parentName != null
+          ? `${parentName}${OPTION_NAME_SEPERATOR}${s.key}`
+          : s.key;
+
       return {
-        value: s.data.value,
-        name: s.data.name,
-        itemStyle: { color: s.data.color },
+        children: !s.isOther
+          ? getSeriesData(getArrayFromMapValues(s.children), ring + 1, name)
+          : undefined,
+        value: s.value,
+        name,
+        itemStyle: { color: s.color, borderWidth: ringBorderWidth },
         label: {
           color: labelColor,
           formatter: () => (isLabelVisible ? label : " "),
+          rotate: ring === 1 ? 0 : "radial",
         },
         emphasis: {
           itemStyle: {
-            color: s.data.color,
+            color: s.color,
             borderColor: renderingContext.theme.pie.borderColor,
           },
         },
@@ -243,7 +291,7 @@ export function getPieChartOption(
             // causing the underlying color to leak. It is safe to use non-hex
             // values here, since this value will never be used in batik
             // (there's no emphasis/blur for static viz).
-            color: Color(s.data.color).fade(0.7).rgb().string(),
+            color: Color(s.color).fade(0.7).rgb().string(),
             opacity: 1,
           },
           label: {
@@ -253,6 +301,67 @@ export function getPieChartOption(
         },
       };
     });
+  }
+
+  return getSeriesData(
+    getArrayFromMapValues(chartModel.sliceTree).filter(s => s.visible),
+  );
+}
+
+export function getPieChartOption(
+  chartModel: PieChartModel,
+  formatters: PieChartFormatters,
+  settings: ComputedVisualizationSettings,
+  renderingContext: RenderingContext,
+  sideLength: number,
+  hoveredIndex?: number,
+  hoveredSliceKeyPath?: string[],
+): EChartsOption {
+  // Sizing
+  const innerSideLength = Math.min(
+    sideLength - DIMENSIONS.padding.side * 2,
+    DIMENSIONS.maxSideLength,
+  );
+  const { outerRadius, innerRadius } = getRadiusOption(
+    innerSideLength,
+    chartModel,
+  );
+
+  const borderWidth =
+    (Math.PI * innerSideLength) / DIMENSIONS.slice.borderProportion; // arc length formula: s = 2πr(θ/360°), we want border to be 1 degree
+
+  const fontSize =
+    chartModel.numRings > 1
+      ? DIMENSIONS.slice.multiRingFontSize
+      : Math.max(
+          DIMENSIONS.slice.maxFontSize *
+            (innerSideLength / DIMENSIONS.maxSideLength),
+          DIMENSIONS.slice.minFontSize,
+        );
+
+  // "Show total" setting
+  const graphicOption = getTotalGraphicOption(
+    settings,
+    chartModel,
+    formatters,
+    renderingContext,
+    hoveredIndex,
+    hoveredSliceKeyPath,
+    outerRadius,
+    innerRadius,
+  );
+
+  // Series data
+  const data = getSeriesDataFromSlices(
+    chartModel,
+    settings,
+    formatters,
+    renderingContext,
+    borderWidth,
+    innerRadius,
+    outerRadius,
+    fontSize,
+  );
 
   return {
     // Unlike the cartesian chart, `animationDuration: 0` does not prevent the
@@ -269,11 +378,9 @@ export function getPieChartOption(
       nodeClick: false,
       radius: [innerRadius, outerRadius],
       itemStyle: {
-        borderWidth,
         borderColor: renderingContext.theme.pie.borderColor,
       },
       label: {
-        rotate: 0,
         overflow: "none",
         fontSize,
         fontWeight: DIMENSIONS.slice.label.fontWeight,
@@ -281,6 +388,9 @@ export function getPieChartOption(
       labelLayout: {
         hideOverlap: true,
       },
+      emphasis: {
+        focus: "ancestor",
+      },
       data,
     },
   };
diff --git a/frontend/src/metabase/visualizations/echarts/pie/tooltip.tsx b/frontend/src/metabase/visualizations/echarts/pie/tooltip.tsx
index 85edaaf18b732e11f65a455e269696cc4fc29c85..45605c7513006f37b0652cb4b1b571bec958e44e 100644
--- a/frontend/src/metabase/visualizations/echarts/pie/tooltip.tsx
+++ b/frontend/src/metabase/visualizations/echarts/pie/tooltip.tsx
@@ -8,19 +8,20 @@ import { getTooltipBaseOption } from "../tooltip";
 
 import type { PieChartFormatters } from "./format";
 import type { PieChartModel } from "./model/types";
+import { getSliceKeyPath } from "./util";
 
 interface ChartItemTooltip {
   chartModel: PieChartModel;
   formatters: PieChartFormatters;
-  dataIndex: number;
+  sliceKeyPath: string[];
 }
 
 const ChartItemTooltip = ({
   chartModel,
   formatters,
-  dataIndex,
+  sliceKeyPath,
 }: ChartItemTooltip) => {
-  const tooltipModel = getTooltipModel(dataIndex, chartModel, formatters);
+  const tooltipModel = getTooltipModel(sliceKeyPath, chartModel, formatters);
   return <EChartsTooltip {...tooltipModel} />;
 };
 
@@ -36,11 +37,15 @@ export const getTooltipOption = (
       if (Array.isArray(params) || typeof params.dataIndex !== "number") {
         return "";
       }
+      // @ts-expect-error - `treePathInfo` is present at runtime, but is not in
+      // the type provided by ECharts.
+      const sliceKeyPath = getSliceKeyPath(params);
+
       return renderToString(
         <ChartItemTooltip
           formatters={formatters}
           chartModel={chartModel}
-          dataIndex={params.dataIndex}
+          sliceKeyPath={sliceKeyPath}
         />,
       );
     },
diff --git a/frontend/src/metabase/visualizations/echarts/pie/types.ts b/frontend/src/metabase/visualizations/echarts/pie/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..81b4e9298857368ebddaa831f6d1080724b04437
--- /dev/null
+++ b/frontend/src/metabase/visualizations/echarts/pie/types.ts
@@ -0,0 +1,11 @@
+import type { EChartsSeriesMouseEvent } from "../types";
+
+type TreePathInfo = {
+  name: string;
+  dataIndex: number;
+  value: number;
+};
+
+export type EChartsSunburstSeriesMouseEvent = EChartsSeriesMouseEvent & {
+  treePathInfo: TreePathInfo[];
+};
diff --git a/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts b/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts
new file mode 100644
index 0000000000000000000000000000000000000000..314572bafb20f444f1ce1f9f972d5f7d5389d4ba
--- /dev/null
+++ b/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts
@@ -0,0 +1,83 @@
+import { aliases, colors } from "metabase/lib/colors";
+import { checkNumber } from "metabase/lib/types";
+import type {
+  ColorGetter,
+  RenderingContext,
+} from "metabase/visualizations/types";
+
+const ACCENT_KEY_PREFIX = "accent";
+
+function getAccentNumberFromHex(hexColor: string) {
+  const hexToAccentNumber = new Map<string, number>();
+
+  for (const [key, hex] of Object.entries(colors)) {
+    if (!key.startsWith(ACCENT_KEY_PREFIX)) {
+      continue;
+    }
+
+    const accentNumber = checkNumber(
+      Number(key.slice(ACCENT_KEY_PREFIX.length)),
+    );
+
+    hexToAccentNumber.set(hex, accentNumber);
+  }
+
+  for (const [key, hexGetter] of Object.entries(aliases)) {
+    if (!key.startsWith(ACCENT_KEY_PREFIX)) {
+      continue;
+    }
+
+    const accentNumber = checkNumber(
+      Number(key.slice(ACCENT_KEY_PREFIX.length, ACCENT_KEY_PREFIX.length + 1)),
+    );
+    const hex = hexGetter(colors);
+
+    hexToAccentNumber.set(hex, accentNumber);
+  }
+
+  return hexToAccentNumber.get(hexColor);
+}
+
+export function getColorForRing(
+  hexColor: string,
+  ring: "inner" | "middle" | "outer",
+  hasMultipleRings: boolean,
+  renderingContext: RenderingContext,
+) {
+  if (!hasMultipleRings) {
+    return hexColor;
+  }
+
+  const accentNumber = getAccentNumberFromHex(hexColor);
+  if (accentNumber == null) {
+    return hexColor;
+  }
+
+  let suffix = "";
+  if (ring === "inner") {
+    suffix = "-dark";
+  } else if (ring === "outer") {
+    suffix = "-light";
+  }
+
+  return renderingContext.getColor(
+    `${ACCENT_KEY_PREFIX}${accentNumber}${suffix}`,
+  );
+}
+
+export function getColorForPicker(
+  hexColor: string | undefined,
+  hasMultipleRings: boolean,
+  getColor: ColorGetter,
+) {
+  if (!hasMultipleRings || hexColor == null) {
+    return hexColor;
+  }
+
+  const accentNumber = getAccentNumberFromHex(hexColor);
+  if (accentNumber == null) {
+    return hexColor;
+  }
+
+  return getColor(`${ACCENT_KEY_PREFIX}${accentNumber}-dark`);
+}
diff --git a/frontend/src/metabase/visualizations/echarts/pie/util/index.ts b/frontend/src/metabase/visualizations/echarts/pie/util/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab2a525ff8b7b1b4cc5158e4797e27e841cb681c
--- /dev/null
+++ b/frontend/src/metabase/visualizations/echarts/pie/util/index.ts
@@ -0,0 +1,29 @@
+import { checkNotNull } from "metabase/lib/types";
+
+import { OPTION_NAME_SEPERATOR } from "../constants";
+import type { SliceTree, SliceTreeNode } from "../model/types";
+import type { EChartsSunburstSeriesMouseEvent } from "../types";
+
+export const getSliceKeyPath = (event: EChartsSunburstSeriesMouseEvent) =>
+  event?.name?.split(OPTION_NAME_SEPERATOR) ?? [];
+
+export function getSliceTreeNodesFromPath(
+  sliceTree: SliceTree,
+  path: string[],
+) {
+  let sliceTreeNode: SliceTreeNode | undefined = undefined;
+  const nodes: SliceTreeNode[] = [];
+
+  for (const key of path) {
+    const currentSliceTree: SliceTree =
+      sliceTreeNode == null ? sliceTree : sliceTreeNode.children;
+
+    sliceTreeNode = checkNotNull(currentSliceTree.get(key));
+    nodes.push(sliceTreeNode);
+  }
+
+  return { sliceTreeNode: checkNotNull(sliceTreeNode), nodes };
+}
+
+export const getArrayFromMapValues = <_, V>(map: Map<_, V>): V[] =>
+  Array(...map.values());
diff --git a/frontend/src/metabase/visualizations/echarts/tooltip/index.tsx b/frontend/src/metabase/visualizations/echarts/tooltip/index.tsx
index 0e4d6d428d11f0a289cf6e5ebb4fcdfe008acf6c..4c79599197cdf67105b3c01b892ed9dfa1854054 100644
--- a/frontend/src/metabase/visualizations/echarts/tooltip/index.tsx
+++ b/frontend/src/metabase/visualizations/echarts/tooltip/index.tsx
@@ -9,7 +9,8 @@ import type { ComputedVisualizationSettings } from "metabase/visualizations/type
 import type { ClickObject } from "metabase-lib";
 
 import type { BaseCartesianChartModel } from "../cartesian/model/types";
-import type { PieChartModel } from "../pie/model/types";
+import type { PieChartModel, SliceTreeNode } from "../pie/model/types";
+import { getArrayFromMapValues } from "../pie/util";
 
 export const TOOLTIP_POINTER_MARGIN = 10;
 
@@ -149,10 +150,18 @@ export const useCartesianChartSeriesColorsClasses = (
   return useInjectSeriesColorsClasses(hexColors);
 };
 
+function getColorsFromSlices(slices: SliceTreeNode[]) {
+  const colors = slices.map(s => s.color);
+  slices.forEach(s =>
+    colors.push(...getColorsFromSlices(getArrayFromMapValues(s.children))),
+  );
+  return colors;
+}
+
 export const usePieChartValuesColorsClasses = (chartModel: PieChartModel) => {
   const hexColors = useMemo(() => {
-    return chartModel.slices.map(slice => slice.data.color);
-  }, [chartModel.slices]);
+    return getColorsFromSlices(getArrayFromMapValues(chartModel.sliceTree));
+  }, [chartModel]);
 
   return useInjectSeriesColorsClasses(hexColors);
 };
diff --git a/frontend/src/metabase/visualizations/shared/settings/pie.ts b/frontend/src/metabase/visualizations/shared/settings/pie.ts
index 1e7dcb36286103f9781dfd34905a8b6c3f5af036..cbd47188cc7f996b826bbc25856670a1991e0294 100644
--- a/frontend/src/metabase/visualizations/shared/settings/pie.ts
+++ b/frontend/src/metabase/visualizations/shared/settings/pie.ts
@@ -9,6 +9,7 @@ import { getPieColumns } from "metabase/visualizations/echarts/pie/model";
 import type { PieRow } from "metabase/visualizations/echarts/pie/model/types";
 import type { ShowWarning } from "metabase/visualizations/echarts/types";
 import { getNumberOr } from "metabase/visualizations/lib/settings/row-values";
+import { getDefaultDimensionsAndMetrics } from "metabase/visualizations/lib/utils";
 import { unaggregatedDataWarningPie } from "metabase/visualizations/lib/warnings";
 import type {
   ComputedVisualizationSettings,
@@ -21,10 +22,41 @@ import type {
   RowValues,
 } from "metabase-types/api";
 
+export function getPieDimensions(settings: ComputedVisualizationSettings) {
+  const dimensionSetting = settings["pie.dimension"];
+
+  if (dimensionSetting == null) {
+    throw new Error("`pie.dimension` is undefined");
+  }
+  if (Array.isArray(dimensionSetting)) {
+    return dimensionSetting;
+  }
+  return [dimensionSetting];
+}
+
+export function getDefaultPieColumns(rawSeries: RawSeries) {
+  const { dimensions, metrics } = getDefaultDimensionsAndMetrics(
+    rawSeries,
+    3,
+    1,
+  );
+  return {
+    dimension: dimensions,
+    metric: metrics[0],
+  };
+}
+
 export const getDefaultShowLegend = () => true;
 
 export const getDefaultShowTotal = () => true;
 
+export function getDefaultShowLabels(settings: ComputedVisualizationSettings) {
+  if (getPieDimensions(settings).length <= 1) {
+    return false;
+  }
+  return true;
+}
+
 export const getDefaultPercentVisibility = () => "legend";
 
 export const getDefaultSliceThreshold = () => SLICE_THRESHOLD * 100;
@@ -117,10 +149,9 @@ export function getColors(
       data: { rows, cols },
     },
   ] = rawSeries;
+  const dimensionName = getPieDimensions(currentSettings)[0];
 
-  const dimensionIndex = cols.findIndex(
-    col => col.name === currentSettings["pie.dimension"],
-  );
+  const dimensionIndex = cols.findIndex(col => col.name === dimensionName);
   const metricIndex = cols.findIndex(
     col => col.name === currentSettings["pie.metric"],
   );
diff --git a/frontend/src/metabase/visualizations/types/hover.ts b/frontend/src/metabase/visualizations/types/hover.ts
index f59ffa6ed7d8a70cb3ea49a801f3b764e4032a2d..9d21261acb73d972d47150bb6aadf721957a4d33 100644
--- a/frontend/src/metabase/visualizations/types/hover.ts
+++ b/frontend/src/metabase/visualizations/types/hover.ts
@@ -51,4 +51,6 @@ export interface HoveredObject {
   event?: MouseEvent;
   stackedTooltipModel?: StackedTooltipModel;
   isAlreadyScaled?: boolean;
+  pieSliceKeyPath?: string[];
+  pieLegendHoverIndex?: number;
 }
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.modules.css b/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.modules.css
new file mode 100644
index 0000000000000000000000000000000000000000..27213c540e5ebda87dcea3447a0c67d186b904a7
--- /dev/null
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.modules.css
@@ -0,0 +1,3 @@
+.dimensionPicker {
+  margin-bottom: 10px;
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.tsx b/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7e12384e89387cc89e7c0e18d73ef32663fded97
--- /dev/null
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/DimensionsWidget.tsx
@@ -0,0 +1,219 @@
+import {
+  DndContext,
+  type DragEndEvent,
+  DragOverlay,
+  type DragStartEvent,
+  PointerSensor,
+  useSensor,
+} from "@dnd-kit/core";
+import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
+import {
+  SortableContext,
+  arrayMove,
+  verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import { useState } from "react";
+import { t } from "ttag";
+
+import { Sortable } from "metabase/core/components/Sortable";
+import { Button, Text } from "metabase/ui";
+import ChartSettingFieldPicker from "metabase/visualizations/components/settings/ChartSettingFieldPicker";
+import { getOptionFromColumn } from "metabase/visualizations/lib/settings/utils";
+import { getPieDimensions } from "metabase/visualizations/shared/settings/pie";
+import type { ComputedVisualizationSettings } from "metabase/visualizations/types";
+import { isDimension } from "metabase-lib/v1/types/utils/isa";
+import type { RawSeries } from "metabase-types/api";
+
+import Styles from "./DimensionsWidget.modules.css";
+import { PieRowsPicker } from "./PieRowsPicker";
+
+function DimensionPicker({
+  value,
+  options,
+  showDragHandle,
+  onChange,
+  onRemove,
+}: {
+  value: string | undefined;
+  options: { name: string; value: string }[];
+  showDragHandle: boolean;
+  onChange?: (value: string) => void;
+  onRemove?: (() => void) | undefined;
+}) {
+  return (
+    <ChartSettingFieldPicker
+      value={value}
+      options={options}
+      columnHasSettings={() => false}
+      onChange={onChange}
+      onRemove={onRemove}
+      showColorPicker={false}
+      showColumnSetting={false}
+      className={Styles.dimensionPicker}
+      colors={undefined}
+      series={undefined}
+      columns={undefined}
+      onShowWidget={() => {}}
+      onChangeSeriesColor={() => {}}
+      showDragHandle={showDragHandle}
+    />
+  );
+}
+
+const INNER_RING_TITLE = t`Inner Ring`;
+const MIDDLE_RING_TITLE = t`Middle Ring`;
+const OUTER_RING_TITLE = t`Outer Ring`;
+
+const TWO_RING_SETTING_TITLES = [INNER_RING_TITLE, OUTER_RING_TITLE];
+const THREE_RING_SETTING_TITLES = [
+  INNER_RING_TITLE,
+  MIDDLE_RING_TITLE,
+  OUTER_RING_TITLE,
+];
+
+export function DimensionsWidget({
+  rawSeries,
+  settings,
+  onChangeSettings,
+  onShowWidget,
+}: {
+  rawSeries: RawSeries;
+  settings: ComputedVisualizationSettings;
+  onChangeSettings: (newSettings: ComputedVisualizationSettings) => void;
+  onShowWidget: (widget: any, ref: any) => void;
+}) {
+  // Dimension settings
+  const [dimensions, setDimensions] = useState<(string | undefined)[]>(() => [
+    ...getPieDimensions(settings),
+  ]);
+
+  const dimensionTitles =
+    dimensions.length < 3 ? TWO_RING_SETTING_TITLES : THREE_RING_SETTING_TITLES;
+
+  const updateDimensions = (newDimensions: (string | undefined)[]) => {
+    setDimensions(newDimensions);
+    onChangeSettings({
+      "pie.dimension": newDimensions.filter(d => d != null) as string[],
+    });
+  };
+
+  const onChangeDimension = (index: number) => (newValue: string) => {
+    const newDimensions = [...dimensions];
+    newDimensions[index] = newValue;
+
+    updateDimensions(newDimensions);
+  };
+
+  const onRemove = (index: number) => () => {
+    const newDimensions = [...dimensions];
+    newDimensions.splice(index, 1);
+
+    updateDimensions(newDimensions);
+  };
+
+  // Dropdown options
+  const dimensionOptions = rawSeries[0].data.cols
+    .filter(isDimension)
+    .map(getOptionFromColumn);
+
+  const getFilteredOptions = (index: number) =>
+    dimensionOptions.filter(
+      opt => opt.value === dimensions[index] || !dimensions.includes(opt.value),
+    );
+
+  // Drag and drop
+  const pointerSensor = useSensor(PointerSensor, {
+    activationConstraint: { distance: 15 },
+  });
+  const [draggedDimensionIndex, setDraggedDimensionIndex] = useState<number>();
+
+  const onDragStart = (event: DragStartEvent) => {
+    setDraggedDimensionIndex(
+      dimensions.findIndex(d => d === String(event.active.id)),
+    );
+  };
+
+  const onDragEnd = (event: DragEndEvent) => {
+    setDraggedDimensionIndex(undefined);
+
+    const over = event.over;
+    if (over == null) {
+      return;
+    }
+    const sourceIndex = dimensions.findIndex(d => d === event.active.id);
+    const destIndex = dimensions.findIndex(d => d === over.id);
+
+    if (sourceIndex === -1 || destIndex === -1) {
+      return;
+    }
+    updateDimensions(arrayMove(dimensions, sourceIndex, destIndex));
+  };
+
+  return (
+    <>
+      <DndContext
+        onDragStart={onDragStart}
+        onDragEnd={onDragEnd}
+        modifiers={[restrictToVerticalAxis]}
+        sensors={[pointerSensor]}
+      >
+        <SortableContext
+          items={(dimensions.filter(d => d != null) as string[]).map(d => ({
+            id: d,
+          }))}
+          strategy={verticalListSortingStrategy}
+        >
+          {dimensions.map((dimension, index) => (
+            <>
+              <Text weight="bold" mb="sm">
+                {dimensionTitles[index]}
+              </Text>
+              <Sortable
+                key={String(dimension)}
+                id={String(dimension)}
+                disabled={dimensions.length === 1 || dimension == null}
+                draggingStyle={{ opacity: 0.5 }}
+              >
+                <DimensionPicker
+                  key={dimension}
+                  value={dimension}
+                  onChange={onChangeDimension(index)}
+                  onRemove={dimensions.length > 1 ? onRemove(index) : undefined}
+                  options={getFilteredOptions(index)}
+                  showDragHandle={dimensions.length > 1 && dimension != null}
+                />
+              </Sortable>
+              {index === 0 && (
+                <PieRowsPicker
+                  rawSeries={rawSeries}
+                  settings={settings}
+                  onChangeSettings={onChangeSettings}
+                  onShowWidget={onShowWidget}
+                  numRings={dimensions.filter(d => d != null).length}
+                />
+              )}
+            </>
+          ))}
+          <DragOverlay>
+            {draggedDimensionIndex != null ? (
+              <DimensionPicker
+                value={dimensions[draggedDimensionIndex]}
+                options={getFilteredOptions(draggedDimensionIndex)}
+                onRemove={() => {}}
+                showDragHandle
+              />
+            ) : null}
+          </DragOverlay>
+        </SortableContext>
+      </DndContext>
+      {dimensions.length < 3 &&
+        dimensions[dimensions.length - 1] != null &&
+        getFilteredOptions(dimensions.length).length > 0 && (
+          <Button
+            variant="subtle"
+            onClick={() => setDimensions([...dimensions, undefined])}
+          >{t`Add Ring`}</Button>
+        )}
+    </>
+  );
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx b/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
index 82c614627e03471a9e23ec0687f3b01bba8fcdd2..debfea2bc38a08b04aa266c737064289e435dd93 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/PieChart.tsx
@@ -3,12 +3,14 @@ import { type MouseEvent, useCallback, useMemo, useRef, useState } from "react";
 import { useSet } from "react-use";
 
 import { isNotNull } from "metabase/lib/types";
+import { extractRemappings } from "metabase/visualizations";
 import ChartWithLegend from "metabase/visualizations/components/ChartWithLegend";
 import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
 import { getPieChartFormatters } from "metabase/visualizations/echarts/pie/format";
 import { getPieChartModel } from "metabase/visualizations/echarts/pie/model";
 import { getPieChartOption } from "metabase/visualizations/echarts/pie/option";
 import { getTooltipOption } from "metabase/visualizations/echarts/pie/tooltip";
+import { getArrayFromMapValues } from "metabase/visualizations/echarts/pie/util";
 import {
   useCloseTooltipOnScroll,
   usePieChartValuesColorsClasses,
@@ -31,6 +33,7 @@ export function PieChart(props: VisualizationProps) {
     isFullscreen,
   } = props;
   const hoveredIndex = props.hovered?.index;
+  const hoveredSliceKeyPath = props.hovered?.pieSliceKeyPath;
 
   const containerRef = useRef<HTMLDivElement>(null);
   const chartRef = useRef<EChartsType>();
@@ -50,16 +53,26 @@ export function PieChart(props: VisualizationProps) {
     isDashboard,
     isFullscreen,
   });
+  const rawSeriesWithRemappings = useMemo(
+    () => extractRemappings(rawSeries),
+    [rawSeries],
+  );
   const chartModel = useMemo(
     () =>
       getPieChartModel(
-        rawSeries,
+        rawSeriesWithRemappings,
         settings,
         Array.from(hiddenSlices),
         renderingContext,
         showWarning,
       ),
-    [rawSeries, settings, hiddenSlices, renderingContext, showWarning],
+    [
+      rawSeriesWithRemappings,
+      settings,
+      hiddenSlices,
+      renderingContext,
+      showWarning,
+    ],
   );
   const formatters = useMemo(
     () => getPieChartFormatters(chartModel, settings, renderingContext),
@@ -74,6 +87,7 @@ export function PieChart(props: VisualizationProps) {
         renderingContext,
         sideLength,
         hoveredIndex,
+        hoveredSliceKeyPath,
       ),
       tooltip: getTooltipOption(chartModel, formatters, containerRef),
     }),
@@ -84,6 +98,7 @@ export function PieChart(props: VisualizationProps) {
       renderingContext,
       sideLength,
       hoveredIndex,
+      hoveredSliceKeyPath,
     ],
   );
 
@@ -100,13 +115,14 @@ export function PieChart(props: VisualizationProps) {
 
   const eventHandlers = useChartEvents(props, chartRef, chartModel);
 
-  const legendTitles = chartModel.slices
-    .filter(s => s.data.includeInLegend)
+  const slices = getArrayFromMapValues(chartModel.sliceTree);
+  const legendTitles = slices
+    .filter(s => s.includeInLegend)
     .map(s => {
-      const label = s.data.name;
+      const label = s.name;
 
       // Hidden slices don't have a percentage
-      const sliceHidden = s.data.normalizedPercentage === 0;
+      const sliceHidden = s.normalizedPercentage === 0;
       const percentDisabled =
         settings["pie.percent_visibility"] !== "legend" &&
         settings["pie.percent_visibility"] !== "both";
@@ -117,18 +133,16 @@ export function PieChart(props: VisualizationProps) {
 
       return [
         label,
-        formatters.formatPercent(s.data.normalizedPercentage, "legend"),
+        formatters.formatPercent(s.normalizedPercentage, "legend"),
       ];
     });
 
-  const hiddenSlicesLegendIndices = chartModel.slices
-    .filter(s => s.data.includeInLegend)
-    .map((s, index) => (hiddenSlices.has(s.data.key) ? index : null))
+  const hiddenSlicesLegendIndices = slices
+    .filter(s => s.includeInLegend)
+    .map((s, index) => (hiddenSlices.has(s.key) ? index : null))
     .filter(isNotNull);
 
-  const legendColors = chartModel.slices
-    .filter(s => s.data.includeInLegend)
-    .map(s => s.data.color);
+  const legendColors = slices.filter(s => s.includeInLegend).map(s => s.color);
 
   const showLegend = settings["pie.show_legend"];
 
@@ -136,6 +150,7 @@ export function PieChart(props: VisualizationProps) {
     props.onHoverChange(
       hoverData && {
         ...hoverData,
+        pieLegendHoverIndex: hoverData.index,
       },
     );
 
@@ -143,12 +158,11 @@ export function PieChart(props: VisualizationProps) {
     event: MouseEvent,
     sliceIndex: number,
   ) => {
-    const slice = chartModel.slices[sliceIndex];
-    const willShowSlice = hiddenSlices.has(slice.data.key);
-    const hasMoreVisibleSlices =
-      chartModel.slices.length - hiddenSlices.size > 1;
+    const slice = slices[sliceIndex];
+    const willShowSlice = hiddenSlices.has(slice.key);
+    const hasMoreVisibleSlices = slices.length - hiddenSlices.size > 1;
     if (hasMoreVisibleSlices || willShowSlice) {
-      toggleSliceVisibility(slice.data.key);
+      toggleSliceVisibility(slice.key);
     }
   };
 
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx b/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1e939f0070fbd5f620069e9f14373b8ae7c1b207
--- /dev/null
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx
@@ -0,0 +1,62 @@
+import { color } from "metabase/lib/colors";
+import {
+  ChartSettingSeriesOrder,
+  type SortableItem,
+} from "metabase/visualizations/components/settings/ChartSettingSeriesOrder";
+import type { PieRow } from "metabase/visualizations/echarts/pie/model/types";
+import { getColorForPicker } from "metabase/visualizations/echarts/pie/util/colors";
+import type { ComputedVisualizationSettings } from "metabase/visualizations/types";
+import type { RawSeries } from "metabase-types/api";
+
+export function PieRowsPicker({
+  rawSeries,
+  settings,
+  numRings,
+  onChangeSettings,
+  onShowWidget,
+}: {
+  rawSeries: RawSeries;
+  settings: ComputedVisualizationSettings;
+  numRings: number;
+  onChangeSettings: (newSettings: ComputedVisualizationSettings) => void;
+  onShowWidget: (widget: any, ref: any) => void;
+}) {
+  const pieRows = settings["pie.rows"];
+  if (pieRows == null) {
+    return null;
+  }
+
+  const onChangeSeriesColor = (sliceKey: string, color: string) =>
+    onChangeSettings({
+      "pie.rows": pieRows.map(row => {
+        if (row.key !== sliceKey) {
+          return row;
+        }
+        return { ...row, color, defaultColor: false };
+      }),
+    });
+
+  const onSortEnd = (newPieRows: SortableItem[]) =>
+    onChangeSettings({
+      "pie.sort_rows": false,
+      "pie.rows": newPieRows as PieRow[],
+    });
+
+  return (
+    <ChartSettingSeriesOrder
+      value={pieRows}
+      series={rawSeries}
+      onChangeSeriesColor={onChangeSeriesColor}
+      onSortEnd={onSortEnd}
+      onChange={rows => onChangeSettings({ "pie.rows": rows as PieRow[] })}
+      onShowWidget={onShowWidget}
+      hasEditSettings
+      accentColorOptions={
+        numRings > 1
+          ? { dark: true, main: false, light: false, harmony: false }
+          : undefined
+      }
+      getItemColor={item => getColorForPicker(item.color, numRings > 1, color)}
+    />
+  );
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts b/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts
index 4af837092b225baa546f6b8ed81ecc6907e2ed9e..f25f3aa53939db1858968d8415e811103037913f 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts
@@ -2,8 +2,6 @@ import { t } from "ttag";
 import _ from "underscore";
 
 import { formatValue } from "metabase/lib/formatting";
-import { ChartSettingSeriesOrder } from "metabase/visualizations/components/settings/ChartSettingSeriesOrder";
-import type { PieRow } from "metabase/visualizations/echarts/pie/model/types";
 import {
   ChartSettingsError,
   MinRowsError,
@@ -16,14 +14,16 @@ import {
 } from "metabase/visualizations/lib/settings/utils";
 import {
   getDefaultPercentVisibility,
+  getDefaultPieColumns,
+  getDefaultShowLabels,
   getDefaultShowLegend,
+  getDefaultShowTotal,
   getDefaultSliceThreshold,
   getDefaultSortRows,
   getPieRows,
   getPieSortRowsDimensionSetting,
 } from "metabase/visualizations/shared/settings/pie";
 import { SERIES_SETTING_KEY } from "metabase/visualizations/shared/settings/series";
-import { getDefaultShowTotal } from "metabase/visualizations/shared/settings/waterfall";
 import {
   getDefaultSize,
   getMinSize,
@@ -33,8 +33,10 @@ import type {
   VisualizationDefinition,
   VisualizationSettingsDefinitions,
 } from "metabase/visualizations/types";
-import type { RawSeries } from "metabase-types/api";
+import { isDimension, isMetric } from "metabase-lib/v1/types/utils/isa";
+import type { RawSeries, Series } from "metabase-types/api";
 
+import { DimensionsWidget } from "./DimensionsWidget";
 import { SliceNameWidget } from "./SliceNameWidget";
 
 export const PIE_CHART_DEFINITION: VisualizationDefinition = {
@@ -43,7 +45,17 @@ export const PIE_CHART_DEFINITION: VisualizationDefinition = {
   iconName: "pie",
   minSize: getMinSize("pie"),
   defaultSize: getDefaultSize("pie"),
-  isSensible: ({ cols }) => cols.length === 2,
+  isSensible: ({ cols, rows }) => {
+    const numDimensions = cols.filter(isDimension).length;
+    const numMetrics = cols.filter(isMetric).length;
+
+    return (
+      rows.length >= 2 &&
+      cols.length >= 2 &&
+      numDimensions >= 1 &&
+      numMetrics >= 1
+    );
+  },
   checkRenderable: (
     [
       {
@@ -85,55 +97,27 @@ export const PIE_CHART_DEFINITION: VisualizationDefinition = {
     },
   ] as RawSeries,
   settings: {
+    ...metricSetting("pie.metric", {
+      section: t`Data`,
+      title: t`Measure`,
+      showColumnSetting: true,
+      getDefault: (rawSeries: Series) => getDefaultPieColumns(rawSeries).metric,
+    }),
     ...columnSettings({ hidden: true }),
     ...dimensionSetting("pie.dimension", {
-      section: t`Data`,
+      hidden: true,
       title: t`Dimension`,
       showColumnSetting: true,
+      getDefault: (rawSeries: Series) =>
+        getDefaultPieColumns(rawSeries).dimension,
     }),
     "pie.rows": {
-      section: t`Data`,
-      widget: ChartSettingSeriesOrder,
-      getHidden: (_rawSeries, settings) => settings["pie.dimension"] == null,
+      hidden: true,
       getValue: (rawSeries, settings) => {
         return getPieRows(rawSeries, settings, (value, options) =>
           String(formatValue(value, options)),
         );
       },
-      getProps: (
-        _rawSeries,
-        vizSettings: ComputedVisualizationSettings,
-        onChange,
-        _extra,
-        onChangeSettings,
-      ) => {
-        return {
-          addButtonLabel: t`Add another row`,
-          searchPickerPlaceholder: t`Select a row`,
-          onChangeSeriesColor: (sliceKey: string, color: string) => {
-            const pieRows = vizSettings["pie.rows"];
-            if (pieRows == null) {
-              throw Error("Missing `pie.rows` setting");
-            }
-
-            onChange(
-              pieRows.map(row => {
-                if (row.key !== sliceKey) {
-                  return row;
-                }
-                return { ...row, color, defaultColor: false };
-              }),
-            );
-          },
-          onSortEnd: (newPieRows: PieRow[]) =>
-            onChangeSettings({
-              "pie.sort_rows": false,
-              "pie.rows": newPieRows,
-              "pie.sort_rows_dimension":
-                getPieSortRowsDimensionSetting(vizSettings),
-            }),
-        };
-      },
       readDependencies: [
         "pie.dimension",
         "pie.metric",
@@ -194,11 +178,23 @@ export const PIE_CHART_DEFINITION: VisualizationDefinition = {
       },
       readDependencies: ["pie.rows"],
     } as any), // any type cast needed to avoid type error from confusion with destructured object params in `nestedSettings`
-    ...metricSetting("pie.metric", {
+
+    "pie._dimensions_widget": {
       section: t`Data`,
-      title: t`Measure`,
-      showColumnSetting: true,
-    }),
+      widget: DimensionsWidget,
+      getProps: (
+        rawSeries: RawSeries,
+        settings: ComputedVisualizationSettings,
+        _onChange: any,
+        _extra: any,
+        onChangeSettings: (newSettings: ComputedVisualizationSettings) => void,
+      ) => ({
+        rawSeries,
+        settings,
+        onChangeSettings,
+      }),
+      readDependencies: ["pie.dimension", "pie.rows"],
+    },
     "pie.show_legend": {
       section: t`Display`,
       title: t`Show legend`,
@@ -213,6 +209,14 @@ export const PIE_CHART_DEFINITION: VisualizationDefinition = {
       widget: "toggle",
       getDefault: getDefaultShowTotal,
       inline: true,
+      marginBottom: "1rem",
+    },
+    "pie.show_labels": {
+      section: t`Display`,
+      title: t`Show labels`,
+      widget: "toggle",
+      getDefault: (_rawSeries, settings) => getDefaultShowLabels(settings),
+      inline: true,
     },
     "pie.percent_visibility": {
       section: t`Display`,
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/use-chart-events.ts b/frontend/src/metabase/visualizations/visualizations/PieChart/use-chart-events.ts
index 0a223a23e68add6c8f87a4502306b77853b8850a..7b53ab6dc090cc52275e62249cc7cb1d59367af3 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart/use-chart-events.ts
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart/use-chart-events.ts
@@ -3,6 +3,7 @@ import { type MutableRefObject, useEffect, useMemo } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 
+import { checkNotNull } from "metabase/lib/types";
 import { formatPercent } from "metabase/static-viz/lib/numbers";
 import type {
   EChartsTooltipModel,
@@ -14,11 +15,16 @@ import {
 } from "metabase/visualizations/components/ChartTooltip/StackedDataTooltip/utils";
 import type { PieChartFormatters } from "metabase/visualizations/echarts/pie/format";
 import type { PieChartModel } from "metabase/visualizations/echarts/pie/model/types";
+import type { EChartsSunburstSeriesMouseEvent } from "metabase/visualizations/echarts/pie/types";
+import {
+  getArrayFromMapValues,
+  getSliceKeyPath,
+  getSliceTreeNodesFromPath,
+} from "metabase/visualizations/echarts/pie/util";
 import {
   getMarkerColorClass,
   useClickedStateTooltipSync,
 } from "metabase/visualizations/echarts/tooltip";
-import type { EChartsSeriesMouseEvent } from "metabase/visualizations/echarts/types";
 import { getFriendlyName } from "metabase/visualizations/lib/utils";
 import type {
   ClickObject,
@@ -27,35 +33,39 @@ import type {
 import type { EChartsEventHandler } from "metabase/visualizations/types/echarts";
 
 export const getTooltipModel = (
-  dataIndex: number,
+  sliceKeyPath: string[],
   chartModel: PieChartModel,
   formatters: PieChartFormatters,
 ): EChartsTooltipModel => {
-  const hoveredIndex = dataIndexToHoveredIndex(dataIndex, chartModel);
-  const hoveredOther =
-    chartModel.slices[hoveredIndex].data.isOther &&
-    chartModel.otherSlices.length > 1;
-
-  const slices = hoveredOther
-    ? chartModel.otherSlices
-    : chartModel.slices.filter(slice => slice.data.visible);
-
-  const rows = slices.map(slice => ({
-    name: slice.data.name,
-    value: slice.data.displayValue,
-    color: hoveredOther ? undefined : slice.data.color,
-    formatter: formatters.formatMetric,
-  }));
+  const { sliceTreeNode, nodes } = getSliceTreeNodesFromPath(
+    chartModel.sliceTree,
+    sliceKeyPath,
+  );
+  const siblingNodes = getArrayFromMapValues(
+    nodes.length >= 2 ? nodes[nodes.length - 2].children : chartModel.sliceTree,
+  );
 
+  const rows = (
+    sliceTreeNode.isOther
+      ? getArrayFromMapValues(sliceTreeNode.children)
+      : siblingNodes
+  )
+    .filter(node => node.visible)
+    .map(slice => ({
+      name: slice.name,
+      value: slice.displayValue,
+      color: nodes.length === 1 ? slice.color : undefined,
+      formatter: formatters.formatMetric,
+      key: slice.key,
+    }));
   const rowsTotal = getTotalValue(rows);
-  const isShowingTotalSensible = rows.length > 1;
 
-  const formattedRows: EChartsTooltipRow[] = rows.map((row, index) => {
+  const formattedRows: EChartsTooltipRow[] = rows.map(row => {
     const markerColorClass = row.color
       ? getMarkerColorClass(row.color)
       : undefined;
     return {
-      isFocused: !hoveredOther && index === dataIndex - 1,
+      isFocused: !sliceTreeNode.isOther && row.key === sliceTreeNode.key,
       markerColorClass,
       name: row.name,
       values: [
@@ -66,92 +76,97 @@ export const getTooltipModel = (
   });
 
   return {
-    header: getFriendlyName(chartModel.colDescs.dimensionDesc.column),
+    header:
+      nodes.length === 1
+        ? getFriendlyName(sliceTreeNode.column)
+        : nodes
+            .slice(0, -1)
+            .map(node => node.name)
+            .join("  >  "),
     rows: formattedRows,
-    footer: isShowingTotalSensible
-      ? {
-          name: t`Total`,
-          values: [
-            formatters.formatMetric(rowsTotal),
-            formatPercent(getPercent(chartModel.total, rowsTotal) ?? 0),
-          ],
-        }
-      : undefined,
+    footer:
+      rows.length > 1
+        ? {
+            name: t`Total`,
+            values: [
+              formatters.formatMetric(rowsTotal),
+              formatPercent(getPercent(chartModel.total, rowsTotal) ?? 0),
+            ],
+          }
+        : undefined,
   };
 };
 
-const dataIndexToHoveredIndex = (index: number, chartModel: PieChartModel) => {
-  const visibleSlices = chartModel.slices.filter(slice => slice.data.visible);
-  const slice = visibleSlices[index - 1];
-  const innerIndex = chartModel.slices.findIndex(
-    s => s.data.key === slice.data.key && s.data.isOther === slice.data.isOther,
-  );
-  return innerIndex;
-};
-
-const hoveredIndexToDataIndex = (index: number, chartModel: PieChartModel) => {
-  const baseIndex = index + 1;
-  const slicesBefore = chartModel.slices.slice(0, index);
-  const hiddenSlicesBefore = slicesBefore.filter(slice => !slice.data.visible);
-  return baseIndex - hiddenSlicesBefore.length;
-};
-
 function getHoverData(
-  event: EChartsSeriesMouseEvent,
+  event: EChartsSunburstSeriesMouseEvent,
   chartModel: PieChartModel,
 ) {
   if (event.dataIndex == null) {
     return null;
   }
-  const index = dataIndexToHoveredIndex(event.dataIndex, chartModel);
 
-  const indexOutOfBounds = chartModel.slices[index] == null;
-  if (indexOutOfBounds || chartModel.slices[index].data.noHover) {
-    return null;
+  const pieSliceKeyPath = getSliceKeyPath(event);
+
+  const dimensionNode = chartModel.sliceTree.get(pieSliceKeyPath[0]);
+  if (dimensionNode == null) {
+    throw Error(`Could not find dimensionNode for key ${pieSliceKeyPath[0]}`);
   }
 
   return {
-    index,
+    index: dimensionNode.legendHoverIndex,
     event: event.event.event,
+    pieSliceKeyPath,
   };
 }
 
 function handleClick(
-  event: EChartsSeriesMouseEvent,
+  event: EChartsSunburstSeriesMouseEvent,
   dataProp: VisualizationProps["data"],
   settings: VisualizationProps["settings"],
   visualizationIsClickable: VisualizationProps["visualizationIsClickable"],
   onVisualizationClick: VisualizationProps["onVisualizationClick"],
   chartModel: PieChartModel,
 ) {
-  if (!event.dataIndex) {
+  if (event.dataIndex == null) {
+    return;
+  }
+
+  const { sliceTreeNode, nodes } = getSliceTreeNodesFromPath(
+    chartModel.sliceTree,
+    getSliceKeyPath(event),
+  );
+
+  if (sliceTreeNode.isOther) {
     return;
   }
-  const index = dataIndexToHoveredIndex(event.dataIndex, chartModel);
-  const slice = chartModel.slices[index];
+
+  const rowIndex = sliceTreeNode.rowIndex;
+
   const data =
-    slice.data.rowIndex != null
-      ? dataProp.rows[slice.data.rowIndex].map((value, index) => ({
+    rowIndex != null
+      ? dataProp.rows[rowIndex].map((value, index) => ({
           value,
           col: dataProp.cols[index],
         }))
       : undefined;
 
+  if (data != null) {
+    data[chartModel.colDescs.metricDesc.index].value = sliceTreeNode.value;
+  }
+
   const clickObject: ClickObject = {
-    value: slice.data.value,
+    value: sliceTreeNode.value,
     column: chartModel.colDescs.metricDesc.column,
     data,
-    dimensions: [
-      {
-        value: slice.data.key,
-        column: chartModel.colDescs.dimensionDesc.column,
-      },
-    ],
+    dimensions: nodes.map(node => ({
+      value: node.key,
+      column: checkNotNull(node.column),
+    })),
     settings,
     event: event.event.event,
   };
 
-  if (visualizationIsClickable(clickObject) && !slice.data.isOther) {
+  if (visualizationIsClickable(clickObject)) {
     onVisualizationClick(clickObject);
   }
 }
@@ -168,30 +183,37 @@ export function useChartEvents(
     visualizationIsClickable,
     onVisualizationClick,
   } = props;
-  const hoveredIndex = props.hovered?.index;
+  // We use `pieLegendHoverIndex` instead of `hovered.index` because we only
+  // want to manually highlight and downplay when the user hovers over the
+  // legend. If the user hovers over the chart, echarts will handle highlighting
+  // the chart itself.
+  const legendHoverIndex = props.hovered?.pieLegendHoverIndex;
   const chart = chartRef?.current;
 
   useEffect(
     function higlightChartOnLegendHover() {
-      if (chart == null || hoveredIndex == null) {
+      if (chart == null || legendHoverIndex == null) {
         return;
       }
 
+      const name = getArrayFromMapValues(chartModel.sliceTree)[legendHoverIndex]
+        .key;
+
       chart.dispatchAction({
         type: "highlight",
-        dataIndex: hoveredIndexToDataIndex(hoveredIndex, chartModel),
+        name,
         seriesIndex: 0,
       });
 
       return () => {
         chart.dispatchAction({
           type: "downplay",
-          dataIndex: hoveredIndexToDataIndex(hoveredIndex, chartModel),
+          name,
           seriesIndex: 0,
         });
       };
     },
-    [chart, chartModel, hoveredIndex],
+    [chart, chartModel, legendHoverIndex],
   );
 
   useClickedStateTooltipSync(chartRef.current, props.clicked);
@@ -208,14 +230,14 @@ export function useChartEvents(
       {
         eventName: "mousemove",
         query: "series",
-        handler: (event: EChartsSeriesMouseEvent) => {
+        handler: (event: EChartsSunburstSeriesMouseEvent) => {
           onHoverChange?.(getHoverData(event, chartModel));
         },
       },
       {
         eventName: "click",
         query: "series",
-        handler: (event: EChartsSeriesMouseEvent) => {
+        handler: (event: EChartsSunburstSeriesMouseEvent) => {
           handleClick(
             event,
             data,