# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Dynamo Dark theme for Plotly charts. Loads design tokens from design_tokens.yaml and produces a reusable plotly.graph_objects.layout.Template aligned with the Dynamo visual identity. Typography: - Titles, headings, body: Arial (sans-serif) - Tick labels, code-like text: Roboto Mono (monospace fallback) Usage: from plotly_dynamo import dynamo_template, load_tokens fig = go.Figure(data=[...], layout=go.Layout(template=dynamo_template)) fig.write_image("chart.svg") fig.write_image("chart.png", scale=2) Or apply globally: import plotly.io as pio pio.templates.default = dynamo_template """ from __future__ import annotations from pathlib import Path from typing import Any import plotly.graph_objects as go import yaml def load_tokens(path: str | Path | None = None) -> dict[str, Any]: """Load design tokens from YAML file. Args: path: Path to design_tokens.yaml. Defaults to the file next to this module. Returns: Parsed token dictionary. """ if path is None: path = Path(__file__).parent / "design_tokens.yaml" with open(path) as f: return yaml.safe_load(f) def build_template(tokens: dict[str, Any] | None = None) -> go.layout.Template: """Build a Plotly Template from design tokens. Args: tokens: Parsed design_tokens dict. Loaded from default path if None. Returns: A fully configured plotly.graph_objects.layout.Template. """ if tokens is None: tokens = load_tokens() colors = tokens["colors"] typo = tokens["typography"] bg_primary = colors["background"]["primary"] text_primary = colors["text"]["primary"] text_secondary = colors["text"]["secondary"] border_subtle = colors["border"]["subtle"] series = colors["chart_series"] font_sans = typo["font_family"] font_mono = typo["font_family_mono"] # Roboto Mono for ticks/code layout = go.Layout( # -- Background -- paper_bgcolor=bg_primary, plot_bgcolor=bg_primary, # -- Typography -- font=dict( family=font_sans, color=text_primary, size=typo["label"]["size"], ), title=dict( font=dict( family=font_sans, size=typo["title"]["size"], color=text_primary, ), x=0.02, xanchor="left", y=0.98, yanchor="top", ), # -- Color palette -- colorway=series, # -- X axis (monospace tick labels) -- xaxis=dict( gridcolor=border_subtle, gridwidth=0.5, zeroline=False, linecolor=border_subtle, linewidth=0.5, tickfont=dict( family=font_mono, size=typo["annotation"]["size"], color=text_secondary, ), title=dict( font=dict( family=font_sans, size=typo["label"]["size"], color=text_secondary, ), ), ), # -- Y axis (monospace tick labels for alignment) -- yaxis=dict( gridcolor=border_subtle, gridwidth=0.5, zeroline=False, linecolor=border_subtle, linewidth=0.5, tickfont=dict( family=font_mono, size=typo["label"]["size"], color=text_primary, ), title=dict( font=dict( family=font_sans, size=typo["label"]["size"], color=text_secondary, ), ), automargin=True, ), # -- Legend -- legend=dict( bgcolor="rgba(0,0,0,0)", font=dict( family=font_sans, size=typo["annotation"]["size"], color=text_secondary, ), bordercolor=border_subtle, borderwidth=0, ), # -- Margins -- margin=dict(l=20, r=20, t=60, b=20), # -- Subtle outer frame (muted green, thin) -- shapes=[ dict( type="rect", xref="paper", yref="paper", x0=-0.01, y0=-0.02, x1=1.01, y1=1.04, line=dict( color=border_subtle, width=0.5, ), fillcolor="rgba(0,0,0,0)", layer="above", ), ], ) # -- Data defaults -- template = go.layout.Template(layout=layout) # Bar chart defaults template.data.bar = [ go.Bar( marker=dict( line=dict(width=0), opacity=0.92, ), textfont=dict( family=font_mono, size=typo["annotation"]["size"], color=text_primary, ), textposition="inside", insidetextanchor="start", error_x=dict( color=text_primary, thickness=1.5, width=4, ), error_y=dict( color=text_primary, thickness=1.5, width=4, ), ), ] # Scatter / line chart defaults template.data.scatter = [ go.Scatter( marker=dict(size=8, line=dict(width=1, color=bg_primary)), line=dict(width=2), ), ] return template # Module-level singleton -- import this directly dynamo_template = build_template() def apply_outer_frame(fig: go.Figure) -> go.Figure: """Add the Dynamo green outer frame to an existing figure. Useful when creating a figure without the template, or when the frame was lost during updates. """ tokens = load_tokens() border_frame = tokens["colors"]["border"]["frame"] frame_width = tokens["borders"]["frame_width"] fig.add_shape( type="rect", xref="paper", yref="paper", x0=-0.01, y0=-0.02, x1=1.01, y1=1.04, line=dict(color=border_frame, width=frame_width), fillcolor="rgba(0,0,0,0)", layer="above", ) return fig def section_annotation( fig: go.Figure, text: str, y: float, x: float = 0.0, ) -> go.Figure: """Add a section header annotation (e.g., 'DISPATCH (us)'). Uses the Dynamo theme font for section headers. Args: fig: The Plotly figure. text: Section header text (pre-formatted; not auto-uppercased). y: Y position in paper coordinates (0-1). x: X position in paper coordinates (0-1). """ tokens = load_tokens() typo = tokens["typography"] text_color = tokens["colors"]["text"]["secondary"] fig.add_annotation( text=text, xref="paper", yref="paper", x=x, y=y, showarrow=False, font=dict( family=typo["font_family"], size=typo["heading"]["size"], color=text_color, ), xanchor="left", yanchor="bottom", ) return fig