自定义图表
import { Chart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { EChartsOption } from "echarts";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
export function PieChartDemo() {
const isDarkMode = useIsDarkMode();
const options = useMemo<EChartsOption>(
() => ({
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" },
],
},
],
}),
[],
);
return (
<Chart
echarts={echarts}
options={options}
height={400}
isDarkMode={isDarkMode}
/>
);
}自定义 HTML Tooltip
对于需要自定义 HTML 格式的 tooltip,请使用 dangerousHtmlFormatter 属性,而不是标准的 formatter。这样安全影响会更加明确。
使用 dangerousHtmlFormatter 时,强烈建议使用 echarts.format.encodeHTML 对任何用户提供的内容进行净化,以防 XSS 漏洞。
import { Chart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { EChartsOption } from "echarts";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* 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<EChartsOption>(
() => ({
tooltip: {
trigger: "item",
formatter: (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}
/>
);
}