No description
Find a file
Vadym Samoilenko d52f088243 Fix bar charts, fonts, axis controls, donut support, and Playwright export
Issue 1 – Bar charts blank/lines only:
- Silent fall-through on unsupported chart_types (donut, stacked_bar, area)
  now raises ValueError instead of producing axes-only output
- Zero-width bars on duplicate/single dates fixed via sorted-gap calculation
- Donut chart type added (ring with percentage labels)
- Pie/donut routing now triggers on any() instead of all()

Issue 2 – Axis controls not applying:
- AxisSpec gains date_min/date_max (x-axis clamping via prompts)
- y-bounds no longer silently widened when user sets min_val/max_val
- Tick clamping: ticks outside user range are dropped not widened
- New dual_y_axis layout with independent left/right Y-axes and y_axis_side per series
- Endpoint Y-axis labels (min/max) always render even when spacing is tight

Issue 3+4 – Font fallback & InDesign compatibility:
- Replace CairoSVG with Playwright/headless Chromium for PNG and PDF export
- Chromium honours @font-face base64 data URIs → Roboto Condensed in all exports
- PDF output contains embedded TTF subsets and real text operators (selectable
  in InDesign/Illustrator, no path-outlining, consistent across regions)
- FastAPI lifespan manages persistent Playwright browser instance

Issue 5 – Stroke weight drift:
- All stroke_width values now carry explicit "px" unit suffix
- SVG root gets width="…px" height="…px" so 1 SVG px = 0.75 PDF pt exactly

AI improvements:
- Prompts document date_min/date_max, scale_kind, dual_y_axis, donut
- Rule 9 softened: user-specified ranges are honoured even if they crop data
- Refinement uses deep-merge so tick_interval/min_val/date_min are never
  accidentally reset to None when Claude modifies unrelated fields
- New donut few-shot example added

Library upgrades: anthropic 0.84→0.97, fastapi 0.135→0.136,
pandas 3.0.1→3.0.2, pydantic 2.12→2.13, uvicorn 0.41→0.46;
cairosvg removed, playwright 1.58.0 added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:15:26 +01:00
.claude Switch to browser-side auth: MSAL.js + JWT validation 2026-03-06 15:24:22 +00:00
app Fix bar charts, fonts, axis controls, donut support, and Playwright export 2026-04-28 13:15:26 +01:00
.env.example Fix OAuth callback to use root path (match Azure AD registration) 2026-03-06 15:07:49 +00:00
.gitignore Initial commit: PIMCO chart generator with iterative refinement 2026-03-05 16:29:47 -05:00
deploy.sh Add Azure AD SSO (MSAL/PKCE) and Docker deployment 2026-03-06 14:56:07 +00:00
docker-compose.yml Add Azure AD SSO (MSAL/PKCE) and Docker deployment 2026-03-06 14:56:07 +00:00
Dockerfile Fix bar charts, fonts, axis controls, donut support, and Playwright export 2026-04-28 13:15:26 +01:00
README.md Initial commit: PIMCO chart generator with iterative refinement 2026-03-05 16:29:47 -05:00
requirements.txt Fix bar charts, fonts, axis controls, donut support, and Playwright export 2026-04-28 13:15:26 +01:00
test_render.py Initial commit: PIMCO chart generator with iterative refinement 2026-03-05 16:29:47 -05:00
test_render_all.py Initial commit: PIMCO chart generator with iterative refinement 2026-03-05 16:29:47 -05:00

PIMCO Chart Generator

Automated publication-quality chart generation matching PIMCO's InDesign visual style. Upload data, describe what you want in plain English, and get a pixel-perfect SVG ready for any platform. Then iterate with natural language — "make lines thicker", "change the title", "remove Japan" — until it's exactly right.

How It Works

                                    PIMCO CHART GENERATOR
                                    =====================

    ┌─────────────┐     ┌─────────────┐
    │  Excel/CSV  │     │  Plain-text  │
    │  Data File  │     │    Brief     │
    └──────┬──────┘     └──────┬──────┘
           │                   │
           ▼                   │
    ┌─────────────┐            │
    │   Loader    │            │
    │  (pandas +  │            │
    │  openpyxl)  │            │
    └──────┬──────┘            │
           │                   │
           ▼                   │
    ┌─────────────┐            │
    │  Analyzer   │            │
    │  Summarize  │            │
    │  columns,   │            │
    │  ranges,    │            │
    │  types      │            │
    └──────┬──────┘            │
           │                   │
           ▼                   ▼
    ┌──────────────────────────────────┐
    │        Claude Opus 4.6           │
    │  ┌────────────────────────────┐  │
    │  │  System Prompt:            │  │
    │  │  - PIMCO style guide      │  │
    │  │  - ChartSpec JSON schema  │  │
    │  │  - Few-shot examples      │  │
    │  └────────────────────────────┘  │
    │                                  │
    │  Input:  data summary + brief    │
    │  Output: structured ChartSpec    │
    │          (via tool use)          │
    └───────────────┬──────────────────┘
                    │
                    ▼
    ┌──────────────────────────────────┐
    │         Spec Validator           │
    │  - Fuzzy column name matching    │
    │  - Range validation              │
    └───────────────┬──────────────────┘
                    │
                    ▼
    ┌──────────────────────────────────┐
    │        SVG Renderer              │
    │  ┌──────────┐  ┌──────────────┐  │
    │  │  Layout   │  │  Scale       │  │
    │  │  (single/ │  │  (data →     │  │
    │  │  dual)    │  │   pixels)    │  │
    │  └──────────┘  └──────────────┘  │
    │  ┌──────────┐  ┌──────────────┐  │
    │  │  Axes    │  │  Series      │  │
    │  │  (grid,  │  │  (lines,     │  │
    │  │  ticks)  │  │  bars, fill) │  │
    │  └──────────┘  └──────────────┘  │
    │  ┌──────────┐  ┌──────────────┐  │
    │  │  Legend   │  │  Annotations │  │
    │  │  (top-   │  │  (ellipses,  │  │
    │  │  right)  │  │  callouts)   │  │
    │  └──────────┘  └──────────────┘  │
    │  ┌─────────────────────────────┐ │
    │  │  Typography + Font Embed    │ │
    │  │  (Roboto base64 in SVG)     │ │
    │  └─────────────────────────────┘ │
    └───────────────┬──────────────────┘
                    │
                    ▼
            ┌──────────────┐
            │  SVG Output  │
            │  2560x1440   │
            └──────┬───────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │       Iterative Refinement       │
    │                                  │
    │  "Make lines thicker"            │
    │  "Change title to ..."           │
    │  "Remove the Japan line"         │
    │  "Add a subtitle"               │
    │  "Use dashed lines for trend"    │
    │                                  │
    │  ┌────────┐    ┌──────────────┐  │
    │  │Current │───▶│ Claude edits │  │
    │  │  Spec  │    │  only what   │  │
    │  │  JSON  │◀───│  you asked   │  │
    │  └────────┘    └──────────────┘  │
    │       │                          │
    │       ▼                          │
    │  Re-render ──▶ Updated SVG       │
    └──────────────────────────────────┘
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
      InDesign   Web     Figma
      Illustr.  Pages    Sketch

Architecture

The AI does NOT generate code or SVG markup. It outputs a structured JSON specification (ChartSpec) which is then rendered deterministically by the Python engine. This means:

  • No visual hallucinations — the renderer is pure code, not generative AI
  • Fully debuggable — inspect the ChartSpec JSON to see exactly what the AI decided
  • Consistent style — every chart follows the same PIMCO style rules regardless of the brief
  • Iterative — refine with natural language, each edit builds on the last

Iterative Refinement

After generating a chart, a refinement bar appears below the preview. Type natural language edits and the chart updates in place, keeping full conversation history.

What you can say

Category Example edits
Lines "Make lines thicker" / "Use dashed lines for the trend"
Colors "Change the U.S. line to the purple color" / "Swap U.K. and Australia colors"
Series "Remove the Japan line" / "Add the Germany data"
Titles "Change title to 'Global Yields'" / "Add subtitle: Source: Bloomberg"
Axes "Y-axis from 0 to 8%" / "Show years only on X-axis"
Annotations "Add an ellipse around 2023-2024" / "Remove the annotation"
Layout "Make it a dual-panel chart" / "Switch to single panel"

Each edit sends the current spec + your full conversation history to Claude, which modifies only what you asked and returns the updated chart. You can chain as many edits as you like.

PIMCO Visual Style

Element Specification
Canvas 2560 x 1440 px (configurable per chart)
Title font Roboto Condensed, 32px
Axis font Roboto Regular, 22px
Legend font Roboto Regular, 20px
Line weight 3.0px (solid), dashed 14,8, dotted 5,7
Gridlines Horizontal only, #D4D4D4, 1.0px
Background White (#FFFFFF)

Color Palette

  ██  #003D5C   Dark teal-blue    (primary / U.S.)
  ██  #5A6B28   Olive             (secondary / Australia / trends)
  ██  #891A6A   Purple-magenta    (tertiary / U.K.)
  ██  #1FBFAA   Cyan-teal         (Germany)
  ██  #8B7D32   Dark gold         (Japan)

Shaded Fills

  • Blue (#003D5C at 20% opacity) — data above reference line
  • Pink (#891A6A at 20% opacity) — data below reference line

Supported Chart Types

  1. Multi-line time series — e.g., bond yields across countries
  2. Annotated line charts — with grey ellipse highlights on regions of interest
  3. Dual-panel side-by-side — with trend lines, reference levels, and shaded deviation fills
  4. Bar charts — vertical bars for categorical or time-based data
  5. Stacked bar charts — multiple categories stacked

Quick Start

1. Setup

cd PIMCO-CHARTS
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

2. Configure API Key

cp .env.example .env
# Edit .env and add your Anthropic API key:
# ANTHROPIC_API_KEY=sk-ant-xxxxx

3. Run

source venv/bin/activate
python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload

Open http://localhost:8080 in your browser.

4. Generate a Chart

  1. Upload an Excel (.xlsx) or CSV file
  2. Optionally specify a sheet name
  3. Write a brief describing the chart you want
  4. Set width/height (default 2560x1440)
  5. Click Generate Chart
  6. Preview the SVG inline, then Download SVG

5. Refine

After generating, type edits in the refinement bar below the chart:

  • "Make lines thicker"
  • "Change title to 'Bond Yields 2020-2025'"
  • "Remove the Japan line"
  • Each edit re-renders instantly

Example Briefs

Simple line chart:

Create a line chart showing 10-year government bond yields for the US, UK, Australia, Germany, and Japan. Y-axis from -1% to 6%.

Annotated chart:

Show quarterly tech and non-tech investment contribution to GDP growth from 1994 to 2024. Highlight the recent convergence area with an ellipse.

Dual panel with fills:

Create a dual-panel chart. Left panel: PIMCO global industrial production indexed to Oct 2024 = 100, with a dashed two-year pre-election trend and dotted election level. Shade blue above trend, pink below. Right panel: same treatment for world exports to U.S.

Project Structure

PIMCO-CHARTS/
├── app/
│   ├── main.py              # FastAPI web app (upload, generate, refine, download)
│   ├── config.py             # Settings, API keys, paths
│   ├── models/
│   │   ├── chart_spec.py     # ChartSpec Pydantic schema
│   │   └── style.py          # PIMCO colors, fonts, layout constants
│   ├── data/
│   │   ├── loader.py         # Excel/CSV parsing
│   │   ├── analyzer.py       # Data summarization for AI prompt
│   │   └── transformer.py    # Date detection, column cleaning
│   ├── ai/
│   │   ├── brief_interpreter.py  # Claude Opus 4.6: brief → ChartSpec + refine
│   │   ├── prompts.py            # System + refinement prompts, few-shot examples
│   │   └── spec_validator.py     # Fuzzy column matching
│   ├── renderer/
│   │   ├── engine.py         # Main orchestrator: spec + data → SVG
│   │   ├── layout.py         # Single/dual-panel positioning
│   │   ├── scale.py          # Data domain → pixel coordinates
│   │   ├── axes.py           # Gridlines, tick labels
│   │   ├── series.py         # Lines, bars, shaded fills
│   │   ├── legend.py         # Horizontal top-right legend
│   │   ├── typography.py     # Title, subtitle text
│   │   └── annotations.py    # Ellipses, callouts
│   ├── templates/            # HTMX HTML templates
│   └── static/
│       ├── style.css
│       └── fonts/            # Roboto & Roboto Condensed TTFs
├── output/                   # Generated SVGs
├── requirements.txt
└── .env                      # ANTHROPIC_API_KEY (not committed)

Technical Details

Font Embedding

SVGs are self-contained — Roboto and Roboto Condensed fonts are base64-encoded directly into the SVG's <defs><style> block as @font-face declarations. This means the SVG renders correctly in any viewer without requiring the fonts to be installed.

ChartSpec Schema

The ChartSpec is the contract between the AI interpreter and the deterministic renderer:

ChartSpec
  layout: "single" | "dual_panel"
  panels: [PanelSpec]
    title, subtitle
    x_axis: AxisSpec (label, suffix, date_format, min/max, tick_interval)
    y_axis: AxisSpec
    series: [SeriesSpec]
      label, data_column, chart_type, color_index, line_style, line_weight
      shaded_fill: {reference_series, above_color, below_color}
    annotations: [AnnotationSpec]
      type: "ellipse" | "callout" | "label", x_range, y_range, text

Shaded Fill Algorithm

For charts that show deviation from a trend line (blue above, pink below), the renderer:

  1. Walks both polylines point-by-point
  2. Detects sign changes (crossings) via linear interpolation
  3. Splits fill paths at intersection points
  4. Renders separate SVG <path> elements for above/below regions

Iterative Refinement Architecture

The refinement system works by:

  1. Keeping an in-memory session with the current ChartSpec, loaded data, and conversation history
  2. When you type an edit, sending Claude the current spec JSON + full history + your edit
  3. Claude returns a complete updated ChartSpec (modifying only what was asked)
  4. The renderer re-renders from the new spec against the same data
  5. The cycle repeats — chain as many edits as needed

Dependencies

Package Purpose
anthropic Claude Opus 4.6 API (brief → spec)
drawsvg Direct SVG element construction
pandas Data manipulation and date parsing
openpyxl Excel file reading
fastapi Web framework
uvicorn ASGI server
jinja2 HTML templating
pydantic Schema validation
python-dotenv Environment variable loading
python-multipart File upload handling