AI-powered tool that generates publication-quality SVG charts matching PIMCO's InDesign style. Upload Excel/CSV data, write a plain-English brief, then iterate with natural language edits until the chart is exactly right. - Claude Opus 4.6 interprets briefs into structured ChartSpec JSON - Deterministic SVG renderer via drawsvg (no visual hallucinations) - Roboto/Roboto Condensed fonts base64-embedded in SVG - FastAPI + HTMX web frontend with live preview - Conversational refinement: "make lines thicker", "change title", etc. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
52 lines
1.2 KiB
Python
52 lines
1.2 KiB
Python
"""Annotation rendering: ellipses, callouts, labels."""
|
|
|
|
from __future__ import annotations
|
|
from datetime import datetime
|
|
import drawsvg as draw
|
|
from app.models.style import COLORS
|
|
from app.renderer.scale import LinearScale, DateScale
|
|
|
|
|
|
def render_ellipse(
|
|
d: draw.Drawing,
|
|
x_scale: DateScale,
|
|
y_scale: LinearScale,
|
|
x_start: datetime,
|
|
x_end: datetime,
|
|
y_start: float,
|
|
y_end: float,
|
|
):
|
|
"""Render a semi-transparent grey ellipse annotation."""
|
|
px_start = x_scale(x_start)
|
|
px_end = x_scale(x_end)
|
|
py_start = y_scale(y_start)
|
|
py_end = y_scale(y_end)
|
|
|
|
cx = (px_start + px_end) / 2
|
|
cy = (py_start + py_end) / 2
|
|
rx = abs(px_end - px_start) / 2
|
|
ry = abs(py_end - py_start) / 2
|
|
|
|
d.append(draw.Ellipse(
|
|
cx, cy, rx, ry,
|
|
fill=COLORS["annotation_ellipse"],
|
|
stroke="none",
|
|
))
|
|
|
|
|
|
def render_text_label(
|
|
d: draw.Drawing,
|
|
text: str,
|
|
x: float,
|
|
y: float,
|
|
font_size: float = 11,
|
|
color: str | None = None,
|
|
):
|
|
"""Render a text annotation at a specific position."""
|
|
d.append(draw.Text(
|
|
text, font_size,
|
|
x, y,
|
|
font_family="Roboto, sans-serif",
|
|
fill=color or COLORS["axis_text"],
|
|
dominant_baseline="middle",
|
|
))
|