diff --git a/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Default.png b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Default.png
new file mode 100644
index 0000000000000000000000000000000000000000..847a02a9ae74b2a6dc84a41dcd44af59905408f6
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Default.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked.png b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked.png
new file mode 100644
index 0000000000000000000000000000000000000000..021f1fb51b320c4013011f6aa1666e9342989ff4
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked.png differ
diff --git a/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked_Normalized.png b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked_Normalized.png
new file mode 100644
index 0000000000000000000000000000000000000000..a411f14425ef4e609e98c8c69d9d26eeabeaa5ff
Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_ComboChart_Bar_Max_Categories_Stacked_Normalized.png differ
diff --git a/e2e/support/helpers/e2e-visual-tests-helpers.js b/e2e/support/helpers/e2e-visual-tests-helpers.js
index 916f0f475ac22534a973267f79a77ad816ff71e6..270f46e3623de24dedd0d23fab00de739e79c31c 100644
--- a/e2e/support/helpers/e2e-visual-tests-helpers.js
+++ b/e2e/support/helpers/e2e-visual-tests-helpers.js
@@ -81,6 +81,10 @@ export function cartesianChartCircleWithColors(colors) {
   return colors.map(color => cartesianChartCircleWithColor(color));
 }
 
+export function otherSeriesChartPaths() {
+  return chartPathWithFillColor("#949AAB");
+}
+
 export function scatterBubbleWithColor(color) {
   return echartsContainer().find(`path[d="${CIRCLE_PATH}"][fill="${color}"]`);
 }
diff --git a/e2e/test/scenarios/visualizations-charts/bar_chart.cy.spec.js b/e2e/test/scenarios/visualizations-charts/bar_chart.cy.spec.js
index ba06be055aa6b9ea4ea5fb01cfa5746151e215a8..baa5338c876d22a73638fad77c5b44c479566be5 100644
--- a/e2e/test/scenarios/visualizations-charts/bar_chart.cy.spec.js
+++ b/e2e/test/scenarios/visualizations-charts/bar_chart.cy.spec.js
@@ -8,13 +8,17 @@ import {
   createQuestion,
   cypressWaitAll,
   echartsContainer,
+  echartsTooltip,
   getDraggableElements,
   getValueLabels,
   leftSidebar,
   modal,
   moveDnDKitElement,
+  openNotebook,
+  otherSeriesChartPaths,
   popover,
   queryBuilderHeader,
+  queryBuilderMain,
   restore,
   sidebar,
   visitDashboard,
@@ -368,16 +372,18 @@ describe("scenarios > visualizations > bar chart", () => {
     });
 
     cy.findByTestId("viz-settings-button").click();
+    leftSidebar().button("90 more series").click();
     cy.get("[data-testid^=draggable-item]").should("have.length", 100);
 
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("ID is less than 101").click();
-    cy.findByDisplayValue("101").type("{backspace}2");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("Update filter").click();
+    cy.findByTestId("qb-filters-panel")
+      .findByText("ID is less than 101")
+      .click();
+    popover().within(() => {
+      cy.findByDisplayValue("101").type("{backspace}2");
+      cy.button("Update filter").click();
+    });
 
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText(
+    queryBuilderMain().findByText(
       "This chart type doesn't support more than 100 series of data.",
     );
     cy.get("[data-testid^=draggable-item]").should("have.length", 0);
@@ -752,6 +758,200 @@ describe("scenarios > visualizations > bar chart", () => {
     });
     resetHoverState();
   });
+
+  it("should allow grouping series into a single 'Other' series", () => {
+    const AK_SERIES_COLOR = "#509EE3";
+
+    const USER_STATE_FIELD_REF = [
+      "field",
+      PEOPLE.STATE,
+      { "source-field": ORDERS.USER_ID },
+    ];
+    const ORDER_CREATED_AT_FIELD_REF = [
+      "field",
+      ORDERS.CREATED_AT,
+      { "temporal-unit": "month" },
+    ];
+
+    function setMaxCategories(value, { viaBreakoutSettings = false } = {}) {
+      if (viaBreakoutSettings) {
+        leftSidebar().findByTestId("settings-STATE").click();
+      } else {
+        leftSidebar().findByLabelText("Other series settings").click();
+      }
+      popover()
+        .findByTestId("graph-max-categories-input")
+        .type(`{selectAll}${value}`)
+        .blur();
+      cy.wait(500); // wait for viz to re-render
+    }
+
+    function setOtherCategoryAggregationFn(fnName) {
+      leftSidebar().findByLabelText("Other series settings").click();
+      popover()
+        .findByTestId("graph-other-category-aggregation-fn-picker")
+        .click();
+      popover().last().findByText(fnName).click();
+    }
+
+    visitQuestionAdhoc({
+      display: "bar",
+      dataset_query: {
+        type: "query",
+        database: SAMPLE_DB_ID,
+        query: {
+          "source-table": ORDERS_ID,
+          aggregation: [["count"]],
+          breakout: [USER_STATE_FIELD_REF, ORDER_CREATED_AT_FIELD_REF],
+          filter: [
+            "and",
+            [
+              "between",
+              ORDER_CREATED_AT_FIELD_REF,
+              "2022-09-01T00:00Z",
+              "2023-02-01T00:00Z",
+            ],
+            [
+              "=",
+              USER_STATE_FIELD_REF,
+              "AK",
+              "AL",
+              "AR",
+              "AZ",
+              "CA",
+              "CO",
+              "CT",
+              "DE",
+              "FL",
+              "GA",
+              "IA",
+              "ID",
+              "IL",
+              "KY",
+            ],
+          ],
+        },
+      },
+    });
+
+    // Enable 'Other' series
+    cy.findByTestId("viz-settings-button").click();
+    leftSidebar().findByTestId("settings-STATE").click();
+    popover().findByLabelText("Enforce maximum number of series").click();
+
+    // Test 'Other' series renders
+    otherSeriesChartPaths().should("have.length", 6);
+
+    // Test drill-through is disabled for 'Other' series
+    otherSeriesChartPaths().first().click();
+    cy.findByTestId("click-actions-view").should("not.exist");
+
+    // Test drill-through is enabled for regular series
+    chartPathWithFillColor(AK_SERIES_COLOR).first().click();
+    cy.findByTestId("click-actions-view").should("exist");
+
+    // Test legend and series visibility toggling
+    queryBuilderMain()
+      .findAllByTestId("legend-item")
+      .should("have.length", 9)
+      .last()
+      .as("other-series-legend-item");
+    cy.get("@other-series-legend-item").findByLabelText("Hide series").click();
+    otherSeriesChartPaths().should("have.length", 0);
+    cy.get("@other-series-legend-item").findByLabelText("Show series").click();
+    otherSeriesChartPaths().should("have.length", 6);
+
+    // Test tooltips
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Other", value: "9" }] });
+    otherSeriesChartPaths().first().realHover();
+    assertEChartsTooltip({
+      header: "September 2022",
+      rows: [
+        { name: "IA", value: "3" },
+        { name: "KY", value: "2" },
+        { name: "FL", value: "1" },
+        { name: "GA", value: "1" },
+        { name: "ID", value: "1" },
+        { name: "IL", value: "1" },
+        { name: "Total", value: "9" },
+      ],
+    });
+
+    // Test "graph.max_categories" change
+    setMaxCategories(4);
+    queryBuilderMain().click(); // close popover
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    echartsTooltip().find("tr").should("have.length", 5);
+    queryBuilderMain().findAllByTestId("legend-item").should("have.length", 5);
+
+    // Test can move series in/out of "Other" series
+    moveDnDKitElement(getDraggableElements().eq(3), { vertical: 150 }); // Move AZ into "Other"
+    moveDnDKitElement(getDraggableElements().eq(6), { vertical: -150 }); // Move CT out of "Other"
+
+    queryBuilderMain().findAllByTestId("legend-item").should("have.length", 5);
+    queryBuilderMain()
+      .findAllByTestId("legend-item")
+      .contains("AZ")
+      .should("not.exist");
+    queryBuilderMain()
+      .findAllByTestId("legend-item")
+      .contains("CT")
+      .should("exist");
+
+    // Test "graph.max_categories" removes "Other" altogether
+    setMaxCategories(0);
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    echartsTooltip().find("tr").should("have.length", 14);
+    queryBuilderMain().findAllByTestId("legend-item").should("have.length", 14);
+    otherSeriesChartPaths().should("not.exist");
+    setMaxCategories(8, { viaBreakoutSettings: true });
+
+    // Test "graph.other_category_aggregation_fn" for native queries
+    openNotebook();
+    queryBuilderHeader().button("View the SQL").click();
+    cy.findByTestId("native-query-preview-sidebar")
+      .button("Convert this question to SQL")
+      .click();
+    cy.wait("@dataset");
+    queryBuilderMain().findByTestId("visibility-toggler").click();
+
+    cy.findByTestId("viz-settings-button").click();
+    setOtherCategoryAggregationFn("Average");
+
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Other", value: "1.5" }] });
+
+    otherSeriesChartPaths().first().realHover();
+    assertEChartsTooltip({
+      header: "September 2022",
+      rows: [
+        { name: "IA", value: "3" },
+        { name: "KY", value: "2" },
+        { name: "FL", value: "1" },
+        { name: "GA", value: "1" },
+        { name: "ID", value: "1" },
+        { name: "IL", value: "1" },
+        { name: "Average", value: "1.5" },
+      ],
+    });
+
+    setOtherCategoryAggregationFn("Min");
+
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Other", value: "1" }] });
+
+    otherSeriesChartPaths().first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Min", value: "1" }] });
+
+    setOtherCategoryAggregationFn("Max");
+
+    chartPathWithFillColor(AK_SERIES_COLOR).first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Other", value: "3" }] });
+
+    otherSeriesChartPaths().first().realHover();
+    assertEChartsTooltip({ rows: [{ name: "Max", value: "3" }] });
+  });
 });
 
 function resetHoverState() {
diff --git a/frontend/src/metabase-types/api/card.ts b/frontend/src/metabase-types/api/card.ts
index b2e072515390173187c5a3b36b633b218362aec0..babfb5c17ef56b9796a5c600894bae833ebc533e 100644
--- a/frontend/src/metabase-types/api/card.ts
+++ b/frontend/src/metabase-types/api/card.ts
@@ -151,6 +151,15 @@ export type VisualizationSettings = {
   "graph.show_values"?: boolean;
   "stackable.stack_type"?: StackType;
   "graph.show_stack_values"?: StackValuesDisplay;
+  "graph.max_categories_enabled"?: boolean;
+  "graph.max_categories"?: number;
+  "graph.other_category_aggregation_fn"?:
+    | "sum"
+    | "avg"
+    | "min"
+    | "max"
+    | "stddev"
+    | "median";
 
   // Table
   "table.columns"?: TableColumnOrderSetting[];
diff --git a/frontend/src/metabase-types/api/dataset.ts b/frontend/src/metabase-types/api/dataset.ts
index 51e7801e79273b89b200d4b9f48edbf2684a35e0..a6125b49b1ee2a53ed06648bc46ed98fb9c8252c 100644
--- a/frontend/src/metabase-types/api/dataset.ts
+++ b/frontend/src/metabase-types/api/dataset.ts
@@ -18,6 +18,18 @@ export type BinningMetadata = {
   num_bins?: number;
 };
 
+export type AggregationType =
+  | "count"
+  | "sum"
+  | "cum-sum"
+  | "cum-count"
+  | "distinct"
+  | "min"
+  | "max"
+  | "avg"
+  | "median"
+  | "stddev";
+
 export interface DatasetColumn {
   id?: FieldId;
   name: string;
@@ -25,6 +37,9 @@ export interface DatasetColumn {
   description?: string | null;
   source: string;
   aggregation_index?: number;
+
+  aggregation_type?: AggregationType;
+
   coercion_strategy?: string | null;
   visibility_type?: FieldVisibilityType;
   table_id?: TableId;
diff --git a/frontend/src/metabase/core/components/Sortable/SortableList.tsx b/frontend/src/metabase/core/components/Sortable/SortableList.tsx
index eb69a9e1416c0ab8dac616deb64c8dae8712c554..91c72702d14732c2e6a7af54e12c94bb9093a7e7 100644
--- a/frontend/src/metabase/core/components/Sortable/SortableList.tsx
+++ b/frontend/src/metabase/core/components/Sortable/SortableList.tsx
@@ -6,12 +6,17 @@ import type {
 } from "@dnd-kit/core";
 import { DndContext, DragOverlay } from "@dnd-kit/core";
 import { SortableContext, arrayMove } from "@dnd-kit/sortable";
-import { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
 import _ from "underscore";
 
 import GrabberS from "metabase/css/components/grabber.module.css";
 import { isNotNull } from "metabase/lib/types";
 
+export type SortableDivider = {
+  afterIndex: number;
+  renderFn: () => React.ReactNode;
+};
+
 type ItemId = number | string;
 export type DragEndEvent = {
   id: ItemId;
@@ -37,6 +42,7 @@ type SortableListProps<T> = {
   sensors?: SensorDescriptor<any>[];
   modifiers?: Modifier[];
   useDragOverlay?: boolean;
+  dividers?: SortableDivider[];
 };
 
 export const SortableList = <T,>({
@@ -48,6 +54,7 @@ export const SortableList = <T,>({
   sensors = [],
   modifiers = [],
   useDragOverlay = true,
+  dividers,
 }: SortableListProps<T>) => {
   const [itemIds, setItemIds] = useState<ItemId[]>([]);
   const [indexedItems, setIndexedItems] = useState<Partial<Record<ItemId, T>>>(
@@ -55,6 +62,13 @@ export const SortableList = <T,>({
   );
   const [activeItem, setActiveItem] = useState<T | null>(null);
 
+  const dividersByIndex = useMemo(() => {
+    return (dividers ?? []).reduce((acc, item) => {
+      acc.set(item.afterIndex, item);
+      return acc;
+    }, new Map<number, SortableDivider>());
+  }, [dividers]);
+
   useEffect(() => {
     setItemIds(items.map(getId));
     setIndexedItems(_.indexBy(items, getId));
@@ -63,14 +77,20 @@ export const SortableList = <T,>({
   const sortableElements = useMemo(
     () =>
       itemIds
-        .map(id => {
+        .map((id, index) => {
           const item = indexedItems[id];
+          const divider = dividersByIndex.get(index);
           if (item) {
-            return renderItem({ item, id });
+            return (
+              <React.Fragment key={id}>
+                {divider ? divider.renderFn() : null}
+                {renderItem({ item, id })}
+              </React.Fragment>
+            );
           }
         })
         .filter(isNotNull),
-    [itemIds, renderItem, indexedItems],
+    [itemIds, indexedItems, dividersByIndex, renderItem],
   );
 
   const handleDragOver = ({ active, over }: DragOverEvent) => {
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/ComboChart.stories.tsx b/frontend/src/metabase/static-viz/components/ComboChart/ComboChart.stories.tsx
index f99d70299bd89e2798a5593c7ddce39751b4a008..80f820e9668c4b7c6b0996222aa70de71e2d8e43 100644
--- a/frontend/src/metabase/static-viz/components/ComboChart/ComboChart.stories.tsx
+++ b/frontend/src/metabase/static-viz/components/ComboChart/ComboChart.stories.tsx
@@ -973,6 +973,33 @@ export const BarStackedAllLabelsTimeseriesWithGap45717 = {
   },
 };
 
+export const BarMaxCategoriesDefault = {
+  render: Template,
+
+  args: {
+    rawSeries: data.barMaxCategoriesDefault as any,
+    renderingContext,
+  },
+};
+
+export const BarMaxCategoriesStacked = {
+  render: Template,
+
+  args: {
+    rawSeries: data.barMaxCategoriesStacked as any,
+    renderingContext,
+  },
+};
+
+export const BarMaxCategoriesStackedNormalized = {
+  render: Template,
+
+  args: {
+    rawSeries: data.barMaxCategoriesStackedNormalized as any,
+    renderingContext,
+  },
+};
+
 export const OffsetBasedTimezone47835 = {
   render: Template,
   args: {
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-histogram-series-breakout.json b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-histogram-series-breakout.json
index 733b3873499fc590be6d770337c84f1def093b1a..d771e36ea13cfe186b41d45669ecfb6a488cebc1 100644
--- a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-histogram-series-breakout.json
+++ b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-histogram-series-breakout.json
@@ -28,7 +28,8 @@
         "graph.series_order": null,
         "graph.x_axis.scale": "histogram",
         "stackable.stack_type": null,
-        "graph.metrics": ["count"]
+        "graph.metrics": ["count"],
+        "graph.max_categories": 0
       },
       "last-edit-info": {
         "id": 1,
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-default.json b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-default.json
new file mode 100644
index 0000000000000000000000000000000000000000..fe50cdadb550a0bb7b6b93f02e45d389496c2086
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-default.json
@@ -0,0 +1,612 @@
+[
+  {
+    "card": {
+      "cache_invalidated_at": null,
+      "description": null,
+      "archived": false,
+      "view_count": 150,
+      "collection_position": null,
+      "source_card_id": null,
+      "table_id": 5,
+      "can_run_adhoc_query": true,
+      "result_metadata": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "database_type": "CHARACTER",
+          "semantic_type": "type/State",
+          "table_id": 3,
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 43,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 48,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0.0,
+                "percent-url": 0.0,
+                "percent-email": 0.0,
+                "percent-state": 1.0,
+                "average-length": 2.0
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PEOPLE__via__USER_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "TIMESTAMP",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 5,
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/DateTime"
+        },
+        {
+          "base_type": "type/Integer",
+          "name": "count",
+          "display_name": "Count",
+          "semantic_type": "type/Quantity",
+          "source": "aggregation",
+          "field_ref": ["aggregation", 0],
+          "aggregation_index": 0
+        }
+      ],
+      "creator": {
+        "email": "anton@metabase.test",
+        "first_name": "Anton",
+        "last_login": "2024-09-24T15:34:26.000532+01:00",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Kulyk",
+        "date_joined": "2024-08-19T15:09:37.030585+01:00",
+        "common_name": "Anton Kulyk"
+      },
+      "initially_published_at": null,
+      "can_write": true,
+      "database_id": 1,
+      "enable_embedding": false,
+      "collection_id": null,
+      "query_type": "query",
+      "name": "Bar chart with \"Other\"",
+      "last_query_start": "2024-10-03T14:40:03.849841+01:00",
+      "dashboard_count": 1,
+      "last_used_at": "2024-10-03T14:40:03.908296+01:00",
+      "type": "question",
+      "average_query_time": 71.93137254901961,
+      "creator_id": 1,
+      "can_restore": false,
+      "moderation_reviews": [],
+      "updated_at": "2024-10-04T14:53:50.393173+01:00",
+      "made_public_by_id": null,
+      "embedding_params": null,
+      "cache_ttl": null,
+      "dataset_query": {
+        "database": 1,
+        "type": "query",
+        "query": {
+          "source-table": 5,
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ]
+          ],
+          "filter": [
+            "and",
+            [
+              "between",
+              [
+                "field",
+                41,
+                {
+                  "base-type": "type/DateTime",
+                  "temporal-unit": "month"
+                }
+              ],
+              "2022-09-01T00:00Z",
+              "2023-02-01T00:00Z"
+            ],
+            [
+              "=",
+              [
+                "field",
+                48,
+                {
+                  "base-type": "type/Text",
+                  "source-field": 43
+                }
+              ],
+              "AK",
+              "AL",
+              "AR",
+              "AZ",
+              "CA",
+              "CO",
+              "CT",
+              "DE",
+              "FL",
+              "GA",
+              "IA",
+              "ID",
+              "IL",
+              "KY"
+            ]
+          ]
+        }
+      },
+      "id": 47,
+      "parameter_mappings": [],
+      "display": "bar",
+      "archived_directly": false,
+      "entity_id": "ez2Yb1JhGrltIJMNwZfwU",
+      "collection_preview": true,
+      "last-edit-info": {
+        "id": 1,
+        "email": "anton@metabase.test",
+        "first_name": "Anton",
+        "last_name": "Kulyk",
+        "timestamp": "2024-10-04T14:53:50.464787+01:00"
+      },
+      "visualization_settings": {
+        "graph.max_categories_enabled": true,
+        "graph.max_categories": 8,
+        "graph.dimensions": ["CREATED_AT", "STATE"],
+        "graph.series_order": [
+          {
+            "key": "AK",
+            "color": "#509EE3",
+            "enabled": true,
+            "name": "AK"
+          },
+          {
+            "key": "AL",
+            "color": "#227FD2",
+            "enabled": true,
+            "name": "AL"
+          },
+          {
+            "key": "AR",
+            "color": "#88BF4D",
+            "enabled": true,
+            "name": "AR"
+          },
+          {
+            "key": "AZ",
+            "color": "#689636",
+            "enabled": true,
+            "name": "AZ"
+          },
+          {
+            "key": "CA",
+            "color": "#A989C5",
+            "enabled": true,
+            "name": "CA"
+          },
+          {
+            "key": "CO",
+            "color": "#8A5EB0",
+            "enabled": true,
+            "name": "CO"
+          },
+          {
+            "key": "CT",
+            "color": "#EF8C8C",
+            "enabled": true,
+            "name": "CT"
+          },
+          {
+            "key": "DE",
+            "color": "#E75454",
+            "enabled": true,
+            "name": "DE"
+          },
+          {
+            "key": "GA",
+            "color": "#F9D45C",
+            "enabled": true,
+            "name": "GA"
+          },
+          {
+            "key": "IA",
+            "color": "#F7C41F",
+            "enabled": true,
+            "name": "IA"
+          },
+          {
+            "key": "ID",
+            "color": "#F2A86F",
+            "enabled": true,
+            "name": "ID"
+          },
+          {
+            "key": "KY",
+            "color": "#ED8535",
+            "enabled": true,
+            "name": "KY"
+          },
+          {
+            "key": "LA",
+            "color": "#98D9D9",
+            "enabled": true,
+            "name": "LA"
+          }
+        ],
+        "graph.series_order_dimension": "STATE",
+        "stackable.stack_type": null,
+        "pie.dimension": ["STATE"],
+        "graph.metrics": ["count"]
+      },
+      "collection": {
+        "metabase.models.collection.root/is-root?": true,
+        "authority_level": null,
+        "name": "Our analytics",
+        "is_personal": false,
+        "id": "root",
+        "can_write": true
+      },
+      "metabase_version": "v0.1.37-SNAPSHOT (5b4a5d6)",
+      "parameters": [],
+      "created_at": "2024-10-01T13:37:28.812936+01:00",
+      "parameter_usage_count": 0,
+      "public_uuid": null,
+      "can_delete": false
+    },
+    "data": {
+      "rows": [
+        ["AK", "2022-09-01T00:00:00+01:00", 2],
+        ["AK", "2022-10-01T00:00:00+01:00", 3],
+        ["AK", "2022-11-01T00:00:00Z", 1],
+        ["AK", "2022-12-01T00:00:00Z", 3],
+        ["AK", "2023-01-01T00:00:00Z", 9],
+        ["AK", "2023-02-01T00:00:00Z", 4],
+        ["AL", "2022-09-01T00:00:00+01:00", 1],
+        ["AL", "2022-10-01T00:00:00+01:00", 3],
+        ["AL", "2022-11-01T00:00:00Z", 2],
+        ["AL", "2022-12-01T00:00:00Z", 6],
+        ["AL", "2023-01-01T00:00:00Z", 6],
+        ["AL", "2023-02-01T00:00:00Z", 6],
+        ["AR", "2022-10-01T00:00:00+01:00", 2],
+        ["AR", "2022-11-01T00:00:00Z", 4],
+        ["AR", "2022-12-01T00:00:00Z", 3],
+        ["AR", "2023-01-01T00:00:00Z", 4],
+        ["AR", "2023-02-01T00:00:00Z", 1],
+        ["AZ", "2023-01-01T00:00:00Z", 1],
+        ["AZ", "2023-02-01T00:00:00Z", 1],
+        ["CA", "2022-09-01T00:00:00+01:00", 5],
+        ["CA", "2022-10-01T00:00:00+01:00", 5],
+        ["CA", "2022-11-01T00:00:00Z", 4],
+        ["CA", "2022-12-01T00:00:00Z", 6],
+        ["CA", "2023-01-01T00:00:00Z", 11],
+        ["CA", "2023-02-01T00:00:00Z", 11],
+        ["CO", "2022-09-01T00:00:00+01:00", 4],
+        ["CO", "2022-10-01T00:00:00+01:00", 6],
+        ["CO", "2022-11-01T00:00:00Z", 12],
+        ["CO", "2022-12-01T00:00:00Z", 8],
+        ["CO", "2023-01-01T00:00:00Z", 7],
+        ["CO", "2023-02-01T00:00:00Z", 9],
+        ["CT", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-12-01T00:00:00Z", 1],
+        ["FL", "2022-09-01T00:00:00+01:00", 1],
+        ["FL", "2022-10-01T00:00:00+01:00", 2],
+        ["FL", "2022-11-01T00:00:00Z", 4],
+        ["FL", "2023-01-01T00:00:00Z", 3],
+        ["FL", "2023-02-01T00:00:00Z", 4],
+        ["GA", "2022-09-01T00:00:00+01:00", 1],
+        ["GA", "2022-10-01T00:00:00+01:00", 7],
+        ["GA", "2022-11-01T00:00:00Z", 3],
+        ["GA", "2022-12-01T00:00:00Z", 1],
+        ["GA", "2023-01-01T00:00:00Z", 8],
+        ["GA", "2023-02-01T00:00:00Z", 3],
+        ["IA", "2022-09-01T00:00:00+01:00", 3],
+        ["IA", "2022-10-01T00:00:00+01:00", 4],
+        ["IA", "2022-11-01T00:00:00Z", 5],
+        ["IA", "2022-12-01T00:00:00Z", 5],
+        ["IA", "2023-01-01T00:00:00Z", 10],
+        ["IA", "2023-02-01T00:00:00Z", 7],
+        ["ID", "2022-09-01T00:00:00+01:00", 1],
+        ["ID", "2022-10-01T00:00:00+01:00", 1],
+        ["ID", "2022-11-01T00:00:00Z", 1],
+        ["ID", "2022-12-01T00:00:00Z", 2],
+        ["ID", "2023-01-01T00:00:00Z", 3],
+        ["ID", "2023-02-01T00:00:00Z", 4],
+        ["IL", "2022-09-01T00:00:00+01:00", 1],
+        ["IL", "2022-10-01T00:00:00+01:00", 3],
+        ["IL", "2022-11-01T00:00:00Z", 3],
+        ["IL", "2022-12-01T00:00:00Z", 6],
+        ["IL", "2023-01-01T00:00:00Z", 5],
+        ["IL", "2023-02-01T00:00:00Z", 5],
+        ["KY", "2022-09-01T00:00:00+01:00", 2],
+        ["KY", "2022-10-01T00:00:00+01:00", 5],
+        ["KY", "2022-11-01T00:00:00Z", 5],
+        ["KY", "2022-12-01T00:00:00Z", 4],
+        ["KY", "2023-01-01T00:00:00Z", 4],
+        ["KY", "2023-02-01T00:00:00Z", 3]
+      ],
+      "cols": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "database_type": "CHARACTER",
+          "semantic_type": "type/State",
+          "table_id": 3,
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 43,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 48,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0.0,
+                "percent-url": 0.0,
+                "percent-email": 0.0,
+                "percent-state": 1.0,
+                "average-length": 2.0
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PEOPLE__via__USER_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "TIMESTAMP",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 5,
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/DateTime"
+        },
+        {
+          "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 \"PEOPLE__via__USER_ID\".\"STATE\" AS \"PEOPLE__via__USER_ID__STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") AS \"CREATED_AT\", COUNT(*) AS \"count\" FROM \"PUBLIC\".\"ORDERS\" LEFT JOIN \"PUBLIC\".\"PEOPLE\" AS \"PEOPLE__via__USER_ID\" ON \"PUBLIC\".\"ORDERS\".\"USER_ID\" = \"PEOPLE__via__USER_ID\".\"ID\" WHERE (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" >= timestamp '2022-09-01 00:00:00.000') AND (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" < timestamp '2023-03-01 00:00:00.000') AND ((\"PEOPLE__via__USER_ID\".\"STATE\" = 'AK') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AR') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AZ') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CO') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CT') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'DE') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'FL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'GA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'ID') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'KY')) GROUP BY \"PEOPLE__via__USER_ID\".\"STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ORDER BY \"PEOPLE__via__USER_ID\".\"STATE\" ASC, DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ASC",
+        "params": null
+      },
+      "format-rows?": true,
+      "results_timezone": "Europe/Lisbon",
+      "results_metadata": {
+        "columns": [
+          {
+            "description": "The state or province of the account’s billing address",
+            "semantic_type": "type/State",
+            "coercion_strategy": null,
+            "name": "STATE",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 48,
+            "visibility_type": "normal",
+            "display_name": "User → State",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 49,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0.0,
+                  "percent-url": 0.0,
+                  "percent-email": 0.0,
+                  "percent-state": 1.0,
+                  "average-length": 2.0
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "description": "The date and time an order was submitted.",
+            "semantic_type": "type/CreationTimestamp",
+            "coercion_strategy": null,
+            "unit": "month",
+            "name": "CREATED_AT",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ],
+            "effective_type": "type/DateTime",
+            "id": 41,
+            "visibility_type": "normal",
+            "display_name": "Created At",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 10001,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/DateTime": {
+                  "earliest": "2022-04-30T18:56:13.352Z",
+                  "latest": "2026-04-19T14:07:15.657Z"
+                }
+              }
+            },
+            "base_type": "type/DateTime"
+          },
+          {
+            "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": 12,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1.0,
+                  "q1": 1.8849307066960952,
+                  "q3": 5.5,
+                  "max": 12.0,
+                  "sd": 2.737211604119948,
+                  "avg": 4.086956521739131
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": [
+        {
+          "previous-value": 4,
+          "unit": "month",
+          "offset": -377.8969468095498,
+          "last-change": -0.25,
+          "col": "count",
+          "slope": 0.019777876708186145,
+          "last-value": 3,
+          "best-fit": [
+            "*",
+            4.201731368215701e-45,
+            ["exp", ["*", 0.005350764152580669, "x"]]
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked-normalized.json b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked-normalized.json
new file mode 100644
index 0000000000000000000000000000000000000000..5c9bd15df42f3352b4a5b60e57453d1c97c69d55
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked-normalized.json
@@ -0,0 +1,606 @@
+[
+  {
+    "card": {
+      "cache_invalidated_at": null,
+      "description": null,
+      "archived": false,
+      "view_count": 151,
+      "collection_position": null,
+      "source_card_id": null,
+      "table_id": 5,
+      "can_run_adhoc_query": true,
+      "result_metadata": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "semantic_type": "type/State",
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 48,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 1,
+                "average-length": 2
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "semantic_type": "type/CreationTimestamp",
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "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/DateTime"
+        },
+        {
+          "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": 12,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 1.8849307066960952,
+                "q3": 5.5,
+                "max": 12,
+                "sd": 2.737211604119948,
+                "avg": 4.086956521739131
+              }
+            }
+          }
+        }
+      ],
+      "creator": {
+        "email": "anton@metabase.test",
+        "first_name": "Anton",
+        "last_login": "2024-09-24T15:34:26.000532+01:00",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Kulyk",
+        "date_joined": "2024-08-19T15:09:37.030585+01:00",
+        "common_name": "Anton Kulyk"
+      },
+      "initially_published_at": null,
+      "can_write": true,
+      "database_id": 1,
+      "enable_embedding": false,
+      "collection_id": null,
+      "query_type": "query",
+      "name": "Bar chart with \"Other\"",
+      "last_query_start": "2024-10-04T14:53:58.731799+01:00",
+      "dashboard_count": 1,
+      "last_used_at": "2024-10-04T14:53:58.783195+01:00",
+      "type": "question",
+      "average_query_time": 71.96116504854369,
+      "creator_id": 1,
+      "can_restore": false,
+      "moderation_reviews": [],
+      "updated_at": "2024-10-04T15:00:55.349248+01:00",
+      "made_public_by_id": null,
+      "embedding_params": null,
+      "cache_ttl": null,
+      "dataset_query": {
+        "database": 1,
+        "type": "query",
+        "query": {
+          "source-table": 5,
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ]
+          ],
+          "filter": [
+            "and",
+            [
+              "between",
+              [
+                "field",
+                41,
+                {
+                  "base-type": "type/DateTime",
+                  "temporal-unit": "month"
+                }
+              ],
+              "2022-09-01T00:00Z",
+              "2023-02-01T00:00Z"
+            ],
+            [
+              "=",
+              [
+                "field",
+                48,
+                {
+                  "base-type": "type/Text",
+                  "source-field": 43
+                }
+              ],
+              "AK",
+              "AL",
+              "AR",
+              "AZ",
+              "CA",
+              "CO",
+              "CT",
+              "DE",
+              "FL",
+              "GA",
+              "IA",
+              "ID",
+              "IL",
+              "KY"
+            ]
+          ]
+        }
+      },
+      "id": 47,
+      "parameter_mappings": [],
+      "display": "bar",
+      "archived_directly": false,
+      "entity_id": "ez2Yb1JhGrltIJMNwZfwU",
+      "collection_preview": true,
+      "last-edit-info": {
+        "timestamp": "2024-10-04T14:00:55.394Z",
+        "id": 1,
+        "first_name": "Anton",
+        "last_name": "Kulyk",
+        "email": "anton@metabase.test"
+      },
+      "visualization_settings": {
+        "graph.max_categories_enabled": true,
+        "graph.max_categories": 10,
+        "graph.dimensions": ["CREATED_AT", "STATE"],
+        "graph.series_order": [
+          {
+            "key": "AK",
+            "color": "#509EE3",
+            "enabled": true,
+            "name": "AK"
+          },
+          {
+            "key": "AL",
+            "color": "#227FD2",
+            "enabled": true,
+            "name": "AL"
+          },
+          {
+            "key": "AR",
+            "color": "#88BF4D",
+            "enabled": true,
+            "name": "AR"
+          },
+          {
+            "key": "AZ",
+            "color": "#689636",
+            "enabled": true,
+            "name": "AZ"
+          },
+          {
+            "key": "CA",
+            "color": "#A989C5",
+            "enabled": true,
+            "name": "CA"
+          },
+          {
+            "key": "CO",
+            "color": "#8A5EB0",
+            "enabled": true,
+            "name": "CO"
+          },
+          {
+            "key": "CT",
+            "color": "#EF8C8C",
+            "enabled": true,
+            "name": "CT"
+          },
+          {
+            "key": "DE",
+            "color": "#E75454",
+            "enabled": true,
+            "name": "DE"
+          },
+          {
+            "key": "GA",
+            "color": "#F9D45C",
+            "enabled": true,
+            "name": "GA"
+          },
+          {
+            "key": "IA",
+            "color": "#F7C41F",
+            "enabled": true,
+            "name": "IA"
+          },
+          {
+            "key": "ID",
+            "color": "#F2A86F",
+            "enabled": true,
+            "name": "ID"
+          },
+          {
+            "key": "KY",
+            "color": "#ED8535",
+            "enabled": true,
+            "name": "KY"
+          },
+          {
+            "key": "LA",
+            "color": "#98D9D9",
+            "enabled": true,
+            "name": "LA"
+          }
+        ],
+        "graph.series_order_dimension": "STATE",
+        "stackable.stack_type": "normalized",
+        "pie.dimension": ["STATE"],
+        "graph.metrics": ["count"]
+      },
+      "collection": null,
+      "metabase_version": "v0.1.37-SNAPSHOT (5b4a5d6)",
+      "parameters": [],
+      "created_at": "2024-10-01T13:37:28.812936+01:00",
+      "parameter_usage_count": 0,
+      "public_uuid": null,
+      "can_delete": false
+    },
+    "data": {
+      "rows": [
+        ["AK", "2022-09-01T00:00:00+01:00", 2],
+        ["AK", "2022-10-01T00:00:00+01:00", 3],
+        ["AK", "2022-11-01T00:00:00Z", 1],
+        ["AK", "2022-12-01T00:00:00Z", 3],
+        ["AK", "2023-01-01T00:00:00Z", 9],
+        ["AK", "2023-02-01T00:00:00Z", 4],
+        ["AL", "2022-09-01T00:00:00+01:00", 1],
+        ["AL", "2022-10-01T00:00:00+01:00", 3],
+        ["AL", "2022-11-01T00:00:00Z", 2],
+        ["AL", "2022-12-01T00:00:00Z", 6],
+        ["AL", "2023-01-01T00:00:00Z", 6],
+        ["AL", "2023-02-01T00:00:00Z", 6],
+        ["AR", "2022-10-01T00:00:00+01:00", 2],
+        ["AR", "2022-11-01T00:00:00Z", 4],
+        ["AR", "2022-12-01T00:00:00Z", 3],
+        ["AR", "2023-01-01T00:00:00Z", 4],
+        ["AR", "2023-02-01T00:00:00Z", 1],
+        ["AZ", "2023-01-01T00:00:00Z", 1],
+        ["AZ", "2023-02-01T00:00:00Z", 1],
+        ["CA", "2022-09-01T00:00:00+01:00", 5],
+        ["CA", "2022-10-01T00:00:00+01:00", 5],
+        ["CA", "2022-11-01T00:00:00Z", 4],
+        ["CA", "2022-12-01T00:00:00Z", 6],
+        ["CA", "2023-01-01T00:00:00Z", 11],
+        ["CA", "2023-02-01T00:00:00Z", 11],
+        ["CO", "2022-09-01T00:00:00+01:00", 4],
+        ["CO", "2022-10-01T00:00:00+01:00", 6],
+        ["CO", "2022-11-01T00:00:00Z", 12],
+        ["CO", "2022-12-01T00:00:00Z", 8],
+        ["CO", "2023-01-01T00:00:00Z", 7],
+        ["CO", "2023-02-01T00:00:00Z", 9],
+        ["CT", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-12-01T00:00:00Z", 1],
+        ["FL", "2022-09-01T00:00:00+01:00", 1],
+        ["FL", "2022-10-01T00:00:00+01:00", 2],
+        ["FL", "2022-11-01T00:00:00Z", 4],
+        ["FL", "2023-01-01T00:00:00Z", 3],
+        ["FL", "2023-02-01T00:00:00Z", 4],
+        ["GA", "2022-09-01T00:00:00+01:00", 1],
+        ["GA", "2022-10-01T00:00:00+01:00", 7],
+        ["GA", "2022-11-01T00:00:00Z", 3],
+        ["GA", "2022-12-01T00:00:00Z", 1],
+        ["GA", "2023-01-01T00:00:00Z", 8],
+        ["GA", "2023-02-01T00:00:00Z", 3],
+        ["IA", "2022-09-01T00:00:00+01:00", 3],
+        ["IA", "2022-10-01T00:00:00+01:00", 4],
+        ["IA", "2022-11-01T00:00:00Z", 5],
+        ["IA", "2022-12-01T00:00:00Z", 5],
+        ["IA", "2023-01-01T00:00:00Z", 10],
+        ["IA", "2023-02-01T00:00:00Z", 7],
+        ["ID", "2022-09-01T00:00:00+01:00", 1],
+        ["ID", "2022-10-01T00:00:00+01:00", 1],
+        ["ID", "2022-11-01T00:00:00Z", 1],
+        ["ID", "2022-12-01T00:00:00Z", 2],
+        ["ID", "2023-01-01T00:00:00Z", 3],
+        ["ID", "2023-02-01T00:00:00Z", 4],
+        ["IL", "2022-09-01T00:00:00+01:00", 1],
+        ["IL", "2022-10-01T00:00:00+01:00", 3],
+        ["IL", "2022-11-01T00:00:00Z", 3],
+        ["IL", "2022-12-01T00:00:00Z", 6],
+        ["IL", "2023-01-01T00:00:00Z", 5],
+        ["IL", "2023-02-01T00:00:00Z", 5],
+        ["KY", "2022-09-01T00:00:00+01:00", 2],
+        ["KY", "2022-10-01T00:00:00+01:00", 5],
+        ["KY", "2022-11-01T00:00:00Z", 5],
+        ["KY", "2022-12-01T00:00:00Z", 4],
+        ["KY", "2023-01-01T00:00:00Z", 4],
+        ["KY", "2023-02-01T00:00:00Z", 3]
+      ],
+      "cols": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "database_type": "CHARACTER",
+          "semantic_type": "type/State",
+          "table_id": 3,
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 43,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 48,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0.0,
+                "percent-url": 0.0,
+                "percent-email": 0.0,
+                "percent-state": 1.0,
+                "average-length": 2.0
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PEOPLE__via__USER_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "TIMESTAMP",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 5,
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/DateTime"
+        },
+        {
+          "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 \"PEOPLE__via__USER_ID\".\"STATE\" AS \"PEOPLE__via__USER_ID__STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") AS \"CREATED_AT\", COUNT(*) AS \"count\" FROM \"PUBLIC\".\"ORDERS\" LEFT JOIN \"PUBLIC\".\"PEOPLE\" AS \"PEOPLE__via__USER_ID\" ON \"PUBLIC\".\"ORDERS\".\"USER_ID\" = \"PEOPLE__via__USER_ID\".\"ID\" WHERE (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" >= timestamp '2022-09-01 00:00:00.000') AND (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" < timestamp '2023-03-01 00:00:00.000') AND ((\"PEOPLE__via__USER_ID\".\"STATE\" = 'AK') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AR') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AZ') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CO') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CT') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'DE') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'FL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'GA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'ID') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'KY')) GROUP BY \"PEOPLE__via__USER_ID\".\"STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ORDER BY \"PEOPLE__via__USER_ID\".\"STATE\" ASC, DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ASC",
+        "params": null
+      },
+      "format-rows?": true,
+      "results_timezone": "Europe/Lisbon",
+      "results_metadata": {
+        "columns": [
+          {
+            "description": "The state or province of the account’s billing address",
+            "semantic_type": "type/State",
+            "coercion_strategy": null,
+            "name": "STATE",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 48,
+            "visibility_type": "normal",
+            "display_name": "User → State",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 49,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0.0,
+                  "percent-url": 0.0,
+                  "percent-email": 0.0,
+                  "percent-state": 1.0,
+                  "average-length": 2.0
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "description": "The date and time an order was submitted.",
+            "semantic_type": "type/CreationTimestamp",
+            "coercion_strategy": null,
+            "unit": "month",
+            "name": "CREATED_AT",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ],
+            "effective_type": "type/DateTime",
+            "id": 41,
+            "visibility_type": "normal",
+            "display_name": "Created At",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 10001,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/DateTime": {
+                  "earliest": "2022-04-30T18:56:13.352Z",
+                  "latest": "2026-04-19T14:07:15.657Z"
+                }
+              }
+            },
+            "base_type": "type/DateTime"
+          },
+          {
+            "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": 12,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1.0,
+                  "q1": 1.8849307066960952,
+                  "q3": 5.5,
+                  "max": 12.0,
+                  "sd": 2.737211604119948,
+                  "avg": 4.086956521739131
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": [
+        {
+          "previous-value": 4,
+          "unit": "month",
+          "offset": -377.8969468095498,
+          "last-change": -0.25,
+          "col": "count",
+          "slope": 0.019777876708186145,
+          "last-value": 3,
+          "best-fit": [
+            "*",
+            4.201731368215701e-45,
+            ["exp", ["*", 0.005350764152580669, "x"]]
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked.json b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked.json
new file mode 100644
index 0000000000000000000000000000000000000000..1dd35791d4954328b261fd884871c78ea4cb09ef
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/bar-max-categories-stacked.json
@@ -0,0 +1,606 @@
+[
+  {
+    "card": {
+      "cache_invalidated_at": null,
+      "description": null,
+      "archived": false,
+      "view_count": 151,
+      "collection_position": null,
+      "source_card_id": null,
+      "table_id": 5,
+      "can_run_adhoc_query": true,
+      "result_metadata": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "semantic_type": "type/State",
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "id": 48,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0,
+                "percent-url": 0,
+                "percent-email": 0,
+                "percent-state": 1,
+                "average-length": 2
+              }
+            }
+          },
+          "base_type": "type/Text"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "semantic_type": "type/CreationTimestamp",
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "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/DateTime"
+        },
+        {
+          "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": 12,
+              "nil%": 0
+            },
+            "type": {
+              "type/Number": {
+                "min": 1,
+                "q1": 1.8849307066960952,
+                "q3": 5.5,
+                "max": 12,
+                "sd": 2.737211604119948,
+                "avg": 4.086956521739131
+              }
+            }
+          }
+        }
+      ],
+      "creator": {
+        "email": "anton@metabase.test",
+        "first_name": "Anton",
+        "last_login": "2024-09-24T15:34:26.000532+01:00",
+        "is_qbnewb": false,
+        "is_superuser": true,
+        "id": 1,
+        "last_name": "Kulyk",
+        "date_joined": "2024-08-19T15:09:37.030585+01:00",
+        "common_name": "Anton Kulyk"
+      },
+      "initially_published_at": null,
+      "can_write": true,
+      "database_id": 1,
+      "enable_embedding": false,
+      "collection_id": null,
+      "query_type": "query",
+      "name": "Bar chart with \"Other\"",
+      "last_query_start": "2024-10-04T14:53:58.731799+01:00",
+      "dashboard_count": 1,
+      "last_used_at": "2024-10-04T14:53:58.783195+01:00",
+      "type": "question",
+      "average_query_time": 71.96116504854369,
+      "creator_id": 1,
+      "can_restore": false,
+      "moderation_reviews": [],
+      "updated_at": "2024-10-04T14:58:53.488082+01:00",
+      "made_public_by_id": null,
+      "embedding_params": null,
+      "cache_ttl": null,
+      "dataset_query": {
+        "database": 1,
+        "type": "query",
+        "query": {
+          "source-table": 5,
+          "aggregation": [["count"]],
+          "breakout": [
+            [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ]
+          ],
+          "filter": [
+            "and",
+            [
+              "between",
+              [
+                "field",
+                41,
+                {
+                  "base-type": "type/DateTime",
+                  "temporal-unit": "month"
+                }
+              ],
+              "2022-09-01T00:00Z",
+              "2023-02-01T00:00Z"
+            ],
+            [
+              "=",
+              [
+                "field",
+                48,
+                {
+                  "base-type": "type/Text",
+                  "source-field": 43
+                }
+              ],
+              "AK",
+              "AL",
+              "AR",
+              "AZ",
+              "CA",
+              "CO",
+              "CT",
+              "DE",
+              "FL",
+              "GA",
+              "IA",
+              "ID",
+              "IL",
+              "KY"
+            ]
+          ]
+        }
+      },
+      "id": 47,
+      "parameter_mappings": [],
+      "display": "bar",
+      "archived_directly": false,
+      "entity_id": "ez2Yb1JhGrltIJMNwZfwU",
+      "collection_preview": true,
+      "last-edit-info": {
+        "timestamp": "2024-10-04T13:58:53.546Z",
+        "id": 1,
+        "first_name": "Anton",
+        "last_name": "Kulyk",
+        "email": "anton@metabase.test"
+      },
+      "visualization_settings": {
+        "graph.max_categories_enabled": true,
+        "graph.max_categories": 4,
+        "graph.dimensions": ["CREATED_AT", "STATE"],
+        "graph.series_order": [
+          {
+            "key": "AK",
+            "color": "#509EE3",
+            "enabled": true,
+            "name": "AK"
+          },
+          {
+            "key": "AL",
+            "color": "#227FD2",
+            "enabled": true,
+            "name": "AL"
+          },
+          {
+            "key": "AR",
+            "color": "#88BF4D",
+            "enabled": true,
+            "name": "AR"
+          },
+          {
+            "key": "AZ",
+            "color": "#689636",
+            "enabled": true,
+            "name": "AZ"
+          },
+          {
+            "key": "CA",
+            "color": "#A989C5",
+            "enabled": true,
+            "name": "CA"
+          },
+          {
+            "key": "CO",
+            "color": "#8A5EB0",
+            "enabled": true,
+            "name": "CO"
+          },
+          {
+            "key": "CT",
+            "color": "#EF8C8C",
+            "enabled": true,
+            "name": "CT"
+          },
+          {
+            "key": "DE",
+            "color": "#E75454",
+            "enabled": true,
+            "name": "DE"
+          },
+          {
+            "key": "GA",
+            "color": "#F9D45C",
+            "enabled": true,
+            "name": "GA"
+          },
+          {
+            "key": "IA",
+            "color": "#F7C41F",
+            "enabled": true,
+            "name": "IA"
+          },
+          {
+            "key": "ID",
+            "color": "#F2A86F",
+            "enabled": true,
+            "name": "ID"
+          },
+          {
+            "key": "KY",
+            "color": "#ED8535",
+            "enabled": true,
+            "name": "KY"
+          },
+          {
+            "key": "LA",
+            "color": "#98D9D9",
+            "enabled": true,
+            "name": "LA"
+          }
+        ],
+        "graph.series_order_dimension": "STATE",
+        "stackable.stack_type": "stacked",
+        "pie.dimension": ["STATE"],
+        "graph.metrics": ["count"]
+      },
+      "collection": null,
+      "metabase_version": "v0.1.37-SNAPSHOT (5b4a5d6)",
+      "parameters": [],
+      "created_at": "2024-10-01T13:37:28.812936+01:00",
+      "parameter_usage_count": 0,
+      "public_uuid": null,
+      "can_delete": false
+    },
+    "data": {
+      "rows": [
+        ["AK", "2022-09-01T00:00:00+01:00", 2],
+        ["AK", "2022-10-01T00:00:00+01:00", 3],
+        ["AK", "2022-11-01T00:00:00Z", 1],
+        ["AK", "2022-12-01T00:00:00Z", 3],
+        ["AK", "2023-01-01T00:00:00Z", 9],
+        ["AK", "2023-02-01T00:00:00Z", 4],
+        ["AL", "2022-09-01T00:00:00+01:00", 1],
+        ["AL", "2022-10-01T00:00:00+01:00", 3],
+        ["AL", "2022-11-01T00:00:00Z", 2],
+        ["AL", "2022-12-01T00:00:00Z", 6],
+        ["AL", "2023-01-01T00:00:00Z", 6],
+        ["AL", "2023-02-01T00:00:00Z", 6],
+        ["AR", "2022-10-01T00:00:00+01:00", 2],
+        ["AR", "2022-11-01T00:00:00Z", 4],
+        ["AR", "2022-12-01T00:00:00Z", 3],
+        ["AR", "2023-01-01T00:00:00Z", 4],
+        ["AR", "2023-02-01T00:00:00Z", 1],
+        ["AZ", "2023-01-01T00:00:00Z", 1],
+        ["AZ", "2023-02-01T00:00:00Z", 1],
+        ["CA", "2022-09-01T00:00:00+01:00", 5],
+        ["CA", "2022-10-01T00:00:00+01:00", 5],
+        ["CA", "2022-11-01T00:00:00Z", 4],
+        ["CA", "2022-12-01T00:00:00Z", 6],
+        ["CA", "2023-01-01T00:00:00Z", 11],
+        ["CA", "2023-02-01T00:00:00Z", 11],
+        ["CO", "2022-09-01T00:00:00+01:00", 4],
+        ["CO", "2022-10-01T00:00:00+01:00", 6],
+        ["CO", "2022-11-01T00:00:00Z", 12],
+        ["CO", "2022-12-01T00:00:00Z", 8],
+        ["CO", "2023-01-01T00:00:00Z", 7],
+        ["CO", "2023-02-01T00:00:00Z", 9],
+        ["CT", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-10-01T00:00:00+01:00", 1],
+        ["DE", "2022-12-01T00:00:00Z", 1],
+        ["FL", "2022-09-01T00:00:00+01:00", 1],
+        ["FL", "2022-10-01T00:00:00+01:00", 2],
+        ["FL", "2022-11-01T00:00:00Z", 4],
+        ["FL", "2023-01-01T00:00:00Z", 3],
+        ["FL", "2023-02-01T00:00:00Z", 4],
+        ["GA", "2022-09-01T00:00:00+01:00", 1],
+        ["GA", "2022-10-01T00:00:00+01:00", 7],
+        ["GA", "2022-11-01T00:00:00Z", 3],
+        ["GA", "2022-12-01T00:00:00Z", 1],
+        ["GA", "2023-01-01T00:00:00Z", 8],
+        ["GA", "2023-02-01T00:00:00Z", 3],
+        ["IA", "2022-09-01T00:00:00+01:00", 3],
+        ["IA", "2022-10-01T00:00:00+01:00", 4],
+        ["IA", "2022-11-01T00:00:00Z", 5],
+        ["IA", "2022-12-01T00:00:00Z", 5],
+        ["IA", "2023-01-01T00:00:00Z", 10],
+        ["IA", "2023-02-01T00:00:00Z", 7],
+        ["ID", "2022-09-01T00:00:00+01:00", 1],
+        ["ID", "2022-10-01T00:00:00+01:00", 1],
+        ["ID", "2022-11-01T00:00:00Z", 1],
+        ["ID", "2022-12-01T00:00:00Z", 2],
+        ["ID", "2023-01-01T00:00:00Z", 3],
+        ["ID", "2023-02-01T00:00:00Z", 4],
+        ["IL", "2022-09-01T00:00:00+01:00", 1],
+        ["IL", "2022-10-01T00:00:00+01:00", 3],
+        ["IL", "2022-11-01T00:00:00Z", 3],
+        ["IL", "2022-12-01T00:00:00Z", 6],
+        ["IL", "2023-01-01T00:00:00Z", 5],
+        ["IL", "2023-02-01T00:00:00Z", 5],
+        ["KY", "2022-09-01T00:00:00+01:00", 2],
+        ["KY", "2022-10-01T00:00:00+01:00", 5],
+        ["KY", "2022-11-01T00:00:00Z", 5],
+        ["KY", "2022-12-01T00:00:00Z", 4],
+        ["KY", "2023-01-01T00:00:00Z", 4],
+        ["KY", "2023-02-01T00:00:00Z", 3]
+      ],
+      "cols": [
+        {
+          "description": "The state or province of the account’s billing address",
+          "database_type": "CHARACTER",
+          "semantic_type": "type/State",
+          "table_id": 3,
+          "coercion_strategy": null,
+          "name": "STATE",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "fk_field_id": 43,
+          "field_ref": [
+            "field",
+            48,
+            {
+              "base-type": "type/Text",
+              "source-field": 43
+            }
+          ],
+          "effective_type": "type/Text",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 48,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "User → State",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 49,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/Text": {
+                "percent-json": 0.0,
+                "percent-url": 0.0,
+                "percent-email": 0.0,
+                "percent-state": 1.0,
+                "average-length": 2.0
+              }
+            }
+          },
+          "base_type": "type/Text",
+          "source_alias": "PEOPLE__via__USER_ID"
+        },
+        {
+          "description": "The date and time an order was submitted.",
+          "database_type": "TIMESTAMP",
+          "semantic_type": "type/CreationTimestamp",
+          "table_id": 5,
+          "coercion_strategy": null,
+          "unit": "month",
+          "name": "CREATED_AT",
+          "settings": null,
+          "source": "breakout",
+          "fk_target_field_id": null,
+          "field_ref": [
+            "field",
+            41,
+            {
+              "base-type": "type/DateTime",
+              "temporal-unit": "month"
+            }
+          ],
+          "effective_type": "type/DateTime",
+          "nfc_path": null,
+          "parent_id": null,
+          "id": 41,
+          "position": 7,
+          "visibility_type": "normal",
+          "display_name": "Created At",
+          "fingerprint": {
+            "global": {
+              "distinct-count": 10001,
+              "nil%": 0.0
+            },
+            "type": {
+              "type/DateTime": {
+                "earliest": "2022-04-30T18:56:13.352Z",
+                "latest": "2026-04-19T14:07:15.657Z"
+              }
+            }
+          },
+          "base_type": "type/DateTime"
+        },
+        {
+          "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 \"PEOPLE__via__USER_ID\".\"STATE\" AS \"PEOPLE__via__USER_ID__STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") AS \"CREATED_AT\", COUNT(*) AS \"count\" FROM \"PUBLIC\".\"ORDERS\" LEFT JOIN \"PUBLIC\".\"PEOPLE\" AS \"PEOPLE__via__USER_ID\" ON \"PUBLIC\".\"ORDERS\".\"USER_ID\" = \"PEOPLE__via__USER_ID\".\"ID\" WHERE (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" >= timestamp '2022-09-01 00:00:00.000') AND (\"PUBLIC\".\"ORDERS\".\"CREATED_AT\" < timestamp '2023-03-01 00:00:00.000') AND ((\"PEOPLE__via__USER_ID\".\"STATE\" = 'AK') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AR') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'AZ') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CO') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'CT') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'DE') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'FL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'GA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IA') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'ID') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'IL') OR (\"PEOPLE__via__USER_ID\".\"STATE\" = 'KY')) GROUP BY \"PEOPLE__via__USER_ID\".\"STATE\", DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ORDER BY \"PEOPLE__via__USER_ID\".\"STATE\" ASC, DATE_TRUNC('month', \"PUBLIC\".\"ORDERS\".\"CREATED_AT\") ASC",
+        "params": null
+      },
+      "format-rows?": true,
+      "results_timezone": "Europe/Lisbon",
+      "results_metadata": {
+        "columns": [
+          {
+            "description": "The state or province of the account’s billing address",
+            "semantic_type": "type/State",
+            "coercion_strategy": null,
+            "name": "STATE",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              48,
+              {
+                "base-type": "type/Text",
+                "source-field": 43
+              }
+            ],
+            "effective_type": "type/Text",
+            "id": 48,
+            "visibility_type": "normal",
+            "display_name": "User → State",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 49,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Text": {
+                  "percent-json": 0.0,
+                  "percent-url": 0.0,
+                  "percent-email": 0.0,
+                  "percent-state": 1.0,
+                  "average-length": 2.0
+                }
+              }
+            },
+            "base_type": "type/Text"
+          },
+          {
+            "description": "The date and time an order was submitted.",
+            "semantic_type": "type/CreationTimestamp",
+            "coercion_strategy": null,
+            "unit": "month",
+            "name": "CREATED_AT",
+            "settings": null,
+            "fk_target_field_id": null,
+            "field_ref": [
+              "field",
+              41,
+              {
+                "base-type": "type/DateTime",
+                "temporal-unit": "month"
+              }
+            ],
+            "effective_type": "type/DateTime",
+            "id": 41,
+            "visibility_type": "normal",
+            "display_name": "Created At",
+            "fingerprint": {
+              "global": {
+                "distinct-count": 10001,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/DateTime": {
+                  "earliest": "2022-04-30T18:56:13.352Z",
+                  "latest": "2026-04-19T14:07:15.657Z"
+                }
+              }
+            },
+            "base_type": "type/DateTime"
+          },
+          {
+            "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": 12,
+                "nil%": 0.0
+              },
+              "type": {
+                "type/Number": {
+                  "min": 1.0,
+                  "q1": 1.8849307066960952,
+                  "q3": 5.5,
+                  "max": 12.0,
+                  "sd": 2.737211604119948,
+                  "avg": 4.086956521739131
+                }
+              }
+            }
+          }
+        ]
+      },
+      "insights": [
+        {
+          "previous-value": 4,
+          "unit": "month",
+          "offset": -377.8969468095498,
+          "last-change": -0.25,
+          "col": "count",
+          "slope": 0.019777876708186145,
+          "last-value": 3,
+          "best-fit": [
+            "*",
+            4.201731368215701e-45,
+            ["exp", ["*", 0.005350764152580669, "x"]]
+          ]
+        }
+      ]
+    }
+  }
+]
diff --git a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/index.ts b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/index.ts
index d6a3293f551c9dfcdaefe263144012774a3d0fa6..89c4f17c37dc82b39ab033ebf3fee8fa5501bdf4 100644
--- a/frontend/src/metabase/static-viz/components/ComboChart/stories-data/index.ts
+++ b/frontend/src/metabase/static-viz/components/ComboChart/stories-data/index.ts
@@ -27,6 +27,9 @@ import barHistogramXScale from "./bar-histogram-x-scale.json";
 import barLinearXScale from "./bar-linear-x-scale.json";
 import barLogYScaleStackedNegative from "./bar-log-y-scale-stacked-negative.json";
 import barLogYScaleStacked from "./bar-log-y-scale-stacked.json";
+import barMaxCategoriesDefault from "./bar-max-categories-default.json";
+import barMaxCategoriesStackedNormalized from "./bar-max-categories-stacked-normalized.json";
+import barMaxCategoriesStacked from "./bar-max-categories-stacked.json";
 import barMinHeightLimit from "./bar-min-height-limit.json";
 import barOrdinalXScaleAutoRotatedLabels from "./bar-ordinal-x-scale-auto-rotated-labels.json";
 import barOrdinalXScale from "./bar-ordinal-x-scale.json";
@@ -226,6 +229,9 @@ export const data = {
   barStackedSeriesLabelsAndTotalsOrdinal,
   barStackedSeriesLabelsNormalizedAutoCompactness,
   barStackedLabelsNullVsZero,
+  barMaxCategoriesDefault,
+  barMaxCategoriesStacked,
+  barMaxCategoriesStackedNormalized,
   barMinHeightLimit,
   comboDataLabelsAutoCompactnessPropagatesFromLine,
   comboDataLabelsAutoCompactnessPropagatesFromTotals,
diff --git a/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx b/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
index a6ba65c316c539b44deef5f30184743f9951f179..3e4f33e2c4799af3b19f71b3c26e80e725d5040a 100644
--- a/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
+++ b/frontend/src/metabase/visualizations/components/ChartTooltip/EChartsTooltip/EChartsTooltip.tsx
@@ -6,6 +6,10 @@ import { isNotNull } from "metabase/lib/types";
 
 import TooltipStyles from "./EChartsTooltip.module.css";
 
+const getPaddedValuesArray = (values: React.ReactNode[], maxValues: number) => {
+  return Object.assign(Array(maxValues).fill(null), values.slice(0, maxValues));
+};
+
 export interface EChartsTooltipRow {
   /* We pass CSS class with marker colors because setting styles in tooltip rendered by ECharts violates CSP */
   markerColorClass?: string;
@@ -40,14 +44,9 @@ export const EChartsTooltip = ({
   }, 0);
 
   const paddedRows = rows.map(row => {
-    const paddedValues = Object.assign(
-      Array(maxValuesColumns).fill(null),
-      row.values.slice(0, maxValuesColumns),
-    );
-
     return {
       ...row,
-      values: paddedValues,
+      values: getPaddedValuesArray(row.values, maxValuesColumns),
     };
   });
 
@@ -79,6 +78,7 @@ export const EChartsTooltip = ({
           <tfoot data-testid="echarts-tooltip-footer">
             <FooterRow
               {...footer}
+              values={getPaddedValuesArray(footer.values, maxValuesColumns)}
               markerContent={hasMarkers ? <span /> : null}
             />
           </tfoot>
diff --git a/frontend/src/metabase/visualizations/components/ClickActions/ClickActionsView.tsx b/frontend/src/metabase/visualizations/components/ClickActions/ClickActionsView.tsx
index 7d874c2e1fbc667b150f8983d468020d36222889..8e5306cc36576eb24633ffbc34c373562a5dc820 100644
--- a/frontend/src/metabase/visualizations/components/ClickActions/ClickActionsView.tsx
+++ b/frontend/src/metabase/visualizations/components/ClickActions/ClickActionsView.tsx
@@ -25,7 +25,7 @@ export const ClickActionsView = ({
   const hasOnlyOneSection = sections.length === 1;
 
   return (
-    <Container>
+    <Container data-testid="click-actions-view">
       {sections.map(([sectionKey, actions]) => {
         const sectionTitle = getSectionTitle(sectionKey, actions);
         const contentDirection = getSectionContentDirection(
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingsSeriesMultiple.unit.spec.js b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingsSeriesMultiple.unit.spec.js
index b45bbc68640e061461122a5470c0dcc3b30f091c..dd85bd52d421d0f2e921e87a0e0bd56c68407133 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingsSeriesMultiple.unit.spec.js
+++ b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingsSeriesMultiple.unit.spec.js
@@ -4,12 +4,13 @@ import userEvent from "@testing-library/user-event";
 import { renderWithProviders, screen, within } from "__support__/ui";
 import { ChartSettings } from "metabase/visualizations/components/ChartSettings";
 import registerVisualizations from "metabase/visualizations/register";
+import { createMockCard } from "metabase-types/api/mocks";
 
 registerVisualizations();
 
 function getSeries(display, index, changeSeriesName) {
   return {
-    card: {
+    card: createMockCard({
       display,
       visualization_settings: changeSeriesName
         ? {
@@ -19,7 +20,7 @@ function getSeries(display, index, changeSeriesName) {
           }
         : {},
       name: `Test ${index}`,
-    },
+    }),
     data: {
       rows: [
         ["a", 1],
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
index 69811949a1b9b689e42f6729f7f2878f91490275..ec825a751d1f504833cfce55ad1ba0bdcae701ed 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorPicker/ChartSettingColorPicker.tsx
@@ -24,7 +24,7 @@ export const ChartSettingColorPicker = ({
   accentColorOptions = { main: true, light: true, dark: true, harmony: false },
 }: ChartSettingColorPickerProps) => {
   return (
-    <div className={cx(CS.flex, CS.alignCenter, CS.mb1, className)}>
+    <div className={cx(CS.flex, CS.alignCenter, className)}>
       <ColorSelector
         value={value}
         colors={getAccentColors(accentColorOptions)}
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorsPicker.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorsPicker.jsx
index 80eae16ce277c9447fec736e5b459ebbb1c485f6..5e382e368d3369cec9b30fd15f2949dba20385b6 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingColorsPicker.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingColorsPicker.jsx
@@ -1,6 +1,8 @@
 /* eslint-disable react/prop-types */
 import { Component } from "react";
 
+import CS from "metabase/css/core/index.css";
+
 import { ChartSettingColorPicker } from "./ChartSettingColorPicker";
 
 export default class ChartSettingColorsPicker extends Component {
@@ -10,6 +12,7 @@ export default class ChartSettingColorsPicker extends Component {
       <div>
         {seriesValues.map((key, index) => (
           <ChartSettingColorPicker
+            className={CS.mb1}
             key={index}
             onChange={color =>
               onChange({
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
index 8caa8715de0f8d1a02d668ae01f3cff871e1ad6b..2e32f7fa5f26bf3a5a8a6af688b90099b496c8ae 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
@@ -1,4 +1,5 @@
 /* eslint-disable react/prop-types */
+import { useMemo } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 
@@ -28,6 +29,7 @@ const ChartSettingFieldPicker = ({
   colors,
   series,
   onChangeSeriesColor,
+  fieldSettingWidget = null,
 }) => {
   let columnKey;
   if (value && showColumnSetting && columns) {
@@ -37,6 +39,25 @@ const ChartSettingFieldPicker = ({
     }
   }
 
+  const menuWidgetInfo = useMemo(() => {
+    if (columnKey && showColumnSetting) {
+      return {
+        id: "column_settings",
+        props: {
+          initialKey: columnKey,
+        },
+      };
+    }
+
+    if (fieldSettingWidget) {
+      return {
+        id: fieldSettingWidget,
+      };
+    }
+
+    return null;
+  }, [columnKey, fieldSettingWidget, showColumnSetting]);
+
   let seriesKey;
   if (series && columnKey && showColorPicker) {
     const seriesForColumn = series.find(single => {
@@ -73,21 +94,14 @@ const ChartSettingFieldPicker = ({
         isInitiallyOpen={value === undefined}
         hiddenIcons
       />
-      {columnKey && (
+      {menuWidgetInfo && (
         <SettingsButton
           onlyIcon
           icon="ellipsis"
           onClick={e => {
-            onShowWidget(
-              {
-                id: "column_settings",
-                props: {
-                  initialKey: columnKey,
-                },
-              },
-              e.target,
-            );
+            onShowWidget(menuWidgetInfo, e.target);
           }}
+          data-testid={`settings-${value}`}
         />
       )}
       {onRemove && (
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.unit.spec.js b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.unit.spec.js
index 7616531c2dbdd5508f1906b86ff4c326aa3444d3..ef81f1493ce72ec14b24bd67ad701d10afb13245 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.unit.spec.js
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.unit.spec.js
@@ -4,19 +4,20 @@ import { within } from "@testing-library/react";
 import { renderWithProviders, screen } from "__support__/ui";
 import { ChartSettings } from "metabase/visualizations/components/ChartSettings";
 import registerVisualizations from "metabase/visualizations/register";
+import { createMockCard } from "metabase-types/api/mocks";
 
 registerVisualizations();
 
 function getSeries(metricColumnProps) {
   return [
     {
-      card: {
+      card: createMockCard({
         display: "line",
         visualization_settings: {
           "graph.dimensions": ["FOO"],
           "graph.metrics": ["BAR"],
         },
-      },
+      }),
       data: {
         rows: [
           ["a", 1],
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx
index f5ffdaa3c3ee6cf92683b85414be840b31d8d6fc..79fc927e22ea2fd6035a0baeee05ac7a2f23db15 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx
@@ -17,6 +17,7 @@ const ChartSettingFieldsPicker = ({
   addAnother,
   showColumnSetting,
   showColumnSettingForIndicies,
+  fieldSettingWidgets = [],
   ...props
 }) => {
   const handleDragEnd = ({ source, destination }) => {
@@ -93,6 +94,7 @@ const ChartSettingFieldsPicker = ({
                                 : null
                             }
                             showDragHandle={fields.length > 1}
+                            fieldSettingWidget={fieldSettingWidgets[fieldIndex]}
                           />
                         </div>
                       )}
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.tsx
index f692dc2bb7ec1510ae27a2738d8516a90ef67e16..715af5d8c670b5d667f299435e7ad7e0d7ec8a2e 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.tsx
@@ -3,6 +3,7 @@ import { useState } from "react";
 import _ from "underscore";
 
 import { ChartSettingNumericInput } from "./ChartSettingInputNumeric.styled";
+import type { ChartSettingWidgetProps } from "./types";
 
 const ALLOWED_CHARS = [
   "0",
@@ -22,10 +23,7 @@ const ALLOWED_CHARS = [
 
 // Note: there are more props than these that are provided by the viz settings
 // code, we just don't have types for them here.
-interface ChartSettingInputProps {
-  value: number | undefined;
-  onChange: (value: number | undefined) => void;
-  onChangeSettings: () => void;
+interface ChartSettingInputProps extends ChartSettingWidgetProps<number> {
   options?: {
     isInteger?: boolean;
     isNonNegative?: boolean;
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingMaxCategories.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingMaxCategories.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9dd8d0fde875deb7d7dc7000e037e7ed8c9ad22e
--- /dev/null
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingMaxCategories.tsx
@@ -0,0 +1,85 @@
+import { useCallback } from "react";
+import { t } from "ttag";
+
+import { Checkbox, Select, Stack, Text } from "metabase/ui";
+import type { VisualizationSettings } from "metabase-types/api";
+
+import { ChartSettingInputNumeric } from "./ChartSettingInputNumeric";
+import type { ChartSettingWidgetProps } from "./types";
+
+type AggregationFunction = Exclude<
+  VisualizationSettings["graph.other_category_aggregation_fn"],
+  undefined
+>;
+
+export interface ChartSettingMaxCategoriesProps
+  extends ChartSettingWidgetProps<number> {
+  isEnabled?: boolean;
+  aggregationFunction: AggregationFunction;
+}
+
+export const ChartSettingMaxCategories = ({
+  isEnabled,
+  aggregationFunction,
+  ...props
+}: ChartSettingMaxCategoriesProps) => {
+  const { onChangeSettings } = props;
+
+  const handleToggleMaxNumberOfSeries = useCallback(
+    (value: boolean) => {
+      onChangeSettings({ "graph.max_categories_enabled": value });
+    },
+    [onChangeSettings],
+  );
+
+  const handleAggregationFunctionChange = useCallback(
+    (value: string | null) => {
+      if (value) {
+        onChangeSettings({
+          "graph.other_category_aggregation_fn": value as AggregationFunction,
+        });
+      }
+    },
+    [onChangeSettings],
+  );
+
+  return (
+    <Stack spacing="md">
+      <Checkbox
+        checked={isEnabled}
+        label={t`Enforce maximum number of series`}
+        onChange={e => handleToggleMaxNumberOfSeries(e.target.checked)}
+      />
+      <ChartSettingInputNumeric
+        {...props}
+        data-testid="graph-max-categories-input"
+      />
+      <Text>{t`Series after this number will be grouped into "Other"`}</Text>
+      <div>
+        <Text
+          component="label"
+          htmlFor="aggregationFunction"
+          color="var(--mb-color-text-dark)"
+          fz="sm"
+          mb="sm"
+        >{t`Aggregation method for Other group`}</Text>
+        <Select
+          name="aggregationFunction"
+          value={aggregationFunction}
+          data={AGGREGATION_FN_OPTIONS}
+          onChange={handleAggregationFunctionChange}
+          data-testid="graph-other-category-aggregation-fn-picker"
+        />
+      </div>
+    </Stack>
+  );
+};
+
+const AGGREGATION_FN_OPTIONS = [
+  { label: t`Sum`, value: "sum" },
+  { label: t`Average`, value: "avg" },
+  { label: t`Median`, value: "median" },
+  { label: t`Standard deviation`, value: "stddev" },
+  { label: t`Min`, value: "min" },
+  { label: t`Max`, value: "max" },
+];
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
index 6869dd3b4d018956fbcc0cdeb1e5de421e493836..1e78b76f9994154169840a71111d2cc5fe1d80ec 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedItems/ChartSettingOrderedItems.tsx
@@ -1,7 +1,10 @@
 import { PointerSensor, useSensor } from "@dnd-kit/core";
 import { useCallback } from "react";
 
-import type { DragEndEvent } from "metabase/core/components/Sortable";
+import type {
+  DragEndEvent,
+  SortableDivider,
+} 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";
@@ -13,6 +16,7 @@ export interface SortableItem {
   color?: string;
   icon?: IconProps["name"];
   isOther?: boolean;
+  hideSettings?: boolean;
 }
 
 interface SortableColumnFunctions<T> {
@@ -32,6 +36,7 @@ interface ChartSettingOrderedItemsProps<T extends SortableItem>
   removeIcon?: IconProps["name"];
   accentColorOptions?: AccentColorOptions;
   getItemColor?: (item: SortableItem) => string | undefined;
+  dividers?: SortableDivider[];
 }
 
 export function ChartSettingOrderedItems<T extends SortableItem>({
@@ -48,6 +53,7 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
   removeIcon,
   accentColorOptions,
   getItemColor = item => item.color,
+  dividers = [],
 }: ChartSettingOrderedItemsProps<T>) {
   const isDragDisabled = items.length < 1;
   const pointerSensor = useSensor(PointerSensor, {
@@ -66,7 +72,7 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
           <ColumnItem
             title={getItemName(item)}
             onEdit={
-              onEdit
+              onEdit && !item.hideSettings
                 ? (targetElement: HTMLElement) => onEdit(item, targetElement)
                 : undefined
             }
@@ -114,6 +120,7 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
       items={items}
       onSortEnd={onSortEnd}
       sensors={[pointerSensor]}
+      dividers={dividers}
     />
   );
 }
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
index 2ce266ac9e30332a49136f60d54c7858bcc6d344..19b6606f161e0d9dd9300e10718cc12c66bea95d 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx
@@ -4,11 +4,15 @@ import { useCallback, useMemo, useState } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 
+import ColorSelector from "metabase/core/components/ColorSelector";
 import type { DragEndEvent } from "metabase/core/components/Sortable";
+import { color } from "metabase/lib/colors";
+import { getAccentColors } from "metabase/lib/colors/groups";
 import type { AccentColorOptions } from "metabase/lib/colors/types";
 import { NULL_DISPLAY_VALUE } from "metabase/lib/constants";
+import { getEventTarget } from "metabase/lib/dom";
 import { isEmpty } from "metabase/lib/validate";
-import { Button, Select } from "metabase/ui";
+import { Button, Flex, Group, Icon, Select, Text } from "metabase/ui";
 import type { Series } from "metabase-types/api";
 
 import {
@@ -28,13 +32,14 @@ export interface SortableItem {
   name: string;
   color?: string;
   hidden?: boolean;
+  hideSettings?: boolean;
 }
 
 interface ChartSettingSeriesOrderProps {
   onChange: (rows: SortableItem[]) => void;
   value: SortableItem[];
   onShowWidget: (
-    widget: { props: { seriesKey: string } },
+    widget: { id?: string; props?: { seriesKey: string } },
     ref: HTMLElement | undefined,
   ) => void;
   series: Series;
@@ -45,6 +50,11 @@ interface ChartSettingSeriesOrderProps {
   getItemColor?: (item: SortableChartSettingOrderedItem) => string | undefined;
   addButtonLabel?: string;
   searchPickerPlaceholder?: string;
+  groupedAfterIndex?: number;
+  otherColor?: string;
+  otherSettingWidgetId?: string;
+  onOtherColorChange?: (newColor: string) => void;
+  truncateAfter?: number;
 }
 
 export const ChartSettingSeriesOrder = ({
@@ -58,10 +68,16 @@ export const ChartSettingSeriesOrder = ({
   onSortEnd,
   getItemColor,
   accentColorOptions,
+  otherColor,
+  groupedAfterIndex = Infinity,
+  otherSettingWidgetId,
+  truncateAfter = Infinity,
+  onOtherColorChange,
 }: ChartSettingSeriesOrderProps) => {
+  const [isListTruncated, setIsListTruncated] = useState<boolean>(true);
   const [isSeriesPickerVisible, setSeriesPickerVisible] = useState(false);
 
-  const [visibleItems, hiddenItems] = useMemo(
+  const [items, hiddenItems] = useMemo(
     () =>
       _.partition(
         orderedItems.filter(item => !item.hidden),
@@ -69,6 +85,27 @@ export const ChartSettingSeriesOrder = ({
       ),
     [orderedItems],
   );
+  const itemsAfterGrouping = useMemo(() => {
+    return items.map((item, index) => {
+      if (index < groupedAfterIndex) {
+        return item;
+      }
+      return {
+        ...item,
+        color: undefined,
+        hideSettings: true,
+      };
+    });
+  }, [groupedAfterIndex, items]);
+
+  const [visibleItems, truncatedItems] = useMemo(
+    () =>
+      _.partition(
+        itemsAfterGrouping,
+        (_item, index) => !isListTruncated || index < truncateAfter,
+      ),
+    [isListTruncated, itemsAfterGrouping, truncateAfter],
+  );
 
   const canAddSeries = hiddenItems.length > 0;
 
@@ -133,6 +170,52 @@ export const ChartSettingSeriesOrder = ({
 
   const getId = useCallback((item: SortableItem) => item.key, []);
 
+  const handleOtherSeriesSettingsClick = useCallback(
+    (e: React.MouseEvent) => {
+      onShowWidget({ id: otherSettingWidgetId }, getEventTarget(e));
+    },
+    [onShowWidget, otherSettingWidgetId],
+  );
+
+  const dividers = useMemo(() => {
+    return [
+      {
+        afterIndex: groupedAfterIndex,
+        renderFn: () => (
+          <Flex justify="space-between" px={4}>
+            <Group p={4} spacing="sm">
+              <ColorSelector
+                value={otherColor ?? color("text-light")}
+                colors={[
+                  ...getAccentColors(),
+                  color("text-light"),
+                  color("text-medium"),
+                  color("text-dark"),
+                ]}
+                onChange={onOtherColorChange}
+                pillSize="small"
+              />
+              <Text truncate fw="bold">{t`Other`}</Text>
+            </Group>
+            <Button
+              compact
+              color="text-medium"
+              variant="subtle"
+              leftIcon={<Icon name="gear" />}
+              aria-label={t`Other series settings`}
+              onClick={handleOtherSeriesSettingsClick}
+            />
+          </Flex>
+        ),
+      },
+    ];
+  }, [
+    groupedAfterIndex,
+    handleOtherSeriesSettingsClick,
+    onOtherColorChange,
+    otherColor,
+  ]);
+
   return (
     <ChartSettingOrderedSimpleRoot>
       {orderedItems.length > 0 ? (
@@ -149,7 +232,18 @@ export const ChartSettingSeriesOrder = ({
             removeIcon="close"
             accentColorOptions={accentColorOptions}
             getItemColor={getItemColor}
+            dividers={dividers}
           />
+          {truncatedItems.length > 0 ? (
+            <div>
+              <Button
+                variant="subtle"
+                onClick={() => setIsListTruncated(false)}
+              >
+                {t`${truncatedItems.length} more series`}
+              </Button>
+            </div>
+          ) : null}
           {canAddSeries && !isSeriesPickerVisible && (
             <Button
               variant="subtle"
diff --git a/frontend/src/metabase/visualizations/components/settings/types.ts b/frontend/src/metabase/visualizations/components/settings/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4ea4317c6983b00615281d71b92eb442c61e2010
--- /dev/null
+++ b/frontend/src/metabase/visualizations/components/settings/types.ts
@@ -0,0 +1,7 @@
+import type { VisualizationSettings } from "metabase-types/api";
+
+export interface ChartSettingWidgetProps<TValue> {
+  value: TValue | undefined;
+  onChange: (value?: TValue | null) => void;
+  onChangeSettings: (settings: Partial<VisualizationSettings>) => void;
+}
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/constants/dataset.ts b/frontend/src/metabase/visualizations/echarts/cartesian/constants/dataset.ts
index a8a1867f897ca77a93d5fe05124c1b5b5fab31ed..ca4cf5c3625691c595fe2e34a0ee5de37fedb0a7 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/constants/dataset.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/constants/dataset.ts
@@ -15,6 +15,9 @@ export const NEGATIVE_BAR_DATA_LABEL_KEY_SUFFIX = `${NULL_CHAR}_negative_bar_dat
 // Key of x-axis values
 export const X_AXIS_DATA_KEY = `${NULL_CHAR}_x` as const;
 
+// Key for the "other" series created by the `graph.max_categories` setting
+export const OTHER_DATA_KEY = `${NULL_CHAR}_other` as const;
+
 // In some cases a datum in `chartModel.transformedDataset` may include this
 // key, its value is equal to the index of that same datum in the original
 // dataset (e.g. `chartModel.dataset`)
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.ts
index eafa4177d5d50bf0d6a8e06e42b06ad097f32329..e4ce18e2d2727d2b370e555c7eb78eb1f80073dd 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.ts
@@ -8,6 +8,7 @@ import {
   ECHARTS_CATEGORY_AXIS_NULL_VALUE,
   NEGATIVE_STACK_TOTAL_DATA_KEY,
   ORIGINAL_INDEX_DATA_KEY,
+  OTHER_DATA_KEY,
   POSITIVE_STACK_TOTAL_DATA_KEY,
   X_AXIS_DATA_KEY,
 } from "metabase/visualizations/echarts/cartesian/constants/dataset";
@@ -47,6 +48,7 @@ import type { ShowWarning } from "../../types";
 import { tryGetDate } from "../utils/timeseries";
 
 import { isCategoryAxis, isNumericAxis, isTimeSeriesAxis } from "./guards";
+import { getAggregatedOtherSeriesValue } from "./other-series";
 import { getBarSeriesDataLabelKey, getColumnScaling } from "./util";
 
 /**
@@ -334,6 +336,23 @@ const getStackedAreasInterpolateTransform = (
   };
 };
 
+function getOtherSeriesTransform(
+  groupedSeriesModels: SeriesModel[],
+  settings: ComputedVisualizationSettings,
+): ConditionalTransform {
+  return {
+    condition: groupedSeriesModels.length > 0,
+    fn: datum => ({
+      ...datum,
+      [OTHER_DATA_KEY]: getAggregatedOtherSeriesValue(
+        groupedSeriesModels,
+        settings["graph.other_category_aggregation_fn"],
+        datum,
+      ),
+    }),
+  };
+}
+
 function getStackedValueTransformFunction(
   seriesDataKeys: DataKey[],
   valueTransform: (value: number) => number | null,
@@ -697,6 +716,7 @@ export const applyVisualizationSettingsDataTransformations = (
   stackModels: StackModel[],
   xAxisModel: XAxisModel,
   seriesModels: SeriesModel[],
+  groupedSeriesModels: SeriesModel[],
   yAxisScaleTransforms: NumericAxisScaleTransforms,
   settings: ComputedVisualizationSettings,
   showWarning?: ShowWarning,
@@ -734,6 +754,7 @@ export const applyVisualizationSettingsDataTransformations = (
 
   return transformDataset(dataset, [
     getNullReplacerTransform(settings, seriesModels),
+    getOtherSeriesTransform(groupedSeriesModels, settings),
     {
       condition: settings["stackable.stack_type"] === "normalized",
       fn: getNormalizedDatasetTransform(stackModels),
@@ -852,13 +873,12 @@ export const getSortedSeriesModels = (
           seriesModel.vizSettingsKey === orderSetting.key &&
           !usedDataKeys.has(seriesModel.dataKey),
       );
-      if (foundSeries === undefined) {
-        throw new TypeError("Series not found");
+      if (foundSeries) {
+        usedDataKeys.add(foundSeries.dataKey);
       }
-
-      usedDataKeys.add(foundSeries.dataKey);
       return foundSeries;
-    });
+    })
+    .filter(isNotNull);
 
   // On stacked charts we reverse the order of series so that the series
   // order in the sidebar matches series order on the chart.
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.unit.spec.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.unit.spec.ts
index abc4303115d1bc37da4c0c1ea549cc758bc13e80..defa3a8b2e747cae742664123b6673c1d4bd91b4 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.unit.spec.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/dataset.unit.spec.ts
@@ -303,6 +303,7 @@ describe("dataset transform functions", () => {
         [],
         xAxisModel,
         seriesModels,
+        [],
         yAxisScaleTransforms,
         createMockComputedVisualizationSettings({
           "stackable.stack_type": "stacked",
@@ -341,6 +342,7 @@ describe("dataset transform functions", () => {
         ],
         xAxisModel,
         seriesModels,
+        [],
         yAxisScaleTransforms,
         createMockComputedVisualizationSettings({
           "stackable.stack_type": "normalized",
@@ -380,6 +382,7 @@ describe("dataset transform functions", () => {
         [],
         xAxisModel,
         seriesModels,
+        [],
         yAxisScaleTransforms,
         createMockComputedVisualizationSettings({
           series: (key: LegacySeriesSettingsObjectKey) => ({
@@ -434,6 +437,7 @@ describe("dataset transform functions", () => {
           [],
           xAxisModel,
           [createMockSeriesModel({ dataKey: "series1" })],
+          [],
           yAxisScaleTransforms,
           createMockComputedVisualizationSettings({
             series: () => ({
@@ -465,6 +469,7 @@ describe("dataset transform functions", () => {
           [],
           { ...xAxisModel, intervalsCount: 10001 },
           [createMockSeriesModel({ dataKey: "series1" })],
+          [],
           yAxisScaleTransforms,
           createMockComputedVisualizationSettings({
             series: () => ({
@@ -511,6 +516,7 @@ describe("dataset transform functions", () => {
           [],
           xAxisModel,
           seriesModels,
+          [],
           yAxisScaleTransforms,
           createMockComputedVisualizationSettings(),
         );
@@ -530,6 +536,7 @@ describe("dataset transform functions", () => {
             [],
             xAxisModel,
             seriesModels,
+            [],
             yAxisScaleTransforms,
             createMockComputedVisualizationSettings(),
           ),
@@ -543,6 +550,7 @@ describe("dataset transform functions", () => {
         [],
         xAxisModel,
         seriesModels,
+        [],
         yAxisScaleTransforms,
         createMockVisualizationSettings({
           series: (key: LegacySeriesSettingsObjectKey) => ({
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/guards.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/guards.ts
index 56f2f4c9d85fd6c7c1a7530b077461bd452b63fa..cea27c4b9e10fa7867f27329661a52dbcdc4e78b 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/guards.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/guards.ts
@@ -1,8 +1,8 @@
 import type {
+  BaseSeriesModel,
   BreakoutSeriesModel,
   CategoryXAxisModel,
   NumericXAxisModel,
-  SeriesModel,
   TimeSeriesInterval,
   TimeSeriesXAxisModel,
   XAxisModel,
@@ -27,7 +27,7 @@ export const isCategoryAxis = (
 };
 
 export const isBreakoutSeries = (
-  seriesModel: SeriesModel,
+  seriesModel: BaseSeriesModel,
 ): seriesModel is BreakoutSeriesModel => {
   return "breakoutColumn" in seriesModel;
 };
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/index.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/index.ts
index 63a6a581e4f7e5d5ae7eeda36457239427895abd..fbbb3ec8e6231becf076f394f498eb03c70694c6 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/index.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/index.ts
@@ -1,3 +1,4 @@
+import { OTHER_DATA_KEY } from "metabase/visualizations/echarts/cartesian/constants/dataset";
 import {
   getXAxisModel,
   getYAxesModels,
@@ -28,6 +29,10 @@ import type { RawSeries, SingleSeries } from "metabase-types/api";
 
 import type { ShowWarning } from "../../types";
 
+import {
+  createOtherGroupSeriesModel,
+  groupSeriesIntoOther,
+} from "./other-series";
 import { getStackModels } from "./stack";
 import { getAxisTransforms } from "./transforms";
 import { getTrendLines } from "./trend-line";
@@ -93,11 +98,6 @@ export const getCartesianChartModel = (
     settings,
   );
 
-  // We currently ignore sorting and visibility settings on combined cards
-  const seriesModels = hasMultipleCards
-    ? unsortedSeriesModels
-    : getSortedSeriesModels(unsortedSeriesModels, settings);
-
   const unsortedDataset = getJoinedCardsDataset(
     rawSeries,
     cardsColumns,
@@ -108,7 +108,27 @@ export const getCartesianChartModel = (
     settings["graph.x_axis.scale"],
     showWarning,
   );
-  const scaledDataset = scaleDataset(dataset, seriesModels, settings);
+
+  const sortedSeriesModels = hasMultipleCards
+    ? unsortedSeriesModels
+    : getSortedSeriesModels(unsortedSeriesModels, settings);
+
+  const scaledDataset = scaleDataset(dataset, sortedSeriesModels, settings);
+
+  const { ungroupedSeriesModels: seriesModels, groupedSeriesModels } =
+    groupSeriesIntoOther(sortedSeriesModels, settings);
+
+  const [sampleGroupedModel] = groupedSeriesModels;
+  if (sampleGroupedModel) {
+    seriesModels.push(
+      createOtherGroupSeriesModel(
+        sampleGroupedModel.column,
+        sampleGroupedModel.columnIndex,
+        settings,
+        !hiddenSeries.includes(OTHER_DATA_KEY),
+      ),
+    );
+  }
 
   const xAxisModel = getXAxisModel(
     dimensionModel,
@@ -128,6 +148,7 @@ export const getCartesianChartModel = (
     stackModels,
     xAxisModel,
     seriesModels,
+    groupedSeriesModels,
     yAxisScaleTransforms,
     settings,
     showWarning,
@@ -186,5 +207,6 @@ export const getCartesianChartModel = (
     seriesLabelsFormatters,
     stackedLabelsFormatters,
     dataDensity,
+    groupedSeriesModels,
   };
 };
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts
index 792a155808afdf7d15a57b60836752cd0e01196f..d6c22da616c0427f53086c067563a7b80157f4f6 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts
@@ -1,8 +1,8 @@
 import { isBreakoutSeries } from "./guards";
-import type { LegendItem, SeriesModel } from "./types";
+import type { BaseSeriesModel, LegendItem } from "./types";
 
 export const getLegendItems = (
-  seriesModels: SeriesModel[],
+  seriesModels: BaseSeriesModel[],
   showAllLegendItems: boolean = false,
 ): LegendItem[] => {
   if (
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts
new file mode 100644
index 0000000000000000000000000000000000000000..063c5068a6f4ba440abafffea046a02c7c25354d
--- /dev/null
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/other-series.ts
@@ -0,0 +1,154 @@
+import { t } from "ttag";
+
+import { checkNumber } from "metabase/lib/types";
+import { isEmpty } from "metabase/lib/validate";
+import { SERIES_SETTING_KEY } from "metabase/visualizations/shared/settings/series";
+import type { ComputedVisualizationSettings } from "metabase/visualizations/types";
+import type { AggregationType, DatasetColumn } from "metabase-types/api";
+
+import { OTHER_DATA_KEY } from "../constants/dataset";
+
+import type { Datum, RegularSeriesModel, SeriesModel } from "./types";
+
+export function groupSeriesIntoOther(
+  seriesModels: SeriesModel[],
+  settings: ComputedVisualizationSettings,
+): {
+  ungroupedSeriesModels: SeriesModel[];
+  groupedSeriesModels: SeriesModel[];
+} {
+  const maxCategories = settings["graph.max_categories"];
+
+  if (
+    !settings["graph.max_categories_enabled"] ||
+    !maxCategories ||
+    maxCategories <= 0 ||
+    seriesModels.length <= maxCategories
+  ) {
+    return {
+      ungroupedSeriesModels: seriesModels,
+      groupedSeriesModels: [],
+    };
+  }
+
+  const isReversed = !isEmpty(settings["stackable.stack_type"]);
+  const _seriesModels = isReversed ? seriesModels.toReversed() : seriesModels;
+
+  const ungroupedSeriesModels = _seriesModels.slice(
+    0,
+    settings["graph.max_categories"],
+  );
+  if (isReversed) {
+    ungroupedSeriesModels.reverse();
+  }
+
+  const groupedSeriesModels = _seriesModels.slice(
+    settings["graph.max_categories"],
+  );
+
+  return {
+    ungroupedSeriesModels,
+    groupedSeriesModels,
+  };
+}
+
+export const createOtherGroupSeriesModel = (
+  column: DatasetColumn,
+  columnIndex: number,
+  settings: ComputedVisualizationSettings,
+  isVisible: boolean,
+): RegularSeriesModel => {
+  const customName = settings[SERIES_SETTING_KEY]?.[OTHER_DATA_KEY]?.title;
+  const name = customName ?? t`Other`;
+
+  return {
+    name,
+    dataKey: OTHER_DATA_KEY,
+    color: settings["graph.other_category_color"],
+    visible: isVisible,
+    column,
+    columnIndex,
+    vizSettingsKey: OTHER_DATA_KEY,
+    legacySeriesSettingsObjectKey: {
+      card: {
+        _seriesKey: OTHER_DATA_KEY,
+      },
+    },
+    tooltipName: name,
+  };
+};
+
+export const getAggregatedOtherSeriesValue = (
+  seriesModels: SeriesModel[],
+  aggregationType: AggregationType = "sum",
+  datum: Datum,
+): number => {
+  const aggregation = AGGREGATION_FN_MAP[aggregationType];
+  const values = seriesModels.map(model =>
+    checkNumber(datum[model.dataKey] ?? 0),
+  );
+  return aggregation.fn(values);
+};
+
+export const getOtherSeriesAggregationLabel = (
+  aggregationType: AggregationType = "sum",
+) => AGGREGATION_FN_MAP[aggregationType].label;
+
+const sum = (values: number[]) => values.reduce((sum, value) => sum + value, 0);
+
+const AGGREGATION_FN_MAP: Record<
+  AggregationType,
+  { fn: (values: number[]) => number; label: string }
+> = {
+  count: {
+    label: t`Total`,
+    fn: sum,
+  },
+  sum: {
+    label: t`Total`,
+    fn: sum,
+  },
+  "cum-sum": {
+    label: t`Total`,
+    fn: sum,
+  },
+  "cum-count": {
+    label: t`Total`,
+    fn: sum,
+  },
+  avg: {
+    label: t`Average`,
+    fn: values => sum(values) / values.length,
+  },
+  distinct: {
+    label: t`Distinct values`,
+    fn: values => new Set(values).size,
+  },
+  min: {
+    label: t`Min`,
+    fn: values => Math.min(...values),
+  },
+  max: {
+    label: t`Max`,
+    fn: values => Math.max(...values),
+  },
+  median: {
+    label: t`Median`,
+    fn: values => {
+      const sortedValues = values.sort((a, b) => a - b);
+      const middleIndex = Math.floor(sortedValues.length / 2);
+      return sortedValues.length % 2
+        ? sortedValues[middleIndex]
+        : (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2;
+    },
+  },
+  stddev: {
+    label: t`Standard deviation`,
+    fn: values => {
+      const mean = sum(values) / values.length;
+      const squaredDifferences = values.map(v => (v - mean) ** 2);
+      const variance = sum(squaredDifferences) / values.length;
+      return Math.sqrt(variance);
+    },
+  },
+};
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/series.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/series.ts
index 4049a1c3f94d7273a490bae1b46c525af120e959..96a964e2982b9e9ca69c60c1450b50e009639b72 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/series.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/series.ts
@@ -1,5 +1,3 @@
-import _ from "underscore";
-
 import { NULL_DISPLAY_VALUE } from "metabase/lib/constants";
 import { formatValue } from "metabase/lib/formatting";
 import type { OptionsType } from "metabase/lib/formatting/types";
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts
index 4c0984c3d135ef65391c96c37755250515964112..770376fb172cbd5b874a87b6d177d04d6d061a37 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts
@@ -234,6 +234,9 @@ export type BaseCartesianChartModel = {
 
   trendLinesModel?: TrendLinesModel;
   seriesLabelsFormatters: SeriesFormatters;
+
+  // For `graph.max_categories` setting
+  groupedSeriesModels?: SeriesModel[];
 };
 
 export type CartesianChartModel = BaseCartesianChartModel & {
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/option/index.ts b/frontend/src/metabase/visualizations/echarts/cartesian/option/index.ts
index f75c987ae8622bfe8c508e85a2e73eb2b0caac93..815309087095895384de5e29203fe0e0380795c1 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/option/index.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/option/index.ts
@@ -3,6 +3,7 @@ import type { OptionSourceData } from "echarts/types/src/util/types";
 
 import {
   NEGATIVE_STACK_TOTAL_DATA_KEY,
+  OTHER_DATA_KEY,
   POSITIVE_STACK_TOTAL_DATA_KEY,
   X_AXIS_DATA_KEY,
 } from "metabase/visualizations/echarts/cartesian/constants/dataset";
@@ -84,6 +85,7 @@ export const getCartesianChartOption = (
   // dataset option
   const dimensions = [
     X_AXIS_DATA_KEY,
+    OTHER_DATA_KEY,
     POSITIVE_STACK_TOTAL_DATA_KEY,
     NEGATIVE_STACK_TOTAL_DATA_KEY,
     ...chartModel.seriesModels.map(seriesModel => [
diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/scatter/model/index.ts b/frontend/src/metabase/visualizations/echarts/cartesian/scatter/model/index.ts
index 067877f2d3472f5c5ceba6f3ab099d9cf846df8c..79798ec4e1eda8ec8d83b6e5c6da2568a5dc3f0b 100644
--- a/frontend/src/metabase/visualizations/echarts/cartesian/scatter/model/index.ts
+++ b/frontend/src/metabase/visualizations/echarts/cartesian/scatter/model/index.ts
@@ -102,6 +102,7 @@ export function getScatterPlotModel(
     [],
     xAxisModel,
     seriesModels,
+    [],
     yAxisScaleTransforms,
     settings,
     showWarning,
diff --git a/frontend/src/metabase/visualizations/lib/settings/graph.js b/frontend/src/metabase/visualizations/lib/settings/graph.js
index d1541aa8bb679c577913d25fefbec68022ceeee0..404b2ce78bd1e0e9becad3e4dc8d6e58f3550198 100644
--- a/frontend/src/metabase/visualizations/lib/settings/graph.js
+++ b/frontend/src/metabase/visualizations/lib/settings/graph.js
@@ -1,17 +1,16 @@
 import { t } from "ttag";
 import _ from "underscore";
 
+import { color } from "metabase/lib/colors";
 import {
   getMaxDimensionsSupported,
   getMaxMetricsSupported,
 } from "metabase/visualizations";
+import { ChartSettingMaxCategories } from "metabase/visualizations/components/settings/ChartSettingMaxCategories";
 import { ChartSettingSeriesOrder } from "metabase/visualizations/components/settings/ChartSettingSeriesOrder";
 import { dimensionIsNumeric } from "metabase/visualizations/lib/numeric";
 import { columnSettings } from "metabase/visualizations/lib/settings/column";
-import {
-  keyForSingleSeries,
-  seriesSetting,
-} from "metabase/visualizations/lib/settings/series";
+import { seriesSetting } from "metabase/visualizations/lib/settings/series";
 import { getOptionFromColumn } from "metabase/visualizations/lib/settings/utils";
 import { dimensionIsTimeseries } from "metabase/visualizations/lib/timeseries";
 import { MAX_SERIES, columnsAreValid } from "metabase/visualizations/lib/utils";
@@ -39,6 +38,7 @@ import {
   getDefaultYAxisTitle,
   getIsXAxisLabelEnabledDefault,
   getIsYAxisLabelEnabledDefault,
+  getSeriesModelsForSettings,
   getSeriesOrderDimensionSetting,
   getSeriesOrderVisibilitySettings,
   getYAxisAutoRangeDefault,
@@ -66,6 +66,13 @@ function canHaveDataLabels(series, vizSettings) {
   return vizSettings["stackable.stack_type"] !== "normalized" || !areAllAreas;
 }
 
+const areAllBars = (series, settings) =>
+  getSeriesDisplays(series, settings).every(display => display === "bar");
+
+const canHaveMaxCategoriesSetting = (series, settings) => {
+  return Boolean(series && areAllBars(series, settings) && series.length >= 2);
+};
+
 export const GRAPH_DATA_SETTINGS = {
   ...columnSettings({
     getColumns: ([
@@ -96,12 +103,18 @@ export const GRAPH_DATA_SETTINGS = {
     getDefault: (series, vizSettings) =>
       getDefaultDimensions(series, vizSettings),
     persistDefault: true,
-    getProps: ([{ card, data }], vizSettings) => {
+    getProps: ([{ card, data }], vizSettings, _, { transformedSeries }) => {
       const addedDimensions = vizSettings["graph.dimensions"];
       const maxDimensionsSupported = getMaxDimensionsSupported(card.display);
       const options = data.cols
         .filter(getDefaultDimensionFilter(card.display))
         .map(getOptionFromColumn);
+      const fieldSettingWidgets = canHaveMaxCategoriesSetting(
+        transformedSeries,
+        vizSettings,
+      )
+        ? [null, "graph.max_categories"] // We want to show "graph.max_categories" setting for the breakout dimension (2nd)
+        : [];
       return {
         options,
         addAnother:
@@ -114,9 +127,7 @@ export const GRAPH_DATA_SETTINGS = {
             ? t`Add series breakout`
             : null,
         columns: data.cols,
-        // When this prop is passed it will only show the
-        // column settings for any index that is included in the array
-        showColumnSettingForIndicies: [0],
+        fieldSettingWidgets,
       };
     },
     writeDependencies: ["graph.metrics"],
@@ -134,18 +145,45 @@ export const GRAPH_DATA_SETTINGS = {
     section: t`Data`,
     widget: ChartSettingSeriesOrder,
     marginBottom: "1rem",
-
-    getValue: (series, settings) => {
-      const seriesKeys = series.map(s => keyForSingleSeries(s));
+    useRawSeries: true,
+    getValue: (rawSeries, settings) => {
+      const seriesModels = getSeriesModelsForSettings(rawSeries, settings);
+      const seriesKeys = seriesModels.map(s => s.vizSettingsKey);
       return getSeriesOrderVisibilitySettings(settings, seriesKeys);
     },
-    getHidden: (series, settings) => {
+    getProps: (rawSeries, settings, _onChange, _extra, onChangeSettings) => {
+      const groupedAfterIndex =
+        settings["graph.max_categories_enabled"] &&
+        settings["graph.max_categories"] !== 0
+          ? settings["graph.max_categories"]
+          : Infinity;
+      const onOtherColorChange = color =>
+        onChangeSettings({ "graph.other_category_color": color });
+      return {
+        rawSeries,
+        settings,
+        groupedAfterIndex,
+        otherColor: settings["graph.other_category_color"],
+        otherSettingWidgetId: "graph.max_categories",
+        onOtherColorChange,
+        truncateAfter: 10,
+      };
+    },
+    getHidden: (series, settings, { transformedSeries }) => {
       return (
-        settings["graph.dimensions"]?.length < 2 || series.length > MAX_SERIES
+        settings["graph.dimensions"]?.length < 2 ||
+        transformedSeries.length > MAX_SERIES
       );
     },
     dashboard: false,
-    readDependencies: ["series_settings.colors", "series_settings"],
+    readDependencies: [
+      "series_settings.colors",
+      "series_settings",
+      "graph.metrics",
+      "graph.dimensions",
+      "graph.max_categories",
+      "graph.other_category_color",
+    ],
     writeDependencies: ["graph.series_order_dimension"],
   },
   "graph.metrics": {
@@ -421,6 +459,45 @@ export const GRAPH_DISPLAY_VALUES_SETTINGS = {
     },
     default: getDefaultDataLabelsFormatting(),
   },
+  "graph.max_categories_enabled": {
+    hidden: true,
+    getDefault: () => false,
+    isValid: (series, settings) => {
+      return canHaveMaxCategoriesSetting(series, settings);
+    },
+    readDependencies: ["series_settings"],
+  },
+  "graph.max_categories": {
+    widget: ChartSettingMaxCategories,
+    hidden: true,
+    default: 8,
+    isValid: (series, settings) => {
+      return canHaveMaxCategoriesSetting(series, settings);
+    },
+    getProps: ([{ card }], settings) => {
+      return {
+        isEnabled: settings["graph.max_categories_enabled"],
+        aggregationFunction: settings["graph.other_category_aggregation_fn"],
+      };
+    },
+    readDependencies: [
+      "graph.max_categories_enabled",
+      "graph.other_category_aggregation_fn",
+      "series_settings",
+    ],
+  },
+  "graph.other_category_color": {
+    default: color("text-light"),
+  },
+  "graph.other_category_aggregation_fn": {
+    hidden: true,
+    getDefault: ([{ data }], settings) => {
+      const [metricName] = settings["graph.metrics"];
+      const metric = data.cols.find(col => col.name === metricName);
+      return metric?.aggregation_type ?? "sum";
+    },
+    readDependencies: ["graph.metrics"],
+  },
 };
 
 export const GRAPH_COLORS_SETTINGS = {
diff --git a/frontend/src/metabase/visualizations/lib/settings/series.js b/frontend/src/metabase/visualizations/lib/settings/series.js
index ad5c829106ffeae2cf5d86cfdbb92bc547c50780..41bae587d643d044f62a496b96504a0d38f041f0 100644
--- a/frontend/src/metabase/visualizations/lib/settings/series.js
+++ b/frontend/src/metabase/visualizations/lib/settings/series.js
@@ -2,6 +2,7 @@ import { getIn } from "icepick";
 import { t } from "ttag";
 
 import ChartNestedSettingSeries from "metabase/visualizations/components/settings/ChartNestedSettingSeries";
+import { OTHER_DATA_KEY } from "metabase/visualizations/echarts/cartesian/constants/dataset";
 import {
   SERIES_COLORS_SETTING_KEY,
   SERIES_SETTING_KEY,
@@ -59,6 +60,10 @@ export function seriesSetting({ readDependencies = [], def } = {}) {
       },
 
       getDefault: (single, settings, { series }) => {
+        if (keyForSingleSeries(single) === OTHER_DATA_KEY) {
+          return "bar"; // "other" series is always a bar chart now
+        }
+
         // FIXME: will move to Cartesian series model further, but now this code is used by other legacy charts
         const transformedSeriesIndex = series.findIndex(
           s => keyForSingleSeries(s) === keyForSingleSeries(single),
diff --git a/frontend/src/metabase/visualizations/shared/settings/cartesian-chart.ts b/frontend/src/metabase/visualizations/shared/settings/cartesian-chart.ts
index 3df9e4d8c732cec06dc7f6e448e84bca3b83a9ab..6c96c496f6042e058e2733f3795f02f007628685 100644
--- a/frontend/src/metabase/visualizations/shared/settings/cartesian-chart.ts
+++ b/frontend/src/metabase/visualizations/shared/settings/cartesian-chart.ts
@@ -7,6 +7,7 @@ import {
   getMaxMetricsSupported,
 } from "metabase/visualizations";
 import { getCardsColumns } from "metabase/visualizations/echarts/cartesian/model";
+import { getCardsSeriesModels } from "metabase/visualizations/echarts/cartesian/model/series";
 import { dimensionIsNumeric } from "metabase/visualizations/lib/numeric";
 import { dimensionIsTimeseries } from "metabase/visualizations/lib/timeseries";
 import {
@@ -437,3 +438,11 @@ export function getComputedAdditionalColumnsValue(
 
   return filteredStoredColumns;
 }
+
+export function getSeriesModelsForSettings(
+  rawSeries: RawSeries,
+  settings: ComputedVisualizationSettings,
+) {
+  const cardsColumns = getCardsColumns(rawSeries, settings);
+  return getCardsSeriesModels(rawSeries, cardsColumns, [], settings);
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/CartesianChart/events.ts b/frontend/src/metabase/visualizations/visualizations/CartesianChart/events.ts
index 9464fe3e3078aedfe7f5500fc506fa1ce1af9e23..d42b983e86cd71e584dbced70476121531e52cf1 100644
--- a/frontend/src/metabase/visualizations/visualizations/CartesianChart/events.ts
+++ b/frontend/src/metabase/visualizations/visualizations/CartesianChart/events.ts
@@ -22,6 +22,7 @@ import {
 import { formatValueForTooltip } from "metabase/visualizations/components/ChartTooltip/utils";
 import {
   ORIGINAL_INDEX_DATA_KEY,
+  OTHER_DATA_KEY,
   X_AXIS_DATA_KEY,
 } from "metabase/visualizations/echarts/cartesian/constants/dataset";
 import {
@@ -29,8 +30,10 @@ import {
   isQuarterInterval,
   isTimeSeriesAxis,
 } from "metabase/visualizations/echarts/cartesian/model/guards";
+import { getOtherSeriesAggregationLabel } from "metabase/visualizations/echarts/cartesian/model/other-series";
 import type {
   BaseCartesianChartModel,
+  BaseSeriesModel,
   ChartDataset,
   DataKey,
   Datum,
@@ -205,7 +208,7 @@ const getEventColumnsData = (
 
 const computeDiffWithPreviousPeriod = (
   chartModel: BaseCartesianChartModel,
-  seriesIndex: number,
+  seriesModel: BaseSeriesModel,
   dataIndex: number,
 ): string | null => {
   if (!isTimeSeriesAxis(chartModel.xAxisModel)) {
@@ -213,7 +216,6 @@ const computeDiffWithPreviousPeriod = (
   }
 
   const datum = chartModel.dataset[dataIndex];
-  const seriesModel = chartModel.seriesModels[seriesIndex];
 
   const currentValue = datum[seriesModel.dataKey];
   const currentDate = parseTimestamp(datum[X_AXIS_DATA_KEY]);
@@ -315,30 +317,6 @@ export const getSeriesHovered = (
   };
 };
 
-export const getSeriesHoverData = (
-  chartModel: BaseCartesianChartModel,
-  settings: ComputedVisualizationSettings,
-  echartsDataIndex: number,
-  seriesId: DataKey,
-) => {
-  const dataIndex = getDataIndex(
-    chartModel.transformedDataset,
-    echartsDataIndex,
-  );
-  const seriesIndex = findSeriesModelIndexById(chartModel, seriesId);
-
-  if (seriesIndex < 0 || dataIndex == null) {
-    return;
-  }
-
-  return {
-    settings,
-    isAlreadyScaled: true,
-    index: seriesIndex,
-    datumIndex: dataIndex,
-  };
-};
-
 const getAdditionalTooltipRowsData = (
   chartModel: BaseCartesianChartModel,
   settings: ComputedVisualizationSettings,
@@ -384,11 +362,21 @@ export const getTooltipModel = (
   if (dataIndex == null) {
     return null;
   }
+
   const datum = chartModel.dataset[dataIndex];
-  const seriesIndex = chartModel.seriesModels.findIndex(
-    seriesModel => seriesModel.dataKey === seriesDataKey,
+
+  if (seriesDataKey === OTHER_DATA_KEY) {
+    return getOtherSeriesTooltipModel(chartModel, settings, dataIndex, datum);
+  }
+
+  const hoveredSeries = chartModel.seriesModels.find(
+    s => s.dataKey === seriesDataKey,
   );
-  const hoveredSeries = chartModel.seriesModels[seriesIndex];
+
+  if (!hoveredSeries) {
+    return null;
+  }
+
   const seriesStack = chartModel.stackModels.find(stackModel =>
     stackModel.seriesKeys.includes(hoveredSeries.dataKey),
   );
@@ -416,7 +404,6 @@ export const getTooltipModel = (
       hoveredSeries,
     );
   }
-
   return getSeriesOnlyTooltipModel(
     chartModel,
     settings,
@@ -490,15 +477,19 @@ export const getSeriesOnlyTooltipModel = (
 
   const seriesRows: EChartsTooltipRow[] = chartModel.seriesModels
     .filter(seriesModel => seriesModel.visible)
-    .map((seriesModel, seriesIndex) => {
+    .map(seriesModel => {
       const isHoveredSeries = seriesModel.dataKey === hoveredSeries.dataKey;
       const isFocused = isHoveredSeries && chartModel.seriesModels.length > 1;
 
-      const prevValue = computeDiffWithPreviousPeriod(
-        chartModel,
-        seriesIndex,
-        dataIndex,
-      );
+      const value =
+        seriesModel.dataKey === OTHER_DATA_KEY
+          ? chartModel.transformedDataset[dataIndex][OTHER_DATA_KEY]
+          : datum[seriesModel.dataKey];
+
+      const prevValue =
+        seriesModel.dataKey === OTHER_DATA_KEY
+          ? null
+          : computeDiffWithPreviousPeriod(chartModel, seriesModel, dataIndex);
 
       return {
         isFocused,
@@ -508,13 +499,13 @@ export const getSeriesOnlyTooltipModel = (
         ),
         values: [
           formatValueForTooltip({
-            value: datum[seriesModel.dataKey],
+            value: value,
             column: seriesModel.column,
             settings,
             isAlreadyScaled: true,
           }),
           prevValue,
-        ],
+        ].filter(isNotNull),
       };
     });
 
@@ -570,12 +561,18 @@ export const getStackedTooltipModel = (
         seriesStack?.seriesKeys.includes(seriesModel.dataKey),
     )
     .map(seriesModel => {
+      const datum = chartModel.dataset[dataIndex];
+      const value =
+        seriesModel.dataKey === OTHER_DATA_KEY
+          ? chartModel.transformedDataset[dataIndex][OTHER_DATA_KEY]
+          : datum[seriesModel.dataKey];
+
       return {
         isFocused: seriesModel.dataKey === seriesDataKey,
         name: seriesModel.name,
         color: seriesModel.color,
-        value: chartModel.dataset[dataIndex][seriesModel.dataKey],
         dataKey: seriesModel.dataKey,
+        value,
       };
     });
 
@@ -647,6 +644,74 @@ export const getStackedTooltipModel = (
   };
 };
 
+export const getOtherSeriesTooltipModel = (
+  chartModel: BaseCartesianChartModel,
+  settings: ComputedVisualizationSettings,
+  dataIndex: number,
+  datum: Datum,
+) => {
+  const { groupedSeriesModels = [] } = chartModel;
+
+  const rows = groupedSeriesModels
+    .map(seriesModel => ({
+      name: seriesModel.name,
+      column: seriesModel.column,
+      value: datum[seriesModel.dataKey],
+      prevValue: computeDiffWithPreviousPeriod(
+        chartModel,
+        seriesModel,
+        dataIndex,
+      ),
+    }))
+    .sort((a, b) => {
+      if (typeof a.value === "number" && typeof b.value === "number") {
+        return b.value - a.value;
+      }
+      return a.value === undefined ? 1 : -1;
+    })
+    .map(row => ({
+      name: row.name,
+      values: [
+        formatValueForTooltip({
+          value: row.value,
+          column: row.column,
+          isAlreadyScaled: true,
+          settings,
+        }),
+        row.prevValue,
+      ],
+    }));
+
+  rows.push({
+    name: getOtherSeriesAggregationLabel(
+      settings["graph.other_category_aggregation_fn"],
+    ),
+    values: [
+      String(
+        formatValueForTooltip({
+          isAlreadyScaled: true,
+          value: chartModel.transformedDataset[dataIndex][OTHER_DATA_KEY],
+          settings,
+          column:
+            chartModel.leftAxisModel?.column ??
+            chartModel.rightAxisModel?.column,
+        }),
+      ),
+    ],
+  });
+
+  return {
+    header: String(
+      formatValueForTooltip({
+        value: datum[X_AXIS_DATA_KEY],
+        column: chartModel.dimensionModel.column,
+        settings,
+      }),
+    ),
+    rows,
+  };
+};
+
 export const getTimelineEventsForEvent = (
   timelineEventsModel: TimelineEventsModel,
   event: EChartsSeriesMouseEvent,
@@ -720,7 +785,11 @@ export const getSeriesClickData = (
   const seriesIndex = findSeriesModelIndexById(chartModel, seriesId);
   const seriesModel = chartModel.seriesModels[seriesIndex];
 
-  if (seriesIndex < 0 || dataIndex == null) {
+  if (
+    seriesIndex < 0 ||
+    dataIndex == null ||
+    seriesModel?.dataKey === OTHER_DATA_KEY
+  ) {
     return;
   }