Rendering¶
sekai.rendering. See the Rendering guide; the classic-control and
finance renderers are documented under Environments, and the market
charts under Finance.
Renderer
¶
Bases: Protocol
Protocol for rendering backends: any object with render(env) and close().
Renderers are separate from Env — they are injected and managed
independently, so you can swap, compose, or record without touching
the environment. Subclass BaseRenderer to also get a no-op close()
and context-manager support.
Example
Canvas
¶
A tiny dependency-free RGB raster for drawing environment frames.
Coordinates are in pixels with the origin at the bottom-left and y pointing up, which suits physics drawings. to_array() returns an (H, W, 3) uint8 array, the RGB frame format used throughout sekai.rendering.
By default shapes are filled by testing pixel centres (crisp, no
anti-aliasing). With antialias=True every shape is drawn with exact
coverage at its edges: smooth lines and curves, rectangles that land on
fractional pixels, round line caps and joins. Text is always anti-aliased.
Every shape takes alpha (opacity in [0, 1]), and clip() restricts
drawing to a rectangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Width in pixels. |
required |
height
|
int
|
Height in pixels. |
required |
background
|
Color
|
Initial fill colour. |
(255, 255, 255)
|
antialias
|
bool
|
Draw shapes with anti-aliased edges. |
False
|
clip
¶
polygon
¶
Fill a simple polygon given as a sequence of (x, y) vertices.
rect
¶
rect(left: float, bottom: float, right: float, top: float, color: Color, *, alpha: float = 1.0, radius: float = 0.0) -> None
Fill an axis-aligned rectangle, optionally with corners rounded by radius.
circle
¶
Fill a disc.
line
¶
line(start: Sequence[float], end: Sequence[float], color: Color, width: float = 1.0, *, alpha: float = 1.0, dash: tuple[float, float] | None = None) -> None
Draw a straight segment of the given width (in pixels).
dash=(on, off) draws it dashed, on pixels drawn then off skipped.
polyline
¶
polyline(points: ArrayLike, color: Color, width: float = 1.0, *, alpha: float = 1.0, dash: tuple[float, float] | None = None) -> None
Draw connected segments through points ((x, y) rows).
A non-finite point breaks the line, so NaN marks a gap in a series.
Anti-aliased polylines have round joins and caps, and overlapping
segments are drawn once (a translucent line stays even). dash=(on, off)
draws it dashed.
fill_between
¶
fill_between(x: ArrayLike, y1: ArrayLike, y2: ArrayLike, color: Color, *, alpha: float = 1.0) -> None
Fill the area between two curves sampled at increasing x.
The curves are linear between samples; a non-finite y leaves a gap. For a step (piecewise-constant) curve, repeat each x with the old and new value.
image
¶
image(image: ArrayLike, left: float, bottom: float, right: float, top: float, *, alpha: float = 1.0) -> None
Draw an RGB (h, w, 3) or RGBA (h, w, 4) uint8 image scaled into a box.
Each source pixel becomes a crisp block (nearest-neighbour scaling): this draws heatmaps and matrices of cells, with an image's first row at the top.
text
¶
text(x: float, y: float, text: str, color: Color, *, size: float = 12, bold: bool = False, align: HAlign = 'left', valign: VAlign = 'baseline', alpha: float = 1.0) -> float
Draw a line of text and return its width in pixels.
align places x at the text's left edge, centre or right edge.
valign places y at the baseline, the middle of the capital letters,
their top, or the bottom of the descenders. The typeface is BDO Grotesk
at sizes 9-32 px (other sizes snap to the nearest; larger ones are
scaled), regular or bold, with tabular digits; see sekai.rendering.text.
rotate
¶
Rotate (x, y) points counter-clockwise by angle (radians) about origin.
text_width
¶
The width in pixels of text drawn by Canvas.text at this size and weight.
RecordFrames
¶
RecordFrames(env: Env[ObsType, ActType], renderer: Renderer, *, enabled: bool = True, max_frames: int | None = None)
Bases: Wrapper[ObsType, ActType, ObsType, ActType]
Captures a frame from renderer after every reset() and step().
Rendering stays outside the env: any Renderer works, and recording can be switched off (enabled = False) without touching the environment.
Example
save_gif
¶
Write RGB frames to an animated GIF. Needs Pillow (pip install 'sekai[render]').
frames: a list of (H, W, 3) arrays or one (T, H, W, 3) array, all of one shape, with integer values in [0, 255]; fps must be > 0. Bad frames raise before anything is written.
save_video
¶
Write RGB frames to a video file (e.g. .mp4). Needs imageio with ffmpeg.
frames: a list of (H, W, 3) arrays or one (T, H, W, 3) array, all of one shape, with integer values in [0, 255]; fps must be > 0. Bad frames raise before anything is written.
Dashboards¶
Dashboard
¶
Dashboard(*items: Any, title: str | Source | None = None, subtitle: str | Source | None = None, stats: Sequence[Stat] = (), heights: Sequence[float] | None = None, width: int = 720, height: int = 450, theme: Theme | str = 'light', step: Source | None = 'elapsed_steps', horizon: float | Source | None = 'auto', window: int | None = None)
Bases: BaseRenderer
A renderer that draws an environment's episode as a dashboard of charts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*items
|
Any
|
The layout, top to bottom: Panels, |
()
|
title
|
str | Source | None
|
The heading (default: the env's registered id or class name); a string or a source. |
None
|
subtitle
|
str | Source | None
|
A line under the title (a string or a source), e.g. the date. |
None
|
stats
|
Sequence[Stat]
|
Headline numbers shown at the top right. |
()
|
heights
|
Sequence[float] | None
|
Relative heights of the top-level items (default: their own |
None
|
width
|
int
|
Frame width in pixels. |
720
|
height
|
int
|
Frame height in pixels. |
450
|
theme
|
Theme | str
|
"light", "dark" or a |
'light'
|
step
|
Source | None
|
The episode clock, which is also the time axis (default:
|
'elapsed_steps'
|
horizon
|
float | Source | None
|
The clock's value at the end of an episode, which fixes the time axis
so the episode draws from left to right: a number, a source, None (the
axis grows with the episode) or "auto" (the env's |
'auto'
|
window
|
int | None
|
Show only the latest |
None
|
set_layout
¶
Replace the charts (and forget recorded histories): the same arguments as above.
A renderer that sizes its charts from the env (one line per trader, say) can
call this from check() the first time it sees the env.
Panel
¶
One chart of a dashboard. Subclass it to draw your own.
A panel declares the values it needs (sources(), recorded every frame, and
context(), read once per episode) and draws them in draw(canvas, box, view),
reading them back from the View with the same keys. Time-series panels
(timeseries = True) share the dashboard's time axis; the helper frame()
draws the title row and legend and returns the plot area.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str | None
|
Shown at the panel's top left. |
None
|
width
|
float
|
Relative width in its row. |
1.0
|
height
|
float
|
Relative height in its column. |
1.0
|
context
¶
Values to read once, at the first frame of an episode, by key.
x_start
¶
The earliest time this panel shows, if before the first recorded step.
frame
¶
frame(canvas: Canvas, box: Box, view: View, legend_items: Sequence[tuple[str, ColorLike, Swatch]] = (), *, gutter: bool | None = None) -> Box
Draw the title row (title left, legend right) and return the plot area.
Time-series panels reserve the dashboard's y-axis gutter on the right and, when they show the time axis, a band underneath for its labels.
View
¶
View(env: Any, history: _History, prefix: str, theme: Theme, scale: float, x_domain: tuple[float, float], show_x: bool, gutter: float)
What a panel sees when it draws: the recorded episode, the time axis and the style.
Attributes:
| Name | Type | Description |
|---|---|---|
env |
The environment being rendered (as passed to |
|
theme |
The colours. |
|
scale |
Size multiplier (1 at 720 x 450). |
|
steps |
NDArray[float64]
|
The episode clock of each recorded sample. |
x_domain |
The time axis shared by every time-series panel. |
|
show_x |
Whether this panel draws the time axis labels (the lowest in its column). |
|
gutter |
Width reserved right of time-series plots for y-axis labels. |
values
¶
A source's recorded values as floats: (n,) for numbers, (n, k) for vectors.
Missing values are NaN; vectors of different lengths are padded with NaN.
Row
¶
Panels or columns side by side. widths overrides the children's own widths.
Column
¶
Panels or rows stacked top to bottom. heights overrides the children's own.
Series
dataclass
¶
Series(source: Source, label: str | None = None, color: ColorSpec = None, fill: bool = False, signed: bool = False, step: bool = False, dashed: bool = False, width: float = 2.0, rebase: float | None = None)
One line of a LineChart.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
The value per step. |
required |
label
|
str | None
|
Legend label (charts with two or more labelled series show a legend). |
None
|
color
|
ColorSpec
|
Theme role, hex, RGB or palette slot; default: the slot of its position. |
None
|
fill
|
bool
|
Shade the area under the line (a 10% wash, down to the chart's baseline). |
False
|
signed
|
bool
|
Shade the area in the |
False
|
step
|
bool
|
Hold each value until the next step (for inventories, positions). |
False
|
dashed
|
bool
|
Draw dashed (benchmarks, plans and other references). |
False
|
width
|
float
|
Line width in pixels (at 720 x 450). |
2.0
|
rebase
|
float | None
|
Divide by the episode's first value and multiply by this (index the
series, e.g. |
None
|
Stat
dataclass
¶
Stat(label: str, source: Source, format: str | Callable[[Any], str] = '{:,.2f}', delta: Literal['relative', 'absolute'] | None = None, higher_is_better: bool = True)
A headline number in a dashboard's header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
What it is ("Equity"). |
required |
source
|
Source
|
Its value. |
required |
format
|
str | Callable[[Any], str]
|
A format string ( |
'{:,.2f}'
|
delta
|
Literal['relative', 'absolute'] | None
|
Also show its change since the episode began: "relative" (percent) or "absolute", with a direction triangle. |
None
|
higher_is_better
|
bool
|
Whether a rise is good: it picks the delta's colour. |
True
|
LineChart
¶
LineChart(*series: Series | Source, title: str | None = None, baseline: float | None = None, percent: bool = False, tag: bool = True, limits: Sequence[float] = (), shade: Source | None = None, shade_label: str | None = None, shade_color: ColorSpec = 'down', width: float = 1.0, height: float = 1.0)
Bases: Panel
Series over time, on one shared y-axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*series
|
Series | Source
|
|
()
|
title
|
str | None
|
Panel title. |
None
|
baseline
|
float | None
|
A reference value drawn as a baseline and kept in view (e.g. 0, or 1.0 for equity that starts at 1). |
None
|
percent
|
bool
|
Label the y-axis (and the value tag) as percentages. |
False
|
tag
|
bool
|
Show the first series' latest value in a tag on the y-axis. |
True
|
limits
|
Sequence[float]
|
Reference values drawn as dashed lines (e.g. inventory limits). |
()
|
shade
|
Source | None
|
A source that is true while a condition holds (a regime, a drawdown, market hours): those steps are shaded behind the series. |
None
|
shade_label
|
str | None
|
Legend label for the shading. |
None
|
shade_color
|
ColorSpec
|
Colour of the shading (default: the |
'down'
|
width
|
float
|
Relative width in its row. |
1.0
|
height
|
float
|
Relative height in its column. |
1.0
|
BarChart
¶
BarChart(source: Source, *, labels: Sequence[str] | Source | None = None, colors: Sequence[ColorSpec] | None = None, color: ColorSpec = None, negative_color: ColorSpec = 'down', available: Source | None = None, percent: bool = True, limit: float | None = None, title: str | None = None, width: float = 1.0, height: float = 1.0)
Bases: Panel
The current values of a vector as labelled horizontal bars (weights, holdings).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
A vector per step. |
required |
labels
|
Sequence[str] | Source | None
|
Bar labels: a sequence, or a source giving one per frame. |
None
|
colors
|
Sequence[ColorSpec] | None
|
One colour per bar (theme roles, hex, RGB or slots); default: slot i for bar i, when bars are separate entities shown elsewhere in those colours. |
None
|
color
|
ColorSpec
|
One colour for every bar instead (a single series identified by its
labels), e.g. "accent". Negative values use |
None
|
negative_color
|
ColorSpec
|
Colour of negative bars when |
'down'
|
available
|
Source | None
|
A source of a boolean per bar; unavailable bars are greyed out. |
None
|
percent
|
bool
|
Write values as percentages. |
True
|
limit
|
float | None
|
The value a full-length bar stands for (default: a round number just above the largest magnitude). |
None
|
title
|
str | None
|
Panel title. |
None
|
width
|
float
|
Relative width in its row. |
1.0
|
height
|
float
|
Relative height in its column. |
1.0
|
StackedArea
¶
StackedArea(source: Source, *, labels: Sequence[str] | None = None, colors: Sequence[ColorSpec] | None = None, percent: bool = True, legend: bool = False, title: str | None = None, width: float = 1.0, height: float = 1.0)
Bases: Panel
A vector over time as stacked areas (an allocation's composition, say).
Positive parts stack up from zero, negative parts down. Layers are separated by a thin line in the surface colour.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
A vector per step. |
required |
labels
|
Sequence[str] | None
|
Layer labels for the legend. |
None
|
colors
|
Sequence[ColorSpec] | None
|
Layer colours; default: slot i for layer i. |
None
|
percent
|
bool
|
Label the axis as percentages. |
True
|
title
|
str | None
|
Panel title. |
None
|
width
|
float
|
Relative width in its row. |
1.0
|
height
|
float
|
Relative height in its column. |
1.0
|
Themes¶
Theme
dataclass
¶
Theme(name: str, background: Color, grid: Color, axis: Color, text: Color, text_secondary: Color, text_muted: Color, price: Color, accent: Color, neutral: Color, up: Color, down: Color, highlight: Color, series: tuple[Color, ...])
The colours of a chart, by role.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
A short name ("light", "dark", ...). |
background |
Color
|
The frame and plot surface. |
grid |
Color
|
Hairline gridlines, one step off the surface. |
axis |
Color
|
Baselines and the progress-bar track. |
text |
Color
|
Primary ink: titles and values. |
text_secondary |
Color
|
Panel titles and legends. |
text_muted |
Color
|
Axis labels and stat labels. |
price |
Color
|
The main price line. |
accent |
Color
|
The series a chart is about, e.g. the agent's equity. |
neutral |
Color
|
De-emphasised series (benchmarks, cash) and reference lines. |
up |
Color
|
Long positions, buys, bids, gains. |
down |
Color
|
Short positions, sells, asks, losses. |
highlight |
Color
|
The agent's own orders and quotes. |
series |
tuple[Color, ...]
|
The categorical palette, in assignment order. |
color
¶
color(slot: int) -> Color
Categorical colour slot (0-based); slots past the palette get neutral.
Colours are never reused: a ninth series is folded into the neutral grey rather than given a colour that could be mistaken for the first.
ink_on
¶
The text colour (light or dark ink) that reads best on a filled fill.
LIGHT
module-attribute
¶
LIGHT = _theme('light', ('#107c5a', '#bd8928', '#82498b', '#c45e3d', '#0098a9', '#7b8935', '#3d73b6', '#cc6b83'), background='#f8f6f2', grid='#eae3d7', axis='#cfc7b8', text='#1f271b', text_secondary='#4d5545', text_muted='#6f6c5f', price='#1f271b', accent='#107c5a', neutral='#a8a294', up='#439458', down='#a12f2f', highlight='#bd8928')
Warm paper surface, forest ink: the Qian Capital light palette.
DARK
module-attribute
¶
DARK = _theme('dark', ('#3b9a76', '#bc8726', '#a86cb1', '#cf6746', '#0ba5b6', '#808f3b', '#578ed4', '#d16f88'), background='#161c13', grid='#262f22', axis='#3b4636', text='#ece3d0', text_secondary='#c4bfae', text_muted='#8e9483', price='#ece3d0', accent='#3b9a76', neutral='#6f7566', up='#3aa273', down='#b94642', highlight='#c0aa81')
Forest-ink surface, gold-white ink: the Qian Capital dark palette.
get_theme
¶
A Theme, or the built-in one named "light" or "dark".
Chart helpers¶
sekai.rendering.charts: what the charts above are drawn with, for drawing your own.
Chart drawing on a Canvas: layout boxes, nice ticks, number formats and plot marks.
These are the pieces sekai's dashboards (sekai.rendering.Dashboard) and finance
renderers are drawn with, and they are public so a custom renderer can draw charts in
the same style:
canvas = Canvas(640, 360, LIGHT.background, antialias=True)
plot = Plot(canvas, Box(16, 24, 580, 340), (0, 100), (0.9, 1.2), LIGHT)
plot.grid(nice_ticks(0.9, 1.2))
plot.line(steps, equity, LIGHT.accent)
Marks follow fixed specs: 2 px lines with round caps and joins, hairline (1 px)
solid gridlines one step off the surface, area fills as a 10% wash of the series
colour, end markers with a 2 px ring in the surface colour, and text in the ink
colours rather than series colours. Sizes scale with scale.
Box
dataclass
¶
Plot
¶
Plot(canvas: Canvas, box: Box, x_domain: tuple[float, float], y_domain: tuple[float, float], theme: Theme, scale: float = 1.0)
A box of the canvas mapped to data coordinates, with methods that draw marks.
Marks are clipped to the box; labels and value tags may extend into the space to its right (the y-axis gutter, where tick labels go).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
canvas
|
Canvas
|
The canvas to draw on (anti-aliased canvases look best). |
required |
box
|
Box
|
The plot area. |
required |
x_domain
|
tuple[float, float]
|
Data range shown across the box. |
required |
y_domain
|
tuple[float, float]
|
Data range shown from bottom to top. |
required |
theme
|
Theme
|
Colours. |
required |
scale
|
float
|
Size multiplier for line widths, markers and text (1 at 720 x 450). |
1.0
|
grid
¶
grid(ticks: ArrayLike, labels: Sequence[str] | None = None, *, size: float = 10, avoid: Sequence[float | None] = ()) -> None
Hairline gridlines at y ticks, labelled in the gutter right of the box.
Labels within a tag's height of a data y in avoid are left out, so a value
tag drawn there (tag()) does not cover one.
x_ticks
¶
Labels under the box at x ticks (no gridlines: time runs left to right).
hline
¶
hline(value: float, color: ColorLike, *, width: float = 1.0, dash: tuple[float, float] | None = None, alpha: float = 1.0) -> None
A horizontal line across the box at data y value (a baseline or reference).
vline
¶
vline(value: float, color: ColorLike, *, width: float = 1.0, alpha: float = 1.0, dash: tuple[float, float] | None = None) -> None
A vertical line across the box at data x value (e.g. the start of an episode).
line
¶
line(xs: ArrayLike, ys: ArrayLike, color: ColorLike, *, width: float = 2.0, dash: tuple[float, float] | None = None, alpha: float = 1.0, step: bool = False) -> None
A series as a line (NaN leaves a gap); step=True holds values between samples.
area
¶
area(xs: ArrayLike, ys: ArrayLike, color: ColorLike, *, base: ArrayLike | float | None = None, alpha: float = 0.1, step: bool = False) -> None
Fill between a series and base (default: the bottom of the box).
shade
¶
A vertical band over the box's full height, from data x x0 to x1.
nice_ticks
¶
Round tick values inside [low, high], about count intervals apart.
integer=True places ticks on whole numbers only.
nice_step
¶
A round step (1, 2, 2.5 or 5 times a power of ten) giving about count intervals.
integer=True keeps the step a whole number (1, 2, 5, 10, ...), for counts.
tick_labels
¶
Labels for evenly spaced ticks, all with the same number of decimals.
format_number
¶
format_number(value: float, decimals: int = 2, *, sign: bool = False, percent: bool = False, compact: bool = False) -> str
A number for display: thousands separators, a true minus sign, optional +.
percent=True multiplies by 100 and appends %; compact=True writes large
magnitudes as 12.5K, 3.4M, 1.2B. NaN and None are written as an em dash.
value_range
¶
value_range(*arrays: ArrayLike, include: Sequence[float] = (), pad: float = 0.08) -> tuple[float, float]
The finite range of some data (plus include), padded by pad of its span.
A flat or empty range is widened so a scale can always be built from it.
legend
¶
legend(canvas: Canvas, x: float, y: float, items: Sequence[tuple[str, ColorLike, Swatch]], theme: Theme, *, scale: float = 1.0, size: float = 10, align: Literal['left', 'right'] = 'right') -> float
Legend entries (label, colour, swatch kind) on one line; returns its width.
x is the right edge (align="right") or left edge of the legend, y the
middle of its line. Labels are in secondary ink: the swatch carries identity.