Visualization Contract
Modules implement visualize() to return one visual spec or a list of them.
Labs Serve UI and the platform render these specs after transport normalization.
Collect visuals from a world
world.run(duration=10.0)
visuals = world.collect_visuals()
for entry in visuals:
print(entry["module"], [v["render"] for v in entry["visuals"]])Visuals built by a downstream module
A module that plots another module’s outputs only sees them after they are
committed at a boundary. Give that module ExecutionPolicy.ONCE_AFTER_RUN.
BioWorld runs it once after the final window, with the final committed inputs.
import biosimulant as biosim
class PopulationTable(biosim.BioModule):
execution_policy = biosim.ExecutionPolicy.ONCE_AFTER_RUN
def __init__(self):
self.final_count = None
def inputs(self):
return {"population_state": biosim.SignalSpec.record(schema={"count": "int64"})}
def execute(self, inputs, *, context):
state = inputs.get("population_state")
if state is not None:
self.final_count = state.value["count"]
return {}
def visualize(self):
return {
"render": "table",
"data": {
"columns": ["Metric", "Value"],
"rows": [["Final count", str(self.final_count)]],
},
}Do not use world.settle() or runtime.settle_steps for this. Settling skips
modules that implement execute() and only calls modules that implement
advance_window().
Spec fields
| Field | Type | Required | Description |
|---|---|---|---|
render | str | Yes | Visual type. Known types are "timeseries", "bar", "graph", "table", "image", "text", and "structure3d", plus "custom:<name>" for your own renderer. Validation only requires a non-empty string. |
data | dict | Yes | Render-type-specific, JSON-serializable content |
description | str | No | Optional text description |
For structure3d file sources, see Artifact Outputs.
Timeseries
def visualize(self):
return {
"render": "timeseries",
"data": {
"title": "Membrane Potential",
"xlabel": "Time (ms)",
"ylabel": "Voltage (mV)",
"series": [
{"name": "Neuron 1", "points": self.v1_history},
{"name": "Neuron 2", "points": self.v2_history}
]
}
}| Field | Type | Description |
|---|---|---|
title | str | Chart title |
xlabel | str | X-axis label |
ylabel | str | Y-axis label |
series | list[{name, points}] | Named data series |
Each points entry is a list of [x, y] pairs: [[0.0, -65.0], [0.1, -60.2], ...].
Bar Chart
def visualize(self):
return {
"render": "bar",
"data": {
"title": "Spike Counts",
"items": [
{"label": "Excitatory", "value": self.exc_count},
{"label": "Inhibitory", "value": self.inh_count}
]
}
}| Field | Type | Description |
|---|---|---|
title | str | Chart title |
items | list[{label, value}] | Labeled values |
Table
def visualize(self):
return {
"render": "table",
"data": {
"columns": ["Metric", "Value"],
"rows": [
["Mean Rate", f"{self.mean_rate:.2f} Hz"],
["Total Spikes", str(self.total_spikes)]
]
}
}| Field | Type | Description |
|---|---|---|
columns | list[str] | Column headers |
rows | list[list[str]] | Row data (strings) |
Image (Raster Plot, Heatmap, etc.)
For complex visualizations generated with matplotlib or similar:
def visualize(self):
return {
"render": "image",
"data": {
"src": self.raster_image_base64, # data:image/png;base64,... or raw base64
"alt": "Spike Raster",
"width": 800,
"height": 400
}
}| Field | Type | Description |
|---|---|---|
src | str | Base64-encoded image (PNG/JPEG) |
alt | str | Alt text |
width | int | Display width in pixels |
height | int | Display height in pixels |
Tips
- Keep timeseries histories bounded or downsampled for long runs.
- Put units in axis labels.
- Keep lab visualisation modules inside the lab when you distribute portable
.bsilabarchives. A lab that imports a shared visualisation package is no longer self-contained.
See Also
- BioModule API: implementing
visualize() - Artifact Outputs: durable file outputs for structures and generated files
- Labs Serve UI Reference: rendering visualizations in the local lab UI