exergyflow 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Juan Andrés Gómez González
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include docs/STEP_BY_STEP.md
3
+ include docs/CHEATSHEET.md
4
+ include docs/diagram.svg
5
+ include LICENSE
6
+ include pyproject.toml
7
+ include requirements.txt
8
+ recursive-exclude __pycache__ *
9
+ recursive-exclude tests __pycache__ *
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: exergyflow
3
+ Version: 0.1.0
4
+ Summary: Sankey and Grassmann diagrams for energy and exergy analysis
5
+ Author-email: Juan Andrés Gómez González <ja.gomez@uniandes.edu.co>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Juan Andrés Gómez González
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/GOMGON777/exergyflow
29
+ Project-URL: Issues, https://github.com/GOMGON777/exergyflow/issues
30
+ Keywords: exergy,energy,grassmann,grassmann diagram,grassmann diagrams,sankey,sankey diagram,sankey diagrams,thermodynamics,thermodynamic analysis,visualization,flow visualization,data visualization,engineering,process engineering,thermal systems,exergy analysis,energy analysis,flow diagram,scientific visualization,python sankey,python exergy,python thermodynamics,brayton cycle,rankine cycle
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Science/Research
33
+ Classifier: Intended Audience :: Education
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3 :: Only
37
+ Classifier: Programming Language :: Python :: 3.9
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: 3.14
43
+ Classifier: Topic :: Scientific/Engineering
44
+ Classifier: Topic :: Scientific/Engineering :: Visualization
45
+ Classifier: Topic :: Scientific/Engineering :: Physics
46
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
47
+ Requires-Python: >=3.9
48
+ Description-Content-Type: text/markdown
49
+ License-File: LICENSE
50
+ Requires-Dist: matplotlib>=3.7
51
+ Requires-Dist: numpy>=1.23
52
+ Dynamic: license-file
53
+
54
+ # ExergyFlow
55
+
56
+ Grassmann/Sankey diagrams for energy and exergy analysis
57
+
58
+ ![ExergyFlow diagram](docs/diagram.svg)
59
+
60
+ ## Install
61
+ ```bash
62
+ python -m pip install exergyflow
63
+ ```
64
+
65
+ ## Quickstart
66
+ ```python
67
+ from exergyflow import Diagram, DiagramConfig, RouteSegment
68
+ d = Diagram(DiagramConfig(auto_scale=True, auto_scale_target=1.0))
69
+ d.add_process("P1")
70
+ d.add_flow("F1", 10, source=None, target="P1")
71
+ d.add_flow("F2", 5, source="P1", target=None)
72
+ route1 = [RouteSegment(kind='rect', length=0.25, direction='right'),
73
+ RouteSegment(kind='elbow', length=0.25, turn='rightup'),
74
+ RouteSegment(kind='rect', length=0.5, direction='up')]
75
+ d.add_flow("F3", 2, source="P1", target=None, route=route1)
76
+ fig, ax = d.draw()
77
+ fig.savefig("diagram.png", dpi=300, bbox_inches="tight")
78
+ ```
79
+
80
+ ## Real engineering example
81
+ A simplified Brayton flow sketch (values illustrative):
82
+ ```python
83
+ from exergyflow import Diagram, DiagramConfig, RouteSegment
84
+
85
+ cfg = DiagramConfig(flow_value_unit="kW", flow_value_format=".0f",
86
+ auto_scale=True, auto_scale_target=2.0)
87
+ d = Diagram(cfg)
88
+
89
+ # Processes
90
+ d.add_process("Compressor", direction="up", length=2.5,
91
+ label_rotation=90, color="#59a2d3")
92
+ d.add_process("Combustor", direction="right", length=2.5, color="#59a2d3")
93
+ d.add_process("Turbine", direction="down", label_rotation=-90, color="#59a2d3")
94
+
95
+ # Routes
96
+ route_work_out = [
97
+ RouteSegment(kind='rect', length=0.25, direction='down'),
98
+ RouteSegment(kind='elbow', length=0.5, turn='downright'),
99
+ RouteSegment(kind='rect', length=2.5, direction='right')
100
+ ]
101
+ route_exhaust = [
102
+ RouteSegment(kind='rect', length=1.5, direction='down'),
103
+ RouteSegment(kind='elbow', length=0.5, turn='downright'),
104
+ RouteSegment(kind='rect', length=2.6, direction='right')
105
+ ]
106
+ route_air = [
107
+ RouteSegment(kind='rect', length=3.5, direction='right'),
108
+ RouteSegment(kind='elbow', length=0.5, turn='rightup'),
109
+ RouteSegment(kind='rect', length=0.5, direction='up')
110
+ ]
111
+
112
+ # Flows
113
+ d.add_flow("Work_out", 220, source="Turbine",
114
+ target=None, label="W_t", route=route_work_out)
115
+ d.add_flow("Exhaust", 180, source="Turbine", target=None,
116
+ label="E_exh", route=route_exhaust)
117
+ d.add_flow("Work_in", 80, source="Turbine",
118
+ target="Compressor", label="W_c", cycle_breaker=True, label_dy=-0.3)
119
+ d.add_flow("Air", 280, source=None,
120
+ target="Compressor", label="E_air", route=route_air)
121
+ d.add_flow("Compressed", 300, source="Compressor",
122
+ target="Combustor", label="E_2", label_dy=0.2)
123
+ d.add_flow("Fuel", 700, source=None,
124
+ target="Combustor", length=7.75, label="E_f")
125
+ d.add_flow("Hot_gas", 560, source="Combustor",
126
+ target="Turbine", label="E_3", label_dy=0.2)
127
+ fig, ax = d.draw()
128
+ fig.savefig("brayton.svg", dpi=300, bbox_inches="tight")
129
+ ```
130
+
131
+ ## Advantages
132
+ - Built for thermodynamic energy and exergy conventions, including imbalance triangles and process-aware flow direction rules.
133
+ - Auto-layout for fast drafts, manual routing for publication-grade control.
134
+ - Matplotlib-native output for crisp vector export (SVG/PDF) without extra tooling.
135
+ - Deterministic layout and explicit routing rules help reproduce figures consistently.
136
+ - Built-in unit formatting for labels keeps diagrams engineering-ready.
137
+
138
+ ## Details
139
+ - Supported Python versions: 3.9–3.14 (see `pyproject.toml`).
140
+ - `flow_label_mode` options: `name_value_units` (default), `value_units`, `value_only`.
141
+ - Units formatting: `flow_value_unit`, `flow_value_format`, `flow_value_unit_sep`.
142
+ - Auto-layout expects a DAG; use `cycle_breaker=True` on a process→process flow to break cycles.
143
+ - Manual routing must alternate `rect` and `elbow` segments and start/end with `rect`.
144
+ - Matplotlib-native output: export PNG, SVG, or PDF via `fig.savefig(...)`.
145
+
146
+ ## Docs
147
+ - [Step-by-step guide](docs/STEP_BY_STEP.md)
148
+ - [Cheatsheet](docs/CHEATSHEET.md)
149
+
150
+ ## Adavanced example
151
+ For an advanced example see: [Example script](examples/grassmann_example.py)
@@ -0,0 +1,98 @@
1
+ # ExergyFlow
2
+
3
+ Grassmann/Sankey diagrams for energy and exergy analysis
4
+
5
+ ![ExergyFlow diagram](docs/diagram.svg)
6
+
7
+ ## Install
8
+ ```bash
9
+ python -m pip install exergyflow
10
+ ```
11
+
12
+ ## Quickstart
13
+ ```python
14
+ from exergyflow import Diagram, DiagramConfig, RouteSegment
15
+ d = Diagram(DiagramConfig(auto_scale=True, auto_scale_target=1.0))
16
+ d.add_process("P1")
17
+ d.add_flow("F1", 10, source=None, target="P1")
18
+ d.add_flow("F2", 5, source="P1", target=None)
19
+ route1 = [RouteSegment(kind='rect', length=0.25, direction='right'),
20
+ RouteSegment(kind='elbow', length=0.25, turn='rightup'),
21
+ RouteSegment(kind='rect', length=0.5, direction='up')]
22
+ d.add_flow("F3", 2, source="P1", target=None, route=route1)
23
+ fig, ax = d.draw()
24
+ fig.savefig("diagram.png", dpi=300, bbox_inches="tight")
25
+ ```
26
+
27
+ ## Real engineering example
28
+ A simplified Brayton flow sketch (values illustrative):
29
+ ```python
30
+ from exergyflow import Diagram, DiagramConfig, RouteSegment
31
+
32
+ cfg = DiagramConfig(flow_value_unit="kW", flow_value_format=".0f",
33
+ auto_scale=True, auto_scale_target=2.0)
34
+ d = Diagram(cfg)
35
+
36
+ # Processes
37
+ d.add_process("Compressor", direction="up", length=2.5,
38
+ label_rotation=90, color="#59a2d3")
39
+ d.add_process("Combustor", direction="right", length=2.5, color="#59a2d3")
40
+ d.add_process("Turbine", direction="down", label_rotation=-90, color="#59a2d3")
41
+
42
+ # Routes
43
+ route_work_out = [
44
+ RouteSegment(kind='rect', length=0.25, direction='down'),
45
+ RouteSegment(kind='elbow', length=0.5, turn='downright'),
46
+ RouteSegment(kind='rect', length=2.5, direction='right')
47
+ ]
48
+ route_exhaust = [
49
+ RouteSegment(kind='rect', length=1.5, direction='down'),
50
+ RouteSegment(kind='elbow', length=0.5, turn='downright'),
51
+ RouteSegment(kind='rect', length=2.6, direction='right')
52
+ ]
53
+ route_air = [
54
+ RouteSegment(kind='rect', length=3.5, direction='right'),
55
+ RouteSegment(kind='elbow', length=0.5, turn='rightup'),
56
+ RouteSegment(kind='rect', length=0.5, direction='up')
57
+ ]
58
+
59
+ # Flows
60
+ d.add_flow("Work_out", 220, source="Turbine",
61
+ target=None, label="W_t", route=route_work_out)
62
+ d.add_flow("Exhaust", 180, source="Turbine", target=None,
63
+ label="E_exh", route=route_exhaust)
64
+ d.add_flow("Work_in", 80, source="Turbine",
65
+ target="Compressor", label="W_c", cycle_breaker=True, label_dy=-0.3)
66
+ d.add_flow("Air", 280, source=None,
67
+ target="Compressor", label="E_air", route=route_air)
68
+ d.add_flow("Compressed", 300, source="Compressor",
69
+ target="Combustor", label="E_2", label_dy=0.2)
70
+ d.add_flow("Fuel", 700, source=None,
71
+ target="Combustor", length=7.75, label="E_f")
72
+ d.add_flow("Hot_gas", 560, source="Combustor",
73
+ target="Turbine", label="E_3", label_dy=0.2)
74
+ fig, ax = d.draw()
75
+ fig.savefig("brayton.svg", dpi=300, bbox_inches="tight")
76
+ ```
77
+
78
+ ## Advantages
79
+ - Built for thermodynamic energy and exergy conventions, including imbalance triangles and process-aware flow direction rules.
80
+ - Auto-layout for fast drafts, manual routing for publication-grade control.
81
+ - Matplotlib-native output for crisp vector export (SVG/PDF) without extra tooling.
82
+ - Deterministic layout and explicit routing rules help reproduce figures consistently.
83
+ - Built-in unit formatting for labels keeps diagrams engineering-ready.
84
+
85
+ ## Details
86
+ - Supported Python versions: 3.9–3.14 (see `pyproject.toml`).
87
+ - `flow_label_mode` options: `name_value_units` (default), `value_units`, `value_only`.
88
+ - Units formatting: `flow_value_unit`, `flow_value_format`, `flow_value_unit_sep`.
89
+ - Auto-layout expects a DAG; use `cycle_breaker=True` on a process→process flow to break cycles.
90
+ - Manual routing must alternate `rect` and `elbow` segments and start/end with `rect`.
91
+ - Matplotlib-native output: export PNG, SVG, or PDF via `fig.savefig(...)`.
92
+
93
+ ## Docs
94
+ - [Step-by-step guide](docs/STEP_BY_STEP.md)
95
+ - [Cheatsheet](docs/CHEATSHEET.md)
96
+
97
+ ## Adavanced example
98
+ For an advanced example see: [Example script](examples/grassmann_example.py)
@@ -0,0 +1,103 @@
1
+ # ExergyFlow Cheatsheet
2
+
3
+ Quick reference for `DiagramConfig`, `Diagram.add_process(...)`, and `Diagram.add_flow(...)`.
4
+
5
+ ## Core Concepts
6
+ - **Diagram**: container for processes and flows; supports auto-layout and auto-routing.
7
+ - **Process**: a rectangular block that can show imbalance via a right triangle.
8
+ - **Flow**: a magnitude drawn as a band (rectangles + elbows); width scales with value.
9
+
10
+ ## Key Rules
11
+ - Flow routes must start/end with a `rect` and alternate `rect`/`elbow`.
12
+ - Elbows are quarter-annulus turns; thickness equals the flow width.
13
+ - Elbow turns use labels like `rightup`, `rightdown`, `leftup`, `leftdown`, `upright`, `downright`, `upleft`, `downleft` which correspond to the change in direction (e.g. rightup is an elbow that changes the flow direction from right to up).
14
+ - `label_rotation` is in degrees; for flows, vertical segments auto-rotate to 90 if `None`.
15
+ - Process directions: `right`, `left`, `up`, `down`.
16
+ - Inlet flows must end with a direction that matches the process direction.
17
+ - Outlet flows must start with a direction that matches the process direction.
18
+
19
+ ### RouteSegment (for manual `route`)
20
+
21
+ | Field | Type | Notes |
22
+ | --- | --- | --- |
23
+ | `kind` | `str` | `rect` or `elbow`. |
24
+ | `length` | `float` | Rect: section length. Elbow: inner radius. |
25
+ | `direction` | `str \| None` | Required for `rect`: `right`, `left`, `up`, `down`. |
26
+ | `turn` | `str \| None` | Required for `elbow`: `rightup`, `rightdown`, `leftup`, `leftdown`, `upright`, `downright`, `upleft`, `downleft` |
27
+
28
+ Manual routes must alternate `rect`/`elbow` segments and start/end with `rect`.
29
+
30
+ ## DiagramConfig
31
+ Create with `DiagramConfig(...)` and pass into `Diagram(config=...)`.
32
+
33
+ | Field | Type | Default | Notes |
34
+ | --- | --- | --- | --- |
35
+ | `scale` | `float` | `1.0` | Multiplies all flow magnitudes for drawing. |
36
+ | `auto_scale` | `bool` | `False` | Auto-scales flows so the largest value maps to `auto_scale_target`. |
37
+ | `auto_scale_target` | `float` | `1.0` | Target width for the largest flow when `auto_scale=True`. |
38
+ | `flow_label_mode` | `str` | `"name_value_units"` | Options: `name_value_units`, `value_units`, `value_only`. |
39
+ | `flow_value_format` | `str \| None` | `None` | Python format spec, e.g. `".2f"` or `".3g"`. |
40
+ | `flow_value_unit` | `str \| None` | `None` | Unit label appended when enabled by `flow_label_mode`. |
41
+ | `flow_value_unit_sep` | `str` | `" "` | Separator between value and unit. |
42
+ | `render_min_flow_width` | `float` | `0.0` | Minimum visible thickness for drawing only. |
43
+ | `flow_label_style` | `dict` | `{"fontsize": 8, "color": "black"}` | Matplotlib text kwargs for flow labels. |
44
+ | `process_label_style` | `dict` | `{"fontsize": 9, "color": "black"}` | Matplotlib text kwargs for process labels. |
45
+ | `triangle_label_style` | `dict` | `{"fontsize": 8, "color": "black"}` | Matplotlib text kwargs for imbalance labels. |
46
+ | `flow_gap` | `float` | `0.0` | Gap between stacked flows on a process edge. |
47
+ | `layer_gap` | `float` | `2.0` | Gap between layout layers (auto-layout). |
48
+ | `process_gap` | `float` | `1.0` | Gap between processes within a layer (auto-layout). |
49
+ | `elbow_inner_radius` | `float` | `0.5` | Inner radius for elbow turns (auto-routing). |
50
+ | `layout_direction` | `str` | `"right"` | Global layout direction: `right`, `left`, `up`, `down`. |
51
+ | `min_straight` | `float` | `0.2` | Minimum straight length between elbows (auto-routing). |
52
+
53
+ ## Diagram.add_process(...)
54
+
55
+ | Parameter | Type | Default | Notes |
56
+ | --- | --- | --- | --- |
57
+ | `name` | `str` | required | Unique process name. |
58
+ | `direction` | `str \| None` | `DiagramConfig.layout_direction` | `right`, `left`, `up`, `down`. |
59
+ | `length` | `float \| None` | `1.8` | Process length along its direction. |
60
+ | `color` | `str \| None` | `"#cfd8dc"` | Fill color. |
61
+ | `triangle_side` | `str \| None` | `None` | If direction is `right/left`: `top` or `bottom`. If `up/down`: `left` or `right`. |
62
+ | `overlay` | `bool \| None` | `False` | Draws an outline-only overlay rectangle. |
63
+ | `overlay_height` | `float \| None` | `None` | Overlay size perpendicular to process direction. |
64
+ | `overlay_edgecolor` | `str \| None` | `None` | Overlay border color. |
65
+ | `overlay_linewidth` | `float \| None` | `None` | Overlay border width. |
66
+ | `overlay_linestyle` | `str \| None` | `None` | Overlay border style. |
67
+ | `overlay_alpha` | `float \| None` | `None` | Overlay border opacity. |
68
+ | `label_dx` | `float` | `0.0` | X offset for the process label. |
69
+ | `label_dy` | `float` | `0.0` | Y offset for the process label. |
70
+ | `label_rotation` | `float \| None` | `None` | Rotation in degrees for the process label. |
71
+ | `triangle_label` | `str \| None` | `None` | Optional label prefix for imbalance triangle. |
72
+ | `triangle_label_dx` | `float` | `0.0` | X offset for triangle label. |
73
+ | `triangle_label_dy` | `float` | `0.0` | Y offset for triangle label. |
74
+ | `x` | `float \| None` | `None` | Fixed X position (skips auto-layout if set). |
75
+ | `y` | `float \| None` | `None` | Fixed Y position (skips auto-layout if set). |
76
+
77
+ ## Diagram.add_flow(...)
78
+
79
+ | Parameter | Type | Default | Notes |
80
+ | --- | --- | --- | --- |
81
+ | `name` | `str` | required | Unique flow name. |
82
+ | `value` | `float` | required | Flow magnitude (must be non‑negative). |
83
+ | `source` | `str \| None` | `None` | Source process name or `None`/`"source"`. |
84
+ | `target` | `str \| None` | `None` | Target process name or `None`/`"sink"`. |
85
+ | `color` | `str \| None` | `"#7fb3d5"` | Flow fill color. |
86
+ | `length` | `float \| None` | `2.2` | Default straight length (used when no manual route). |
87
+ | `inlet_tri_height` | `float \| None` | `0.3` | Multiplier of flow width for inlet triangle depth. |
88
+ | `outlet_tri_height` | `float \| None` | `0.6` | Multiplier of flow width for outlet triangle depth. |
89
+ | `label_dx` | `float` | `0.0` | X offset for flow label. |
90
+ | `label_dy` | `float` | `0.0` | Y offset for flow label. |
91
+ | `label_rotation` | `float \| None` | `None` | Rotation in degrees for flow label. |
92
+ | `cycle_breaker` | `bool` | `False` | Marks a process→process flow to break layout cycles. |
93
+ | `route` | `list[RouteSegment] \| None` | `None` | Manual route; skips auto-routing. |
94
+ | `label` | `str \| None` | `None` | Custom label name used in `name = value` mode. |
95
+
96
+ ## Where Things Live
97
+ - `exergyflow/grassmann_types.py`: dataclasses and config
98
+ - `exergyflow/grassmann_geometry.py`: geometry helpers
99
+ - `exergyflow/grassmann_layout.py`: auto layout + routing
100
+ - `exergyflow/grassmann_render.py`: drawing engine
101
+ - `exergyflow/grassmann_diagram.py`: public API
102
+ - `examples/grassmann_example.py`: example usage
103
+ - `docs/diagram.svg`: example output (generated by the example script)
@@ -0,0 +1,61 @@
1
+ # Step-by-Step: Build a Grassmann/Sankey Diagram
2
+
3
+ This guide walks you through creating a diagram from scratch using the public API.
4
+
5
+ **Step 1: Install dependencies**
6
+ ```bash
7
+ python -m pip install exergyflow
8
+ ```
9
+
10
+ **Step 2: Import the API**
11
+ ```python
12
+ from exergyflow import Diagram, DiagramConfig, RouteSegment
13
+ ```
14
+
15
+ **Step 3: Define a configuration (optional but recommended)**
16
+ ```python
17
+ cfg = DiagramConfig(
18
+ auto_scale=True,
19
+ auto_scale_target=1.0,
20
+ flow_value_unit="kW",
21
+ flow_value_format=".2f",
22
+ flow_label_style={"fontsize": 8, "color": "#1d3557"},
23
+ process_label_style={"fontsize": 9, "fontweight": "bold"},
24
+ triangle_label_style={"fontsize": 8, "color": "#444444"},
25
+ )
26
+ ```
27
+
28
+ **Step 4: Create the diagram and add processes**
29
+ ```python
30
+ d = Diagram(config=cfg)
31
+
32
+ d.add_process("P1", direction="right", length=1.6)
33
+ d.add_process("P2", direction="right", length=1.2)
34
+ ```
35
+
36
+ **Step 5: Add flows**
37
+ Use `source=None` for a source and `target=None` for a sink.
38
+ ```python
39
+ d.add_flow("F1", 10, source=None, target="P1")
40
+ d.add_flow("F2", 8, source="P1", target="P2", label="Q_in")
41
+ d.add_flow("F3", 2, source="P1", target=None)
42
+ d.add_flow("F4", 6, source="P2", target=None)
43
+ ```
44
+
45
+ **Step 6: (Optional) Add a manual route**
46
+ Manual routes must alternate `rect` and `elbow`, and start/end with `rect`.
47
+ ```python
48
+ route = [
49
+ RouteSegment(kind="rect", length=1.5, direction="right"),
50
+ RouteSegment(kind="elbow", length=0.4, turn="rightup"),
51
+ RouteSegment(kind="rect", length=1.0, direction="up"),
52
+ ]
53
+
54
+ d.add_flow("F5", 3, source="P1", target="P2", route=route)
55
+ ```
56
+
57
+ **Step 7: Draw and save**
58
+ ```python
59
+ fig, ax = d.draw()
60
+ fig.savefig("diagram.png", dpi=300, bbox_inches="tight")
61
+ ```