generate_radar_chart
Compare multidimensional data across four or more dimensions with a radar chart. Visualize criteria like design, performance, camera, and battery life for multiple entities using grouped series.
Instructions
Generate a radar chart to display multidimensional data (four dimensions or more), such as, evaluate Huawei and Apple phones in terms of five dimensions: ease of use, functionality, camera, benchmark scores, and battery life.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Data for radar chart, such as, [{ name: 'Design', value: 70 }, { name: 'Performance', value: 85 }] or [{ name: 'Design', value: 70, group: 'iPhone' }]. | |
| height | No | Set the height of the chart, default is 600px. | |
| theme | No | Set the theme for the chart, optional, default is 'default'. | default |
| title | No | Set the title of the chart. | |
| width | No | Set the width of the chart, default is 800px. | |
| outputType | No | The output type of the diagram. Can be 'png', 'svg' or 'option'. Default is 'png', 'png' will return the rendered PNG image, 'svg' will return the rendered SVG string, and 'option' will return the valid ECharts option. | png |
Implementation Reference
- src/tools/radar.ts:41-177 (handler)The main handler function (run method) that executes the radar chart generation logic. It processes input data (supports both single-series and multi-series grouped data), builds ECharts radar configuration with indicators and series, and calls generateChartImage to render the chart.
run: async (params: { data: Array<{ name: string; value: number; group?: string }>; height: number; theme?: "default" | "dark"; title?: string; width: number; outputType?: "png" | "svg" | "option"; }) => { const { data, height, theme, title, width, outputType } = params; // Check if data has group field for multiple series const hasGroups = data.some((item) => item.group); // Collect all unique dimensions const dimensionSet = new Set<string>(); for (const item of data) { dimensionSet.add(item.name); } const dimensions = Array.from(dimensionSet).sort(); // Create radar indicator configuration // Calculate the maximum value for all dimensions, then use the same max value to avoid alignTicks warning const allValues = data.map((item) => item.value); const globalMaxValue = Math.max(...allValues); const unifiedMax = Math.ceil((globalMaxValue * 1.2) / 10) * 10; const indicator = dimensions.map((name) => ({ name, max: unifiedMax, })); let series: Array<SeriesOption> = []; if (hasGroups) { // Handle multiple series data (grouped) const groupMap = new Map< string, Array<{ name: string; value: number }> >(); // Group data by group field for (const item of data) { const groupName = item.group || "Default"; if (!groupMap.has(groupName)) { groupMap.set(groupName, []); } const groupData = groupMap.get(groupName); if (groupData) { groupData.push({ name: item.name, value: item.value }); } } // Create series data for each group const seriesData = Array.from(groupMap.entries()).map( ([groupName, groupData]) => { // Create a map for quick lookup const dataMap = new Map(groupData.map((d) => [d.name, d.value])); // Fill values for all dimensions (0 for missing data) const values = dimensions.map( (dimension) => dataMap.get(dimension) ?? 0, ); return { name: groupName, value: values, }; }, ); series = [ { data: seriesData, type: "radar", }, ]; } else { // Handle single series data const dataMap = new Map(data.map((d) => [d.name, d.value])); const values = dimensions.map((dimension) => dataMap.get(dimension) ?? 0); series = [ { data: [ { value: values, name: title || "Data", }, ], type: "radar", }, ]; } const echartsOption: EChartsOption = { legend: hasGroups ? { left: "center", orient: "horizontal", bottom: "5%", } : undefined, radar: { indicator, radius: "60%", splitNumber: 4, axisName: { formatter: "{value}", color: "#666", }, splitArea: { areaStyle: { color: ["rgba(250, 250, 250, 0.3)", "rgba(200, 200, 200, 0.3)"], }, }, }, series, title: { left: "center", text: title, top: "5%", }, tooltip: { trigger: "item", }, }; return await generateChartImage( echartsOption, width, height, theme, outputType, "generate_radar_chart", ); }, }; - src/tools/radar.ts:12-40 (schema)Input schema definitions using Zod. Defines the 'data' item schema (name, value, optional group) and the full inputSchema for the tool, including data array, height, theme, title, width, and outputType fields.
// Radar chart data schema const data = z.object({ name: z.string().describe("Dimension name, such as 'Design'."), value: z.number().describe("Value of the dimension, such as 70."), group: z .string() .optional() .describe( "Group name for multiple series, used for comparing different entities", ), }); export const generateRadarChartTool = { name: "generate_radar_chart", description: "Generate a radar chart to display multidimensional data (four dimensions or more), such as, evaluate Huawei and Apple phones in terms of five dimensions: ease of use, functionality, camera, benchmark scores, and battery life.", inputSchema: z.object({ data: z .array(data) .describe( "Data for radar chart, such as, [{ name: 'Design', value: 70 }, { name: 'Performance', value: 85 }] or [{ name: 'Design', value: 70, group: 'iPhone' }].", ) .nonempty({ message: "Radar chart data cannot be empty." }), height: HeightSchema, theme: ThemeSchema, title: TitleSchema, width: WidthSchema, outputType: OutputTypeSchema, }), - src/tools/index.ts:20-26 (registration)Registration of generateRadarChartTool in the tools array (line 26) alongside all other chart tools, making it available as an MCP tool.
export const tools = [ generateEChartsTool, generateAreaChartTool, generateLineChartTool, generateBarChartTool, generatePieChartTool, generateRadarChartTool, - src/tools/index.ts:13-13 (registration)Import of generateRadarChartTool from './radar' module.
import { generateRadarChartTool } from "./radar"; - src/utils/imageHandler.ts:39-170 (helper)The generateChartImage helper function called by the radar chart handler. It renders the chart via ECharts, handles output (PNG/SVG/option), supports MinIO storage with Base64 fallback.
export async function generateChartImage( echartsOption: EChartsOption, width = 800, height = 600, theme: "default" | "dark" = "default", outputType: ImageOutputFormat = "png", toolName = "unknown", ): Promise<ImageHandlerResult> { // Debug logging if (process.env.DEBUG_MCP_ECHARTS) { console.error(`[DEBUG] ${toolName} generating chart:`, { width, height, theme, outputType, optionKeys: Object.keys(echartsOption), }); } try { // Render chart const result = await renderECharts( echartsOption, width, height, theme, outputType, ); // Determine output type const isImage = outputType !== "svg" && outputType !== "option"; if (!isImage) { // SVG or configuration options, return text directly const response = { content: [ { type: "text" as const, text: result as string, }, ], }; if (process.env.DEBUG_MCP_ECHARTS) { console.error(`[DEBUG] ${toolName} chart generated successfully:`, { contentType: "text", textLength: (result as string).length, }); } return response; } // PNG image type const buffer = result as Buffer; if (isMinIOConfigured()) { try { // Use MinIO storage, return URL const url = await storeBufferToMinIO(buffer, "png", "image/png"); const response = { content: [ { type: "text" as const, text: url, }, ], }; if (process.env.DEBUG_MCP_ECHARTS) { console.error(`[DEBUG] ${toolName} chart generated successfully:`, { contentType: "text", url: url, }); } return response; } catch (minioError) { // MinIO failed, log warning and fallback to Base64 if (process.env.DEBUG_MCP_ECHARTS) { console.error( `[DEBUG] ${toolName} MinIO storage failed, falling back to Base64:`, { error: minioError instanceof Error ? minioError.message : String(minioError), }, ); } // Continue to Base64 fallback below } } // Fallback to Base64 const base64Data = buffer.toString("base64"); const response = { content: [ { type: "image" as const, data: base64Data, mimeType: "image/png", }, ], }; if (process.env.DEBUG_MCP_ECHARTS) { console.error(`[DEBUG] ${toolName} chart generated successfully:`, { contentType: "image", dataLength: base64Data.length, }); } return response; } catch (error) { // Error logging if (process.env.DEBUG_MCP_ECHARTS) { console.error(`[DEBUG] ${toolName} chart generation failed:`, { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } throw new Error( `Chart rendering failed: ${ error instanceof Error ? error.message : String(error) }`, ); } }