Custom Chart
@cloudflare/kumo

Custom Chart

import { Chart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { KumoChartOption } from "@cloudflare/kumo";
import { useMemo } from "react";

export function PieChartDemo() {
  const isDarkMode = useIsDarkMode();

  const options = useMemo(
    () =>
      ({
        animation: true,
        animationDuration: 2000,
        tooltip: {
          show: true,
        },
        series: [
          {
            type: "pie",
            data: [
              { value: 101, name: "Series A" },
              { value: 202, name: "Series B" },
              { value: 303, name: "Series C" },
              { value: 404, name: "Series D" },
              { value: 505, name: "Series E" },
            ],
          },
        ],
      }) satisfies KumoChartOption,
    [],
  );

  return (
    <Chart
      echarts={echarts}
      options={options}
      height={400}
      isDarkMode={isDarkMode}
    />
  );
}

Custom Tooltip with HTML

For tooltips that require custom HTML formatting, use the dangerousHtmlFormatter property instead of the standard formatter. This makes the security implications more explicit.

When using dangerousHtmlFormatter, it is strongly recommended to sanitize any user-provided content using echarts.format.encodeHTML to prevent XSS vulnerabilities.

import { Chart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { KumoChartOption } from "@cloudflare/kumo";
import { useMemo } from "react";

/**
 * Custom chart with HTML tooltip using dangerousHtmlFormatter.
 * USE WITH CAUTION: Only use dangerousHtmlFormatter for trusted HTML content.
 * Always sanitize any user-provided data using echarts.format.encodeHTML
 * or similar utilities to prevent XSS vulnerabilities.
 */
export function CustomTooltipChartDemo() {
  const isDarkMode = useIsDarkMode();

  const options = useMemo<KumoChartOption>(
    () => ({
      tooltip: {
        trigger: "item",
        // Use dangerousHtmlFormatter instead of formatter to make the
        // security implications explicit. Only use with trusted content.
        dangerousHtmlFormatter: (params: any) => {
          // IMPORTANT: Always escape ALL dynamic values using encodeHTML
          // from echarts/format before including in HTML. This prevents
          // XSS attacks from malicious data like:
          // { name: "<img src=x onerror=alert('xss')>", value: "..." }
          const safeName = echarts.format.encodeHTML(params.name);
          const safeValue = echarts.format.encodeHTML(String(params.value));
          const safePercent = echarts.format.encodeHTML(
            String(Math.round(params.percent)),
          );

          return `
            <div style="padding: 8px;">
              <div style="font-weight: 600; margin-bottom: 4px;">${safeName}</div>
              <div>Value: <strong>${safeValue}</strong></div>
              <div style="font-size: 12px; opacity: 0.7; margin-top: 4px;">
                ${safePercent}% of total
              </div>
            </div>
          `;
        },
      },
      series: [
        {
          type: "pie",
          data: [
            { value: 101, name: "Series A" },
            { value: 202, name: "Series B" },
            // Malicious series name to demonstrate XSS protection via encodeHTML.
            // Without encoding, this would render an alert popup. With encodeHTML,
            // it safely displays as plain text.
            { value: 150, name: "<img src=x onerror=alert('XSS')>" },
            { value: 303, name: "Series C" },
            { value: 404, name: "Series D" },
          ],
        },
      ],
    }),
    [],
  );

  return (
    <Chart
      echarts={echarts}
      options={options}
      height={400}
      isDarkMode={isDarkMode}
    />
  );
}