matplotlib-sankey 0.3.1__tar.gz → 0.3.2__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.
@@ -1,49 +1,46 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matplotlib-sankey
3
- Version: 0.3.1
4
- Summary: Sankey plot for matplotlib
3
+ Version: 0.3.2
4
+ Summary: Create Sankey diagrams with Matplotlib.
5
5
  Author: harryhaller001
6
- Maintainer-email: harryhaller001 <harryhaller001@gmail.com>
7
- Requires-Python: >=3.10
8
- Description-Content-Type: text/markdown
6
+ Author-email: harryhaller001 <harryhaller001@gmail.com>
7
+ License-Expression: MIT
9
8
  Classifier: Development Status :: 3 - Alpha
10
- Classifier: Framework :: Matplotlib
11
9
  Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: OSI Approved :: MIT License
13
10
  Classifier: Natural Language :: English
14
11
  Classifier: Operating System :: OS Independent
15
12
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
13
  Classifier: Programming Language :: Python :: 3.11
18
14
  Classifier: Programming Language :: Python :: 3.12
19
15
  Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
20
17
  Classifier: Typing :: Typed
21
- License-File: LICENSE
22
18
  Requires-Dist: matplotlib
23
- Requires-Dist: numpy
24
- Requires-Dist: ipykernel ; extra == "docs"
25
- Requires-Dist: ipython ; extra == "docs"
26
- Requires-Dist: ipywidgets ; extra == "docs"
27
- Requires-Dist: myst-parser ; extra == "docs"
28
- Requires-Dist: nbsphinx ; extra == "docs"
29
- Requires-Dist: networkx[default] ; extra == "docs"
30
- Requires-Dist: sphinx>=4 ; extra == "docs"
31
- Requires-Dist: sphinx-autoapi ; extra == "docs"
32
- Requires-Dist: sphinx-autodoc-typehints ; extra == "docs"
33
- Requires-Dist: sphinx-book-theme>=1 ; extra == "docs"
34
- Requires-Dist: coverage ; extra == "test"
35
- Requires-Dist: flit ; extra == "test"
36
- Requires-Dist: mypy ; extra == "test"
37
- Requires-Dist: pre-commit ; extra == "test"
38
- Requires-Dist: pytest ; extra == "test"
39
- Requires-Dist: ruff ; extra == "test"
40
- Requires-Dist: setuptools ; extra == "test"
41
- Requires-Dist: twine>=4.0.2 ; extra == "test"
42
- Project-URL: Documentation, https://harryhaller001.github.io/matplotlib-sankey/
43
- Project-URL: Homepage, https://github.com/harryhaller001/matplotlib-sankey
19
+ Requires-Dist: ipython ; extra == 'docs'
20
+ Requires-Dist: myst-parser ; extra == 'docs'
21
+ Requires-Dist: nbsphinx ; extra == 'docs'
22
+ Requires-Dist: sphinx ; extra == 'docs'
23
+ Requires-Dist: sphinx-autoapi ; extra == 'docs'
24
+ Requires-Dist: sphinx-autodoc-typehints ; extra == 'docs'
25
+ Requires-Dist: sphinx-book-theme ; extra == 'docs'
26
+ Requires-Dist: coverage ; extra == 'test'
27
+ Requires-Dist: ipykernel ; extra == 'test'
28
+ Requires-Dist: ipython ; extra == 'test'
29
+ Requires-Dist: ipywidgets ; extra == 'test'
30
+ Requires-Dist: pre-commit ; extra == 'test'
31
+ Requires-Dist: pytest ; extra == 'test'
32
+ Requires-Dist: responses ; extra == 'test'
33
+ Requires-Dist: ruff ; extra == 'test'
34
+ Requires-Dist: twine ; extra == 'test'
35
+ Requires-Dist: ty>=0.0.16 ; extra == 'test'
36
+ Requires-Dist: types-requests ; extra == 'test'
37
+ Maintainer: harryhaller001
38
+ Maintainer-email: harryhaller001 <harryhaller001@gmail.com>
39
+ Requires-Python: >=3.11
44
40
  Project-URL: Source, https://github.com/harryhaller001/matplotlib-sankey
45
41
  Provides-Extra: docs
46
42
  Provides-Extra: test
43
+ Description-Content-Type: text/markdown
47
44
 
48
45
  # matplotlib-sankey
49
46
 
@@ -59,16 +56,20 @@ Sankey plot for matplotlib
59
56
 
60
57
  ### Installation
61
58
 
62
- Install with pip:
59
+ Install with `pip`:
63
60
 
64
61
  `pip install matplotlib-sankey`
65
62
 
63
+ Install with `uv`:
64
+
65
+ `uv add matplotlib-sankey`
66
+
66
67
  Install from source:
67
68
 
68
69
  ```bash
69
70
  git clone https://github.com/harryhaller001/matplotlib-sankey
70
71
  cd matplotlib-sankey
71
- pip install .
72
+ pip install -e .
72
73
  ```
73
74
 
74
75
 
@@ -100,10 +101,15 @@ sankey(
100
101
  ### Development
101
102
 
102
103
  ```bash
103
- python3.10 -m virtualenv venv
104
- source venv/bin/activate
104
+ git clone https://github.com/harryhaller001/matplotlib-sankey
105
+ cd matplotlib-sankey
106
+
107
+ # Create virtual env with uv
108
+ uv venv
105
109
 
106
110
  # Install dev dependencies
107
111
  make install
108
- ```
109
112
 
113
+ # Run all checks
114
+ make check
115
+ ```
@@ -12,16 +12,20 @@ Sankey plot for matplotlib
12
12
 
13
13
  ### Installation
14
14
 
15
- Install with pip:
15
+ Install with `pip`:
16
16
 
17
17
  `pip install matplotlib-sankey`
18
18
 
19
+ Install with `uv`:
20
+
21
+ `uv add matplotlib-sankey`
22
+
19
23
  Install from source:
20
24
 
21
25
  ```bash
22
26
  git clone https://github.com/harryhaller001/matplotlib-sankey
23
27
  cd matplotlib-sankey
24
- pip install .
28
+ pip install -e .
25
29
  ```
26
30
 
27
31
 
@@ -53,9 +57,15 @@ sankey(
53
57
  ### Development
54
58
 
55
59
  ```bash
56
- python3.10 -m virtualenv venv
57
- source venv/bin/activate
60
+ git clone https://github.com/harryhaller001/matplotlib-sankey
61
+ cd matplotlib-sankey
62
+
63
+ # Create virtual env with uv
64
+ uv venv
58
65
 
59
66
  # Install dev dependencies
60
67
  make install
68
+
69
+ # Run all checks
70
+ make check
61
71
  ```
@@ -0,0 +1,89 @@
1
+ [build-system]
2
+ build-backend = "uv_build"
3
+ requires = [ "uv-build>=0.9.28,<0.10" ]
4
+
5
+ [project]
6
+ name = "matplotlib-sankey"
7
+ version = "0.3.2"
8
+ description = "Create Sankey diagrams with Matplotlib."
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ license = "MIT"
11
+ maintainers = [ { name = "harryhaller001", email = "harryhaller001@gmail.com" } ]
12
+ authors = [ { name = "harryhaller001", email = "harryhaller001@gmail.com" } ]
13
+ requires-python = ">=3.11"
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "Natural Language :: English",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [ "matplotlib" ]
27
+ optional-dependencies.docs = [
28
+ "ipython", # Required for syntax highlighing (https://github.com/spatialaudio/nbsphinx/issues/24)
29
+ "myst-parser",
30
+ "nbsphinx",
31
+ "sphinx",
32
+ "sphinx-autoapi",
33
+ "sphinx-autodoc-typehints",
34
+ "sphinx-book-theme",
35
+ ]
36
+ optional-dependencies.test = [
37
+ "coverage",
38
+ "ipykernel",
39
+ "ipython",
40
+ "ipywidgets",
41
+ "pre-commit",
42
+ "pytest",
43
+ "responses",
44
+ "ruff",
45
+ "twine",
46
+ "ty>=0.0.16",
47
+ "types-requests",
48
+ ]
49
+ urls.Source = "https://github.com/harryhaller001/matplotlib-sankey"
50
+
51
+ [tool.ruff]
52
+ line-length = 120
53
+ extend-include = [ "*.ipynb" ]
54
+ format.docstring-code-format = true
55
+ lint.select = [ "B", "BLE", "C4", "D", "E", "F", "I", "RUF100", "TID", "UP", "W" ]
56
+ lint.ignore = [ "B008", "C408", "D100", "D104", "D105", "D107", "D203", "D213", "D400", "D401", "E501", "E731", "E741" ]
57
+ lint.per-file-ignores."*/__init__.py" = [ "F401" ]
58
+ lint.per-file-ignores."docs/*" = [ "I" ]
59
+ lint.per-file-ignores."test/*" = [ "D" ]
60
+ lint.pydocstyle.convention = "google"
61
+
62
+ [tool.pyproject-fmt]
63
+ column_width = 120 # after how many column width split arrays/dicts into multiple lines, 1 will force always
64
+ indent = 4
65
+ keep_full_version = false # if false will remove unnecessary trailing ``.0``'s from version specifiers
66
+ max_supported_python = "3.14" # maximum Python version to use when generating version specifiers
67
+
68
+ [tool.pytest]
69
+ ini_options.minversion = "7.0"
70
+ ini_options.log_format = "%(asctime)s %(levelname)s %(message)s"
71
+ ini_options.log_date_format = "%Y-%m-%d %H:%M:%S"
72
+ ini_options.log_level = "INFO"
73
+ ini_options.log_cli = true
74
+ ini_options.python_files = "test_*.py"
75
+ ini_options.testpaths = [ "tests" ]
76
+ ini_options.xfail_strict = true
77
+ ini_options.addopts = [
78
+ "--import-mode=importlib", # allow using test files with same name
79
+ ]
80
+
81
+ [tool.coverage]
82
+ run.omit = [ "*/tests/*" ]
83
+ run.source = [ "src/matplotlib_sankey" ]
84
+ report.exclude_lines = [ "raise" ]
85
+ report.ignore_errors = true
86
+ html.directory = "coverage_report"
87
+
88
+ [tool.uv]
89
+ package = true
@@ -0,0 +1,5 @@
1
+ from matplotlib_sankey._plotting import sankey
2
+ from matplotlib_sankey._utils import from_matrix
3
+ from matplotlib_sankey._version import __version__
4
+
5
+ __all__ = ["sankey", "__version__", "from_matrix"]
@@ -1,11 +1,11 @@
1
- from typing import Any
2
1
  import re
3
- from matplotlib.colors import get_named_colors_mapping, Normalize, to_rgb
4
-
5
- from ._utils import isinstance_list_of
6
- from ._types import ColorTuple
2
+ from typing import Any
7
3
 
8
4
  from matplotlib import colormaps
5
+ from matplotlib.colors import Normalize, get_named_colors_mapping, to_rgb
6
+
7
+ from matplotlib_sankey._types import ColorTuple
8
+ from matplotlib_sankey._utils import isinstance_list_of
9
9
 
10
10
 
11
11
  def is_colormap(name: str) -> bool:
@@ -1,16 +1,16 @@
1
1
  from collections.abc import Sequence
2
- from typing import Literal, Any
2
+ from typing import Any, Literal, cast
3
3
 
4
4
  import matplotlib.pyplot as plt
5
5
  from matplotlib.axes import Axes
6
- from matplotlib.patches import Rectangle, PathPatch, Patch
7
- from matplotlib.ticker import FixedLocator
8
6
  from matplotlib.colors import Colormap
7
+ from matplotlib.patches import Patch, PathPatch, Rectangle
8
+ from matplotlib.ticker import FixedLocator
9
9
 
10
- from ._types import CurveType, ColorTuple
11
- from ._utils import _clean_axis, is_light_color
12
- from ._patches import patch_curve3, patch_curve4, patch_line
13
- from ._colors import colormap_to_list, is_color, is_colormap, unify_color
10
+ from matplotlib_sankey._colors import colormap_to_list, is_color, is_colormap, unify_color
11
+ from matplotlib_sankey._patches import patch_curve3, patch_curve4, patch_line
12
+ from matplotlib_sankey._types import ColorTuple, CurveType
13
+ from matplotlib_sankey._utils import _clean_axis, is_light_color
14
14
 
15
15
 
16
16
  def sankey(
@@ -27,7 +27,10 @@ def sankey(
27
27
  | Sequence[Colormap]
28
28
  | str
29
29
  | Sequence[tuple[float, float, float]]
30
- | Sequence[tuple[float, float, float, float]] = "tab10",
30
+ | Sequence[tuple[float, float, float, float]]
31
+ | Sequence[
32
+ str | Sequence[str] | Colormap | tuple[float, float, float] | tuple[float, float, float, float]
33
+ ] = "tab10",
31
34
  curve_type: CurveType = "curve4",
32
35
  ribbon_alpha: float = 0.2,
33
36
  ribbon_color: str = "black",
@@ -38,6 +41,7 @@ def sankey(
38
41
  column_labels: list[str] | None = None,
39
42
  annotate_columns_font_kwargs: dict[str, Any] | None = None,
40
43
  annotate_columns_font_color: Literal["auto"] | ColorTuple | str = "auto",
44
+ column_item_totals: list[dict[int | str, float | int]] | None = None,
41
45
  ) -> Axes:
42
46
  """Sankey plot.
43
47
 
@@ -60,6 +64,7 @@ def sankey(
60
64
  column_labels (list[str] | None, optional): Labels for columns. Defaults to `None`.
61
65
  annotate_columns_font_kwargs (dict[str, Any] | None, optional): Extra arguments for column `ax.text` method of column annotation. Defaults to `None`.
62
66
  annotate_columns_font_color (Literal["auto"] | ColorTuple | str, optional): Color of column annotation text. Defaults to `"auto"`, thereby automatically selectes text color based on background color.
67
+ column_item_totals (list[dict[int | str, float | int]] | None, optional): Total values for each column item. If provided, column items are sized based on these totals instead of the sum of ribbon weights. Ribbons are adjusted proportionally within the column items. Defaults to `None`.
63
68
 
64
69
  Returns:
65
70
  Matplotlib axes instance.
@@ -108,6 +113,24 @@ def sankey(
108
113
  column_weights[frame_index + 1].get(target_index, 0) + weight
109
114
  )
110
115
 
116
+ # Validate column_item_totals if provided
117
+ if column_item_totals is not None:
118
+ assert len(column_item_totals) == ncols, f"column_item_totals must have {ncols} entries, one for each column."
119
+ for col_index in range(ncols):
120
+ for item_key in column_weights[col_index].keys():
121
+ if item_key not in column_item_totals[col_index]:
122
+ raise ValueError(
123
+ f"Column {col_index}, item '{item_key}' has ribbons but no total value provided in column_item_totals."
124
+ )
125
+ if column_item_totals[col_index][item_key] < column_weights[col_index][item_key]:
126
+ raise ValueError(
127
+ f"Column {col_index}, item '{item_key}': total value {column_item_totals[col_index][item_key]} "
128
+ f"is less than sum of ribbon weights {column_weights[col_index][item_key]}."
129
+ )
130
+
131
+ # Use totals for rectangle sizing if provided, otherwise use calculated weights
132
+ column_display_weights = column_item_totals if column_item_totals is not None else column_weights
133
+
111
134
  # Total number of column rects
112
135
  total_rects: int = sum([len(col.keys()) for col in column_weights])
113
136
 
@@ -139,31 +162,35 @@ def sankey(
139
162
  raise ValueError("If cmap argument is a string, please provide color name, hex code or name of colormap.")
140
163
 
141
164
  elif isinstance(color, list | tuple | set):
142
- assert len(color) == ncols
165
+ # Type narrowing: at this point we know color is a sequence
166
+ color_seq = cast(Sequence[Any], color)
167
+ assert len(color_seq) == ncols
143
168
  # process column wise definition of color
144
169
  for col_index in range(len(column_rect_counts)):
145
170
  new_column = []
171
+ col_color = color_seq[col_index]
146
172
 
147
- if isinstance(color[col_index], str) and is_colormap(color[col_index]):
173
+ if isinstance(col_color, str) and is_colormap(col_color):
148
174
  for rect_index in range(column_rect_counts[col_index]):
149
175
  new_column.append(
150
176
  colormap_to_list(
151
- name=color[col_index],
177
+ name=col_color,
152
178
  num=len(column_weights[col_index].keys()),
153
179
  rollover=True,
154
180
  )[rect_index]
155
181
  )
156
- elif is_color(color[col_index]):
182
+ elif is_color(col_color):
157
183
  for _ in range(column_rect_counts[col_index]):
158
- new_column.append(unify_color(color[col_index]))
184
+ new_column.append(unify_color(col_color))
159
185
 
160
- elif isinstance(color[col_index], list | tuple | set):
186
+ elif isinstance(col_color, list | tuple | set):
161
187
  # List of list -> individual definition of column rect color
162
- assert all(is_color(c) for c in color[col_index]), "All items must be a color."
163
- assert len(color[col_index]) == column_rect_counts[col_index]
188
+ col_color_seq = cast(Sequence[Any], col_color)
189
+ assert all(is_color(c) for c in col_color_seq), "All items must be a color."
190
+ assert len(col_color_seq) == column_rect_counts[col_index]
164
191
 
165
192
  for rect_index in range(column_rect_counts[col_index]):
166
- new_column.append(unify_color(color[col_index][rect_index]))
193
+ new_column.append(unify_color(col_color_seq[rect_index]))
167
194
 
168
195
  color_matrix.append(new_column)
169
196
 
@@ -177,14 +204,14 @@ def sankey(
177
204
  legend_handles: list[tuple[str, ColorTuple]] = []
178
205
 
179
206
  for frame_index in range(ncols):
180
- column_total_weight = sum(column_weights[frame_index].values())
207
+ column_total_weight = sum(column_display_weights[frame_index].values())
181
208
  column_prev_weight = 0.0
182
209
 
183
- column_n_spacing = len(column_weights[frame_index].values()) - 1
210
+ column_n_spacing = len(column_display_weights[frame_index].values()) - 1
184
211
 
185
212
  spacing_scale_factor = 1 - (spacing * column_n_spacing)
186
213
 
187
- for column_index, (column_key, weights) in enumerate(column_weights[frame_index].items()):
214
+ for column_index, (column_key, weights) in enumerate(column_display_weights[frame_index].items()):
188
215
  rect_x = frame_index - (rel_column_width / 2)
189
216
  rect_y = column_prev_weight / column_total_weight + (column_index * spacing)
190
217
  rect_height = (weights * spacing_scale_factor) / column_total_weight
@@ -281,25 +308,23 @@ def sankey(
281
308
  ribbon_offset: float = 0.0
282
309
 
283
310
  for target_index, ribbon_weight in column_targets.items():
284
- # Start coords
285
- y1_start = rect_y + +(rect_height * (ribbon_offset / sum(column_targets.values())))
286
- y2_end = rect_y + (rect_height * ((ribbon_offset + ribbon_weight) / sum(column_targets.values())))
311
+ # Start coords - use actual ribbon weights for positioning within source rectangle
312
+ source_item_total = column_display_weights[frame_index][column_key]
313
+ y1_start = rect_y + (rect_height * (ribbon_offset / source_item_total))
314
+ y2_end = rect_y + (rect_height * ((ribbon_offset + ribbon_weight) / source_item_total))
287
315
 
288
316
  ribbon_offset += ribbon_weight
289
317
 
290
318
  _, target_rect_y, _, target_rect_height = column_rects[frame_index + 1][target_index]
291
319
 
292
- # End coords
320
+ # End coords - use actual ribbon weights for positioning within target rectangle
321
+ target_item_total = column_display_weights[frame_index + 1][target_index]
293
322
  y1_end = target_rect_y + (
294
- target_rect_height
295
- * (target_ribbon_offset.get(target_index, 0) / column_weights[frame_index + 1][target_index])
323
+ target_rect_height * (target_ribbon_offset.get(target_index, 0) / target_item_total)
296
324
  )
297
325
  y2_start = target_rect_y + (
298
326
  target_rect_height
299
- * (
300
- (ribbon_weight + target_ribbon_offset.get(target_index, 0))
301
- / column_weights[frame_index + 1][target_index]
302
- )
327
+ * ((ribbon_weight + target_ribbon_offset.get(target_index, 0)) / target_item_total)
303
328
  )
304
329
 
305
330
  target_ribbon_offset[target_index] = target_ribbon_offset.get(target_index, 0) + ribbon_weight
@@ -1,13 +1,13 @@
1
1
  from collections.abc import Sequence
2
- from typing import Any, Literal
3
2
  from types import UnionType
3
+ from typing import Any, Literal
4
4
 
5
5
  import numpy as np
6
6
  from matplotlib import colormaps
7
7
  from matplotlib.axes import Axes
8
- from matplotlib.colors import Colormap, ListedColormap
8
+ from matplotlib.colors import Colormap, ListedColormap, to_rgb
9
9
 
10
- from ._types import AcceptedColors, ColorTuple
10
+ from matplotlib_sankey._types import AcceptedColors, ColorTuple
11
11
 
12
12
 
13
13
  def _clean_axis(
@@ -51,7 +51,12 @@ def _generate_cmap(value: AcceptedColors, nrows: int) -> Colormap:
51
51
  return _convert_sequential_cmap_to_listed(colormaps.get_cmap(value))
52
52
 
53
53
  elif isinstance(value, Sequence):
54
- return ListedColormap(value)
54
+ # Convert color names/strings to RGB tuples for ListedColormap
55
+ rgb_colors: list[tuple[float, float, float]] = [
56
+ to_rgb(c) if isinstance(c, str) else tuple(c) # type: ignore[arg-type]
57
+ for c in value
58
+ ]
59
+ return ListedColormap(rgb_colors)
55
60
 
56
61
  elif isinstance(value, Colormap):
57
62
  return _convert_sequential_cmap_to_listed(value)
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__: str = version("matplotlib-sankey")
@@ -1,15 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = space
5
- indent_size = 4
6
- end_of_line = lf
7
- charset = utf-8
8
- trim_trailing_whitespace = true
9
- insert_final_newline = true
10
-
11
- [{*.{yml,yaml,toml},.cruft.json}]
12
- indent_size = 2
13
-
14
- [Makefile]
15
- indent_style = tab
@@ -1,164 +0,0 @@
1
- /docs/coverage_report
2
-
3
- # Byte-compiled / optimized / DLL files
4
- __pycache__/
5
- *.py[cod]
6
- *$py.class
7
-
8
- # C extensions
9
- *.so
10
-
11
- # Distribution / packaging
12
- .Python
13
- build/
14
- develop-eggs/
15
- dist/
16
- downloads/
17
- eggs/
18
- .eggs/
19
- lib/
20
- lib64/
21
- parts/
22
- sdist/
23
- var/
24
- wheels/
25
- share/python-wheels/
26
- *.egg-info/
27
- .installed.cfg
28
- *.egg
29
- MANIFEST
30
-
31
- # PyInstaller
32
- # Usually these files are written by a python script from a template
33
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
- *.manifest
35
- *.spec
36
-
37
- # Installer logs
38
- pip-log.txt
39
- pip-delete-this-directory.txt
40
-
41
- # Unit test / coverage reports
42
- htmlcov/
43
- .tox/
44
- .nox/
45
- .coverage
46
- .coverage.*
47
- .cache
48
- nosetests.xml
49
- coverage.xml
50
- *.cover
51
- *.py,cover
52
- .hypothesis/
53
- .pytest_cache/
54
- cover/
55
-
56
- # Translations
57
- *.mo
58
- *.pot
59
-
60
- # Django stuff:
61
- *.log
62
- local_settings.py
63
- db.sqlite3
64
- db.sqlite3-journal
65
-
66
- # Flask stuff:
67
- instance/
68
- .webassets-cache
69
-
70
- # Scrapy stuff:
71
- .scrapy
72
-
73
- # Sphinx documentation
74
- docs/_build/
75
-
76
- # PyBuilder
77
- .pybuilder/
78
- target/
79
-
80
- # Jupyter Notebook
81
- .ipynb_checkpoints
82
-
83
- # IPython
84
- profile_default/
85
- ipython_config.py
86
-
87
- # pyenv
88
- # For a library or package, you might want to ignore these files since the code is
89
- # intended to run in multiple environments; otherwise, check them in:
90
- # .python-version
91
-
92
- # pipenv
93
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
- # install all needed dependencies.
97
- #Pipfile.lock
98
-
99
- # poetry
100
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
101
- # This is especially recommended for binary packages to ensure reproducibility, and is more
102
- # commonly ignored for libraries.
103
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
104
- #poetry.lock
105
-
106
- # pdm
107
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
108
- #pdm.lock
109
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
110
- # in version control.
111
- # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
112
- .pdm.toml
113
- .pdm-python
114
- .pdm-build/
115
-
116
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117
- __pypackages__/
118
-
119
- # Celery stuff
120
- celerybeat-schedule
121
- celerybeat.pid
122
-
123
- # SageMath parsed files
124
- *.sage.py
125
-
126
- # Environments
127
- .env
128
- .venv
129
- env/
130
- venv/
131
- ENV/
132
- env.bak/
133
- venv.bak/
134
-
135
- # Spyder project settings
136
- .spyderproject
137
- .spyproject
138
-
139
- # Rope project settings
140
- .ropeproject
141
-
142
- # mkdocs documentation
143
- /site
144
-
145
- # mypy
146
- .mypy_cache/
147
- .dmypy.json
148
- dmypy.json
149
-
150
- # Pyre type checker
151
- .pyre/
152
-
153
- # pytype static type analyzer
154
- .pytype/
155
-
156
- # Cython debug symbols
157
- cython_debug/
158
-
159
- # PyCharm
160
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162
- # and can be added to the global gitignore or merged into this file. For a more nuclear
163
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164
- #.idea/
@@ -1,16 +0,0 @@
1
- cff-version: 1.2.0
2
- message: "If you use matplotlib-sankey, please cite it as below."
3
- authors:
4
- - family-names: "Hellmig"
5
- given-names: "Malte"
6
- orcid: "https://orcid.org/0000-0002-6692-5618"
7
- - family-names: "Krebs"
8
- given-names: "Christian F."
9
- orcid: "https://orcid.org/0000-0003-2739-5578"
10
- title: "matplotlib-sankey"
11
- version: 0.3.0
12
- date-released: 2025-05-14
13
- url: "https://github.com/harryhaller001/matplotlib-sankey"
14
- identifiers:
15
- - type: doi
16
- value: 10.5281/zenodo.15420062
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 harryhaller001
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.
@@ -1,5 +0,0 @@
1
- from ._plotting import sankey
2
- from ._version import __version__
3
- from ._utils import from_matrix
4
-
5
- __all__ = ["sankey", "__version__", "from_matrix"]
@@ -1 +0,0 @@
1
- __version__ = "0.3.1"
@@ -1,160 +0,0 @@
1
- [build-system]
2
- # Package build system
3
- build-backend = "flit_core.buildapi"
4
- requires = [ "flit-core>=3.2,<4" ]
5
-
6
- [project]
7
- name = "matplotlib-sankey"
8
- description = "Sankey plot for matplotlib"
9
- readme = { file = "README.md", content-type = "text/markdown" }
10
-
11
- license = { file = "LICENSE" }
12
-
13
- maintainers = [ { name = "harryhaller001", email = "harryhaller001@gmail.com" } ]
14
- authors = [ { name = "harryhaller001" } ]
15
-
16
- requires-python = ">=3.10"
17
-
18
- classifiers = [
19
- "Development Status :: 3 - Alpha",
20
- "Framework :: Matplotlib",
21
- "Intended Audience :: Science/Research",
22
- "License :: OSI Approved :: MIT License",
23
- "Natural Language :: English",
24
- "Operating System :: OS Independent",
25
- "Programming Language :: Python :: 3 :: Only",
26
- "Programming Language :: Python :: 3.10",
27
- "Programming Language :: Python :: 3.11",
28
- "Programming Language :: Python :: 3.12",
29
- "Programming Language :: Python :: 3.13",
30
- "Typing :: Typed",
31
- ]
32
- dynamic = [ "version" ]
33
-
34
- dependencies = [ "matplotlib", "numpy" ]
35
-
36
- optional-dependencies.docs = [
37
- "ipykernel",
38
- "ipython",
39
- "ipywidgets",
40
- "myst-parser",
41
- "nbsphinx",
42
- "networkx[default]",
43
- "sphinx>=4",
44
- "sphinx-autoapi",
45
- "sphinx-autodoc-typehints",
46
- "sphinx-book-theme>=1",
47
- ]
48
- optional-dependencies.test = [
49
- "coverage",
50
- "flit",
51
- "mypy",
52
- "pre-commit",
53
- "pytest",
54
- "ruff",
55
- "setuptools",
56
- "twine>=4.0.2",
57
- ]
58
-
59
- # https://docs.pypi.org/project_metadata/#project-urls
60
- urls.Documentation = "https://harryhaller001.github.io/matplotlib-sankey/"
61
- urls.Homepage = "https://github.com/harryhaller001/matplotlib-sankey"
62
- urls.Source = "https://github.com/harryhaller001/matplotlib-sankey"
63
-
64
- [tool.flit.sdist]
65
- include = [ "matplotlib_sankey/", "tests/" ]
66
- exclude = [
67
- "docs/",
68
- "coverage",
69
- ".git/",
70
- ".github/",
71
- ".pre-commit-config.yaml",
72
- "Makefile",
73
- ".python-version",
74
- # "requirements.txt",
75
- ".vscode/",
76
- "dev/",
77
- ]
78
-
79
- [tool.ruff]
80
- line-length = 120
81
- extend-include = [ "*.ipynb" ]
82
-
83
- format.docstring-code-format = true
84
-
85
- lint.select = [
86
- "B", # flake8-bugbear
87
- "BLE", # flake8-blind-except
88
- "C4", # flake8-comprehensions
89
- "D", # pydocstyle
90
- "E", # Error detected by Pycodestyle
91
- "F", # Errors detected by Pyflakes
92
- "I", # isort
93
- "RUF100", # Report unused noqa directives
94
- "TID", # flake8-tidy-imports
95
- "UP", # pyupgrade
96
- "W", # Warning detected by Pycodestyle
97
- ]
98
- lint.ignore = [
99
- "B008", # Errors from function calls in argument defaults. These are fine when the result is immutable.
100
- "D100", # Missing docstring in public module
101
- "D104", # Missing docstring in public package
102
- "D105", # __magic__ methods are often self-explanatory, allow missing docstrings
103
- "D107", # Missing docstring in __init__
104
- # Disable one in each pair of mutually incompatible rules
105
- "D203", # We don’t want a blank line before a class docstring
106
- "D213", # <> We want docstrings to start immediately after the opening triple quote
107
- "D400", # first line should end with a period [Bug: doesn’t work with single-line docstrings]
108
- "D401", # First line should be in imperative mood; try rephrasing
109
- "E501", # line too long -> we accept long comment lines; formatter gets rid of long code lines
110
- "E731", # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
111
- "E741", # allow I, O, l as variable names -> I is the identity matrix
112
- "I001",
113
- ]
114
-
115
- [tool.pyproject-fmt]
116
- column_width = 120 # after how many column width split arrays/dicts into multiple lines, 1 will force always
117
- indent = 4
118
- keep_full_version = false # if false will remove unnecessary trailing ``.0``'s from version specifiers
119
- max_supported_python = "3.13" # maximum Python version to use when generating version specifiers
120
-
121
- [tool.pytest.ini_options]
122
- # Pytest config
123
- minversion = "7.0"
124
-
125
- log_format = "%(asctime)s %(levelname)s %(message)s"
126
- log_date_format = "%Y-%m-%d %H:%M:%S"
127
- log_level = "INFO"
128
- log_cli = true
129
-
130
- python_files = "test_*.py"
131
- testpaths = [ "tests" ]
132
-
133
- xfail_strict = true
134
- addopts = [
135
- "--import-mode=importlib", # allow using test files with same name
136
- ]
137
-
138
- [tool.coverage.run]
139
- # Coverage config
140
- source = [ "matplotlib_sankey" ]
141
- omit = [ "*/tests/*" ]
142
-
143
- [tool.coverage.report]
144
- exclude_lines = [ "raise" ]
145
- ignore_errors = true
146
-
147
- [tool.coverage.html]
148
- directory = "docs/coverage_report"
149
-
150
- [tool.mypy]
151
- # Mypy config (https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml-file)
152
- python_version = "3.10"
153
- warn_return_any = true
154
- warn_unused_configs = true
155
-
156
- # Ignore libs which are not PEP 561 compliant
157
- # [[tool.mypy.overrides]]
158
- # module = [
159
- # ]
160
- # ignore_missing_imports = true
@@ -1,133 +0,0 @@
1
- accessible-pygments==0.0.5
2
- alabaster==1.0.0
3
- astroid==3.3.9
4
- asttokens==3.0.0
5
- attrs==25.3.0
6
- babel==2.17.0
7
- backports.tarfile==1.2.0
8
- beautifulsoup4==4.13.4
9
- bleach==6.2.0
10
- certifi==2025.4.26
11
- cffi==1.17.1
12
- cfgv==3.4.0
13
- charset-normalizer==3.4.1
14
- comm==0.2.2
15
- contourpy==1.3.2
16
- coverage==7.8.0
17
- cryptography==44.0.2
18
- cycler==0.12.1
19
- debugpy==1.8.14
20
- decorator==5.2.1
21
- defusedxml==0.7.1
22
- distlib==0.3.9
23
- docutils==0.21.2
24
- exceptiongroup==1.2.2
25
- executing==2.2.0
26
- fastjsonschema==2.21.1
27
- filelock==3.18.0
28
- flit==3.12.0
29
- flit_core==3.12.0
30
- fonttools==4.57.0
31
- id==1.5.0
32
- identify==2.6.10
33
- idna==3.10
34
- imagesize==1.4.1
35
- importlib_metadata==8.6.1
36
- iniconfig==2.1.0
37
- ipykernel==6.29.5
38
- ipython==8.36.0
39
- ipywidgets==8.1.6
40
- jaraco.classes==3.4.0
41
- jaraco.context==6.0.1
42
- jaraco.functools==4.1.0
43
- jedi==0.19.2
44
- jeepney==0.9.0
45
- Jinja2==3.1.6
46
- jsonschema==4.23.0
47
- jsonschema-specifications==2025.4.1
48
- jupyter_client==8.6.3
49
- jupyter_core==5.7.2
50
- jupyterlab_pygments==0.3.0
51
- jupyterlab_widgets==3.0.14
52
- keyring==25.6.0
53
- kiwisolver==1.4.8
54
- markdown-it-py==3.0.0
55
- MarkupSafe==3.0.2
56
- matplotlib==3.10.3
57
- matplotlib-inline==0.1.7
58
- mdit-py-plugins==0.4.2
59
- mdurl==0.1.2
60
- mistune==3.1.3
61
- more-itertools==10.7.0
62
- mypy==1.15.0
63
- mypy_extensions==1.1.0
64
- myst-parser==4.0.1
65
- nbclient==0.10.2
66
- nbconvert==7.16.6
67
- nbformat==5.10.4
68
- nbsphinx==0.9.7
69
- nest-asyncio==1.6.0
70
- networkx==3.4.2
71
- nh3==0.2.21
72
- nodeenv==1.9.1
73
- numpy==2.2.5
74
- packaging==25.0
75
- pandas==2.2.3
76
- pandocfilters==1.5.1
77
- parso==0.8.4
78
- pexpect==4.9.0
79
- pillow==11.2.1
80
- platformdirs==4.3.7
81
- pluggy==1.5.0
82
- pre_commit==4.2.0
83
- prompt_toolkit==3.0.51
84
- psutil==7.0.0
85
- ptyprocess==0.7.0
86
- pure_eval==0.2.3
87
- pycparser==2.22
88
- pydata-sphinx-theme==0.15.4
89
- Pygments==2.19.1
90
- pyparsing==3.2.3
91
- pytest==8.3.5
92
- python-dateutil==2.9.0.post0
93
- pytz==2025.2
94
- PyYAML==6.0.2
95
- pyzmq==26.4.0
96
- readme_renderer==44.0
97
- referencing==0.36.2
98
- requests==2.32.3
99
- requests-toolbelt==1.0.0
100
- rfc3986==2.0.0
101
- rich==14.0.0
102
- rpds-py==0.24.0
103
- ruff==0.11.9
104
- scipy==1.15.2
105
- SecretStorage==3.3.3
106
- six==1.17.0
107
- snowballstemmer==2.2.0
108
- soupsieve==2.7
109
- Sphinx==8.1.3
110
- sphinx-autoapi==3.6.0
111
- sphinx-autodoc-typehints==3.0.1
112
- sphinx-book-theme==1.1.4
113
- sphinxcontrib-applehelp==2.0.0
114
- sphinxcontrib-devhelp==2.0.0
115
- sphinxcontrib-htmlhelp==2.1.0
116
- sphinxcontrib-jsmath==1.0.1
117
- sphinxcontrib-qthelp==2.0.0
118
- sphinxcontrib-serializinghtml==2.0.0
119
- stack-data==0.6.3
120
- tinycss2==1.4.0
121
- tomli==2.2.1
122
- tomli_w==1.2.0
123
- tornado==6.5
124
- traitlets==5.14.3
125
- twine==6.1.0
126
- typing_extensions==4.13.2
127
- tzdata==2025.2
128
- urllib3==2.4.0
129
- virtualenv==20.30.0
130
- wcwidth==0.2.13
131
- webencodings==0.5.1
132
- widgetsnbextension==4.0.14
133
- zipp==3.21.0
File without changes
@@ -1,25 +0,0 @@
1
- from matplotlib_sankey._colors import is_color, is_colormap, colormap_to_list, is_hex_color, unify_color
2
-
3
-
4
- def test_color_utils() -> None:
5
- """Testing color utils."""
6
- assert all(
7
- [
8
- is_color("blue"),
9
- is_color("tab:red"),
10
- is_color("#3456ad"),
11
- is_color([1, 0.4, 0.2]),
12
- is_color([255, 60, 60]),
13
- ]
14
- )
15
-
16
- assert is_hex_color("#345") is False
17
- assert is_hex_color("test") is False
18
- assert is_hex_color([255, 60, 60]) is False
19
-
20
- assert unify_color("#FFFFFF") == (1, 1, 1)
21
-
22
- assert is_colormap("tab10")
23
- assert is_colormap("blue") is False
24
-
25
- assert len(colormap_to_list("Reds", 20)) == 20
@@ -1,23 +0,0 @@
1
- import matplotlib.pyplot as plt
2
- from matplotlib_sankey import sankey
3
-
4
-
5
- def test_sankey_simple_plot():
6
- """Testing simple sankey plot."""
7
- data = [
8
- [(0, 2, 20), (0, 1, 10), (3, 4, 15), (3, 2, 10), (5, 1, 5), (5, 2, 50)],
9
- [(2, 6, 40), (1, 6, 15), (2, 7, 40), (4, 6, 15)],
10
- [(7, 8, 5), (7, 9, 5), (7, 10, 20), (7, 11, 10), (6, 11, 55), (6, 8, 15)],
11
- ]
12
- sankey(data, frameon=True)
13
- sankey(data, curve_type="curve3")
14
- sankey(data, curve_type="line")
15
- sankey(data, title="test", annotate_columns="index")
16
- sankey(data, color="Reds", annotate_columns="weight")
17
- sankey(data, color="tab:red", annotate_columns="weight_percent", annotate_columns_font_color="white")
18
- sankey(data, color=["tab:red", "Reds", (0.1, 0.4, 1.0), "viridis"])
19
- sankey(data, show_legend=True)
20
- sankey(data, color="tab:red", column_labels=["A", "B", "C", "D"], show=False)
21
-
22
- _, ax = plt.subplots()
23
- sankey(data, ax=ax)
@@ -1,40 +0,0 @@
1
- from matplotlib import colormaps
2
- from matplotlib.colors import Colormap
3
-
4
- from matplotlib_sankey._utils import _generate_cmap, from_matrix, isinstance_list_of, is_light_color
5
-
6
-
7
- def test_utils_cmap() -> None:
8
- """Testing utils function to generate cmap."""
9
- assert isinstance(_generate_cmap("tab10", 4), Colormap)
10
- assert isinstance(_generate_cmap("viridis", 4), Colormap)
11
- assert _generate_cmap("viridis", 4).N == 4
12
- assert isinstance(_generate_cmap(["#ec4899", "#0284c7", "#16a34a", "#f59e0b"], 4), Colormap)
13
- assert isinstance(_generate_cmap([(0.4, 0.1, 0.9), (0.1, 0.1, 0.7)], 4), Colormap)
14
- assert isinstance(_generate_cmap(colormaps["tab10"], 4), Colormap)
15
-
16
-
17
- def test_from_matrix() -> None:
18
- """Testing from matrix helper function."""
19
- assert len(from_matrix([[0, 0], [0, 0]])) == 0
20
-
21
- assert len(from_matrix([[0, 0], [0, 0]], source_indicies=["A", "B"], target_indicies=["C", "D"])) == 0
22
-
23
- assert len(from_matrix([[0, 1], [0, 0]])) == 1
24
-
25
- assert len(from_matrix([[0, 1], [0, 1]], source_indicies=["A", "B"], target_indicies=["C", "D"])) == 2
26
-
27
-
28
- def test_isinstance_list_of() -> None:
29
- """Testing is instance list of type."""
30
- assert isinstance_list_of(["A", "b", "c"], str)
31
- assert isinstance_list_of([1, 2, 3, 4], int)
32
- assert isinstance_list_of(["A", "b", 1], str) is False
33
- assert isinstance_list_of("test", str) is False
34
-
35
-
36
- def test_luminance() -> None:
37
- """Testing is light color function."""
38
- assert is_light_color((1, 1, 1)) is True
39
- assert is_light_color((0, 0, 0)) is False
40
- assert is_light_color((255, 255, 255), color_range_max=255) is True