swmmx Documentation

Plotting Reference: Layout

Rendered from 15_all_plot_functions.ipynb.

Plotting families

Public APIPurposeNeeds 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

CaseBehavior
No save_path, no save_formatDo not save.
Only save_formatSave beside the model file, or in the current working directory for unsaved models.
Existing folder in save_pathCreate a sensible filename inside that folder.
Path without extensionAppend the requested format, or .png by default.
Path with extensionInfer format from extension unless save_format overrides it.
Supported formatspng, 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.

InputTypeDefaultMeaning
legendboolTrueShow ordinary layer legend entries.
gridboolFalseShow reference grid lines; they remain visible even when coordinate labels stay hidden.
title`str \None`None
legend_title`str \None`None
axisboolFalseShow 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`
figsizetuple[float, float](10, 8)Figure size when ax is not supplied.
dpiint300Figure resolution for new figures and saved output.
axmatplotlib `Axes \None`None
showboolTrueDisplay 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

LayerSupported keysImportant defaults
nodesvisible, label, legend, alpha, zorder, size, color, edge_color, marker, type_markers, linewidth, idsType markers: junction o, outfall v, divider D, storage s
linksvisible, label, legend, alpha, zorder, width, size, color, linestyle, type_linestyles, idsType lines: conduit solid, pump dash-dot, orifice dotted, weir dashed, outlet long-dashed
subcatchmentsvisible, label, legend, alpha, zorder, color, edge_color, linewidth, idscolor='lightgreen', edge_color='green', alpha=0.25
subcatchment_outletsvisible, label, legend, alpha, zorder, width, color, linestyle, idsDashed gray connector from centroid to outlet
rain_gagesvisible, label, legend, alpha, zorder, size, color, marker, idssize=45, color='tab:blue', marker='^'
lidsvisible, label, legend, alpha, zorder, size, color, edge_color, linewidth, markersOne distinct marker per LID control ID when usages exist
labelsvisible, alpha, zorder, fontsize, colorvisible=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.

KeyAccepted valuesMeaning
bystatic, parameter, result, or userSelect where styling values come from.
valuestatic color/size/widthUsed when by='static'.
category, variablepublic getter namesRequired for parameter and result styling.
datamapping or pandas SeriesRequired for user styling.
modecontinuous or discreteColor mapping mode; sizes/widths currently scale numeric values continuously.
cmapmatplotlib colormap nameColormap for continuous or discrete color encoding.
vmin, vmaxnumeric boundsOptional continuous color scaling bounds.
aggregationlast, max, min, mean, median, or sumCollapse result time series to one value per object.
time_step, timeinteger index or timestamp-like valueSelect one result instant instead of aggregating.
legend, legend_titlebool, strControl the dedicated custom-style legend section.
min_size, max_sizenumeric boundsNode-size scaling bounds.
min_width, max_widthnumeric boundsLink-width scaling bounds.
labels, binssequence metadataAccepted 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.
    },
)
Code cell 1
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 formExampleMeaning
Stringannotation={"nodes": "id"}Show one field.
Listannotation={"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/suffixprefix, suffix, formatAdd labels, units, and numeric precision.
Stylingfontsize, color, bbox, offset, rotationControl appearance.
Filteringids, where, max_labelsLimit which objects are annotated.
User datadata, data_field, data_idMerge 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.

Code cell 2
# 1. Simple node IDs.
fig, ax = m.plot_layout(
    annotation={
        "nodes": "id"
    },
    show=False,
)
plt.close(fig)
Code cell 3
# 2. Node and conduit labels.
fig, ax = m.plot_layout(
    annotation={
        "nodes": "id",
        "conduits": "id",
    },
    show=False,
)
plt.close(fig)
Code cell 4
# 3. Multiple fields.
fig, ax = m.plot_layout(
    annotation={
        "nodes": ["id", "invert_elevation"],
        "conduits": ["id", "length", "diameter"],
    },
    show=False,
)
plt.close(fig)
Code cell 5
# 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)
Code cell 6
# 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)
Code cell 7
# 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)
Code cell 8
# 7. Filtering by IDs.
fig, ax = m.plot_layout(
    annotation={
        "nodes": {
            "template": "{id}",
            "ids": selected_node_ids,
        }
    },
    show=False,
)
plt.close(fig)
Code cell 9
# 8. Maximum label count.
fig, ax = m.plot_layout(
    annotation={
        "nodes": {
            "template": "{id}",
            "max_labels": 10,
        }
    },
    show=False,
)
plt.close(fig)
Code cell 10
# 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)
Code cell 11
# 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)
Code cell 12
# 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)
Code cell 13
# 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)