Plotting Reference: Layout
Rendered from 15_all_plot_functions.ipynb.
Plotting families
| Public API | Purpose | Needs results? | Return value |
|---|---|---|---|
m.plot_layout(...) | Draw mapped nodes, links, subcatchments, rain gages, labels, and optional data-driven styling. | Only for result-driven styling. | (fig, ax) |
m.plot_timeseries.<category>.<variable>(ids=None, ...) | Plot one or many result series against time. | Yes. | (fig, ax) |
m.plot_profile.nodes(...) / .links(...) / .longest(...) | Plot a longitudinal hydraulic path with geometry and optional result overlays. | Only for HGL/water overlays. | (fig, ax) |
Shared save behavior
| Case | Behavior |
|---|---|
No save_path, no save_format | Do not save. |
Only save_format | Save beside the model file, or in the current working directory for unsaved models. |
Existing folder in save_path | Create a sensible filename inside that folder. |
| Path without extension | Append the requested format, or .png by default. |
| Path with extension | Infer format from extension unless save_format overrides it. |
| Supported formats | png, jpg, jpeg, pdf, svg, tiff. |
m.plot_layout()
Layout plots read map geometry from [COORDINATES], [VERTICES], [POLYGONS], and [SYMBOLS]. Elements with unusable coordinates are skipped with warnings; if no plottable geometry exists, the call raises PlotDataError. Subcatchment centroids are derived from polygon geometry, then connected to outlet nodes or downstream subcatchments with dashed lines. Node/link symbology is type-aware by default, and LID usage markers are drawn at subcatchment centroids when present. Titles, grids, and visible coordinate axes use safe ordinary artists instead of Matplotlib's native title/tick/grid machinery, avoiding the Spyder/Agg recursive tick-copy path. On non-interactive matplotlib canvases such as Agg, show=True avoids useless GUI calls but keeps the figure available for Spyder/Jupyter rendering; show=False removes only the library-created figure manager so hidden plots stay hidden.
| Input | Type | Default | Meaning |
|---|---|---|---|
legend | bool | True | Show ordinary layer legend entries. |
grid | bool | False | Show reference grid lines; they remain visible even when coordinate labels stay hidden. |
title | `str \ | None` | None |
legend_title | `str \ | None` | None |
axis | bool | False | Show coordinate axes and ticks; hidden by default for maps. |
x_axis_title | `str \ | None` | None |
y_axis_title | `str \ | None` | None |
save_format | `str \ | None` | None |
save_path | `str \ | Path \ | None` |
figsize | tuple[float, float] | (10, 8) | Figure size when ax is not supplied. |
dpi | int | 300 | Figure resolution for new figures and saved output. |
ax | matplotlib `Axes \ | None` | None |
show | bool | True | Display the completed figure; False suppresses automatic notebook display for function-created figures. |
nodes | `dict \ | bool \ | None` |
links | `dict \ | bool \ | None` |
subcatchments | `dict \ | bool \ | None` |
subcatchment_outlets | `dict \ | bool \ | None` |
rain_gages | `dict \ | bool \ | None` |
lids | `dict \ | bool \ | None` |
labels | `dict \ | bool \ | None` |
link_color_by | `str \ | None` | None |
link_color_mode | `str \ | None` | None |
link_cmap | `str \ | None` | None |
node_color_result | `str \ | None` | None |
node_result_aggregation | `str \ | None` | None |
link_user_data | `mapping \ | Series \ | None` |
annotation | `None \ | str \ | list[str] \ |
Layout layer dictionaries
| Layer | Supported keys | Important defaults |
|---|---|---|
nodes | visible, label, legend, alpha, zorder, size, color, edge_color, marker, type_markers, linewidth, ids | Type markers: junction o, outfall v, divider D, storage s |
links | visible, label, legend, alpha, zorder, width, size, color, linestyle, type_linestyles, ids | Type lines: conduit solid, pump dash-dot, orifice dotted, weir dashed, outlet long-dashed |
subcatchments | visible, label, legend, alpha, zorder, color, edge_color, linewidth, ids | color='lightgreen', edge_color='green', alpha=0.25 |
subcatchment_outlets | visible, label, legend, alpha, zorder, width, color, linestyle, ids | Dashed gray connector from centroid to outlet |
rain_gages | visible, label, legend, alpha, zorder, size, color, marker, ids | size=45, color='tab:blue', marker='^' |
lids | visible, label, legend, alpha, zorder, size, color, edge_color, linewidth, markers | One distinct marker per LID control ID when usages exist |
labels | visible, alpha, zorder, fontsize, color | visible=False, fontsize=8, color='black' |
Data-driven layout style dictionaries
The color, node size, and link width/size keys can be static values or richer dictionaries. Result-driven encodings require m.run() first.
| Key | Accepted values | Meaning |
|---|---|---|
by | static, parameter, result, or user | Select where styling values come from. |
value | static color/size/width | Used when by='static'. |
category, variable | public getter names | Required for parameter and result styling. |
data | mapping or pandas Series | Required for user styling. |
mode | continuous or discrete | Color mapping mode; sizes/widths currently scale numeric values continuously. |
cmap | matplotlib colormap name | Colormap for continuous or discrete color encoding. |
vmin, vmax | numeric bounds | Optional continuous color scaling bounds. |
aggregation | last, max, min, mean, median, or sum | Collapse result time series to one value per object. |
time_step, time | integer index or timestamp-like value | Select one result instant instead of aggregating. |
legend, legend_title | bool, str | Control the dedicated custom-style legend section. |
min_size, max_size | numeric bounds | Node-size scaling bounds. |
min_width, max_width | numeric bounds | Link-width scaling bounds. |
labels, bins | sequence metadata | Accepted style metadata for discrete presentation; labels are used for color categories. |
Layout examples
m.plot_layout()
m.plot_layout(
nodes={'size': 40, 'color': 'black'},
links={'width': 1.5, 'color': 'gray'},
)
m.plot_layout(
links={
'color': {
'by': 'parameter',
'category': 'conduit',
'variable': 'roughness',
'mode': 'continuous',
'cmap': 'viridis',
}
}
)
m.plot_layout(
nodes={'ids': selected_node_ids, 'size': {'by': 'user', 'data': node_size_data}},
links={'ids': selected_link_ids, 'width': {'by': 'user', 'data': link_width_data}},
subcatchments={'ids': selected_subcatchment_ids, 'color': {'by': 'parameter', 'variable': 'area', 'mode': 'continuous'}},
)
m.run()
m.plot_layout(
nodes={'size': {'by': 'result', 'variable': 'depth', 'aggregation': 'max'}},
links={'color': {'by': 'result', 'category': 'link', 'variable': 'flow', 'aggregation': 'max'}},
)
m.plot_layout(link_color_by='roughness', link_color_mode='continuous', link_cmap='viridis')
Seven fully annotated m.plot_layout() layer examples
These examples use IDs discovered from examples/example.inp, so they are safe to execute after the setup cell above. Result-driven examples require m.run() first; user-driven examples use dictionaries prepared in the setup cell.
1. nodes={...}
m.plot_layout(
nodes={
'visible': True, # True draws nodes; False hides the whole node layer.
'label': 'Nodes', # Generic layer label used in the ordinary object legend.
'legend': True, # Include node information in legends when True.
'alpha': 1.0, # Marker transparency from 0.0 (clear) to 1.0 (opaque).
'zorder': 3, # Drawing order; larger numbers appear above lower layers.
'size': {
'by': 'user', # Choose 'static', 'parameter', 'result', or 'user'.
# 'value': 40, # Static size, used only when by='static'.
# 'category': 'node', # Public getter category for parameter/result styling.
# 'variable': 'depth', # Public getter variable to encode.
'data': node_size_data, # ID -> value mapping, used only when by='user'.
'mode': 'continuous', # Reserved metadata for size encodings; numeric scaling is continuous.
# 'bins': [0, 1, 2], # Optional metadata for future/discrete presentation.
# 'labels': ['low', 'high'], # Optional presentation labels.
# 'aggregation': 'max', # Result reducer: last/max/min/mean/median/sum.
# 'time_step': -1, # Select one result row instead of aggregating.
# 'time': '2026-01-01 01:00', # Select one result timestamp instead of aggregating.
'min_size': 20, # Smallest rendered marker area.
'max_size': 200, # Largest rendered marker area.
'legend': True, # Add a dedicated custom-style legend section.
'legend_title': 'Node size score', # Custom legend section title.
},
'color': {
'by': 'user', # Here color is based on user-defined classes.
# 'value': 'black', # Static color, used only when by='static'.
# 'category': 'node', # Required for parameter/result styling.
# 'variable': 'depth', # Required for parameter/result styling.
'data': node_class_data, # ID -> value mapping, required when by='user'.
'mode': 'discrete', # 'continuous' for numeric ramps, 'discrete' for classes.
'cmap': 'tab10', # Any matplotlib colormap name.
# 'bins': [0, 1, 2], # Optional metadata for custom/discrete grouping.
'labels': ['Junction', 'Outfall'], # Display labels for discrete classes.
# 'vmin': 0.0, # Lower color bound for continuous color maps.
# 'vmax': 2.0, # Upper color bound for continuous color maps.
# 'aggregation': 'max', # Result reducer when by='result'.
# 'time_step': -1, # Single result row when by='result'.
# 'time': '2026-01-01 01:00', # Single result timestamp when by='result'.
'legend': True, # Add a dedicated color legend section.
'legend_title': 'Node class',
},
'edge_color': 'white', # Marker edge color.
'marker': 'o', # Fallback marker when no type-specific marker is supplied.
'type_markers': { # Marker by SWMM node type.
'junction': 'o',
'outfall': 'v',
'divider': 'D',
'storage_unit': 's',
},
'linewidth': 0.5, # Marker edge-line width.
'ids': selected_node_ids, # Optional node subset; omit to draw all nodes.
},
)
2. links={...}
m.plot_layout(
links={
'visible': True, # True draws links; False hides the whole link layer.
'label': 'Links', # Generic layer label for the ordinary object legend.
'legend': True, # Include link information in legends when True.
'alpha': 1.0, # Line transparency.
'zorder': 2, # Draw links above polygons but below nodes.
'width': {
'by': 'parameter', # Width can come from static/parameter/result/user data.
# 'value': 1.5, # Static width when by='static'.
'category': 'conduit', # Getter category for the width source.
'variable': 'roughness', # Getter variable for the width source.
# 'data': link_width_data, # ID -> value mapping when by='user'.
'mode': 'continuous', # Numeric width scaling is continuous.
# 'bins': [0.01, 0.02], # Optional metadata.
# 'labels': ['smooth', 'rough'], # Optional presentation labels.
# 'aggregation': 'max', # Result reducer when by='result'.
# 'time_step': -1, # Single result row when by='result'.
# 'time': '2026-01-01 01:00', # Single result timestamp when by='result'.
'min_width': 0.5, # Smallest rendered line width.
'max_width': 4.0, # Largest rendered line width.
'legend': True, # Add a dedicated width legend section.
'legend_title': 'Conduit roughness',
},
# 'size': {...}, # Alias for 'width'; use either 'width' or 'size'.
'color': {
'by': 'user', # Here color is based on your own link classes.
# 'value': 'gray', # Static color when by='static'.
# 'category': 'link', # Result getter category.
# 'variable': 'flow', # Result getter variable.
'data': link_class_data, # ID -> value mapping when by='user'.
'mode': 'discrete', # Discrete classes or continuous numeric values.
'cmap': 'tab10', # Matplotlib colormap.
# 'bins': [0, 1, 2], # Optional metadata.
'labels': ['Main', 'Secondary'], # Optional labels for discrete mode.
# 'vmin': 0.0, # Lower color-map limit for continuous mode.
# 'vmax': 10.0, # Optional upper color-map limit.
# 'aggregation': 'max', # Result reducer.
# 'time_step': -1, # Single result row instead of aggregation.
# 'time': '2026-01-01 01:00', # Single result timestamp instead of aggregation.
'legend': True, # Add a result-driven custom legend section.
'legend_title': 'Link class',
},
'linestyle': '-', # Fallback line style.
'type_linestyles': { # Line style by SWMM link type.
'conduit': '-',
'pump': '-.',
'orifice': ':',
'weir': '--',
'outlet': (0, (5, 1)),
},
'ids': selected_link_ids, # Optional link subset; omit to draw all links.
},
)
3. subcatchments={...}
m.plot_layout(
subcatchments={
'visible': True, # Draw polygon areas when True.
'label': 'Subcatchments', # Ordinary legend label.
'legend': True, # Include polygons in legends when True.
'alpha': 0.25, # Polygon fill transparency.
'zorder': 1, # Background layer below network geometry.
'color': {
'by': 'parameter', # Static/parameter/result/user are all supported.
# 'value': 'lightgreen', # Static fill color when by='static'.
'category': 'subcatchment', # Getter category for parameter/result styling.
'variable': 'area', # Getter variable to encode.
# 'data': subcatchment_class_data, # ID -> value mapping when by='user'.
'mode': 'continuous', # Continuous or discrete color mapping.
'cmap': 'YlGn', # Matplotlib colormap.
# 'bins': [0, 1, 2], # Optional metadata.
# 'labels': ['small', 'large'], # Optional labels for discrete mode.
# 'vmin': 0.0, # Optional lower color bound.
# 'vmax': 10.0, # Optional upper color bound.
# 'aggregation': 'max', # Result reducer when by='result'.
# 'time_step': -1, # Single result row when by='result'.
# 'time': '2026-01-01 01:00', # Single result timestamp when by='result'.
'legend': True, # Add a custom legend section for the fill encoding.
'legend_title': 'Subcatchment area',
},
'edge_color': 'green', # Polygon boundary color.
'linewidth': 1.0, # Polygon boundary width.
'ids': selected_subcatchment_ids, # Optional polygon subset.
},
)
4. subcatchment_outlets={...}
m.plot_layout(
subcatchment_outlets={
'visible': True, # Draw centroid -> outlet connectors.
'label': 'Subcatchment outlets', # Ordinary legend label.
'legend': True, # Include connector style in the legend.
'alpha': 0.8, # Connector transparency.
'zorder': 1.5, # Above polygons, below links.
'width': 1.0, # Connector line width.
'color': '0.45', # Any matplotlib color.
'linestyle': '--', # Dashed drainage cue.
'ids': selected_subcatchment_ids, # Optional subcatchment subset.
},
)
5. rain_gages={...}
m.plot_layout(
rain_gages={
'visible': True, # Draw rain-gage symbols when True.
'label': 'Rain gages', # Ordinary legend label.
'legend': True, # Include rain gages in the legend.
'alpha': 1.0, # Marker transparency.
'zorder': 4, # Above the hydraulic network.
'size': 45, # Marker area.
'color': 'tab:blue', # Marker fill color.
'marker': '^', # Matplotlib marker shape.
'ids': selected_rain_gage_ids, # Optional rain-gage subset.
},
)
6. lids={...}
m.plot_layout(
lids={
'visible': True, # Draw LID usage markers when usages exist.
'label': 'LID controls', # Generic layer label; each control also gets its own legend item.
'legend': True, # Include LID markers in the legend.
'alpha': 1.0, # Marker transparency.
'zorder': 4.5, # Above nodes/rain gages when desired.
'size': 55, # Marker area.
'color': 'tab:purple', # Marker fill color.
'edge_color': 'white', # Marker edge color.
'linewidth': 0.5, # Marker edge width.
'markers': ['P', 'X', '*', 'h', '8', 'd'], # Cycled across distinct LID control IDs.
},
)
7. labels={...}
m.plot_layout(
labels={
'visible': True, # Draw node-ID text labels when True.
'alpha': 1.0, # Text transparency.
'zorder': 5, # Text usually sits above all symbol layers.
'fontsize': 8, # Text size in points.
'color': 'black', # Text color.
},
)
fig, ax = m.plot_layout(
title='Network layout',
nodes={'size': 40, 'color': 'black'},
links={'width': 1.5, 'color': 'gray'},
show=False,
save_path=output_dir / 'notebook_layout_example.png',
)
plt.close(fig)Layout annotations
plot_layout() supports optional map annotations through the annotation= argument. Annotations can show object IDs, input parameters, derived values, result variables when available, or custom text. The feature supports simple field names, multiple fields, templates, prefixes, suffixes, formatting, styling, filtering, callable functions, external user data, and conservative link-aligned rotation.
A top-level string such as annotation="id" is intentionally documented as a node-label shorthand. For other layers, use explicit layer keys.
| Input form | Example | Meaning |
|---|---|---|
| String | annotation={"nodes": "id"} | Show one field. |
| List | annotation={"nodes": ["id", "invert_elevation"]} | Show multiple fields, one per line. |
| Template | {"template": "{id} Inv={invert_elevation:.2f} m"} | Fully custom text using Python format syntax. |
| Prefix/suffix | prefix, suffix, format | Add labels, units, and numeric precision. |
|---|---|---|
| Styling | fontsize, color, bbox, offset, rotation | Control appearance. |
| Filtering | ids, where, max_labels | Limit which objects are annotated. |
| User data | data, data_field, data_id | Merge external dictionaries, Series, or DataFrames into labels. |
Supported layer keys include nodes, junctions, outfalls, storage_units, flow_dividers, links, conduits, pumps, orifices, weirs, outlets, subcatchments, rain_gages, lid_usages, and labels. Singular aliases such as node, conduit, and rain_gage are accepted.
Missing annotation fields use on_annotation_error="raise" by default. Use "skip" or "warn" to omit labels instead of stopping the plot. External user data uses on_missing_data="empty" by default; "skip", "raise", and "warn" are also supported.
# 1. Simple node IDs.
fig, ax = m.plot_layout(
annotation={
"nodes": "id"
},
show=False,
)
plt.close(fig)# 2. Node and conduit labels.
fig, ax = m.plot_layout(
annotation={
"nodes": "id",
"conduits": "id",
},
show=False,
)
plt.close(fig)# 3. Multiple fields.
fig, ax = m.plot_layout(
annotation={
"nodes": ["id", "invert_elevation"],
"conduits": ["id", "length", "diameter"],
},
show=False,
)
plt.close(fig)# 4. Prefix, suffix, units, and formatting.
fig, ax = m.plot_layout(
annotation={
"nodes": {
"fields": ["id", "invert_elevation"],
"prefix": {"invert_elevation": "Inv: "},
"suffix": {"invert_elevation": " m"},
"format": {"invert_elevation": ".2f"},
},
"conduits": {
"fields": ["id", "diameter", "length"],
"prefix": {"diameter": "D: ", "length": "L: "},
"suffix": {"diameter": " m", "length": " m"},
"format": {"diameter": ".2f", "length": ".1f"},
},
},
show=False,
)
plt.close(fig)# 5. Template strings.
fig, ax = m.plot_layout(
annotation={
"nodes": {
"template": "{id}
Inv={invert_elevation:.2f} m",
},
"conduits": {
"template": "{id}
D={diameter:.2f} m
L={length:.1f} m",
},
"subcatchments": {
"template": "{id}
A={area:.2f}",
},
},
show=False,
)
plt.close(fig)# 6. Styled annotations.
fig, ax = m.plot_layout(
annotation={
"nodes": {
"template": "{id}",
"fontsize": 8,
"color": "black",
"offset": (4, 4),
"bbox": True,
"bbox_alpha": 0.7,
},
"conduits": {
"template": "D={diameter:.2f} m",
"fontsize": 7,
"color": "darkblue",
"offset": (0, 6),
"rotation": "link",
},
},
show=False,
)
plt.close(fig)# 7. Filtering by IDs.
fig, ax = m.plot_layout(
annotation={
"nodes": {
"template": "{id}",
"ids": selected_node_ids,
}
},
show=False,
)
plt.close(fig)# 8. Maximum label count.
fig, ax = m.plot_layout(
annotation={
"nodes": {
"template": "{id}",
"max_labels": 10,
}
},
show=False,
)
plt.close(fig)# 9. Conditional filtering.
fig, ax = m.plot_layout(
annotation={
"conduits": {
"template": "{id}
D={diameter:.2f} m",
"where": {
"diameter": [">", 1.0],
},
}
},
show=False,
)
plt.close(fig)# 10. Callable annotation.
def node_label(row):
return f"{row['id']}
Inv={row['invert_elevation']:.2f} m"
fig, ax = m.plot_layout(
annotation={
"nodes": node_label,
},
show=False,
)
plt.close(fig)# 11. Result-based annotation, if the model has been run.
m.run()
fig, ax = m.plot_layout(
annotation={
"conduits": {
"template": "{id}
Q={flow:.3f}",
},
"nodes": {
"template": "{id}
Depth={depth:.2f}",
},
},
show=False,
)
plt.close(fig)# 12. External user data from dictionaries, Series, and DataFrames.
pipe_data = {
selected_link_ids[0]: {"risk": "High", "age": 42},
}
pipe_risk = pd.Series({selected_link_ids[0]: "High"}, name="risk")
pipe_frame = pd.DataFrame({"id": [selected_link_ids[0]], "risk": ["High"], "age": [42]})
fig, ax = m.plot_layout(
annotation={
"conduits": {
"template": "{id}
Risk={risk}
Age={age} yr",
"data": pipe_data,
"on_missing_data": "skip",
}
},
show=False,
)
plt.close(fig)
fig, ax = m.plot_layout(
annotation={
"conduits": {
"template": "{id}
Risk={risk}",
"data": pipe_risk,
"on_missing_data": "skip",
}
},
show=False,
)
plt.close(fig)
fig, ax = m.plot_layout(
annotation={
"conduits": {
"template": "{id}
Risk={risk}
Age={age} yr",
"data": pipe_frame,
"data_id": "id",
"on_missing_data": "skip",
}
},
show=False,
)
plt.close(fig)