generate_parallel_chart
Visualize multi-dimensional data with a parallel coordinates chart. Compare multiple items across several numeric dimensions to identify patterns and trade-offs.
Instructions
Generate a parallel coordinates chart to display multi-dimensional data, such as, comparing different products across multiple attributes.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Data for parallel chart, such as, [{ name: 'Product A', values: [4.2, 3.4, 2.3, 1.8] }]. | |
| dimensions | Yes | Names of the dimensions/axes, such as, ['Price', 'Quality', 'Service', 'Value']. | |
| 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/parallel.ts:41-149 (handler)The `run` function that executes the parallel chart tool logic. It calculates axis ranges, transforms data into ECharts parallel series, builds the full ECharts option with parallelAxis, parallel config, title, legend, tooltip, and calls generateChartImage to render.
run: async (params: { data: Array<{ name: string; values: number[] }>; dimensions: string[]; height: number; theme?: "default" | "dark"; title?: string; width: number; outputType?: "png" | "svg" | "option"; }) => { const { data, dimensions, height, theme, title, width, outputType } = params; // Calculate min/max for each dimension const parallelAxis = dimensions.map((dim, index) => { const values = data .map((item) => item.values[index]) .filter((v) => v !== undefined); const min = Math.min(...values); const max = Math.max(...values); const range = max - min; return { dim: index, name: dim, min: min - range * 0.1, max: max + range * 0.1, nameLocation: "start" as const, }; }); // Transform data for ECharts - create separate series for each data item const series: Array<SeriesOption> = data.map((item) => ({ name: item.name, type: "parallel", data: [ { name: item.name, value: item.values, }, ], lineStyle: { width: 2, opacity: 0.7, }, emphasis: { lineStyle: { width: 4, opacity: 1, }, }, smooth: true, })); const echartsOption: EChartsOption = { parallelAxis: parallelAxis, parallel: { left: "5%", right: "13%", bottom: "20%", top: "15%", parallelAxisDefault: { type: "value", nameLocation: "end", nameGap: 20, nameTextStyle: { fontSize: 12, }, axisLine: { lineStyle: { color: "#aaa", }, }, axisTick: { lineStyle: { color: "#777", }, }, splitLine: { show: false, }, axisLabel: { color: "#999", }, }, }, series, title: { left: "center", text: title, }, tooltip: { trigger: "item", }, legend: { bottom: 30, data: data.map((item) => item.name), }, }; return await generateChartImage( echartsOption, width, height, theme, outputType, "generate_parallel_chart", ); }, }; - src/tools/parallel.ts:13-40 (schema)The input schema defined using zod: 'data' (array of {name, values}), 'dimensions' (array of strings), plus shared schemas HeightSchema, ThemeSchema, TitleSchema, WidthSchema, OutputTypeSchema.
const data = z.object({ name: z.string().describe("Name or identifier for this data series."), values: z.array(z.number()).describe("Array of values for each dimension."), }); export const generateParallelChartTool = { name: "generate_parallel_chart", description: "Generate a parallel coordinates chart to display multi-dimensional data, such as, comparing different products across multiple attributes.", inputSchema: z.object({ data: z .array(data) .describe( "Data for parallel chart, such as, [{ name: 'Product A', values: [4.2, 3.4, 2.3, 1.8] }].", ) .nonempty({ message: "Parallel chart data cannot be empty." }), dimensions: z .array(z.string()) .describe( "Names of the dimensions/axes, such as, ['Price', 'Quality', 'Service', 'Value'].", ) .nonempty({ message: "At least one dimension is required." }), height: HeightSchema, theme: ThemeSchema, title: TitleSchema, width: WidthSchema, outputType: OutputTypeSchema, }), - src/tools/parallel.ts:18-149 (registration)The full tool definition object (generateParallelChartTool) with name 'generate_parallel_chart', description, inputSchema, and run handler.
export const generateParallelChartTool = { name: "generate_parallel_chart", description: "Generate a parallel coordinates chart to display multi-dimensional data, such as, comparing different products across multiple attributes.", inputSchema: z.object({ data: z .array(data) .describe( "Data for parallel chart, such as, [{ name: 'Product A', values: [4.2, 3.4, 2.3, 1.8] }].", ) .nonempty({ message: "Parallel chart data cannot be empty." }), dimensions: z .array(z.string()) .describe( "Names of the dimensions/axes, such as, ['Price', 'Quality', 'Service', 'Value'].", ) .nonempty({ message: "At least one dimension is required." }), height: HeightSchema, theme: ThemeSchema, title: TitleSchema, width: WidthSchema, outputType: OutputTypeSchema, }), run: async (params: { data: Array<{ name: string; values: number[] }>; dimensions: string[]; height: number; theme?: "default" | "dark"; title?: string; width: number; outputType?: "png" | "svg" | "option"; }) => { const { data, dimensions, height, theme, title, width, outputType } = params; // Calculate min/max for each dimension const parallelAxis = dimensions.map((dim, index) => { const values = data .map((item) => item.values[index]) .filter((v) => v !== undefined); const min = Math.min(...values); const max = Math.max(...values); const range = max - min; return { dim: index, name: dim, min: min - range * 0.1, max: max + range * 0.1, nameLocation: "start" as const, }; }); // Transform data for ECharts - create separate series for each data item const series: Array<SeriesOption> = data.map((item) => ({ name: item.name, type: "parallel", data: [ { name: item.name, value: item.values, }, ], lineStyle: { width: 2, opacity: 0.7, }, emphasis: { lineStyle: { width: 4, opacity: 1, }, }, smooth: true, })); const echartsOption: EChartsOption = { parallelAxis: parallelAxis, parallel: { left: "5%", right: "13%", bottom: "20%", top: "15%", parallelAxisDefault: { type: "value", nameLocation: "end", nameGap: 20, nameTextStyle: { fontSize: 12, }, axisLine: { lineStyle: { color: "#aaa", }, }, axisTick: { lineStyle: { color: "#777", }, }, splitLine: { show: false, }, axisLabel: { color: "#999", }, }, }, series, title: { left: "center", text: title, }, tooltip: { trigger: "item", }, legend: { bottom: 30, data: data.map((item) => item.name), }, }; return await generateChartImage( echartsOption, width, height, theme, outputType, "generate_parallel_chart", ); }, }; - src/tools/index.ts:11-39 (registration)Import of generateParallelChartTool from './parallel' and inclusion in tools array (line 37) and re-export (line 59).
import { generateParallelChartTool } from "./parallel"; import { generatePieChartTool } from "./pie"; import { generateRadarChartTool } from "./radar"; import { generateSankeyChartTool } from "./sankey"; import { generateScatterChartTool } from "./scatter"; import { generateSunburstChartTool } from "./sunburst"; import { generateTreeChartTool } from "./tree"; import { generateTreemapChartTool } from "./treemap"; export const tools = [ generateEChartsTool, generateAreaChartTool, generateLineChartTool, generateBarChartTool, generatePieChartTool, generateRadarChartTool, generateScatterChartTool, generateSankeyChartTool, generateFunnelChartTool, generateGaugeChartTool, generateTreemapChartTool, generateSunburstChartTool, generateHeatmapChartTool, generateCandlestickChartTool, generateBoxplotChartTool, generateGraphChartTool, generateParallelChartTool, generateTreeChartTool, ]; - src/utils/imageHandler.ts:39-170 (helper)The generateChartImage helper function called by the handler to render the ECharts option and return image data, SVG string, or ECharts option object.
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) }`, ); } }