What This Skill Does
Generate publication-quality data visualization charts as standalone files. The output can be:
- HTML: Single
.htmlfile with inline SVG and embedded CSS (default, no JavaScript) - SVG: Standalone
.svgfile - Mermaid: Text syntax (limited chart support, see Mermaid rules)
- PNG/PDF: Via the export script at
../gm-architecture/skills/gm-architecture/scripts/export.sh
Output Directory Structure
All example files must be output to the correct directory structure:
assets/examples/
├── {example-name}.json # JSON schema file
├── {example-name}.html # HTML rendered output (same name as JSON)
└── images/ # For preview screenshots only
└── {example-name}-preview.png
Rules
Correct approach:
- HTML and JSON files go in the
examples/root directory - Preview images go in the
examples/images/subdirectory - Filenames must be consistent (only extensions differ)
Prohibited approach:
- Do NOT put HTML files in the
images/directory - Do NOT generate any non-image files in the
images/directory - Do NOT put JSON files in subdirectories (unless there's explicit categorization need)
Example
For the chrome-architecture example:
assets/examples/chrome-architecture.jsonassets/examples/chrome-architecture.htmlassets/examples/images/chrome-architecture-preview.pngassets/examples/images/chrome-architecture.html(WRONG: HTML should not be in images/)
Interactive Selection
When the user's request does NOT already specify all three choices (chart type, style, output format), use the AskUserQuestion tool to let the user choose.
Important: AskUserQuestion supports at most 4 options per question and 4 questions per call.
Selection Strategy
For each dimension, follow this logic:
- Can the choice be inferred from the user's description? → Skip asking, use the inferred value directly.
- Does the user explicitly state it? → Skip asking, use the stated value.
- Still ambiguous? → Ask via
AskUserQuestion.
Question 1 — Chart Type (ask only if ambiguous)
The user's description usually makes the type obvious. Only ask when multiple types could fit. Since there are 9 chart types, split into groups:
First ask which group:
question: "Which chart category fits your needs?"
header: "Category"
options:
- label: "Comparison"
description: "Bar chart — compare categories with bars"
- label: "Proportion"
description: "Pie / Donut — show proportions of a whole"
- label: "Trend / Distribution"
description: "Line / Area / Scatter — show trends, area, or correlation"
- label: "Special"
description: "Radar / Funnel / Bubble / Table — multi-axis, pipeline, 3D data, or grid"
Then drill down within the category:
- Comparison → Bar Chart (no further question needed)
- Proportion → Ask: "Pie or Donut?" (2 options, simple)
- Trend / Distribution → Ask: "Line / Area / Scatter?"
- Special → Ask: "Radar / Funnel / Bubble / Table?"
Question 2 — Visual Style
Use the same 3-family approach as architecture-diagram:
First ask:
"Which style family?" → Dark / Light+Clean / Creative
Then drill down within the family.
Infer from context when possible: technical content → dark, business → light-corporate, creative → cyberpunk/pastel.
Question 3 — Output Format
Default is HTML. Only ask if the user mentions a specific use case (GitHub → Mermaid, print → SVG/PDF).
Metadata Version Field
When generating JSON, metadata.version must follow these rules:
- Format: Must conform to the SemVer (Semantic Versioning) format:
major.minor.patch - Example:
"0.6.1","1.2.0","2.0.0" - Forbidden placeholder versions:
"0.0.0","1.0","1.0.0"
Recommended version strategy: It is recommended to use a value consistent with the plugin version, but this is not mandatory. What matters is maintaining format compliance.
JSON example:
"metadata": {
"author": "gmdiagram",
"date": "2026-04-14",
"version": "0.6.1"
}
Validation rules:
- Must contain 3 numeric segments separated by dots
- Each segment must be pure digits
- Leading zeros are not allowed (e.g.
"01.02.03"is invalid)
Two-Step Generation Process
ALL chart generation follows exactly two steps. Never skip to output directly.
Step 1 — Extract to JSON Schema
Extract data from the user's natural language description into the typed JSON schema for the chosen chart type.
Schema files:
| Chart Type | Schema File |
|---|---|
| bar | assets/schema-bar.json |
| pie | assets/schema-pie.json |
| line | assets/schema-line.json |
| area | assets/schema-area.json |
| scatter | assets/schema-scatter.json |
| radar | assets/schema-radar.json |
| funnel | assets/schema-funnel.json |
| bubble | assets/schema-bubble.json |
| table | assets/schema-table.json |
Shared schema definitions are in assets/schema-shared.json (styleEnum, formatEnum, legendConfig, axisConfig, scatterAxisConfig, sanitizedString, etc.). Individual schemas reference shared definitions via $ref.
Critical: Nice Numbers algorithm runs in this step.
Read references/axis-and-grid.md for the full algorithm. Compute Y-axis ticks and store them in axis.y.ticks. For scatter and bubble charts, compute Nice Numbers for both X and Y axes and store in axis.x.ticks and axis.y.ticks. This avoids complex math in Step 2.
Data extraction rules:
- Extract numeric values and labels from user text
- Infer series names when multiple data sets are mentioned
- When user provides percentages, store as raw values (not pre-multiplied)
- When user says "compare X and Y", infer grouped bar chart with 2 series
- All string fields have maxLength constraints — truncate if necessary with a note to the user
- Scatter/Bubble: Extract x/y pairs from user data. Infer axis labels from context (e.g., "height vs weight" → x label "Height", y label "Weight"). For bubble, also extract size values.
- Radar: Extract dimension labels (axes) and values per series. Ensure all series have the same number of data points as axes.
- Funnel: Extract stage names and values. Note that values should be in descending order; if not, auto-sort will be applied during rendering.
- Table: Extract column headers and row data. Ensure each row has a value for every column.
Validation:
- Bar/Line/Area:
seriesmax 8,dataper series max 30. If exceeded, must ask user to simplify. - Scatter:
seriesmax 8,dataper series max 50. - Bubble:
seriesmax 5,dataper series max 30. - Radar:
axesmin 3 max 12,seriesmax 6,datalength must matchaxeslength. - Pie/Funnel:
datamax 12 (pie) / max 8 (funnel). If exceeded, suggest merging smallest items into "Others" (pie) or simplifying stages (funnel). - Table:
columnsmin 2 max 12,rowsmax 50. Each row must contain all column keys. - All
valuefields must be finite numbers (no NaN, Infinity). - All
colorfields must match^#[0-9a-fA-F]{6}$. - Radar
axes[].maxValue: either all specified or all omitted (all-or-nothing). Partial specification is rejected in Step 1 validation.
Step 2 — Render Output
Read the corresponding render reference and template, then generate SVG wrapped in HTML.
Render references:
| Chart Type | Render Reference |
|---|---|
| bar | references/render-bar.md |
| pie | references/render-pie.md |
| line | references/render-line.md |
| area | references/render-area.md |
| scatter | references/render-scatter.md |
| radar | references/render-radar.md |
| funnel | references/render-funnel.md |
| bubble | references/render-bubble.md |
| table | references/render-table.md |
Shared references (read for all chart types):
references/axis-and-grid.md— axis, grid, tick renderingreferences/color-palettes.md— palette selection and color assignment
Template: Use ../../assets/template-{style}.html (shared templates with chart CSS classes already added).
Rendering approach — Hybrid Mode:
- Data marks (bars, arcs, lines, dots): pure SVG native elements (
<rect>,<path>,<circle>,<line>) - Text (labels, legend, axis titles):
foreignObject+ CSS layout - All
foreignObjectroot elements must includexmlns="http://www.w3.org/1999/xhtml"
HTML Language Attribute
Set the correct <html lang="..."> attribute according to the content language:
| Content language | lang attribute value | Use case |
|---|---|---|
| English | en |
Title, labels, and description are all in English |
| Simplified Chinese | zh-CN |
Title, labels, and description contain Simplified Chinese |
| Traditional Chinese | zh-TW |
Title, labels, and description contain Traditional Chinese |
| Japanese | ja |
Title, labels, and description contain Japanese |
Automatic detection rules
Use the following heuristic rules to determine the content language:
- Check the title field: If it contains Chinese characters (
\u4e00-\u9fff), usezh-CN - Check the subtitle field: If it contains Chinese characters, use
zh-CN - Check the main labels: If more than 50% of the labels contain Chinese, use
zh-CN - Default: If uncertain or mixed languages, use
en
Example
Chinese content example:
<html lang="zh-CN">
<head><title>2025年各季度营收</title></head>
English content example:
<html lang="en">
<head><title>Q1-Q4 Revenue Comparison</title></head>
Importance
A correct lang attribute helps with:
- Correct pronunciation by screen readers
- Correct font rendering by browsers
- SEO optimization
Chart Type Reference
| Type | Trigger Keywords | Status |
|---|---|---|
| bar | bar chart, column chart, bar chart, bar graph, comparison | Available |
| pie | pie chart, donut chart, pie chart, ring chart, share, proportion, proportion, percentage | Available |
| line | line chart, line graph, line chart, trend, trend, time series, time series | Available |
| area | area chart, area chart, stacked area, stacked area | Available |
| scatter | scatter chart, scatter plot, scatter plot, correlation, correlation, distribution | Available |
| radar | radar chart, spider chart, radar chart, spider chart, multi-dimensional, multi-dimensional | Available |
| funnel | funnel chart, funnel chart, conversion rate, conversion, pipeline, pipeline | Available |
| bubble | bubble chart, bubble chart, three-dimensional data, three-dimensional data | Available |
| table | data table, table chart, table, data table | Available |
Mermaid output matrix:
| Type | Mermaid Support | Syntax |
|---|---|---|
| Bar | Yes | xychart-beta |
| Pie | Yes | pie title |
| Line | Yes | xychart-beta |
| Area | No | — |
| Scatter | No | — |
| Radar | No | — |
| Funnel | No | — |
| Bubble | No | — |
| Table | No | — |
For chart types without Mermaid support, inform the user of the limitation and suggest HTML or SVG instead.
Data Extraction Rules
- Extract numbers and labels directly from the user's description
- Support formats like "Q1=1.2 million, Q2=1.5 million" or "Chrome 65%, Safari 19%"
- Remove units from values (store "120" not "1.2 million"), put units in
axis.y.labelorsubtitle - When multiple series are mentioned, create separate series objects
- Handle percentage input: if all values sum to ~100, they're percentages; store as-is
File Naming Convention
Example file names must use kebab-case(lowercase letters connected by hyphens).
Naming format
{context}-{type}[-{variant}].{ext}
- context: Scenario description (e.g. chrome, saas, ecommerce, quarterly-revenue)
- type: Chart type (optional, e.g. architecture, flowchart, bar-chart)
- variant: Variant identifier (optional, e.g. cyberpunk, minimalist, dark)
- ext: file extension (json or html)
Correct and incorrect examples
| Naming style | Example | Status |
|---|---|---|
| kebab-case ✅ | chrome-architecture.json |
Correct |
| kebab-case ✅ | quarterly-revenue.json |
Correct |
| kebab-case ✅ | team-skills-radar.json |
Correct |
| camelCase ❌ | chromeArchitecture.json |
Incorrect |
| PascalCase ❌ | QuarterlyRevenue.json |
Incorrect |
| snake_case ❌ | team_skills.json |
Incorrect |
| space ❌ | chrome architecture.json |
Incorrect |
Rules
- All lowercase: the file name contains no uppercase letters
- Word separation: use hyphens
-to separate words, do not use underscores or spaces - No special characters: use only letters, numbers and hyphens
- Clear semantics: the file name should clearly express the example content
Batch naming examples
For multiple style variants of the same system:
architecture-system.json(default style)architecture-system-cyberpunk.jsonarchitecture-system-minimalist.jsonarchitecture-system-terminal.json
Quality Checklist
Before finalizing any chart output, verify ALL of the following:
Data & Schema
- JSON validates against the chart type's schema file
-
axis.y.ticksuses nice numbers (no awkward decimals unless data has decimals) - For scatter/bubble:
axis.x.ticksalso uses nice numbers - All values are finite numbers (no NaN or Infinity)
- Array sizes within limits (series, data, axes, columns, rows as per type constraints)
Visual
- Bar width and spacing visually balanced
- Text labels readable (>= 11px, contrast >= 3:1 against background)
- ViewBox fits content — no clipping, no more than 20% whitespace
- Legend matches actual data series/colors
- Grid lines don't overlap with axis line
Technical
- All
foreignObjectroot elements havexmlns="http://www.w3.org/1999/xhtml" - All user text is HTML-entity-escaped (
<,>,&in text content; additionally"and control chars in attribute values) - No user text in element name or attribute name positions
- Colors in
#rrggbbformat only — norgb(),rgba(), named colors,url() - No JavaScript in output
- ViewBox width and height are integers (no decimals)
- CSS classes match template definitions (
.axis-line,.grid-line,.bar,.tick-label, etc.)
Security
- No
<scripttags in output - No
onerror,onload,onclickor other event handler attributes - No
url()in inline style attributes - No
<iframe>,<object>,<embed>tags - No extra
<style>tags beyond what the template provides
Bar Chart Specific
- Bars are proportional to data values
- Y-axis ticks evenly spaced
- X-axis labels centered under each bar/group
- Negative values handled correctly (bars extend below baseline)
Pie Chart Specific
- Slice angles are proportional to data values
- Arcs start from 12 o'clock position (-90 degree offset)
- Donut variant: inner arc path has correct annular sector (sweep-flag = 0 for inner arc)
- Percentages computed and displayed correctly:
(item.value / totalValue * 100).toFixed(1) - Slices < 5% have no external label (still shown in legend)
Line Chart Specific
- Edge-to-edge X spacing used (NOT grouped slots like bar chart):
pointX(g) = plotX + g * (plotWidth / (numDataPoints - 1)) - Catmull-Rom smooth mode: phantom points computed correctly for boundary segments (
P[-1] = 2*P[0] - P[1],P[n] = 2*P[n-1] - P[n-2]) - Data point markers match the specified shape (circle, square, diamond, none)
Area Chart Specific
- Stacked mode: cumulative values computed correctly, top-to-bottom render order
- Overlaid mode: opacity applied correctly to each series fill
- Nice Numbers applied to cumulative max (stacked) or individual max (overlaid)
- Each area path closed to the baseline correctly
Scatter Chart Specific
- Dual-axis Nice Numbers computed (both X and Y axes have ticks)
- Both axes display tick labels
- Triangle point shape rendered correctly (upright equilateral triangle)
- Data point labels only shown when
data[].labelis present
Radar Chart Specific
- Concentric polygon grid drawn (not circular grid)
-
axes[].maxValueis all-or-nothing: either all specified or all auto-computed - Polygon vertices computed correctly using polar coordinates from center
- Data polygon normalized against per-axis maxValue
- Axis labels positioned outside the outer polygon edge
Funnel Chart Specific
- Data auto-sorted in descending order if not already sorted
- Last stage has flat bottom (not tapered to a point)
- Conversion rates displayed between adjacent stages (when
showConversionRate: true) - Stage widths proportional to values relative to the maximum value
Bubble Chart Specific
- Size mapped via area (r-squared), not linear radius:
area = minR^2 + (size - minSize) / (maxSize - minSize) * (maxR^2 - minR^2) - Render order: ascending by size (small bubbles drawn first, large on top)
- Dual numeric axes with Nice Numbers on both X and Y
- Size legend included showing min/max bubble mapping
Table Chart Specific
- No SVG graphic elements — pure foreignObject + HTML table
-
sanitizedStringenforced on all cell content (no<>characters) - Highlight colors are fixed system values (Max: green, Min: red), not user-customizable
-
columns[].widthmatches pattern^(\d{1,4}px|auto)$— no arbitrary CSS values - Table width matches column widths appropriately
Iteration Workflow
Follow-up requests to modify an existing chart:
- Modify existing JSON — add/remove data points, change series
- Re-render from modified JSON
- Style change — re-render with different template (keep JSON unchanged)
- Format change — re-export to different output format
Common modifications:
- "Add another series" → add to
seriesarray, re-render - "Change to horizontal" → set
direction: "horizontal", re-render - "Stack the bars" → set
variant: "stacked", re-render - "Switch to hand-drawn style" → change
style, re-render with different template - "Convert pie to donut" → set
variant: "donut", configuredonutobject, re-render - "Smooth the lines" → set
smooth: true, re-render - "Show as stacked area" → set
variant: "stacked"on area chart, re-render