matplotlib-sankey 0.3.0__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.0
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
 
@@ -51,6 +48,7 @@ Provides-Extra: test
51
48
  [![Version](https://img.shields.io/pypi/v/matplotlib-sankey)](https://pypi.org/project/matplotlib-sankey/)
52
49
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/matplotlib-sankey)
53
50
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/harryhaller001/matplotlib-sankey/testing.yml)
51
+ [![DOI](https://zenodo.org/badge/893904012.svg)](https://doi.org/10.5281/zenodo.15420062)
54
52
 
55
53
  [Documentation](https://harryhaller001.github.io/matplotlib-sankey/) | [PyPI](https://pypi.org/project/matplotlib-sankey/) | [Github repository](https://github.com/harryhaller001/matplotlib-sankey) | [Codecov](https://codecov.io/gh/harryhaller001/matplotlib-sankey)
56
54
 
@@ -58,16 +56,20 @@ Sankey plot for matplotlib
58
56
 
59
57
  ### Installation
60
58
 
61
- Install with pip:
59
+ Install with `pip`:
62
60
 
63
61
  `pip install matplotlib-sankey`
64
62
 
63
+ Install with `uv`:
64
+
65
+ `uv add matplotlib-sankey`
66
+
65
67
  Install from source:
66
68
 
67
69
  ```bash
68
70
  git clone https://github.com/harryhaller001/matplotlib-sankey
69
71
  cd matplotlib-sankey
70
- pip install .
72
+ pip install -e .
71
73
  ```
72
74
 
73
75
 
@@ -99,10 +101,15 @@ sankey(
99
101
  ### Development
100
102
 
101
103
  ```bash
102
- python3.10 -m virtualenv venv
103
- 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
104
109
 
105
110
  # Install dev dependencies
106
111
  make install
107
- ```
108
112
 
113
+ # Run all checks
114
+ make check
115
+ ```
@@ -4,6 +4,7 @@
4
4
  [![Version](https://img.shields.io/pypi/v/matplotlib-sankey)](https://pypi.org/project/matplotlib-sankey/)
5
5
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/matplotlib-sankey)
6
6
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/harryhaller001/matplotlib-sankey/testing.yml)
7
+ [![DOI](https://zenodo.org/badge/893904012.svg)](https://doi.org/10.5281/zenodo.15420062)
7
8
 
8
9
  [Documentation](https://harryhaller001.github.io/matplotlib-sankey/) | [PyPI](https://pypi.org/project/matplotlib-sankey/) | [Github repository](https://github.com/harryhaller001/matplotlib-sankey) | [Codecov](https://codecov.io/gh/harryhaller001/matplotlib-sankey)
9
10
 
@@ -11,16 +12,20 @@ Sankey plot for matplotlib
11
12
 
12
13
  ### Installation
13
14
 
14
- Install with pip:
15
+ Install with `pip`:
15
16
 
16
17
  `pip install matplotlib-sankey`
17
18
 
19
+ Install with `uv`:
20
+
21
+ `uv add matplotlib-sankey`
22
+
18
23
  Install from source:
19
24
 
20
25
  ```bash
21
26
  git clone https://github.com/harryhaller001/matplotlib-sankey
22
27
  cd matplotlib-sankey
23
- pip install .
28
+ pip install -e .
24
29
  ```
25
30
 
26
31
 
@@ -52,9 +57,15 @@ sankey(
52
57
  ### Development
53
58
 
54
59
  ```bash
55
- python3.10 -m virtualenv venv
56
- 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
57
65
 
58
66
  # Install dev dependencies
59
67
  make install
68
+
69
+ # Run all checks
70
+ make check
60
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,23 +162,36 @@ 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 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))
185
+
186
+ elif isinstance(col_color, list | tuple | set):
187
+ # List of list -> individual definition of column rect color
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]
191
+
192
+ for rect_index in range(column_rect_counts[col_index]):
193
+ new_column.append(unify_color(col_color_seq[rect_index]))
194
+
159
195
  color_matrix.append(new_column)
160
196
 
161
197
  else:
@@ -168,14 +204,14 @@ def sankey(
168
204
  legend_handles: list[tuple[str, ColorTuple]] = []
169
205
 
170
206
  for frame_index in range(ncols):
171
- column_total_weight = sum(column_weights[frame_index].values())
207
+ column_total_weight = sum(column_display_weights[frame_index].values())
172
208
  column_prev_weight = 0.0
173
209
 
174
- column_n_spacing = len(column_weights[frame_index].values()) - 1
210
+ column_n_spacing = len(column_display_weights[frame_index].values()) - 1
175
211
 
176
212
  spacing_scale_factor = 1 - (spacing * column_n_spacing)
177
213
 
178
- 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()):
179
215
  rect_x = frame_index - (rel_column_width / 2)
180
216
  rect_y = column_prev_weight / column_total_weight + (column_index * spacing)
181
217
  rect_height = (weights * spacing_scale_factor) / column_total_weight
@@ -272,25 +308,23 @@ def sankey(
272
308
  ribbon_offset: float = 0.0
273
309
 
274
310
  for target_index, ribbon_weight in column_targets.items():
275
- # Start coords
276
- y1_start = rect_y + +(rect_height * (ribbon_offset / sum(column_targets.values())))
277
- 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))
278
315
 
279
316
  ribbon_offset += ribbon_weight
280
317
 
281
318
  _, target_rect_y, _, target_rect_height = column_rects[frame_index + 1][target_index]
282
319
 
283
- # 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]
284
322
  y1_end = target_rect_y + (
285
- target_rect_height
286
- * (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)
287
324
  )
288
325
  y2_start = target_rect_y + (
289
326
  target_rect_height
290
- * (
291
- (ribbon_weight + target_ribbon_offset.get(target_index, 0))
292
- / column_weights[frame_index + 1][target_index]
293
- )
327
+ * ((ribbon_weight + target_ribbon_offset.get(target_index, 0)) / target_item_total)
294
328
  )
295
329
 
296
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,11 +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
- - family-names: "Krebs"
7
- given-names: "Christian F."
8
- title: "matplotlib-sankey"
9
- version: 0.3.0
10
- date-released: 2025-05-11
11
- url: "https://github.com/harryhaller001/matplotlib-sankey"
@@ -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.0"
@@ -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.4.2
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
@@ -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