generate_graph_chart
Generate a network graph chart to visualize relationships between entities using nodes and edges, with customizable layout, theme, and output formats including PNG, SVG, or ECharts option.
Instructions
Generate a network graph chart to show relationships (edges) between entities (nodes), such as, relationships between people in social networks.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Data for network graph chart, such as, { nodes: [{ id: 'node1', name: 'Node 1' }], edges: [{ source: 'node1', target: 'node2' }] } | |
| height | No | Set the height of the chart, default is 600px. | |
| layout | No | Layout algorithm for the graph. Default is 'force'. | force |
| 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/graph.ts:33-186 (handler)The tool definition object containing the handler logic (the 'run' function) that transforms graph data into ECharts options and calls generateChartImage to produce the output.
export const generateGraphChartTool = { name: "generate_graph_chart", description: "Generate a network graph chart to show relationships (edges) between entities (nodes), such as, relationships between people in social networks.", inputSchema: z.object({ data: z .object({ nodes: z .array(NodeSchema) .describe("Array of nodes in the network.") .nonempty({ message: "At least one node is required." }), edges: z .array(EdgeSchema) .describe("Array of edges connecting nodes.") .optional() .default([]), }) .describe( "Data for network graph chart, such as, { nodes: [{ id: 'node1', name: 'Node 1' }], edges: [{ source: 'node1', target: 'node2' }] }", ), height: HeightSchema, layout: z .enum(["force", "circular", "none"]) .optional() .default("force") .describe("Layout algorithm for the graph. Default is 'force'."), theme: ThemeSchema, title: TitleSchema, width: WidthSchema, outputType: OutputTypeSchema, }), run: async (params: { data: { nodes: Array<{ id: string; name: string; value?: number; category?: string; }>; edges: Array<{ source: string; target: string; value?: number }>; }; height: number; layout?: "force" | "circular" | "none"; theme?: "default" | "dark"; title?: string; width: number; outputType?: "png" | "svg" | "option"; }) => { const { data, height, layout = "force", theme, title, width, outputType, } = params; // Validate that all edge nodes exist in nodes array const nodeIds = new Set(data.nodes.map((node) => node.id)); const validEdges = data.edges.filter( (edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target), ); // Extract unique categories for legend const categories = Array.from( new Set( data.nodes .map((node) => node.category) .filter((cat): cat is string => Boolean(cat)), ), ); // Transform nodes for ECharts const nodes = data.nodes.map((node) => ({ id: node.id, name: node.name, symbolSize: node.value ? Math.sqrt(node.value) * 10 : 20, category: node.category, value: node.value, })); // Transform edges for ECharts const links = validEdges.map((edge) => ({ source: edge.source, target: edge.target, value: edge.value, })); const series: Array<SeriesOption> = [ { type: "graph", data: nodes, links: links, categories: categories.map((cat) => ({ name: cat })), roam: true, layout: layout, force: layout === "force" ? { repulsion: 100, gravity: 0.02, edgeLength: 150, layoutAnimation: true, } : undefined, label: { show: true, position: "right", formatter: "{b}", }, lineStyle: { color: "source", curveness: 0.3, }, emphasis: { focus: "adjacency", label: { fontSize: 16, }, }, }, ]; const echartsOption: EChartsOption = { series, title: { left: "center", text: title, }, tooltip: { trigger: "item", }, legend: categories.length > 0 ? { left: "center", orient: "horizontal", bottom: 10, data: categories, } : undefined, }; return await generateChartImage( echartsOption, width, height, theme, outputType, "generate_graph_chart", ); }, }; - src/tools/graph.ts:12-31 (schema)Zod schemas defining the structure of nodes and edges in the graph data input.
// Node schema const NodeSchema = z.object({ id: z.string().describe("Unique identifier for the node."), name: z.string().describe("Display name of the node."), value: z .number() .optional() .describe("Value associated with the node (affects size)."), category: z .string() .optional() .describe("Category of the node (affects color)."), }); // Edge schema const EdgeSchema = z.object({ source: z.string().describe("Source node id."), target: z.string().describe("Target node id."), value: z.number().optional().describe("Weight or value of the edge."), }); - src/tools/graph.ts:37-63 (schema)The input schema for generate_graph_chart tool, defining accepted parameters including data, height, layout, theme, title, width, and outputType.
inputSchema: z.object({ data: z .object({ nodes: z .array(NodeSchema) .describe("Array of nodes in the network.") .nonempty({ message: "At least one node is required." }), edges: z .array(EdgeSchema) .describe("Array of edges connecting nodes.") .optional() .default([]), }) .describe( "Data for network graph chart, such as, { nodes: [{ id: 'node1', name: 'Node 1' }], edges: [{ source: 'node1', target: 'node2' }] }", ), height: HeightSchema, layout: z .enum(["force", "circular", "none"]) .optional() .default("force") .describe("Layout algorithm for the graph. Default is 'force'."), theme: ThemeSchema, title: TitleSchema, width: WidthSchema, outputType: OutputTypeSchema, }), - src/tools/index.ts:8-61 (registration)The tool is imported from './graph', added to the 'tools' array, and re-exported for use by the MCP server.
import { generateGraphChartTool } from "./graph"; import { generateHeatmapChartTool } from "./heatmap"; import { generateLineChartTool } from "./line"; 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, ]; // Re-export individual tools for convenient use in tests and other places export { 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 that handles rendering the ECharts option into an image (PNG/SVG/option), with MinIO storage support and 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) }`, ); } }