Adding a Custom Context Menu to CanvasJS Charts on Right-Click

CanvasJS enables interactive charts for web apps. Adding a custom context menu on right-click allows quick actions like printing or exporting. In this guide, we’ll implement one using a monthly sales column chart demo. It’s straightforward and customizable.

Why Add a Custom Context Menu?

Context menus boost UX by providing an intuitive, native feel, right-clicking works just like in desktop apps. They also promote efficiency by hiding advanced features until needed, which keeps your charts looking clean and uncluttered. Plus, they’re highly flexible, letting you extend them for things like exports, data tweaks, or even dynamic annotations without complicating the interface.

Prerequisites

To follow along, you’ll need a basic grasp of HTML, CSS, and JavaScript, along with the CanvasJS Chart library.

Step 1: Set Up the Basic Chart

To demonstrate, we’ll use a basic multi-series column chart for monthly sales. Embed the library and container first:

<script src="https://cdn.canvasjs.com/canvasjs.min.js"></script>
<div id="chartContainer"></div>

Briefly initialize chart with shared tooltips. Add a callback to hide tooltips during menu use (to prevent overlap).

var chart = new CanvasJS.Chart("chartContainer", {
  animationEnabled: true,
  theme: "light2",
  title: { text: "Monthly Sales Comparison (Electronics vs Home Appliances)", fontSize: 20 },
  subtitles: [{ text: "Right click to get the custom context menu" }],
  axisX: { title: "Months", titleFontColor: "#333", labelFontColor: "#555", labelFontSize: 14 },
  axisY: { title: "Sales", labelFontSize: 14, prefix: "$", includeZero: true },
  toolTip: {
    shared: true,
    updated: function(e) {
      if (contextMenu && contextMenu.style.display === "block") {
        chart.toolTip.hide();
      }
    }
  },
  data: [ /* data array */ ]
});
chart.render();

Step 2: Create the Context Menu HTML

The foundation of the menu is a hidden overlay dropdown placed immediately following the chart container. For accessibility, it must be structured as a semantic HTML list (<ul> with <li> elements), each containing an interactive element (<a>). The data-exportType attributes are essential, serving as JavaScript hooks to route specific actions:

  • “print”: Triggers the chart’s native print method.

  • “png” / “jpg”: Defines the desired image export format.

  • “cancel”: Simply dismisses the dropdown.

This modular approach ensures that adding new export options (such as “pdf”) is a straightforward task.

<div id="contextMenu">
  <ul role="list">
    <li role="listitem">
      <button data-exportType="print">Print</button>
    </li>
    <li role="listitem">
      <button data-exportType="png">Save as PNG</button>
    </li>
    <li role="listitem">
      <button data-exportType="jpg">Save as JPG</button>
    </li>
    <li role="listitem" class="seperator">
      <button data-exportType="cancel">Cancel</button>
    </li>
  </ul>
</div>

Step 4: Handle Events with JavaScript

Event handling brings the menu to life. First, cache DOM elements: the chart’s container (retrieved via the CanvasJS chart.get("container") method for direct access to the DOM element, which is more reliable than querying by ID), the menu for toggling, and buttons via querySelectorAll for efficiency. This avoids repeated lookups during interactions and ties the logic closely to the chart instance.

var chartDiv = chart.get("container");
var contextMenu = document.getElementById("contextMenu");
var exportButtons = contextMenu.querySelectorAll("button[data-exportType]");

To implement the export mechanism, we must iterate through the available buttons using a for loop, attaching an event listener to each. Within the handler, the button’s data-exportType attribute must be extracted to execute the appropriate function. Specifically, invoking the ‘print’ type should trigger a print preview via chart.print(). Conversely, the ‘PNG’ and ‘JPG’ types must utilize chart.exportChart({ format: '...' }) to initiate a direct file download. To ensure a seamless user experience, the export menu must be hidden immediately after the action, maintaining consistent dismissal even if the user cancels. This standardized protocol also enhances the scalability for integrating new export formats.

for (let i = 0; i < exportButtons.length; i++) {
  var button = exportButtons[i];
  button.addEventListener("click", function() {
    var exportType = this.getAttribute("data-exportType");
    if (exportType === "print") {
      chart.print();
    } else if (exportType === "png") {
      chart.exportChart({ format: "png" });
    } else if (exportType === "jpg") {
      chart.exportChart({ format: "jpg" });
    }
    contextMenu.style.display = "none";
  });
}

The core trigger is a contextmenu listener on the chart’s container div. preventDefault() blocks the browser’s native menu, then set the menu’s left and top to the event’s pageX/pageY for cursor-following precision. Show it with display: block and hide tooltips via the chart API.

chartDiv.addEventListener("contextmenu", function(event) {
  event.preventDefault();
  contextMenu.style.left = event.pageX + "px";
  contextMenu.style.top = event.pageY + "px";
  contextMenu.style.display = "block";
  chart.toolTip.hide();
})

To close on outside clicks, attach a document-level listener—any click bubbles up and hides the menu, unless it’s on a button (handled separately). This creates intuitive, escape-hatch behavior.

document.addEventListener("click", function() {
  contextMenu.style.display = "none";
});

Full Working Demo

Customization Tips

You can further enhance this approach by adding dynamic annotations — capturing click coordinates on the chart, prompting the user for annotation text, and pushing the resulting note into an annotations array followed by chart.render() for instant visual updates. This makes your charts more interactive and allows users to leave contextual notes or insights directly within the visualization.

This approach gives developers complete control over user interactions, enabling chart-specific actions such as exporting, filtering, or drilling down on data. It’s lightweight, dependency-free, and easily adaptable to multiple charts or dynamic datasets.

Similar Posts