Markdown Converter
Agent skill for markdown-converter
A chart & report builder designed for use by AI.
Sign in to like and favorite skills
mviz v1.5.4
Generate clean, data-focused charts and dashboards from compact JSON specs or markdown. Maximizes data-ink ratio with minimal chartjunk, gridlines, and decorative elements. Uses a 16-column grid layout system.
No installation required. Use
npx -y -q mviz which auto-downloads from npm. The -q flag reduces npm output while still showing lint errors.
For faster repeated use, install globally:
npm install -g mviz
Converts minimal JSON specifications into standalone HTML visualizations using ECharts. Instead of writing 50-100 lines of chart code, write a compact spec that gets expanded into a full HTML artifact with professional styling.
#f8f8f8 light) / Dark (#231f20 dark)echo '<json_spec>' | npx -y -q mviz > chart.html
npx -y -q mviz dashboard.md > dashboard.html
npx -y -q mviz my-dashboard/ > dashboard.html
Components are sized using
size=[cols,rows] syntax:
```big_value size=[4,2] {"value": 1250000, "label": "Revenue", "format": "usd0m"} ``` ```bar size=[8,6] {"title": "Sales", "x": "month", "y": "sales", "file": "data/sales.json"} ```
Height Guidelines:
| Row Units | Approximate Height | Good For |
|---|---|---|
| 2 | ~64px | KPIs, single-line notes |
| 4 | ~128px | Small tables, text blocks |
| 5-6 | ~160-192px | Standard charts |
| 8+ | ~256px+ | Dense tables, detailed charts |
For charts with many categories (10+ bars, 10+ rows in dumbbell), increase row units to prevent compression.
Critical: To place components side-by-side, their code blocks must have NO blank lines between them:
```bar size=[8,5] {"title": "Chart A", ...} ``` ```line size=[8,5] {"title": "Chart B", ...} ```
This renders Chart A and Chart B on the same row. Adding a blank line between them would put them on separate rows.
| Syntax | Effect |
|---|---|
| Major section title |
| Section title |
| Light inline header (subtle, smaller text) |
| Visual divider line |
| Page break for printing |
| Explicit page break: forces new page in PDF |
| Invisible grid cell spacer (default 4 cols × 2 rows) |
Heading Guidelines:
# H1 for major document sections that warrant their own page when printed## H2 for content sections within a page (most common)### H3 for lightweight subheadings that don't interrupt flowcontinuous: true mode, H1 page breaks are suppressedSection vs Page Breaks:
--- to separate logical sections visually. Content flows naturally to the next page when needed.=== only when you explicitly want to force a new page (e.g., separating chapters or major report sections for PDF output).=== by default. Only add page breaks when the user specifically requests them.| Component | Default Size | Notes |
|---|---|---|
| [4, 2] | Fits 4 per row |
| [4, 2] | Fits 4 per row |
| [4, 2] | Compact inline chart |
, , | [8, 5] | Half width |
, , | [8, 5] | Half width |
, , | [8, 5] | Half width |
, , | [8, 5] | Half width |
| [8, 5] | Half width |
| [12, 6] | 3/4 width |
| [16, 4] | Full width |
| [16, 4] | Full width |
| [16, 3] | Full width |
| [16, 6] | Full width, tall |
| [8, 5] | Half width (use for text art) |
, , | [16, 1] | Full width, single row |
| [4, 2] | Invisible spacer |
| Layout Goal | Components | Sizes |
|---|---|---|
| 4 KPIs in a row | 4× | [4,2] each |
| 5 KPIs in a row | 4× + 1 wider | [3,2] + [4,2] |
| KPI + context | + | [3,2] + [13,2] |
| KPI + chart | + | [4,2] + [12,5] |
```big_value size=[3,2] {"value": 1250000, "label": "Revenue", "format": "usd0m"} ``` ```big_value size=[3,2] {"value": 8450, "label": "Orders", "format": "num0k"} ``` ```big_value size=[3,2] {"value": 2400000000, "label": "Queries", "format": "num0b"} ``` ```delta size=[3,2] {"value": 0.15, "label": "MoM", "format": "pct0"} ``` ```delta size=[4,2] {"value": 0.08, "label": "vs Target", "format": "pct0"} ```
This creates a row with 5 KPIs (3+3+3+3+4 = 16 columns).
```bar size=[8,6] file=data/region-sales.json ``` ```line size=[8,6] file=data/monthly-trend.json ```
Charts: bar, line, area, pie, scatter, bubble, boxplot, histogram, waterfall, xmr, sankey, funnel, heatmap, calendar, sparkline, combo, dumbbell, mermaid
UI Components: big_value, delta, alert, note, text, textarea, empty_space, table
Tables support column-level and cell-level formatting:
Column options:
bold, italic, type ("sparkline" or "heatmap")
{ "type": "table", "columns": [ {"id": "product", "title": "Product", "bold": true}, {"id": "category", "title": "Category", "italic": true}, {"id": "sales", "title": "Sales", "fmt": "usd"}, {"id": "margin", "title": "Margin", "type": "heatmap", "fmt": "pct"}, {"id": "trend", "title": "Trend", "type": "sparkline", "sparkType": "line"} ], "data": [ {"product": "Widget", "category": "Electronics", "sales": 125000, "margin": 0.85, "trend": [85, 92, 88, 95, 102, 125]} ] }
Cell-level overrides: Use
{"value": "text", "bold": true} to override column defaults.
Heatmap: Applies color gradient from low to high values. Text auto-switches to white on dark backgrounds.
Sparkline types:
line, bar, area, pct_bar (progress bar), dumbbell (before/after comparison)
Notes support three severity levels via
noteType:
| Type | Border Color | Use For |
|---|---|---|
| Red | Important notices (default) |
| Yellow | Cautions, preliminary data |
| Green | Best practices, pro tips |
Notes also support an optional
label for bold prefix text:
{"type": "note", "label": "Pro Tip:", "content": "Use keyboard shortcuts for faster navigation.", "noteType": "tip"}
big_value - Hero metrics with large display:
{"type": "big_value", "value": 1250000, "label": "Revenue", "format": "usd0m"}
comparison object: {"value": 10300, "format": "usd", "label": "vs last month"} shows change with arrowdumbbell - Before/after comparisons with directional coloring:
{ "type": "dumbbell", "title": "ELO Changes", "category": "team", "start": "before", "end": "after", "startLabel": "Week 1", "endLabel": "Week 2", "higherIsBetter": true, "data": [ {"team": "Chiefs", "before": 1650, "after": 1720}, {"team": "Bills", "before": 1600, "after": 1550} ] }
higherIsBetter: false for rankings (lower = better)delta - Change metrics with directional coloring:
{"type": "delta", "value": 0.15, "label": "MoM Growth", "format": "pct0"}
comparison object: {"value": 0.05, "label": "vs Target"}area - Filled line chart for cumulative/volume data:
{ "type": "area", "title": "Daily Active Users", "x": "date", "y": "users", "data": [{"date": "Mon", "users": 1200}, {"date": "Tue", "users": 1450}] }
combo - Bar + line with dual Y-axis:
{ "type": "combo", "title": "Revenue vs Growth Rate", "x": "quarter", "y": ["revenue", "growth_rate"], "data": [ {"quarter": "Q1", "revenue": 1000000, "growth_rate": 0.15}, {"quarter": "Q2", "revenue": 1200000, "growth_rate": 0.20} ] }
heatmap - 2D matrix visualization:
{ "type": "heatmap", "title": "Activity by Hour", "xCategories": ["Mon", "Tue", "Wed", "Thu", "Fri"], "yCategories": ["9am", "12pm", "3pm", "6pm"], "format": "num0", "data": [[0, 0, 85], [1, 0, 90], [2, 0, 72]] }
format option applies to cell labels (e.g., num0k, usd0k, pct)funnel - Conversion or elimination flows:
{ "type": "funnel", "title": "Sales Pipeline", "format": "num0", "data": [ {"stage": "Leads", "value": 1000}, {"stage": "Qualified", "value": 600}, {"stage": "Proposal", "value": 300}, {"stage": "Closed", "value": 100} ] }
format option applies to labels/tooltips (e.g., usd_auto, pct, num0)waterfall - Cumulative change visualization:
{ "type": "waterfall", "title": "Revenue Bridge", "x": "item", "y": "value", "data": [ {"item": "Start", "value": 1000, "isTotal": true}, {"item": "Growth", "value": 200}, {"item": "Churn", "value": -50}, {"item": "End", "value": 1150, "isTotal": true} ] }
bubble - Scatter plot with size dimension:
{ "type": "bubble", "title": "Market Analysis", "x": "growth", "y": "profit", "size": "revenue", "data": [ {"growth": 5, "profit": 20, "revenue": 100}, {"growth": 10, "profit": 15, "revenue": 200} ] }
sankey - Flow diagrams showing relationships:
{ "type": "sankey", "title": "Traffic Sources", "data": [ {"source": "Organic", "target": "Landing", "value": 500}, {"source": "Paid", "target": "Landing", "value": 300}, {"source": "Landing", "target": "Signup", "value": 400} ] }
mermaid - Diagrams from Mermaid syntax (flowcharts, sequence, state, class, ER). Use array for multi-line code:
{ "type": "mermaid", "title": "User Flow", "code": [ "graph TD", " A[Start] --> B{Decision}", " B -->|Yes| C[Action]", " B -->|No| D[End]" ] }
mermaid (ASCII) - ASCII/Unicode text-based diagrams (set
ascii: true):
{ "type": "mermaid", "title": "Process Flow", "code": ["graph LR", " A[Input] --> B[Process] --> C[Output]"], "ascii": true }
Mermaid lint rules (errors that will fail validation):
<br/> tags in labels (render as literal text, not line breaks)A["text"] in flowcharts (quotes appear in output)| Format | Example | Use For |
|---|---|---|
| 1.000m, 10.00k | Smart auto-format (recommended) |
| $1.000m, $10.00k | Smart auto-format with $ prefix |
| $1.2m | Millions |
| $1.2b | Billions |
| $125k | Thousands |
| $1,250,000 | Detailed amounts |
| 1.2m | Millions |
| 1.2b | Billions |
| 125k | Thousands |
| 1,250,000 | Detailed counts |
| 15.0% | Percentage with decimal |
| 15% | Percentage integer |
| 15.0% | Percentage with 1 decimal |
Important: Percentage formats expect decimal values (0.25 = 25%), not whole numbers.
Smart formatting (
/auto
) is recommended. The usd_auto
format option applies to both axis labels and data labels on bar charts. It automatically picks the right suffix (k, m, b) based on magnitude and always shows 4 significant digits. Negative values are wrapped in parentheses: (1.000m).
When no format is specified, smart formatting is used by default.
Chart axes automatically detect the appropriate format based on field names:
| Field Pattern | Auto Format | Example |
|---|---|---|
| revenue, sales, price, cost, profit, amount | | $1.250m |
| pct, percent, rate, ratio | | 15.0% |
| All other numeric fields | | 1.250m |
Override with an explicit
format field in the chart spec.
The chart generator auto-detects columnar query results. Instead of manually converting
columns/rows to data, pass the result directly:
{ "type": "bar", "title": "Sales by Region", "x": "region", "y": "sales", "columns": ["region", "sales"], "rows": [["North", 45000], ["South", 32000], ["East", 28000]] }
This is automatically converted internally. No manual JSON reconstruction needed.
For line, area, bar, and combo charts, control y-axis range with
yMin and yMax:
{ "type": "line", "title": "Elo Rating Trend", "x": "date", "y": "elo", "yMin": 1400, "data": [{"date": "Oct", "elo": 1511}, {"date": "Jan", "elo": 1636}] }
Use
yMin when:
Use
yMax when:
The CLI validates specs automatically using built-in lint rules. Use
--lint flag for validation-only mode:
npx -y -q mviz --lint dashboard.md # Validate without generating HTML
| Rule | Severity | Trigger |
|---|---|---|
| warning | Missing required fields like , , or |
| warning | Field not recognized for the chart type |
| error | Time series data not in chronological order |
| error | Using / instead of / |
| error | Passing string instead of number |
| warning | Duplicate values on x-axis |
| error | tags in mermaid code (render as literal text) |
| error | Quoted labels like in flowcharts |
Errors exit with code 1. Warnings log to stderr but don't fail.
Time series error: Sort your data by date before passing to the chart.
Sankey wrong keys: Use
source, target, value in your data:
{"source": "A", "target": "B", "value": 100}
big_value string: Pass numeric value with format option:
{"type": "big_value", "value": 0.625, "format": "pct0", "label": "Rate"}
The generator outputs helpful warnings to stderr when issues are detected:
| Warning | Cause | Solution |
|---|---|---|
| Malformed JSON syntax | Check JSON syntax, ensure proper quoting |
| Typo in chart type | Use suggested type (e.g., not ) |
| File reference without base directory | Use file path argument or inline JSON |
| Too many components in one row | Reduce component widths or split into rows |
Warnings include context like content previews, similar type suggestions, and section/row info.
If data labels on bar, line, or area charts are being cut off at the top:
yMax to ~10-15% higher than that valueExample: If max value is 200, set
"yMax": 220
{ "type": "bar", "title": "Sales", "x": "month", "y": "sales", "yMax": 250, "data": [{"month": "Jan", "sales": 180}, {"month": "Feb", "sales": 220}] }
This provides headroom for the label text above the bars.
Use SQL to generate data files instead of manually authoring JSON. This reduces errors and ensures data accuracy:
-- Generate chart data file COPY ( SELECT month, SUM(sales) as sales, SUM(revenue) as revenue FROM orders GROUP BY month ORDER BY month ) TO 'data/monthly-sales.json' (FORMAT JSON, ARRAY true);
Then reference the generated file:
```bar file=data/monthly-sales.json {"title": "Monthly Sales", "x": "month", "y": "sales"} ```
This approach:
Reference external data files to save tokens and enable data/visualization separation:
```bar size=[8,6] file=data/sales.json ```
CSV files work great with DuckDB for data exploration:
# Export query results to CSV duckdb -csv -c "SELECT quarter, revenue FROM sales" > data/quarterly.csv
```bar file=data/quarterly.csv {"title": "Quarterly Revenue", "x": "quarter", "y": "revenue"} ```
| Approach | Best For |
|---|---|
| Inline JSON | Small, static specs |
| JSON files | Reusable chart configs |
| CSV files | DuckDB workflows, frequently updated data |
--- theme: light title: My Dashboard --- # Page Title ## Section Name ```big_value size=[4,2] {"value": 125000, "label": "Revenue", "format": "usd0k"} ``` ```bar size=[12,6] file=data/sales.json ```
Rules:
# Title sets the page title (first occurrence only)## Section creates a new section with divider (border, spacing)### Header creates a soft header within the current section (no divider)--- creates a section break (untitled, visual divider only)=== creates a page break (forces new page when printing to PDF)size=[cols,rows] controls layout (16-column grid)size=auto auto-calculates size from datafile=path references external JSONDashboards include a theme toggle button (top right) that switches between light and dark modes. All charts dynamically update when the theme changes.
Set the default theme in frontmatter:
--- title: My Dashboard theme: dark orientation: landscape print: true ---
| Option | Description |
|---|---|
| Dashboard title displayed at top |
| (default) or |
| (default) or for print layout |
| When , requires explicit on all components |
| When , removes section breaks between headers for flowing layout |
Page capacity: Portrait fits 30 row units, landscape fits 22 row units (Letter paper, 0.5" margins).
The theme toggle affects all charts globally - individual chart
theme settings are ignored in favor of the global toggle.
Load custom brand colors and fonts from a YAML file:
npx -y -q mviz --theme my_theme.yaml dashboard.md > dashboard.html
Example theme file:
name: brand-colors extends: light colors: primary: "#1a73e8" secondary: "#ea4335" palette: - "#1a73e8" - "#ea4335" - "#fbbc04" fonts: family: "'Roboto', sans-serif" import: "https://fonts.googleapis.com/css2?family=Roboto&display=swap"
Custom themes merge with defaults - only specify what you want to override.
Charts are optimized for printing to PDF:
When printing dashboards to PDF, all content stays intact without being cut off mid-chart.
Use formatted (multi-line) JSON when data may need editing. This enables smaller, more precise edits:
```bar size=[8,5] { "title": "Monthly Sales", "x": "month", "y": "sales", "data": [ {"month": "Jan", "sales": 120}, {"month": "Feb", "sales": 150}, {"month": "Mar", "sales": 180} ] } ```
Benefits:
When to use compact JSON:
{"value": 1250000, "label": "Revenue"}mviz specs can be validated using the JSON Schema at:
https://raw.githubusercontent.com/matsonj/mviz/main/schema/mviz.schema.json
Add
$schema to enable editor autocomplete and validation:
{ "$schema": "https://raw.githubusercontent.com/matsonj/mviz/main/schema/mviz.schema.json", "type": "bar", "title": "Sales", ... }
| Color | Hex | Use |
|---|---|---|
| Primary Blue | | Primary series |
| Secondary Orange | | Secondary series, accent |
| Info Blue | | Tertiary, informational |
| Positive Green | | Success, positive values |
| Warning Amber | | Warnings |
| Error Red | | Errors, negative emphasis |
See
reference/chart-types.md for complete documentation.
You are an analytics assistant helping a human who has decision-making context that you lack. Your job is to present data clearly and surface patterns worth investigating—not to draw conclusions or make recommendations.
Key principles:
For additional guidance on creating effective data visualizations—including Tufte-inspired principles, anti-patterns to avoid, and layout examples—see
Best_practices.md.
Having issues with mviz? Ask Claude to create a friction log documenting the problem, then open it as an issue at https://github.com/matsonj/mviz/issues