unichart example gallery

Every plot, one call each.

19 figures, each one produced by the few lines of code printed under it. The data is the same four engine dyno runs throughout — load once, then choose what to show. Every chart is live, exactly as it comes out of the notebook: hover a trace for values, drag to zoom, double-click to reset.

What you can draw

14 kinds of chart. Pick one to jump to its example.

The data

Four runs of a piston engine on a test stand, 240 samples each at 2.5 s, sharing one schema — the shape unichart is built for.

Load it once and every example below starts from this state. No example re-reads the file or touches Plotly directly.

time_s
elapsed seconds, 0–600
rpm
crank speed, 650–3350
torque_nm
brake torque
power_kw
brake power
fuel_kgh
fuel flow
cht_c
cylinder head temp
egt_c
exhaust gas temp
eta_pct
brake thermal efficiency
phase
idle · cruise · climb · max
run_id
0–3, one dataset each
from unichart import UnichartNotebook
import pandas as pd

# 4 runs x 240 samples, one tidy frame
df = pd.read_csv('dyno_runs.csv')

nb = UnichartNotebook()
nb.set_default_format(marker=None, linestyle='-',
                      linewidth=1.8)      # traces, not markers
nb.load_df(df, set_idx_column='run_id',
               set_name_column='run_name')
nb.set_plot_size(4.6, 3.0)   # one panel size, every plot

Start here

The four layouts that cover most of a test campaign.

nb.plot

One line per run

Every selected dataset is drawn on the same axes, each with its own color and marker from the palette. Nothing to loop over.

nb.plot(x='time_s', y='cht_c')

nb.plot(by='vars')

A subplot per variable

Pass a list of Y columns and you get a grid — the default layout. The runs stay color-matched across the panels.

nb.plot(x='time_s', y=['rpm', 'torque_nm', 'cht_c', 'eta_pct'], ncols=2)

nb.plot(by='sets')

A subplot per run

Same data, transposed: one panel per dataset with every variable in it. ncols sets the grid; hspace and vspace set the gaps between panels, in pixels.

nb.select([0,3])
nb.plot(x='time_s', y=['cht_c', 'egt_c'], by='sets', vspace=110)

nb.plot_ymult

Several scales, one panel

Stacked Y axes for quantities that share an X but nothing else — rpm in thousands next to a fuel flow in single digits.

nb.select([0, 2])
nb.linestyle(2, ':')
nb.color('rpm', 'blue')
nb.color('cht_c', 'red')
nb.color('fuel_kgh', 'orange')
nb.plot_ymult(x='time_s', y=['rpm', 'cht_c', 'fuel_kgh'])

Slice and shape

Choose what is drawn before you worry about how it looks.

nb.select · nb.query

Select runs, filter rows

Selection decides which datasets are drawn; a query filters rows inside them. Both stick until you change them.

nb.select([0, 3])                       # draw these two runs
nb.query('all', 'phase == "climb"')      # keep these rows
nb.plot(x='time_s', y=['rpm', 'eta_pct'])

nb.line · nb.highlight

Limits and events

Reference lines carry a label drawn inside the plot area; highlights shade a band of the X axis. Both persist across plots.

nb.line('cht_c', 150, color='firebrick', linestyle='--',
        label='CHT limit', label_position='left above')
nb.highlight('time_s', (300, 450), color='orange', alpha=0.12)
nb.plot(x='time_s', y='cht_c')

nb.var_format

Format by variable, not by call

Where the variables are the series rather than the runs, a per-variable override gives each column its own identity — and it outranks the per-dataset style, so CHT is dashed red everywhere.

nb.select(0)
nb.var_format('cht_c', color='firebrick', linestyle='--')
nb.var_format('egt_c', color='darkorange')
nb.var_format('fuel_kgh', color='steelblue')
nb.plot_ymult(x='time_s', y=['cht_c', 'egt_c', 'fuel_kgh'])

Distributions

When the spread matters more than the trace.

nb.plot_marginal

Scatter with its margins

The operating points, with the distribution of each axis in a strip beside it. Swap in 'box', 'violin', 'rug' or 'kde'.

nb.marker('all', 'o'); nb.linestyle('all', ''); nb.markersize('all', 5)
nb.plot_marginal(x='rpm', y='torque_nm', marginal='box')

nb.histogram

Where the run spent its time

Overlaid histograms per dataset, with the binning, normalization and opacity you would expect to be able to set.

nb.histogram(x='cht_c', nbins=40, alpha=0.6)

nb.box

Spread by category

One box per run per category, grouped along a categorical X column.

nb.box(x='phase', y='eta_pct', points='outliers')

nb.bar

Reduced to one number each

Bar height is an aggregate of Y over the rows in each category — 'mean' by default, or any pandas reducer.

nb.bar(x='phase', y=['fuel_kgh', 'eta_pct'], agg='mean', barmode='group')

Fields and tables

Scattered data as a surface, and numbers as numbers.

nb.contour

A map from scattered points

Efficiency over the rpm/torque plane, interpolated from the samples the runs happened to visit, with one run's track drawn over it.

nb.combine_sets('all', title='all runs')
nb.select(4)
nb.color(0, 'black')
nb.linestyle(0, '--')
nb.contour(x='rpm', y='torque_nm', z='eta_pct', overlay_sets=0)

nb.table

Values at the X you ask for

Interpolation mode reads each run's Y at your X inputs, so runs sampled at different times line up in one table.

nb.table(cols=['rpm', 'eta_pct'], x_col='time_s',
         x_in=[150, 300, 450], sig_figs=4, output='fig')

nb.summary

Descriptive stats per run

Count, min, mean, max and std for the columns you name — the same sortable table widget as nb.table.

nb.summary(cols=['rpm', 'torque_nm', 'eta_pct'], sig_figs=4, output='fig')

Analysis

Differences against a baseline, and fits through the cloud.

nb.delta

Against a baseline

Each study run is aligned to the base run and differenced, producing DL_<parm> and DLPCT_<parm> columns in new datasets that keep the study run's color.

nb.delta(base_idx=0, study_indices=[1, 2, 3],
         align_on='time_s', delta_parms=['cht_c', 'eta_pct'])
nb.select([4, 5, 6])
nb.plot(x='time_s', y=['DL_cht_c', 'DLPCT_eta_pct'], hspace=100)

nb.reg_order

Fits through the cloud

Give a dataset a regression order and its line becomes the fit, labelled in the legend. LOWESS needs statsmodels.

nb.marker('all', 'o'); nb.markersize('all', 4); nb.alpha_marker('all', 0.7)
nb.reg_order('all', 2)
nb.plot(x='rpm', y='eta_pct')

nb.hue

Color by a third column

A dataset can take its point colors from any column, turning a scatter into a three-variable read.

nb.select(3)
nb.marker(3, 'o'); nb.linestyle(3, ''); nb.markersize(3, 6)
nb.hue(3, 'eta_pct')
nb.plot(x='rpm', y='torque_nm')

Looks

The same figures, restyled.

nb.set_plot_style

Plotly look

Every figure above is drawn in the default Matplotlib-like style — four spines, outward ticks, DejaVu Sans, the tab10 cycle. One call switches the whole environment to Plotly's own look instead.

nb.set_plot_style('plotly')
nb.plot(x='time_s', y=['torque_nm', 'eta_pct'], hspace=200)

nb.toggle_darkmode

Dark mode

One switch, applied to the whole environment — plots, tables and dashboards alike.

nb.toggle_darkmode(True)
nb.plot(x='time_s', y=['rpm', 'cht_c'], hspace=200)