svg-chart-lora v1.0 β€” SVG Chart Generation LoRA Adapters

12 per-type LoRA adapters for SVG business chart generation, fine-tuned on Gemma 3 12B
Built and published by John Williams / fxops.ai


What This Is

A set of 12 specialist LoRA adapters β€” one per chart type β€” that turn Gemma 3 12B into a deterministic SVG chart generator. Given structured JSON input (headings, labels, values), the model produces a valid, renderable SVG chart with correct coordinate geometry, proportional data representation, and consistent visual style.

The entire stack runs on-device. No API calls at inference time. Trained and deployed on an Apple M4 Mac mini with 24GB unified memory.

Chart types: bar Β· line Β· area Β· pie Β· donut Β· funnel Β· scatter Β· bubble Β· grouped_bar Β· stacked_bar Β· waterfall Β· horizontal_bar


Quick Start

from mlx_lm import load, generate
from mlx_lm.sample_utils import make_sampler

# Load base model + chart-type adapter
model, tokenizer = load(
    "mlx-community/gemma-3-12b-it-4bit",
    adapter_path="John-Williams-ATL/svg-chart-lora/adapters_funnel"
)

# Build prompt (see per_type_system_prompts.py for full system prompt)
messages = [
    {"role": "system", "content": "You are a funnel chart specialist..."},
    {"role": "user",   "content": """Chart type: funnel
Heading: Sales Pipeline
Pre-computed bars (centered at x=250, use coordinates exactly):
  'Leads' (1000): x=50.0 y=40 width=400.0 height=44 label at x=250 y=66
  'Qualified' (600): x=110.0 y=90 width=280.0 height=44 label at x=250 y=116
  'Proposal' (300): x=170.0 y=140 width=160.0 height=44 label at x=250 y=166
  'Closed' (120): x=202.0 y=190 width=96.0 height=44 label at x=250 y=216

Return ONLY the SVG code."""}
]

prompt = tokenizer.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True
)

sampler = make_sampler(temp=0.1, top_p=0.95)
svg_output = generate(model, tokenizer, prompt=prompt,
                      max_tokens=2000, sampler=sampler, verbose=False)

print(svg_output)  # Valid SVG β€” write to file or pipe to renderer

Switch chart types by changing the adapter path and system prompt:

# Bar chart
model, tokenizer = load("mlx-community/gemma-3-12b-it-4bit",
    adapter_path="John-Williams-ATL/svg-chart-lora/adapters_bar")

# Line chart
model, tokenizer = load("mlx-community/gemma-3-12b-it-4bit",
    adapter_path="John-Williams-ATL/svg-chart-lora/adapters_line")

Full inference pipeline with geometry pre-computation and 4-stage validation:
β†’ See gemma3_svg_chart_generation_v2_1.ipynb in this repository.


v1.0 Evaluation Results

Evaluated across 20 runs per type at temp=0.1 using an automated scoring harness.
Pre-training baseline included for retrained types (marked *).

Type Pass Rate Before* Delta Status
area 100% 100% = βœ… Production
bubble 100% 100% = βœ… Production
donut 100% 100% = βœ… Production
funnel 100% 100% = βœ… Production
grouped_bar 100% 100% = βœ… Production
line * 100% 0% +100% βœ… Production
stacked_bar * 95% 65% +30% βœ… Near production
horizontal_bar 90% 100% -10% βœ… Near production
pie 80% 90% -10% βœ… Near production
bar * 0% 0% = πŸ”„ v2.0 target
scatter * 0% 0% = πŸ”„ v2.0 target
waterfall * 0% 0% = πŸ”„ v2.0 target

* Retrained in v1.0. Line adapter is the standout result β€” 0%β†’100% in one training pass.


Architecture

Why per-type adapters?

Each chart type has distinct failure modes. A single multi-type adapter spreads training signal across 12 geometrically different patterns. Per-type adapters focus each adapter's capacity on one layout β€” coordinate arithmetic, element ordering, label placement β€” specific to that chart.

Geometry pre-computation

The inference pipeline pre-computes all coordinate arithmetic in Python before sending to the model. For complex types (donut, pie, scatter, bubble, waterfall), the prompt includes exact SVG path strings, circle positions, and bar coordinates. The model's job is SVG assembly, not trigonometry.

This separates concerns cleanly:

  • Python geometry layer β€” correct math, guaranteed
  • Model layer β€” correct SVG structure and element assembly

4-Stage Validation Pipeline

Every generated SVG passes through GemmaChartValidator before use:

Gate Check
Gate 0 Guard against empty output
Gate 1 SVG extraction β€” strip think blocks, markdown fences
Gate 2 XML parse validation
Gate 3 Content rules β€” viewBox, coordinate bounds, no expressions
Gate 4 Text overflow repair (x-clamp)
Gate 4b Rect coordinate repair (bar baseline)
Gate 4c Axis line stroke injection

Base Model & Training

Parameter Value
Base model google/gemma-3-12b-it (text variant)
Quantisation BF16 training / 4-bit inference
Framework MLX + mlx-lm
Hardware Apple M4 Mac mini 24GB
Adapter type LoRA, 16 layers
Iterations 300 per type
Sequence length 4096 tokens
Training examples ~156 per type (v1.0, merged original + retrained)
Dataset John-Williams-ATL/svg-chart-training-data
Temperature 0.1 (deterministic at this setting)

Training data generated using Claude (Anthropic) for initial examples, then migrated to local gemma3:12b via Ollama for scale, and finally to MLX direct inference for the v1.0 retraining pass.


Canvas Specification

All 12 adapters share a fixed output canvas:

viewBox="0 0 500 300"  width="500"  height="300"
Background : #f5f5f5
Palette    : #4a90d9 Β· #e67e22 Β· #27ae60 Β· #8e44ad
Text color : #333333
Font       : sans-serif
Origin     : top-left (0,0). x rightward, y downward.
Chart area : x=50..480, y=30..270
Baseline   : y=270 (vertical bar and waterfall charts)

Repository Structure

adapters_area/
adapters_bar/
adapters_bubble/
adapters_donut/
adapters_funnel/
adapters_grouped_bar/
adapters_horizontal_bar/
adapters_line/
adapters_pie/
adapters_scatter/
adapters_stacked_bar/
adapters_waterfall/
    adapter_config.json      ← LoRA config (rank, alpha, base model path)
    adapters.safetensors     ← trained adapter weights

scripts/
    per_type_system_prompts.py   ← system prompts for all 12 types
    generate_training_data.py    ← dataset generation pipeline (MLX backend)
    chart_registry.py            ← chart type registry
    run_per_type_training.sh     ← MLX LoRA training runner
    eval_harness.py              ← automated 20-run evaluation harness

gemma3_svg_chart_generation_v2_1.ipynb   ← demo notebook

Known Limitations (v1.0)

  • Fixed 500Γ—300px canvas β€” not suitable for responsive outputs without post-processing
  • Bar, scatter, waterfall β€” pass rate 0% in automated eval; geometry is partially correct but fails specific coordinate invariants. Targeted for v2.0 with Gemma 4 teacher/student pipeline
  • Grouped bar capped at 3 series β€” 4+ series overflows the 500px canvas
  • Waterfall running total arithmetic is the hardest pattern for autoregressive generation β€” v2.0 will use relative dy coordinates and a Gemma 4 spatial critic

Roadmap

v2.0 β€” in development, targeting Gemma 4:

  • Base model: Gemma 4 E4B (student) + Gemma 4 27B MoE (teacher/critic)
  • Spatial critic layer: Gemma 4 26B validates coordinate geometry before training data inclusion
  • Waterfall: relative dy coordinates replacing absolute running totals
  • Canvas: 1000Γ—1000 coordinate system aligned with Gemma 4 spatial reasoning
  • Eval harness: autoresearch-style temperature sweep per type

Author

John Williams β€” AI transformation practitioner and independent operator.
fxops.ai Β· HuggingFace Β· X @fxopsAI

Built in public as part of the fxops.ai private AI infrastructure stack. Training methodology, architecture decisions, and operational history documented in Substack series: Teaching a Small Model to Do One Thing Well.


License

Apache 2.0. Base model (Gemma 3) is subject to Google's Gemma Terms of Use.

Downloads last month

-

Downloads are not tracked for this model. How to track
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for John-Williams-ATL/svg-chart-lora

Adapter
(348)
this model

Dataset used to train John-Williams-ATL/svg-chart-lora