pimco-charts/app/renderer/layout.py
Vadym Samoilenko d8461076b8 fix: axis labels, bar charts, legend, PDF fonts per Nina's feedback
- layout: _PAD_BOTTOM 100→160, _PAD_LEFT 120→160 to prevent label clipping
- axes: fix _format_tick float-comparison bug (1.1→"1.1" not "1.10");
  add auto-rotation for categorical x-axis labels when they overflow slot width
- engine: tick guard (>20 ticks falls back to nice_ticks); stacked_bar falls
  back to bar instead of crashing; extend _find_date_column keywords
- series: bar width uses 25th-percentile gap instead of min to avoid invisible bars
- legend: line series use line swatch with dash pattern; proportional square size
- export: @font-face injected in HTML <head> for PDF to prevent T3Font in InDesign
- prompts: add categorical bar chart few-shot example; ban stacked_bar; improve
  tick_interval guidance; add bar chart refine examples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 18:48:17 +01:00

87 lines
2.5 KiB
Python

"""Canvas sizing and panel layout computation."""
from __future__ import annotations
from dataclasses import dataclass
from app.models.style import LAYOUT
_PAD_TOP_MIN = 160
_PAD_BOTTOM = 160
_PAD_LEFT = 160
_PAD_RIGHT_STANDARD = 30
_PAD_RIGHT_PIE = 320
_PAD_RIGHT_DUAL_Y = 130 # right-side Y-axis labels for dual_y_axis layout
@dataclass
class PanelBounds:
left: float
right: float
top: float
bottom: float
@property
def width(self) -> float:
return self.right - self.left
@property
def height(self) -> float:
return self.bottom - self.top
def compute_layout(
layout_type: str,
vertical_legend: bool = False,
pad_top: int | None = None,
) -> tuple[int, int, list[PanelBounds]]:
"""Compute canvas dimensions and panel bounds."""
outer = LAYOUT["margins"]["top"]
_pad_top = max(pad_top, _PAD_TOP_MIN) if pad_top is not None else _PAD_TOP_MIN
if layout_type == "dual_panel":
dims = LAYOUT["dual_panel"]
w, h = dims["width"], dims["height"]
gap = LAYOUT["panel_gap"]
pad_right = _PAD_RIGHT_PIE if vertical_legend else _PAD_RIGHT_STANDARD
plot_left = outer + _PAD_LEFT
plot_top = outer + _pad_top
plot_bottom = h - outer - _PAD_BOTTOM
usable_width = w - outer - _PAD_LEFT - pad_right - outer - gap
panel_width = usable_width / 2
left_panel = PanelBounds(
left=plot_left,
right=plot_left + panel_width,
top=plot_top,
bottom=plot_bottom,
)
right_panel = PanelBounds(
left=plot_left + panel_width + gap,
right=w - outer - pad_right,
top=plot_top,
bottom=plot_bottom,
)
return w, h, [left_panel, right_panel]
elif layout_type == "dual_y_axis":
dims = LAYOUT["dual_y_axis"]
w, h = dims["width"], dims["height"]
panel = PanelBounds(
left=outer + _PAD_LEFT,
right=w - outer - _PAD_RIGHT_DUAL_Y,
top=outer + _pad_top,
bottom=h - outer - _PAD_BOTTOM,
)
return w, h, [panel]
else: # single
dims = LAYOUT["single"]
w, h = dims["width"], dims["height"]
pad_right = _PAD_RIGHT_PIE if vertical_legend else _PAD_RIGHT_STANDARD
panel = PanelBounds(
left=outer + _PAD_LEFT,
right=w - outer - pad_right,
top=outer + _pad_top,
bottom=h - outer - _PAD_BOTTOM,
)
return w, h, [panel]