Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
dataYesData for radar chart, such as, [{ name: 'Design', value: 70 }, { name: 'Performance', value: 85 }] or [{ name: 'Design', value: 70, group: 'iPhone' }].
heightNoSet the height of the chart, default is 600px.
themeNoSet the theme for the chart, optional, default is 'default'.default
titleNoSet the title of the chart.
widthNoSet the width of the chart, default is 800px.
outputTypeNoThe 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

  • 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",
        );
      },
    };
  • 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,
      }),
  • 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,
  • Import of generateRadarChartTool from './radar' module.
    import { generateRadarChartTool } from "./radar";
  • 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)
          }`,
        );
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It only states what the tool does, not how it behaves (e.g., output format, error handling, performance considerations). The example is helpful but insufficient for transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that efficiently communicates purpose and provides a concrete example. It is front-loaded and contains no unnecessary words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (multiple parameters, outputType), the description is incomplete. It does not mention the output formats or that the tool can return an image, SVG, or option. The output schema is absent, so the description should cover return behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds value by illustrating how to structure data for multiple series (groups) and dimensions, which goes beyond the schema's definitions. This enhances understanding of the data parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool generates a radar chart for multidimensional data with four or more dimensions. It includes an example comparing phones, which helps clarify the purpose. However, it could more explicitly differentiate radar charts from other chart types beyond mentioning 'multidimensional'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides an example but no explicit guidance on when to use this tool versus alternatives. It does not mention prerequisites, limitations, or when not to use radar charts. For a tool with many siblings, this lack of usage context is a gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hustcc/mcp-echarts'

If you have feedback or need assistance with the MCP directory API, please join our Discord server