Custom Chart
Charts are registered with a class-level decorator and auto-discovered by Visualizer. Adding a new chart is a two-step process: implement the Chart subclass, then apply @Visualizer.register.
Step 1: Implement the chart
# dantinox/visualization/charts/loss_histogram.py
from __future__ import annotations
from pathlib import Path
from typing import ClassVar
import pandas as pd
from dantinox.visualization.base import Chart, RenderConfig
from dantinox.visualization.style import apply_style
class LossHistogramChart(Chart):
"""Distribution of final per-step loss values across training runs.
Renders a histogram (or KDE) of ``data["loss"]`` values so that
training instabilities (spikes, heavy tails) are immediately visible.
Attributes:
name: Registry key — ``"loss_histogram"``.
accepts: Expects a ``pd.DataFrame`` with a ``"loss"`` column.
bins: Number of histogram bins.
"""
name: ClassVar[str] = "loss_histogram"
accepts: ClassVar[type] = pd.DataFrame
def __init__(self, bins: int = 50) -> None:
"""Initialize the chart.
Args:
bins: Number of histogram bins. Default ``50``.
"""
self.bins = bins
def _render_mpl(
self,
data: pd.DataFrame,
config: RenderConfig,
fig,
ax,
) -> None:
"""Render the loss histogram using matplotlib.
Args:
data: DataFrame with a ``"loss"`` column.
config: Render configuration (style, dpi, …).
fig: Matplotlib figure.
ax: Matplotlib axes.
"""
apply_style(config.style)
if "loss" not in data.columns:
ax.text(0.5, 0.5, "No 'loss' column found",
ha="center", va="center", transform=ax.transAxes)
return
ax.hist(data["loss"].dropna(), bins=self.bins,
color="#3A86FF", edgecolor="white", linewidth=0.5)
ax.set_xlabel("Loss", fontsize=11)
ax.set_ylabel("Count", fontsize=11)
ax.set_title("Training Loss Distribution", fontsize=13, fontweight="bold")
ax.grid(axis="y", alpha=0.3)
Step 2: Register it
Apply @Visualizer.register and import the class in dantinox/visualization/charts/__init__.py:
# dantinox/visualization/charts/__init__.py
from dantinox.visualization.visualizer import Visualizer
from dantinox.visualization.charts.training import TrainingCurveChart
from dantinox.visualization.charts.throughput import ThroughputChart, ThroughputBatchChart
from dantinox.visualization.charts.latency import LatencyChart
from dantinox.visualization.charts.pareto import ParetoChart
from dantinox.visualization.charts.radar import RadarChart
from dantinox.visualization.charts.loss_histogram import LossHistogramChart # ← new
# Auto-register all default-constructible charts
for _cls in [TrainingCurveChart, ThroughputChart, ThroughputBatchChart,
LatencyChart, ParetoChart, LossHistogramChart]: # ← add here
Visualizer.register(_cls)
# RadarChart requires constructor args — register but skip auto-render
Visualizer.register(RadarChart)
Step 3: Use it
import pandas as pd
from dantinox.visualization import Visualizer
df = pd.read_csv("training_log.csv")
Visualizer().render(df, charts=["loss_histogram"], out_dir="plots/")
Or via CLI:
dantinox plot --in_csv training_log.csv --out_dir plots/ --groups perf
Optional: implement the Plotly backend
Override _render_plotly for interactive HTML output:
def _render_plotly(self, data: pd.DataFrame, config: RenderConfig):
import plotly.express as px
return px.histogram(data, x="loss", nbins=self.bins,
title="Training Loss Distribution")
Users activate it via RenderConfig(backend="plotly", fmt="html").
Checklist
name: ClassVar[str]— unique, snake_caseaccepts: ClassVar[type]— the data typerender()passes to_render_mpl__init__has a Google docstring (needed forinterrogate)_render_mplhas a Google docstringRegistered in
charts/__init__.pyUnit test verifying the chart produces a file at the expected path
make doccheckpasses