---
title: "Context Menu"
enterprise: true
framework: react
version: "14.1.0"
---

# Context Menu

The Context Menu provides context-aware interactions with the chart elements.

#### Context Menu

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  AnimationModule,
  BarSeriesModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrosshairModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";

ModuleRegistry.registerModules([
  AnimationModule,
  BarSeriesModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrosshairModule,
  LegendModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    title: {
      text: "Sweaters made",
    },
    data: [
      {
        month: "Jun",
        sweaters: 50,
        hats: 40,
      },
      {
        month: "Jul",
        sweaters: 70,
        hats: 50,
      },
      {
        month: "Aug",
        sweaters: 60,
        hats: 30,
      },
    ],
    series: [
      {
        type: "bar",
        xKey: "month",
        yKey: "sweaters",
        yName: "Sweaters Made",
      },
      {
        type: "bar",
        xKey: "month",
        yKey: "hats",
        yName: "Hats Made",
      },
    ],
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Context Menu](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/context-menu)

In this example:

- Right clicking anywhere on the chart shows the Context Menu with the option to download the chart.
- Right clicking on a legend item will show the Context Menu with additional options to toggle series visibility.

The Context Menu is enabled by default, to disable set `contextMenu.enabled` to `false`.

```js
{
    contextMenu: {
        enabled: false,
    },
}
```

## Built-in Items

The Context Menu displays default items based on the right-clicked element.

The `contextMenu.items` array accepts special string values to conveniently reconfigure these defaults.

#### Context Menu Built-in Items

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  AgContextMenuItemLiteral,
  AnimationModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrosshairModule,
  LegendModule,
  LineSeriesModule,
  ModuleRegistry,
  NumberAxisModule,
  ZoomModule,
} from "ag-charts-enterprise";
import { generateCurrencyData } from "./data";
import clone from "clone";

const CUSTOM_ORDER: AgContextMenuItemLiteral[] = [
  "toggle-series-visibility",
  "toggle-other-series",
  "separator",
  "zoom-to-cursor",
  "pan-to-cursor",
  "reset-zoom",
  "separator",
  "download",
];
ModuleRegistry.registerModules([
  AnimationModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrosshairModule,
  LegendModule,
  LineSeriesModule,
  NumberAxisModule,
  ZoomModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    title: { text: "Inflation-Adjusted Currency Values (1800–2025)" },
    zoom: { enabled: true },
    contextMenu: {
      items: CUSTOM_ORDER,
    },
    data: generateCurrencyData(),
    series: [
      { type: "line", xKey: "year", yKey: "USD" },
      { type: "line", xKey: "year", yKey: "GBP" },
      { type: "line", xKey: "year", yKey: "JPY" },
    ],
    axes: {
      x: { type: "category", label: { autoRotate: false } },
    },
  });

  const onCustomOrder = () => {
    const nextOptions = clone(options);

    nextOptions.contextMenu!.items = CUSTOM_ORDER;

    setOptions(nextOptions);
  };

  const onDefaultOrder = () => {
    const nextOptions = clone(options);

    nextOptions.contextMenu!.items = ["defaults"];

    setOptions(nextOptions);
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <button onClick={onDefaultOrder}>Default Order</button>
          <button onClick={onCustomOrder}>Custom Order</button>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Context Menu Built-in Items](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/context-menu-builtins)

```js
{
    contextMenu: {
        items: [
            'defaults', //all the default menu-items in the pre-determined order.
            'separator', // a non-interactive horizontal line.
            'toggle-series-visibility', // used for legend items.
            'toggle-other-series', // used for legend items in multi-series charts.
            'zoom-to-cursor', // used with zoom.
            'pan-to-cursor', // used with zoom.
            'reset-zoom', // used with zoom.
            'download',
        ],
    },
}
```

In this example:

- The "Custom Order" button reorders the default built-in Menu Items and adds separators.
- The "Default Order" button resets the Menu Items to the default `['defaults']` value.

These string values can be used in combination with [Custom Actions](#custom-actions).

## Custom Actions

The Context Menu's custom actions can be used to run arbitrary functions based on the element that is right-clicked.

#### Context Menu Custom Actions

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgAxisContextMenuActionEvent,
  AgCartesianChartOptions,
  AnimationModule,
  BarSeriesModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrossLinesModule,
  CrosshairModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";
import { DataType, getData } from "./data";

ModuleRegistry.registerModules([
  AnimationModule,
  BarSeriesModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrossLinesModule,
  CrosshairModule,
  LegendModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
    title: {
      text: "Q3 Production",
    },
    subtitle: {
      text: "Total clothing manufactured from July to September",
    },
    footnote: {
      text: "Note: This data excludes products made with manufacturing defects.",
    },
    contextMenu: {
      items: [
        "defaults",
        "separator",
        {
          showOn: "always",
          label: "Say hello",
          action: () => {
            console.log("Hello world!");
          },
        },
        "separator",
        {
          showOn: "axis",
          label: "Say hello to an axis",
          action: (ev: AgAxisContextMenuActionEvent) => {
            console.log(`Hello in axis "${ev.axisId}":"`, ev);
          },
        },
        "separator",
        {
          showOn: "caption",
          label: "Say hello in a caption",
          action: ({ captionType, text }) => {
            console.log(`Hello in a ${captionType} caption: "${text}"`);
          },
        },
        "separator",
        {
          showOn: "series-area",
          label: "Say hello in the series area",
          action: () => {
            console.log("Hello in the series area!");
          },
        },
        "separator",
        {
          showOn: "series-node",
          label: "Say hello to a node",
          action: ({ datum, yKey }) => {
            console.log(`Hello ${yKey} in ${datum.month}!`);
          },
        },
        "separator",
        {
          showOn: "legend-item",
          label: "Say hello to a legend item",
          action: ({ itemId }) => {
            console.log(`Hello ${itemId}!`);
          },
        },
        {
          showOn: "cross-line",
          label: "Say hello to a cross line",
          action: ({ value, direction, crossLineType }) => {
            console.log(`Hello ${direction}-${crossLineType} ${value}!`);
          },
        },
      ],
    },
    data: getData(),
    series: [
      {
        type: "bar",
        xKey: "month",
        yKey: "sweaters",
        yName: "Sweaters Made",
      },
      {
        type: "bar",
        xKey: "month",
        yKey: "hats",
        yName: "Hats Made",
      },
    ],
    axes: {
      y: {
        type: "number",
        crossLines: [
          {
            type: "line",
            value: 53,
            label: {
              text: "Target",
              position: "top-right",
              fontStyle: "italic",
            },
            lineDash: [2, 4],
          },
        ],
      },
    },
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Context Menu Custom Actions](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/context-menu-actions)

Use the `showOn` property to specify when a custom item should be shown:

- `always` - shown regardless of what was clicked.
- `axis` - shown when right-clicking an axis.
- `caption` - shown when right-clicking a caption (title, subtitle, footnote).
- `cross-line` - shown when right-clicking a [Cross Line's](https://www.ag-grid.com/charts/react/axes-cross-lines/) line or fill.
- `series-area` - shown when right-clicking anywhere within the series area bounds.
- `series-node` - shown when right-clicking a datum node.
- `legend-item` - shown when right-clicking a legend item.

If there are multiple elements that match the `showOn` value, all of them are shown in the Context Menu.

```js
{
    contextMenu: {
        items: [
            'defaults',
            'separator',
            {
                showOn: 'always',
                label: 'Say hello',
                action: () => console.log('Hello world!'),
            },
            'separator',
            {
                showOn: 'axis',
                label: 'Say hello to an axis',
                action: (ev) => console.log(`Hello in axis "${ev.axisId}":"`, ev),
            },
            {
                showOn: 'series-area',
                label: 'Say hello in the series area',
                action: () => console.log('Hello in the series area!'),
            },
            'separator',
            {
                showOn: 'series-node',
                label: 'Say hello to a node',
                action: ({ datum, yKey }) => console.log(`Hello ${yKey} in ${datum.month}!`),
            },
            //...more custom actions for legend items, captions, etc.
        ],
    },
}
```

In this configuration:

- A single custom action is added for the entire chart, the captions, the axes, the series area, the series node and the legend items.
- Multiple entries per `showOn` element can be specified within the array if required.
- Right clicking on one of these areas will show these additional actions in the Context Menu.
- Clicking these extra actions will display information in the console, demonstrating that the action is aware of details about the right-clicked item.

## Sub-Menus

To add Sub-Menus to the Context Menu, simply use the `items` property recursively.

#### Context Menu Sub-Menus

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgAxisContextMenuActionEvent,
  AgCartesianChartOptions,
  AnimationModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrossLinesModule,
  CrosshairModule,
  LegendModule,
  LineSeriesModule,
  ModuleRegistry,
  NumberAxisModule,
  ZoomModule,
} from "ag-charts-enterprise";
import { DataType, getData } from "./data";

ModuleRegistry.registerModules([
  AnimationModule,
  CategoryAxisModule,
  ContextMenuModule,
  CrossLinesModule,
  CrosshairModule,
  LegendModule,
  LineSeriesModule,
  NumberAxisModule,
  ZoomModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
    title: { text: "GDP Growth (1995–2024)" },
    subtitle: { text: "Trillions USD" },
    footnote: {
      text: "Disclaimer: This data is for illustration purposes only.",
    },
    contextMenu: {
      items: [
        "download",
        {
          showOn: "series-area",
          label: "Zoom Controls",
          items: ["zoom-to-cursor", "pan-to-cursor", "reset-zoom"],
        },
        {
          showOn: "legend-item",
          label: "Legend Controls",
          items: ["toggle-series-visibility", "toggle-other-series"],
        },
        "separator",
        {
          label: "Debug Console",
          items: [
            {
              showOn: "always",
              label: `On 'always'`,
              action: () => console.log(`On 'always' clicked.`),
            },
            {
              showOn: "axis",
              label: `On 'axis'`,
              action: (ev: AgAxisContextMenuActionEvent) =>
                console.log(`On 'axis' clicked -`, ev.axisId, ev),
            },
            {
              showOn: "caption",
              label: `On 'caption'`,
              action: ({ captionType, text }) =>
                console.log(`On 'caption' clicked -`, captionType, text),
            },
            {
              showOn: "series-area",
              label: `On 'series-area'`,
              action: () => console.log(`On 'series-area' clicked.`),
            },
            {
              showOn: "series-node",
              label: `On 'series-node'`,
              action: ({ datum, xKey, yKey }) =>
                console.log(
                  `On 'series-node' clicked -`,
                  yKey,
                  datum[xKey!],
                  datum[yKey!],
                ),
            },
            {
              showOn: "legend-item",
              label: `On 'legend-item'`,
              action: ({ itemId }) =>
                console.log(`On 'legend-item' clicked -`, itemId),
            },
            {
              showOn: "cross-line",
              label: `On 'cross-line'`,
              action: ({ crossLineType, value }) =>
                console.log(`On 'cross-line' clicked -`, crossLineType, value),
            },
          ],
        },
      ],
    },
    data: getData(),
    legend: { position: "bottom" },
    zoom: { enabled: true },
    series: [
      {
        type: "line",
        marker: { size: 12 },
        tooltip: { range: "exact" },
        xKey: "year",
        yKey: "USA",
      },
      {
        type: "line",
        marker: { size: 12 },
        tooltip: { range: "exact" },
        xKey: "year",
        yKey: "EU",
      },
      {
        type: "line",
        marker: { size: 12 },
        tooltip: { range: "exact" },
        xKey: "year",
        yKey: "China",
      },
      {
        type: "line",
        marker: { size: 12 },
        tooltip: { range: "exact" },
        xKey: "year",
        yKey: "India",
      },
    ],
    axes: {
      y: { type: "number", title: { text: "GDP (Trillions USD)" } },
      x: {
        type: "category",
        label: { autoRotate: false },
        crossLines: [
          {
            type: "line",
            value: 2020,
            label: {
              text: "China overtakes EU",
              position: "top",
              fontStyle: "italic",
            },
            lineDash: [2, 4],
          },
        ],
      },
    },
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Context Menu Sub-Menus](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/context-menu-submenus)

```js
{
    contextMenu: {
        items: [
            'download',
            {
                showOn: 'series-area',
                label: 'Zoom Controls',
                items: ['zoom-to-cursor', 'pan-to-cursor', 'reset-zoom'],
            },
            {
                showOn: 'legend-item',
                label: 'Legend Controls',
                items: ['toggle-series-visibility', 'toggle-other-series'],
            },
            'separator',
            {
                label: 'Debug Console',
                items: [
                    {
                        showOn: 'always',
                        label: `On 'always'`,
                        action: () => console.log(`On 'always' clicked.`),
                    },
                    {
                        showOn: 'axis',
                        label: `On 'axis'`,
                        action: (ev) => console.log(`On 'axis' clicked -`, ev.axisId, ev),
                    },
                    {
                        showOn: 'caption',
                        label: `On 'caption'`,
                        action: ({ captionType, text }) =>
                            console.log(`On 'caption' clicked -`, captionType, text),
                    },
                    {
                        showOn: 'series-area',
                        label: `On 'series-area'`,
                        action: () => console.log(`On 'series-area' clicked.`),
                    },
                    {
                        showOn: 'series-node',
                        label: `On 'series-node'`,
                        action: ({ datum, xKey, yKey }) =>
                            console.log(`On 'series-node' clicked -`, yKey, datum[xKey], datum[yKey]),
                    },
                    {
                        showOn: 'legend-item',
                        label: `On 'legend-item'`,
                        action: ({ itemId }) => console.log(`On 'legend-item' clicked -`, itemId),
                    },
                ],
            },
        ],
    },
}
```

In this configuration:

- The built-in Zoom and Legend menu items are placed into Sub-Menus.
- An additional "Debug Console" Sub-Menu is added with custom actions to log events to the console.

## Dynamic Items

The `getItems` callback can be used to implement Dynamic Context Menus. This is typically used to write Menu Items that depend on underlying state or data in some way.

#### Dynamic Context Menu

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  BarSeriesModule,
  CategoryAxisModule,
  ContextMenuModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";
import { AgCartesianChartOptions, AgContextMenuItem } from "ag-charts-types";

type DatumType = {
  sector: string;
  nyse: number;
  lse: number;
  tyo: number;
};

function updateVisibility(seriesId: string, visible: boolean) {
  for (const series of options.series!) {
    if (series.id === seriesId) {
      series.visible = visible;
    }
  }
  chart.update(options);
}
ModuleRegistry.registerModules([
  BarSeriesModule,
  NumberAxisModule,
  CategoryAxisModule,
  LegendModule,
  ContextMenuModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions<DatumType>>({
    title: {
      text: "Stock Investment Portfolio by Sector",
    },
    subtitle: {
      text: "Allocation (%) by Market",
    },
    data: [
      { sector: "Industrial", nyse: 28, lse: 22, tyo: 18 },
      { sector: "Financial", nyse: 24, lse: 30, tyo: 20 },
      { sector: "Energy", nyse: 20, lse: 18, tyo: 25 },
      { sector: "Technology", nyse: 15, lse: 10, tyo: 12 },
      { sector: "Healthcare", nyse: 8, lse: 13, tyo: 10 },
      { sector: "Consumer Staples", nyse: 5, lse: 7, tyo: 15 },
    ],
    series: [
      {
        id: "New York Stock Exchange",
        type: "bar",
        xKey: "sector",
        yKey: "nyse",
        yName: "NYSE",
      },
      {
        id: "London Stock Exchange",
        type: "bar",
        xKey: "sector",
        yKey: "lse",
        yName: "LSE",
      },
      {
        id: "Tokyo Stock Exchange",
        type: "bar",
        xKey: "sector",
        yKey: "tyo",
        yName: "TYO",
      },
    ],
    contextMenu: {
      getItems: (params): AgContextMenuItem[] | undefined => {
        if (params.showOn === "series-node") {
          const xName = params.datum[params.xKey!];
          return [
            "defaults",
            "separator",
            // Dynamic Context Menu Item
            {
              type: "action",
              showOn: "series-node",
              label: `Log Datum "${params.seriesId} - ${xName}"`,
              action: () => console.log(params.datum),
            },
          ];
        }
      },
    },
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Dynamic Context Menu](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/dynamic-context-menu)

```js
{
    contextMenu: {
        getItems: (params) => {
            if (params.showOn === 'series-node') {
                const xName = params.datum[params.xKey];
                return [
                    'defaults',
                    'separator',
                    // Dynamic Context Menu Item
                    {
                        type: 'action',
                        showOn: 'series-node',
                        label: `Log Datum "${params.seriesId} - ${xName}"`,
                        action: () => console.log(params.datum),
                    },
                ];
            }
        },
    },
}
```

In this example:

- The Context Menu on Bar Nodes includes a console `Log Datum` Menu Item, whose text is dynamically derived from the Context Menu Event parameters.
- The `getItems` params includes a `showOn` property which indicates which element type was right-clicked, alongside relevant information about the clicked element.
- If there are multiple elements at the same click point, the `allShowOnParams` array on the `getItems` params provides the parameters for every element, not just the winning one.

### Example: Interactive Customisation

The `getItems` callback can be used to change the state or appearance of a data point or series.

#### Dynamic Context Menu Data

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  CategoryAxisModule,
  ContextMenuModule,
  LegendModule,
  LineSeriesModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";
import {
  AgCartesianChartOptions,
  AgContextMenuGetItemsParams,
  AgContextMenuItem,
  AgLineSeriesOptions,
} from "ag-charts-types";
import clone from "clone";

type DatumType = {
  year: string;
  usa: number | null;
  china: number | null;
  india: number | null;
};

const baseData: DatumType[] = [
  { year: "2015", usa: 2.9, china: 7.0, india: 8.0 },
  { year: "2016", usa: 1.8, china: 6.9, india: 8.3 },
  { year: "2017", usa: 2.0, china: 5.9, india: 6.0 },
  { year: "2018", usa: 3.0, china: 5.7, india: 5.6 },
  { year: "2019", usa: 3.2, china: 6.0, india: 3.4 },
  { year: "2020", usa: -2.2, china: 2.2, india: -5.8 },
  { year: "2021", usa: 5.8, china: 8.4, india: 9.7 },
  { year: "2022", usa: 1.9, china: 3.0, india: 7.0 },
  { year: "2023", usa: 2.5, china: 5.2, india: 8.2 },
  { year: "2024", usa: 2.8, china: 5.0, india: 6.5 },
];
// Default theme palette fills, in palette order, so the initial colours match the default theme.
const seriesMeta: {
  id: string;
  yKey: keyof DatumType;
  yName: string;
  color: string;
}[] = [
  {
    id: "United States",
    yKey: "usa",
    yName: "United States",
    color: "#5090dc",
  },
  { id: "China", yKey: "china", yName: "China", color: "#ffa03a" },
  { id: "India", yKey: "india", yName: "India", color: "#459d55" },
];
const seriesColors: {
  label: string;
  value: string;
}[] = [
  { label: "Blue", value: "#5090dc" },
  { label: "Green", value: "#459d55" },
  { label: "Orange", value: "#ffa03a" },
  { label: "Purple", value: "#9669cb" },
  { label: "Red", value: "#ef5452" },
];
const emphasisedPoints = new Set<string>();
function pointKey(seriesId: string, year: string) {
  return `${seriesId}::${year}`;
}
function colorSwatch(color: string) {
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="7" fill="${color}"/></svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}
function createSeries(): AgLineSeriesOptions<DatumType>[] {
  return seriesMeta.map(
    ({ id, yKey, yName, color }): AgLineSeriesOptions<DatumType> => ({
      id,
      type: "line",
      xKey: "year",
      yKey,
      yName,
      // Assign an explicit colour per series so the palette isn't reassigned by position when a series is removed.
      stroke: color,
      marker: {
        enabled: true,
        fill: color,
        stroke: color,
        itemStyler: ({ seriesId, datum }) =>
          emphasisedPoints.has(pointKey(seriesId, datum.year))
            ? { size: 14 }
            : {},
      },
      label: {
        enabled: true,
        color: { ref: "textColor", mix: 0.6, ontoColor: color },
        fill: { ref: "chartBackgroundColor", mix: 0.8, ontoColor: color },
        border: {
          enabled: true,
          stroke: color,
        },
        padding: 4,
        placement: ["top", "bottom", "left", "right"],
        collision: {
          alwaysShow: true,
        },
        itemStyler: ({ seriesId, datum }) => ({
          enabled: emphasisedPoints.has(pointKey(seriesId, datum.year)),
        }),
      },
    }),
  );
}
let data: DatumType[] = baseData.map((datum) => ({ ...datum }));
let series: AgLineSeriesOptions<DatumType>[] = createSeries();
ModuleRegistry.registerModules([
  LineSeriesModule,
  NumberAxisModule,
  CategoryAxisModule,
  LegendModule,
  ContextMenuModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions<DatumType>>({
    title: {
      text: "Annual GDP Growth by Country",
    },
    subtitle: {
      text: "Right-click a point or legend item to change the point or its series",
    },
    theme: {
      overrides: {
        line: {
          series: {
            highlight: {
              highlightedItem: {
                strokeWidth: 2,
              },
              unhighlightedSeries: {
                opacity: 1,
              },
            },
          },
        },
      },
    },
    data,
    series,
    contextMenu: { getItems: (params) => getItems(params) },
  });

  const getItems = (
    params: AgContextMenuGetItemsParams<DatumType>,
  ): AgContextMenuItem<DatumType>[] | undefined => {
    if (params.showOn === "legend-item") {
      const { seriesId } = params;
      return [
        "toggle-series-visibility",
        {
          type: "action",
          showOn: "legend-item",
          label: `Remove "${seriesId}"`,
          action: () => removeSeries(seriesId),
        },
        {
          showOn: "legend-item",
          label: `Colour "${seriesId}"`,
          items: seriesColors.map((color) => ({
            type: "action",
            showOn: "legend-item",
            label: color.label,
            iconUrl: colorSwatch(color.value),
            action: () => colorSeries(seriesId, color.value),
          })),
        },
      ];
    }
    const result: AgContextMenuItem<DatumType>[] = [];
    for (const paramsEntry of params.allShowOnParams) {
      if (paramsEntry.showOn === "series-node") {
        const { yKey, xKey, seriesId } = paramsEntry;
        const year = String(paramsEntry.datum[xKey!]);
        const yKeyFormatting: Record<keyof DatumType, string | undefined> = {
          china: "China",
          india: "India",
          usa: "USA",
          year: undefined,
        };
        const formattedDatum = `${yKeyFormatting[yKey!]} ${year}`;
        const isEmphasised = emphasisedPoints.has(pointKey(seriesId, year));
        result.push(
          {
            type: "action",
            showOn: "series-node",
            label: `${isEmphasised ? "Remove Emphasis from" : "Emphasise"} "${formattedDatum}" Point`,
            action: () => toggleEmphasis(seriesId, year),
          },
          {
            type: "action",
            showOn: "series-node",
            label: `Remove "${formattedDatum}" Point`,
            action: () => removeDataPoint(year, yKey!),
          },
        );
      }
    }
    return result;
  };

  const syncOptions = () => {
    const nextOptions = clone(options);

    // Sync both data and series so every update reflects the full current state.
    nextOptions.data = data;
    nextOptions.series = series;

    setOptions(nextOptions);
  };

  const toggleEmphasis = (seriesId: string, year: string) => {
    const key = pointKey(seriesId, year);
    if (emphasisedPoints.has(key)) {
      emphasisedPoints.delete(key);
    } else {
      emphasisedPoints.add(key);
    }
    // Reassign the data so the styler and label re-run with the updated emphasis set.
    data = data.map((datum) => ({ ...datum }));
    syncOptions();
  };

  const removeDataPoint = (year: string, yKey: keyof DatumType) => {
    data = data.map((datum) =>
      datum.year === year ? { ...datum, [yKey]: null } : datum,
    );
    syncOptions();
  };

  const colorSeries = (seriesId: string, color: string) => {
    series = series.map((s) =>
      s.id === seriesId
        ? {
            ...s,
            stroke: color,
            marker: { ...s.marker, fill: color, stroke: color },
          }
        : s,
    );
    syncOptions();
  };

  const removeSeries = (seriesId: string) => {
    series = series.filter((s) => s.id !== seriesId);
    syncOptions();
  };

  const reset = () => {
    emphasisedPoints.clear();
    data = baseData.map((datum) => ({ ...datum }));
    series = createSeries();
    syncOptions();
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <button onClick={reset}>
            <code>Reset</code>
          </button>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Dynamic Context Menu Data](https://www.ag-grid.com/charts/reactFunctionalTs/context-menu/examples/dynamic-context-menu-data)

```js
{
    contextMenu: {
        getItems: (params) => {
            if (params.showOn === 'series-node') {
                const { seriesId } = params;
                const year = String(params.datum[params.xKey]);
                const yKey = params.yKey;
                const isEmphasised = emphasisedPoints.has(pointKey(seriesId, year));
                return [
                    {
                        type: 'action',
                        showOn: 'series-node',
                        label: `${isEmphasised ? 'Remove Emphasis from' : 'Emphasise'} "${year}" Point`,
                        action: () => toggleEmphasis(seriesId, year),
                    },
                    {
                        type: 'action',
                        showOn: 'series-node',
                        label: `Remove "${year}" Point`,
                        action: () => removeDataPoint(year, yKey),
                    },
                ];
            }
            if (params.showOn === 'legend-item') {
                const { seriesId } = params;
                return [
                    'toggle-series-visibility',
                    {
                        type: 'action',
                        showOn: 'legend-item',
                        label: `Remove "${seriesId}"`,
                        action: () => removeSeries(seriesId),
                    },
                    // A submenu of colour options
                    {
                        showOn: 'legend-item',
                        label: `Colour "${seriesId}"`,
                        items: seriesColors.map((color) => ({
                            type: 'action',
                            showOn: 'legend-item',
                            label: color.label,
                            action: () => colorSeries(seriesId, color.value),
                        })),
                    },
                ];
            }
        },
    },
}
```

In this example, right-clicking a data point shows custom actions for that point:

- "Emphasise" enlarges the marker and displays the value; triggering it again removes the emphasis.
- "Remove Point" sets the point's value to `null`, leaving a gap in the line.

Right-clicking a legend item shows actions for the whole series:

- The built-in `toggle-series-visibility` item hides or shows the series.
- "Remove Series" drops the series.
- "Colour" is a submenu of preset colours; selecting one recolours the series' line and markers.

## API Reference

#### Context Menu

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| enabled | boolean | true | Whether to show the context menu. |
| items | AgContextMenuItem[] | ['defaults'] | List of menu items (and submenus) for the context menu. |
| getItems | AgContextMenuGetItemsCallback | undefined | Callback to list the menu items (and submenus) for the context menu. Overrides `items` if return-value is defined, otherwise `items` is used as a fallback. |

#### Always

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn | 'always' | 'always' | Which clicked element this menu item should be shown for. `'always'` menu items are always shown. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Axis

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'axis' | 'axis' | Which clicked element this menu item should be shown for. `'axis'` menu items are when clicking any part of an axis. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Caption

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'caption' | 'caption' | Which clicked element this menu item should be shown for. `'caption'` menu items are when clicking on a caption (title, subtitle, footnote). |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Cross-Line

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'cross-line' |  | Which clicked element this menu item should be shown for. `'cross-line'` menu items are shown when right-clicking a cross line's line or fill. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Series-Area

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'series-area' |  | Which clicked element this menu item should be shown for. `'series-area'` menu items are shown when clicking anywhere within the series area bounds. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Series-Node

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'series-node' |  | Which clicked element this menu item should be shown for. `'series-node'` menu items are shown when clicking when clicking on a datum node. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |

#### Legend

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| type | 'action' \| 'separator' | 'action' | The type of UI element that this item represents. |
| showOn (required) | 'legend-item' |  | Which clicked element this menu item should be shown for. `'legend-item'` menu items are shown when clicking on a legend item. |
| label (required) | string |  | The text label of this menu item. This property is required for Accessibility compliance. |
| action | Function |  | Function called when clicking on this menu item. |
| enabled | boolean | true | State of this menu-item. |
| items | AgContextMenuItem[] |  | The submenu items. If undefined or empty, then this item will just be treat like a regular menu item. Otherwise, this menu item will have a submenu popup attached to it. |
