drawlib.charts.box

Statistical box plot, 1D/jittered strip plot, and hybrid distribution overlay builder supporting grouped multi-series categories, pre-aggregated BoxStats summaries, Tukey IQR / min-max / percentile whiskers, mean and outlier markers, logarithmic axes, and partial animation reveal. Import from drawlib.charts.box (from drawlib.charts.box import Axis, BoxChart, BoxStats, Series).

18b_charts_box_1
Vertical Box + Strip Overlay and Horizontal Logarithmic BoxStats Chart
Source Code
from drawlib.canvas import save, setup
from drawlib.charts.box import BoxChart, BoxStats
from drawlib.shapes import rectangle
from drawlib.styles import Colors, Styles

setup(width=120, height=58)

card_style = Styles.Neutral.patch(shape_r=2.0, shape_line_color=Colors.Gray4, shape_fill_color=Colors.White)
rectangle((31.5, 29.0), width=53.0, height=50.0, style=card_style)
rectangle((88.5, 29.0), width=53.0, height=50.0, style=card_style)

# Left: Vertical Box + Strip Overlay from Raw Samples
v_box = BoxChart(
    axis_line_style=Styles.MutedDashed,
    categories=["Auth", "Search", "Pay"],
    width=48.0,
    height=37.0,
    orientation="vertical",
    show_box=True,
    show_points=True,
    show_mean=True,
    box_width_ratio=0.72,
    box_r=0.5,
    jitter=0.42,
    point_radius=0.52,
    axis_text_style=Styles.Muted.patch(text_size=10.0),
    grid_style=Styles.MutedThin,
    title="Box + Strip Overlay (ms)",
    title_style=Styles.BlackBold.patch(text_size=11.5),
)
v_box.configure_y_axis(min_value=0.0, max_value=80.0, tick_step=20.0, unit="ms")
v_box.add_series(
    "2025",
    [
        [18.0, 22.0, 25.0, 27.0, 31.0, 36.0, 44.0],
        [30.0, 35.0, 39.0, 42.0, 47.0, 53.0, 66.0],
        [24.0, 28.0, 32.0, 35.0, 40.0, 46.0, 58.0],
    ],
    style=Styles.SecondaryNeutral,
)
v_box.add_series(
    "2026",
    [
        [10.0, 12.0, 14.0, 15.0, 18.0, 21.0, 26.0],
        [16.0, 19.0, 22.0, 24.0, 27.0, 31.0, 38.0],
        [13.0, 16.0, 18.0, 20.0, 23.0, 27.0, 33.0],
    ],
    style=Styles.PrimaryNeutral,
)
v_box.draw(xy=(7.5, 13.5))
v_box.draw_legend(xy=(21.5, 8.5), text_style=Styles.DarkBold.patch(text_size=10.0), orientation="horizontal", item_gap=3.5)

# Right: Horizontal Logarithmic BoxChart from Pre-Aggregated BoxStats
h_box = BoxChart(
    axis_line_style=Styles.MutedDashed,
    categories=["Edge", "Worker", "Batch"],
    width=48.0,
    height=37.0,
    orientation="horizontal",
    show_outliers=True,
    show_mean=True,
    box_width_ratio=0.66,
    box_r=0.5,
    point_radius=0.55,
    axis_text_style=Styles.Muted.patch(text_size=10.0),
    grid_style=Styles.MutedThin,
    title="Horizontal Log BoxStats",
    title_style=Styles.BlackBold.patch(text_size=11.5),
)
h_box.configure_x_axis(scale="log", min_value=1.0, max_value=1000.0, ticks=[1.0, 10.0, 100.0, 1000.0], unit="ms")
h_box.add_series(
    "gRPC",
    [
        BoxStats(min=1.4, q1=2.2, median=3.2, q3=4.8, max=7.5, mean=3.6, outliers=[12.0]),
        BoxStats(min=4.5, q1=7.5, median=12.0, q3=19.0, max=32.0, mean=14.2, outliers=[55.0]),
        BoxStats(min=22.0, q1=38.0, median=62.0, q3=105.0, max=180.0, mean=74.0, outliers=[340.0]),
    ],
    style=Styles.PrimaryFlat,
)
h_box.add_series(
    "REST",
    [
        BoxStats(min=2.8, q1=4.5, median=6.8, q3=10.5, max=17.0, mean=7.9, outliers=[28.0]),
        BoxStats(min=11.0, q1=18.0, median=28.0, q3=45.0, max=76.0, mean=33.5, outliers=[135.0]),
        BoxStats(min=55.0, q1=95.0, median=155.0, q3=260.0, max=460.0, mean=185.0, outliers=[780.0]),
    ],
    style=Styles.SecondaryNeutral,
)
h_box.draw(xy=(64.5, 13.5))
h_box.draw_legend(xy=(78.5, 8.5), text_style=Styles.DarkBold.patch(text_size=10.0), orientation="horizontal", item_gap=3.5)

save()

classAxis#

Represents a Cartesian coordinate axis (numerical value axis or categorical axis) shared across BoxChart, BarChart, LineChart, AreaChart, and ScatterChart. Controls linear or logarithmic scaling, domain bounds, tick generation, gridline visibility, and tick value formatting.

class Axis:
    def __init__(
        self,
        scale: ScaleType = "linear",
        min_value: float | None = None,
        max_value: float | None = None,
        ticks: list[float] | None = None,
        tick_step: float | None = None,
        format: FormatterType = None,
        unit: str = "",
        label: str = "",
        show_grid: bool = True,
        grid_style: Style | None = None,
        show_axis_line: bool = True,
        line_style: Style | None = None,
        show_ticks: bool = True,
        tick_label_style: Style | None = None,
        label_style: Style | None = None,
    ) -> None: ...

Parameters#

Parameter Type Default Description
scale ScaleType "linear" Nice-numbers linear scaling ("linear") or base-10 logarithmic scaling ("log").
min_value float | None None Explicit minimum axis domain bound. Auto-computed when None.
max_value float | None None Explicit maximum axis domain bound. Auto-computed when None.
ticks list[float] | None None Explicit tick positions overriding automatic tick generation.
tick_step float | None None Explicit linear interval step between adjacent ticks.
format FormatterType None Format string (e.g. "{:.1f}ms", "${:,.0f}") or callable Callable[[float], str] for tick labels.
unit str "" Unit suffix appended to formatted tick labels if not already present (e.g. "ms", "%").
label str "" Descriptive axis title text.
show_grid bool True Whether to render gridlines along this axis when chart grid_style is set.
grid_style Style | None None Optional per-axis Style override for gridlines.
show_axis_line bool True Whether to render the axis baseline stroke.
line_style Style | None None Optional per-axis Style override for the axis baseline.
show_ticks bool True Whether to render tick labels along this axis.
tick_label_style Style | None None Optional per-axis Style override for tick labels.
label_style Style | None None Optional Style override for the axis label title.

Methods#

configure

def configure(
    self,
    scale: ScaleType | None = None,
    min_value: float | None = None,
    max_value: float | None = None,
    ticks: list[float] | None = None,
    tick_step: float | None = None,
    format: FormatterType = None,
    unit: str | None = None,
    label: str | None = None,
    show_grid: bool | None = None,
    grid_style: Style | None = None,
    show_axis_line: bool | None = None,
    line_style: Style | None = None,
    show_ticks: bool | None = None,
    tick_label_style: Style | None = None,
    label_style: Style | None = None,
) -> None

Updates any non-None axis configuration attributes in place.

format_value

def format_value(self, value: float) -> str

Formats value into a display string using format (string template, callable, or compact numeric default) and appends unit when configured.


classBoxChart#

Configurable container and fluent builder for statistical box plots, 1D or jittered strip plots, and hybrid box-and-strip distribution overlays across grouped multi-series categories.

  • Coordinate Anchor: Bottom-Left (x0, y0) by default in draw(xy=..., align="bottom_left"), or geometric center when align="center".
  • Visibility Rule: At least one of show_box or show_points must be True (on both chart defaults and per-series overrides).
class BoxChart:
    def __init__(
        self,
        *,
        axis_line_style: Style,
        width: float = 68.0,
        height: float = 44.0,
        categories: list[str] | None = None,
        orientation: Orientation = "vertical",
        show_box: bool = True,
        show_points: bool = False,
        show_outliers: bool = True,
        show_mean: bool = False,
        whisker_mode: WhiskerMode = "iqr",
        iqr_k: float = 1.5,
        whisker_percentiles: tuple[float, float] = (5.0, 95.0),
        box_width_ratio: float = 0.65,
        cap_width_ratio: float = 0.45,
        box_r: float = 0.0,
        jitter: float = 0.45,
        jitter_seed: int = 42,
        point_radius: float = 0.58,
        point_shape: PointShape = "circle",
        outlier_shape: PointShape = "circle",
        mean_shape: PointShape = "rhombus",
        axis_text_style: Style | None = None,
        grid_style: Style | None = None,
        value_text_style: Style | None = None,
        value_format: FormatterType = None,
        background_style: Style | None = None,
        title: str = "",
        title_style: Style | None = None,
    ) -> None: ...

Parameters#

Parameter Type Default Description
axis_line_style Style Required Mandatory style defining baseline coordinate axes.
width float 68.0 Total chart bounding box width in canvas units (> 0.0).
height float 44.0 Total chart bounding box height in canvas units (> 0.0).
categories list[str] | None None Category names along the categorical axis (x_axis when vertical, y_axis when horizontal).
orientation Orientation "vertical" Distribution direction ("vertical" along y_axis or "horizontal" along x_axis).
show_box bool True Default visibility of Q1–Q3 interquartile boxes, whiskers, and median lines.
show_points bool False Default visibility of individual strip/jitter sample markers.
show_outliers bool True Default visibility of outlier markers beyond whiskers when show_box=True and show_points=False.
show_mean bool False Default visibility of arithmetic mean markers.
whisker_mode WhiskerMode "iqr" Whisker extent calculation rule ("iqr", "min_max", or "percentile") applied to raw sample sequences.
iqr_k float 1.5 Fence multiplier $k \ge 0.0$ applied to $\text{IQR} = Q_3 - Q_1$ when whisker_mode="iqr".
whisker_percentiles tuple[float, float] (5.0, 95.0) (low_p, high_p) percentiles (0.0 <= low < high <= 100.0) used when whisker_mode="percentile".
box_width_ratio float 0.65 Ratio of each category slot occupied by the series box cluster (0.0 < ratio <= 1.0).
cap_width_ratio float 0.45 Ratio of an individual box's thickness occupied by whisker T-caps (0.0 to 1.0).
box_r float 0.0 Corner rounding radius (>= 0.0) applied to Q1–Q3 boxes when series.style.shape_r is None.
jitter float 0.45 Default normalized transverse spread ratio (0.0 to 1.0) for strip points (0.0 renders an aligned 1D strip).
jitter_seed int 42 Deterministic integer seed producing reproducible per-(series, category) strip point offsets.
point_radius float 0.58 Default marker radius (> 0.0) in canvas units for strip, outlier, and mean points.
point_shape PointShape "circle" Default marker geometry for strip/jitter sample points.
outlier_shape PointShape "circle" Marker geometry for outlier points.
mean_shape PointShape "rhombus" Marker geometry for arithmetic mean points.
axis_text_style Style | None None Style for tick labels and category names. Omitted when None.
grid_style Style | None None Style for value-axis gridlines. Omitted when None.
value_text_style Style | None None Style for inline median value labels placed outside the upper whisker/point tip. Omitted when None.
value_format FormatterType None Format string (e.g. "{:.1f}ms") or callable for inline median value labels.
background_style Style | None None Outer chart container card style (shape_fill_color, shape_r). Omitted when None.
title str "" Chart title text displayed at the top of the container.
title_style Style | None None Typography style required to render title. Omitted when None.

Methods & Properties#

add_series

def add_series(
    self,
    name: str,
    data: Sequence[BoxStats | Sequence[float] | None],
    style: Style,
    legend_text_style: Style | None = None,
    *,
    show_box: bool | None = None,
    show_points: bool | None = None,
    show_outliers: bool | None = None,
    show_mean: bool | None = None,
    median_style: Style | None = None,
    whisker_style: Style | None = None,
    point_style: Style | None = None,
    outlier_style: Style | None = None,
    mean_style: Style | None = None,
    point_radius: float | None = None,
    point_shape: PointShape | None = None,
    jitter: float | None = None,
    show: bool = True,
    draw_ratio: float = 1.0,
    draw_direction: DrawDirection | None = None,
) -> Series

Registers a categorical distribution series and returns the mutable Series instance. Each element in data may be a raw Sequence[float] (summarized automatically via BoxStats.from_values() using the chart's whisker_mode, iqr_k, and whisker_percentiles), a pre-aggregated BoxStats object, or None / [] for an empty category slot. Sub-element styles (median_style, whisker_style, point_style, outlier_style, mean_style) auto-derive luminance-aware high-contrast strokes and fills from style when None.

category_axis *(property)*

@property
def category_axis(self) -> Axis

Orientation-aware property returning the categorical Axis (x_axis when orientation="vertical", y_axis when orientation="horizontal").

configure_x_axis

def configure_x_axis(
    self,
    scale: ScaleType | None = None,
    min_value: float | None = None,
    max_value: float | None = None,
    ticks: list[float] | None = None,
    tick_step: float | None = None,
    format: FormatterType = None,
    unit: str | None = None,
    label: str | None = None,
    show_grid: bool | None = None,
    grid_style: Style | None = None,
    show_axis_line: bool | None = None,
    line_style: Style | None = None,
    show_ticks: bool | None = None,
    tick_label_style: Style | None = None,
) -> Axis

Configures horizontal X-axis parameters in a single call and returns the updated Axis.

configure_y_axis

def configure_y_axis(
    self,
    scale: ScaleType | None = None,
    min_value: float | None = None,
    max_value: float | None = None,
    ticks: list[float] | None = None,
    tick_step: float | None = None,
    format: FormatterType = None,
    unit: str | None = None,
    label: str | None = None,
    show_grid: bool | None = None,
    grid_style: Style | None = None,
    show_axis_line: bool | None = None,
    line_style: Style | None = None,
    show_ticks: bool | None = None,
    tick_label_style: Style | None = None,
) -> Axis

Configures vertical Y-axis parameters in a single call and returns the updated Axis.

draw

def draw(
    self,
    xy: tuple[float, float] = (0.0, 0.0),
    *,
    width: float | None = None,
    height: float | None = None,
    align: Literal["bottom_left", "center"] = "bottom_left",
    scale: float = 1.0,
) -> None

Renders the box/strip chart onto the canvas anchored at xy (Bottom-Left (x0, y0) when align="bottom_left", or geometric center when align="center"), with optional temporary dimension overrides and uniform proportional scale.

draw_legend

def draw_legend(
    self,
    xy: tuple[float, float],
    text_style: Style,
    orientation: Orientation = "vertical",
    swatch_width: float = 2.4,
    swatch_height: float = 1.2,
    item_gap: float = 4.0,
    *,
    scale: float = 1.0,
) -> None

Renders a standalone series legend at xy (top-left start when "vertical", middle-left start when "horizontal"). Hidden series (show=False) are automatically excluded.

get_size

def get_size(self) -> tuple[float, float]

Returns (width, height) of the unscaled chart container in canvas units.

series *(property)*

@property
def series(self) -> list[Series]

Returns a list copy of registered Series instances.

value_axis *(property)*

@property
def value_axis(self) -> Axis

Orientation-aware property returning the numerical value Axis (y_axis when orientation="vertical", x_axis when orientation="horizontal").


classBoxStats#

Validated Pydantic BaseModel (extra="forbid") representing summary distribution statistics for a single category slot in a BoxChart. Enforces finite numeric inputs and monotonic quantile order (min <= q1 <= median <= q3 <= max).

class BoxStats(BaseModel):
    min: float
    q1: float
    median: float
    q3: float
    max: float
    mean: float | None = None
    outliers: list[float] = Field(default_factory=list)
    points: list[float] = Field(default_factory=list)

Parameters & Attributes#

Parameter / Attribute Type Default Description
min float Required Lower whisker endpoint (e.g. sample minimum, $Q_1 - 1.5\cdot\text{IQR}$ fence, or p5).
q1 float Required First quartile (25th percentile, min <= q1).
median float Required Median value (50th percentile, q1 <= median).
q3 float Required Third quartile (75th percentile, median <= q3).
max float Required Upper whisker endpoint (e.g. sample maximum, $Q_3 + 1.5\cdot\text{IQR}$ fence, or p95; q3 <= max).
mean float | None None Optional arithmetic mean value rendered when show_mean=True.
outliers list[float] [] Outlier values beyond [min, max] rendered when show_box=True, show_outliers=True, and show_points=False.
points list[float] [] Raw sample values rendered as strip/jitter markers when show_points=True (required if show_box=False).

Methods#

from_values *(classmethod)*

@classmethod
def from_values(
    cls,
    values: Sequence[float],
    *,
    whisker_mode: WhiskerMode = "iqr",
    iqr_k: float = 1.5,
    whisker_percentiles: tuple[float, float] = (5.0, 95.0),
) -> BoxStats

Computes a BoxStats summary (min, q1, median, q3, max, arithmetic mean, outliers, and raw points) from a non-empty sequence of finite floats using inclusive linear percentile interpolation.

extent_values

def extent_values(
    self,
    *,
    show_box: bool,
    show_points: bool,
    show_outliers: bool,
    show_mean: bool,
) -> list[float]

Returns all numerical values from this slot that contribute to automatic value-axis domain scaling under the active visibility flags.


typeDrawDirection#

Spatial partial-rendering direction used with Series.draw_ratio (0.0 to 1.0) for progressive box and strip plot animations.

DrawDirection = Literal["bottom_to_top", "left_to_right"]
Literal Value Vertical BoxChart Behavior Horizontal BoxChart Behavior
"bottom_to_top" All category boxes, whiskers, and markers expand outward from each slot's median (default). Category slots reveal sequentially from bottom to top, expanding outward from median.
"left_to_right" Category slots reveal sequentially from left to right, expanding outward from median. All category boxes, whiskers, and markers expand outward from each slot's median (default).

typeFormatterType#

Union type controlling numeric formatting for axis ticks (Axis.format) and inline median labels (BoxChart.value_format).

FormatterType = str | Callable[[float], str] | None
  • str: Standard Python format specification or template string (e.g. "{:.1f}ms", "${:,.0f}", "{:.0f}%").
  • Callable[[float], str]: Custom formatter function mapping a numeric float value to a display string.
  • None: Uses Drawlib's automatic compact number formatting (and Axis.format_value() fallback).

typeLegendPosition#

Literal type representing preset legend placement identifiers for chart layout helpers.

LegendPosition = Literal["top", "bottom", "right", "none", "auto"]
  • Note: BoxChart renders legends at explicit canvas coordinates via BoxChart.draw_legend() for full dashboard layout control.

typeOrientation#

Literal type specifying the primary distribution axis (BoxChart.orientation) or legend item flow (draw_legend).

Orientation = Literal["vertical", "horizontal"]
Literal Value Description
"vertical" Distributions span vertically along y_axis across horizontal x_axis categories; legend items stack top-to-bottom.
"horizontal" Distributions span horizontally along x_axis across vertical y_axis categories; legend items flow left-to-right.

typePointShape#

Literal type specifying the geometric marker shape for strip sample points (point_shape), outliers (outlier_shape), and arithmetic means (mean_shape).

PointShape = Literal["circle", "square", "rhombus", "triangle", "none"]
Literal Value Description
"circle" Circular point marker (default for point_shape and outlier_shape).
"square" Axis-aligned square marker.
"rhombus" Rotated diamond (45°) marker (default for mean_shape).
"triangle" Upward-pointing equilateral triangle marker.
"none" Suppresses marker geometry.

typeScaleType#

Literal type controlling the numerical coordinate mapping algorithm on Axis (configure_x_axis / configure_y_axis).

ScaleType = Literal["linear", "log"]
Literal Value Description
"linear" Standard arithmetic linear scaling with nice-number tick intervals ($1, 2, 5 \times 10^k$) (default).
"log" Base-10 logarithmic ($\log_{10}$) scaling for skewed multi-order-of-magnitude distributions (requires positive values).

classSeries#

Mutable data model representing a single registered distribution series in a BoxChart.

class Series:
    def __init__(
        self,
        name: str,
        stats: list[BoxStats | None],
        style: Style,
        legend_text_style: Style | None = None,
        *,
        show_box: bool = True,
        show_points: bool = False,
        show_outliers: bool = True,
        show_mean: bool = False,
        median_style: Style | None = None,
        whisker_style: Style | None = None,
        point_style: Style | None = None,
        outlier_style: Style | None = None,
        mean_style: Style | None = None,
        point_radius: float = 0.58,
        point_shape: PointShape = "circle",
        jitter: float = 0.45,
        show: bool = True,
        draw_ratio: float = 1.0,
        draw_direction: DrawDirection = "bottom_to_top",
    ) -> None: ...

Parameters & Attributes#

Parameter / Attribute Type Default Description
name str Required Series display name shown in draw_legend().
stats list[BoxStats | None] Required Per-category BoxStats summaries aligned with BoxChart.categories (None for empty slots).
style Style Required Base Style defining box fill/border and automatic sub-element contrast colors.
legend_text_style Style | None None Optional per-series text style override in draw_legend().
show_box bool True Whether to draw Q1–Q3 boxes, whiskers, and median lines for this series.
show_points bool False Whether to draw strip/jitter sample markers for this series.
show_outliers bool True Whether to draw outlier markers when show_box=True and show_points=False.
show_mean bool False Whether to draw arithmetic mean markers for this series.
median_style Style | None None Optional Style override for the median line.
whisker_style Style | None None Optional Style override for whisker stems and T-caps.
point_style Style | None None Optional Style override for strip/jitter sample markers.
outlier_style Style | None None Optional Style override for outlier markers (also highlights tail points beyond [min, max] when show_points=True).
mean_style Style | None None Optional Style override for the arithmetic mean marker.
point_radius float 0.58 Marker radius (> 0.0) for points, outliers, and mean markers in this series.
point_shape PointShape "circle" Marker geometry for strip/jitter sample points in this series.
jitter float 0.45 Normalized transverse jitter spread ratio (0.0 to 1.0) for strip points.
show bool True Toggles series visibility while preserving full-dataset axis domain bounds.
draw_ratio float 1.0 Partial rendering progress (0.0 to 1.0) for keyframe animations.
draw_direction DrawDirection "bottom_to_top" Direction of partial spatial rendering ("bottom_to_top" or "left_to_right").

typeWhiskerMode#

Literal type selecting the statistical rule used by BoxChart and BoxStats.from_values() to compute lower/upper whisker endpoints (min, max) and outliers from raw sample values.

WhiskerMode = Literal["iqr", "min_max", "percentile"]
Literal Value Description
"iqr" Standard Tukey fence rule: whiskers extend to the most extreme inlier samples within $[Q_1 - k\cdot\text{IQR},; Q_3 + k\cdot\text{IQR}]$ (k = iqr_k, default 1.5), classifying values beyond fences as outliers (default).
"min_max" Full-range rule: whiskers span $[\min(\text{values}),; \max(\text{values})]$ with outliers = [].
"percentile" Quantile rule: whiskers extend to inclusive (low_p, high_p) percentiles (whisker_percentiles, default (5.0, 95.0)), classifying tail samples outside [min, max] as outliers.