Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ Build hero slides, insight cards, and diagrams from one CLI with clarity-first d
|---|---|
| [![Equation dark example](examples/output/showcase/create-first/equation-dark.png)](examples/output/showcase/create-first/equation-dark.png) | [![Equation apple example](examples/output/showcase/create-first/equation-apple.png)](examples/output/showcase/create-first/equation-apple.png) |

| Line Chart | Bar Chart | Grouped Bar |
|---|---|---|
| [![Line chart example](examples/output/showcase/charts/chart_line.png)](examples/output/showcase/charts/chart_line.png) | [![Bar chart example](examples/output/showcase/charts/chart_bar.png)](examples/output/showcase/charts/chart_bar.png) | [![Grouped bar example](examples/output/showcase/charts/chart_grouped_bar.png)](examples/output/showcase/charts/chart_grouped_bar.png) |

| Horizontal Bar | Stacked Bar | Grouped Stacked Bar |
|---|---|---|
| [![Horizontal bar example](examples/output/showcase/charts/chart_horizontal_bar.png)](examples/output/showcase/charts/chart_horizontal_bar.png) | [![Stacked bar example](examples/output/showcase/charts/chart_stacked_bar.png)](examples/output/showcase/charts/chart_stacked_bar.png) | [![Grouped stacked bar example](examples/output/showcase/charts/chart_grouped_stacked_bar.png)](examples/output/showcase/charts/chart_grouped_stacked_bar.png) |

| Stacked Area | Pie | Donut |
|---|---|---|
| [![Stacked area example](examples/output/showcase/charts/chart_stacked_area.png)](examples/output/showcase/charts/chart_stacked_area.png) | [![Pie chart example](examples/output/showcase/charts/chart_pie.png)](examples/output/showcase/charts/chart_pie.png) | [![Donut chart example](examples/output/showcase/charts/chart_donut.png)](examples/output/showcase/charts/chart_donut.png) |

| Sankey | Cohort Retention Heatmap |
|---|---|
| [![Sankey example](examples/output/showcase/charts/chart_sankey.png)](examples/output/showcase/charts/chart_sankey.png) | [![Cohort retention example](examples/output/showcase/charts/chart_cohort.png)](examples/output/showcase/charts/chart_cohort.png) |

Canonical showcase assets live in `examples/output/showcase/`.

## Quick Switch (Jobs To Be Done)
Expand Down Expand Up @@ -176,6 +192,52 @@ Defaults (good for most first runs):

Full docs map: [`docs/README.md`](docs/README.md)

## Data Charts

Eleven chart layouts render quantitative data. Built on Chart.js (vendored locally — no network required for PNG export) plus a pure HTML/CSS cohort heatmap. All respect the active theme.

| Layout | Required args | Use for |
|---|---|---|
| `line-chart` | `--labels`, `--series-json` | trends over time, multi-series |
| `bar-chart` | `--labels`, `--values` | single-series category comparison |
| `grouped-bar-chart` | `--labels`, `--series-json` | multi-series side-by-side |
| `horizontal-bar-chart` | `--labels`, `--values` | ranked categories |
| `stacked-bar-chart` | `--labels`, `--series-json` | composition per category |
| `grouped-stacked-bar-chart` | `--labels`, `--series-json` (with `"stack"`) | two stacks side-by-side per x label |
| `stacked-area-chart` | `--labels`, `--series-json` | composition over time |
| `pie-chart` / `donut-chart` | `--labels`, `--values` | parts-of-a-whole |
| `sankey-chart` | `--links-json` | flows, funnels, allocations |
| `cohort-chart` | `--cohorts-json` | Mixpanel-style retention heatmap |

Optional on every chart: `--title`, `--subtitle`, `--x-label`, `--y-label`, `--no-legend`, `--theme`. JSON args accept inline strings or `@path/to/file.json`.

```bash
# Simple bar chart
modern-graphics create --layout bar-chart \
--labels "North,South,East,West" --values "42,58,71,34" \
--title "Units shipped" --y-label "Units (thousands)" \
--output bar.png --png

# Multi-series line chart
modern-graphics create --layout line-chart \
--labels "Q1,Q2,Q3,Q4" \
--series-json '[{"name":"2024","values":[42,58,71,88]},{"name":"2025","values":[60,75,95,118]}]' \
--title "Revenue growth" --y-label "Revenue (\$M)" \
--output line.png --png

# Sankey flow
modern-graphics create --layout sankey-chart \
--links-json '[{"from":"Visit","to":"Trial","value":80},{"from":"Trial","to":"Paid","value":35}]' \
--title "Funnel flow" --output sankey.png --png

# Cohort retention heatmap (large data — use @file)
modern-graphics create --layout cohort-chart \
--cohorts-json @cohorts.json \
--title "Weekly retention" --output cohort.png --png
```

Runnable examples for every chart live in [`examples/chart_*.py`](examples/). The same layouts are available via MCP (`generate_graphic`) and the web gallery.

## Text Rendering Mode

Use Pretext when you care about deterministic SVG text layout and quote-heavy typography.
Expand Down
72 changes: 72 additions & 0 deletions docs/DIAGRAM_TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,78 @@ Supports CSS and Pretext (`--text-render pretext`) rendering modes for pixel-per

**Use cases**: Conceptual equations, business formulas, framework definitions, LinkedIn social graphics

### Data Charts

Eleven quantitative chart layouts, powered by a locally vendored Chart.js build (plus a pure HTML/CSS cohort heatmap). All are themeable and export cleanly to PNG via the same Playwright pipeline as other layouts.

| Line | Bar | Grouped Bar |
|---|---|---|
| [![Line chart](../examples/output/showcase/charts/chart_line.png)](../examples/output/showcase/charts/chart_line.png) | [![Bar chart](../examples/output/showcase/charts/chart_bar.png)](../examples/output/showcase/charts/chart_bar.png) | [![Grouped bar](../examples/output/showcase/charts/chart_grouped_bar.png)](../examples/output/showcase/charts/chart_grouped_bar.png) |

| Horizontal Bar | Stacked Bar | Grouped Stacked Bar |
|---|---|---|
| [![Horizontal bar](../examples/output/showcase/charts/chart_horizontal_bar.png)](../examples/output/showcase/charts/chart_horizontal_bar.png) | [![Stacked bar](../examples/output/showcase/charts/chart_stacked_bar.png)](../examples/output/showcase/charts/chart_stacked_bar.png) | [![Grouped stacked bar](../examples/output/showcase/charts/chart_grouped_stacked_bar.png)](../examples/output/showcase/charts/chart_grouped_stacked_bar.png) |

| Stacked Area | Pie | Donut |
|---|---|---|
| [![Stacked area](../examples/output/showcase/charts/chart_stacked_area.png)](../examples/output/showcase/charts/chart_stacked_area.png) | [![Pie](../examples/output/showcase/charts/chart_pie.png)](../examples/output/showcase/charts/chart_pie.png) | [![Donut](../examples/output/showcase/charts/chart_donut.png)](../examples/output/showcase/charts/chart_donut.png) |

| Sankey | Cohort Retention |
|---|---|
| [![Sankey](../examples/output/showcase/charts/chart_sankey.png)](../examples/output/showcase/charts/chart_sankey.png) | [![Cohort](../examples/output/showcase/charts/chart_cohort.png)](../examples/output/showcase/charts/chart_cohort.png) |

**Common flags** (all charts): `--title`, `--subtitle`, `--x-label`, `--y-label`, `--no-legend`, `--theme`. JSON flags accept inline strings or `@path/to/file.json`.

**Simple charts** (`bar-chart`, `horizontal-bar-chart`, `pie-chart`, `donut-chart`): take `--labels` and `--values`.

```bash
modern-graphics create --layout bar-chart \
--labels "North,South,East,West" --values "42,58,71,34" \
--y-label "Units" --output bar.png --png
```

**Multi-series** (`line-chart`, `grouped-bar-chart`, `stacked-bar-chart`, `stacked-area-chart`): take `--labels` and `--series-json` where each item is `{"name": str, "values": [...]}`.

```bash
modern-graphics create --layout line-chart \
--labels "Q1,Q2,Q3,Q4" \
--series-json '[{"name":"2024","values":[42,58,71,88]},{"name":"2025","values":[60,75,95,118]}]' \
--y-label "Revenue (\$M)" --output line.png --png
```

**Grouped stacked** (`grouped-stacked-bar-chart`): each series additionally carries a `"stack"` key; series sharing the same stack are layered, and different stacks sit side-by-side.

```bash
modern-graphics create --layout grouped-stacked-bar-chart \
--labels "Q1,Q2,Q3,Q4" \
--series-json '[
{"name":"Product A","stack":"2024","values":[40,52,60,72]},
{"name":"Product B","stack":"2024","values":[28,35,41,48]},
{"name":"Product A","stack":"2025","values":[55,68,80,95]},
{"name":"Product B","stack":"2025","values":[38,47,55,65]}
]' --output gsb.png --png
```

**Sankey** (`sankey-chart`): `--links-json` as `[{"from": str, "to": str, "value": number}]`; `--nodes` optional (inferred otherwise).

```bash
modern-graphics create --layout sankey-chart \
--links-json '[{"from":"Visit","to":"Trial","value":80},{"from":"Trial","to":"Paid","value":35}]' \
--title "Funnel flow" --output sankey.png --png
```

**Cohort retention heatmap** (`cohort-chart`): `--cohorts-json` as `[{"date": str, "size": int, "values": [pct, ...]}]`; optional `--period-labels` for custom column headers. Cells are colored by retention % on an opacity ramp of the theme accent. Ragged rows (incomplete cohorts) are allowed.

```bash
modern-graphics create --layout cohort-chart \
--cohorts-json @cohorts.json \
--title "Weekly retention" --output cohort.png --png
```

Runnable Python examples for every chart live in [`examples/chart_*.py`](../examples/). The same layouts are available via MCP (`generate_graphic`).

**Use cases**: performance dashboards, growth reports, attribution flows, retention analyses, social/investor graphics that need real data (not illustrative placeholders).

### Additional CLI Layouts

The following supported layouts are currently exposed as dedicated CLI commands (not `create` layouts):
Expand Down
24 changes: 24 additions & 0 deletions examples/chart_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Example: single-series vertical bar chart."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_bar_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Bar chart", attribution=Attribution())
html = generate_bar_chart(
gen,
labels=["North", "South", "East", "West", "Central"],
values=[42, 58, 71, 34, 95],
title="Units shipped by region",
y_axis_label="Units (thousands)",
)
gen.save(html, output_dir / "chart_bar.html")
gen.export_to_png(html, output_dir / "chart_bar.png")
print("Done:", output_dir / "chart_bar.png")
37 changes: 37 additions & 0 deletions examples/chart_cohort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Example: cohort retention heatmap (Mixpanel-style)."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.cohort_chart import generate_cohort_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

cohorts = [
{"date": "Jan 19, 2026", "size": 7262, "values": [95.61, 33.53, 31.34, 29.00, 28.44, 27.64, 27.61, 26.60, 26.55, 24.90, 23.70, 20.02]},
{"date": "Jan 26, 2026", "size": 7187, "values": [95.58, 33.74, 30.51, 29.32, 29.11, 27.91, 27.29, 26.81, 24.09, 24.66, 20.72, 3.80]},
{"date": "Feb 2, 2026", "size": 7291, "values": [96.06, 32.53, 30.74, 28.98, 28.93, 27.91, 27.72, 24.72, 24.89, 21.44, 3.61]},
{"date": "Feb 9, 2026", "size": 7071, "values": [95.80, 32.84, 30.99, 30.38, 29.57, 28.95, 25.94, 26.06, 21.91, 4.09]},
{"date": "Feb 16, 2026", "size": 7151, "values": [95.97, 32.42, 31.62, 30.02, 29.13, 26.78, 26.49, 22.30, 4.32]},
{"date": "Feb 23, 2026", "size": 6864, "values": [95.86, 34.13, 31.86, 31.59, 28.07, 27.19, 23.28, 4.50]},
{"date": "Mar 2, 2026", "size": 6844, "values": [95.95, 34.13, 33.28, 29.32, 28.58, 23.99, 4.54]},
{"date": "Mar 9, 2026", "size": 7120, "values": [96.60, 33.69, 29.85, 29.06, 24.24, 4.40]},
{"date": "Mar 16, 2026", "size": 7127, "values": [95.16, 31.19, 29.47, 24.41, 5.11]},
{"date": "Mar 23, 2026", "size": 7565, "values": [96.73, 28.34, 24.18, 4.60]},
{"date": "Mar 30, 2026", "size": 6390, "values": [95.92, 28.78, 5.34]},
]

gen = ModernGraphicsGenerator("Cohort retention", attribution=Attribution())
html = generate_cohort_chart(
gen,
cohorts=cohorts,
title="Weekly retention",
subtitle="The number of weeks later your users were retained.",
)
gen.save(html, output_dir / "chart_cohort.html")
gen.export_to_png(html, output_dir / "chart_cohort.png")
print("Done:", output_dir / "chart_cohort.png")
23 changes: 23 additions & 0 deletions examples/chart_donut.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Example: donut chart."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_donut_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Donut", attribution=Attribution())
html = generate_donut_chart(
gen,
labels=["Retained", "Upsold", "Churned", "New"],
values=[62, 14, 9, 15],
title="Customer outcomes (FY)",
)
gen.save(html, output_dir / "chart_donut.html")
gen.export_to_png(html, output_dir / "chart_donut.png")
print("Done:", output_dir / "chart_donut.png")
28 changes: 28 additions & 0 deletions examples/chart_grouped_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Example: grouped (multi-series) vertical bar chart."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_grouped_bar_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Grouped bar", attribution=Attribution())
html = generate_grouped_bar_chart(
gen,
labels=["Q1", "Q2", "Q3", "Q4"],
series=[
{"name": "Product A", "values": [40, 55, 62, 78]},
{"name": "Product B", "values": [28, 42, 51, 60]},
{"name": "Product C", "values": [15, 22, 30, 45]},
],
title="Sales by product",
y_axis_label="Units",
)
gen.save(html, output_dir / "chart_grouped_bar.html")
gen.export_to_png(html, output_dir / "chart_grouped_bar.png")
print("Done:", output_dir / "chart_grouped_bar.png")
34 changes: 34 additions & 0 deletions examples/chart_grouped_stacked_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Example: grouped stacked bar — products stacked, years side-by-side per quarter."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_grouped_stacked_bar_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Grouped stacked bar", attribution=Attribution())
html = generate_grouped_stacked_bar_chart(
gen,
labels=["Q1", "Q2", "Q3", "Q4"],
series=[
# 2024 stack
{"name": "Product A", "stack": "2024", "values": [40, 52, 60, 72]},
{"name": "Product B", "stack": "2024", "values": [28, 35, 41, 48]},
{"name": "Product C", "stack": "2024", "values": [12, 18, 22, 28]},
# 2025 stack
{"name": "Product A", "stack": "2025", "values": [55, 68, 80, 95]},
{"name": "Product B", "stack": "2025", "values": [38, 47, 55, 65]},
{"name": "Product C", "stack": "2025", "values": [20, 28, 34, 42]},
],
title="Revenue by product (2024 vs 2025)",
subtitle="Two bars per quarter — 2024 left, 2025 right — stacked by product",
y_axis_label="Revenue ($K)",
)
gen.save(html, output_dir / "chart_grouped_stacked_bar.html")
gen.export_to_png(html, output_dir / "chart_grouped_stacked_bar.png")
print("Done:", output_dir / "chart_grouped_stacked_bar.png")
24 changes: 24 additions & 0 deletions examples/chart_horizontal_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Example: horizontal bar chart (ranked categories)."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_horizontal_bar_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Horizontal bar", attribution=Attribution())
html = generate_horizontal_bar_chart(
gen,
labels=["Search", "Email", "Social", "Referral", "Direct"],
values=[320, 245, 198, 140, 92],
title="Sessions by channel",
x_axis_label="Sessions (thousands)",
)
gen.save(html, output_dir / "chart_horizontal_bar.html")
gen.export_to_png(html, output_dir / "chart_horizontal_bar.png")
print("Done:", output_dir / "chart_horizontal_bar.png")
29 changes: 29 additions & 0 deletions examples/chart_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Example: line chart with two series."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_line_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Line chart", attribution=Attribution())
html = generate_line_chart(
gen,
labels=["Q1", "Q2", "Q3", "Q4"],
series=[
{"name": "2024", "values": [42, 58, 71, 88]},
{"name": "2025", "values": [60, 75, 95, 118]},
],
title="Revenue growth",
subtitle="Quarterly, YoY",
x_axis_label="Quarter",
y_axis_label="Revenue ($M)",
)
gen.save(html, output_dir / "chart_line.html")
gen.export_to_png(html, output_dir / "chart_line.png")
print("Done:", output_dir / "chart_line.png")
23 changes: 23 additions & 0 deletions examples/chart_pie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Example: pie chart."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from modern_graphics import ModernGraphicsGenerator, Attribution
from modern_graphics.diagrams.charts import generate_pie_chart

output_dir = Path(__file__).parent / "output" / "generated"
output_dir.mkdir(parents=True, exist_ok=True)

gen = ModernGraphicsGenerator("Pie", attribution=Attribution())
html = generate_pie_chart(
gen,
labels=["Mobile", "Web", "API", "Other"],
values=[48, 32, 15, 5],
title="Traffic share by platform",
)
gen.save(html, output_dir / "chart_pie.html")
gen.export_to_png(html, output_dir / "chart_pie.png")
print("Done:", output_dir / "chart_pie.png")
Loading
Loading