bumplot 0.1.0__tar.gz → 0.2.1__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.
- {bumplot-0.1.0 → bumplot-0.2.1}/.github/workflows/tests.yaml +1 -2
- {bumplot-0.1.0 → bumplot-0.2.1}/.gitignore +2 -1
- {bumplot-0.1.0 → bumplot-0.2.1}/LICENSE +1 -1
- {bumplot-0.1.0 → bumplot-0.2.1}/PKG-INFO +18 -12
- {bumplot-0.1.0 → bumplot-0.2.1}/README.md +15 -9
- bumplot-0.2.1/README_files/figure-commonmark/cell-3-output-1.png +0 -0
- bumplot-0.2.1/bumplot/__init__.py +5 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot/_utils.py +10 -11
- bumplot-0.2.1/bumplot/main.py +131 -0
- bumplot-0.2.1/bumplot/opts.py +191 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot.egg-info/PKG-INFO +18 -12
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot.egg-info/SOURCES.txt +7 -3
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/README.qmd +22 -4
- bumplot-0.2.1/docs/advanced-usage.md +231 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/contributing.md +4 -2
- bumplot-0.2.1/docs/data/water-africa.csv +7 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/examples.md +59 -20
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/index.md +9 -4
- bumplot-0.1.0/Makefile → bumplot-0.2.1/justfile +0 -2
- {bumplot-0.1.0 → bumplot-0.2.1}/mkdocs.yaml +1 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/pyproject.toml +6 -4
- bumplot-0.2.1/tests/test_main.py +196 -0
- bumplot-0.2.1/tests/test_opts.py +108 -0
- bumplot-0.1.0/README_files/figure-commonmark/cell-2-output-1.png +0 -0
- bumplot-0.1.0/bumplot/__init__.py +0 -4
- bumplot-0.1.0/bumplot/main.py +0 -104
- bumplot-0.1.0/tests/test_main.py +0 -74
- {bumplot-0.1.0 → bumplot-0.2.1}/.coverage +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/.github/workflows/doc.yaml +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/.github/workflows/lint.yaml +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/.github/workflows/pypi.yaml +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/.github/workflows/type.yaml +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/.pre-commit-config.yaml +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot/bezier.py +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot.egg-info/dependency_links.txt +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot.egg-info/requires.txt +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/bumplot.egg-info/top_level.txt +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/coverage-badge.svg +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/.gitignore +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/reference/bezier.md +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/reference/bumplot.md +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/docs/stylesheets/style.css +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/overrides/partials/footer.html +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/setup.cfg +0 -0
- {bumplot-0.1.0 → bumplot-0.2.1}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bumplot
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Easily create and customize bump charts in Python
|
|
5
5
|
Author-email: Joseph Barbier <joseph.barbierdarnal@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,10 +8,10 @@ Project-URL: Homepage, https://y-sunflower.github.io/bumplot/
|
|
|
8
8
|
Project-URL: Issues, https://github.com/y-sunflower/bumplot/issues
|
|
9
9
|
Project-URL: Documentation, https://y-sunflower.github.io/bumplot/
|
|
10
10
|
Project-URL: Repository, https://github.com/y-sunflower/bumplot
|
|
11
|
-
Keywords: bump,bump plot,bump charts,matplotlib,data,visualization
|
|
11
|
+
Keywords: bump,bump plot,bump charts,matplotlib,data,visualization,bezier,curve
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Development Status ::
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
@@ -19,12 +19,10 @@ Requires-Dist: matplotlib>=3.9.4
|
|
|
19
19
|
Requires-Dist: narwhals>=2.0.0
|
|
20
20
|
Dynamic: license-file
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# `bumplot`: easily create and customize bump charts in Python
|
|
23
23
|
|
|
24
24
|
<!-- Automatically generated, uses README.qmd to modify README.md -->
|
|
25
25
|
|
|
26
|
-
# `bumplot`: easily create and customize bump charts in Python
|
|
27
|
-
|
|
28
26
|
<img src="https://github.com/JosephBARBIERDARNAL/static/blob/main/python-libs/bumplot/image.png?raw=true" alt="bumplot logo" align="right" width="180px"/>
|
|
29
27
|
|
|
30
28
|
`bumplot` is a small Python package made to facilitate the creation of
|
|
@@ -42,7 +40,7 @@ documentation](https://y-sunflower.github.io/bumplot/)
|
|
|
42
40
|
|
|
43
41
|
## Get started
|
|
44
42
|
|
|
45
|
-
```
|
|
43
|
+
```python
|
|
46
44
|
import matplotlib.pyplot as plt
|
|
47
45
|
import pandas as pd
|
|
48
46
|
|
|
@@ -56,13 +54,11 @@ data = pd.DataFrame(
|
|
|
56
54
|
"C": [90, 20, 70, 40],
|
|
57
55
|
}
|
|
58
56
|
)
|
|
59
|
-
x = "x"
|
|
60
|
-
y_columns = ["A", "B", "C"]
|
|
61
57
|
|
|
62
58
|
fig, ax = plt.subplots(figsize=(8, 4))
|
|
63
59
|
bumplot(
|
|
64
|
-
x=x,
|
|
65
|
-
y_columns=
|
|
60
|
+
x="x",
|
|
61
|
+
y_columns=["A", "B", "C"],
|
|
66
62
|
data=data,
|
|
67
63
|
curve_force=0.5,
|
|
68
64
|
plot_kwargs={"lw": 4},
|
|
@@ -74,7 +70,7 @@ ax.spines[["top", "right", "left", "bottom"]].set_visible(False)
|
|
|
74
70
|
ax.grid(alpha=0.4)
|
|
75
71
|
```
|
|
76
72
|
|
|
77
|
-

|
|
78
74
|
|
|
79
75
|
[See more examples](https://y-sunflower.github.io/bumplot/examples/)
|
|
80
76
|
|
|
@@ -83,3 +79,13 @@ ax.grid(alpha=0.4)
|
|
|
83
79
|
## Installation
|
|
84
80
|
|
|
85
81
|
pip install bumplot
|
|
82
|
+
|
|
83
|
+
<br><br>
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
- Automatic ranking of your data (with support for both `pandas` and
|
|
88
|
+
`polars`)
|
|
89
|
+
- Accept categorical data for x-axis
|
|
90
|
+
- Infinite customization capabilities
|
|
91
|
+
- Easy control of the curvature intensity
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
# `bumplot`: easily create and customize bump charts in Python
|
|
2
2
|
|
|
3
3
|
<!-- Automatically generated, uses README.qmd to modify README.md -->
|
|
4
4
|
|
|
5
|
-
# `bumplot`: easily create and customize bump charts in Python
|
|
6
|
-
|
|
7
5
|
<img src="https://github.com/JosephBARBIERDARNAL/static/blob/main/python-libs/bumplot/image.png?raw=true" alt="bumplot logo" align="right" width="180px"/>
|
|
8
6
|
|
|
9
7
|
`bumplot` is a small Python package made to facilitate the creation of
|
|
@@ -21,7 +19,7 @@ documentation](https://y-sunflower.github.io/bumplot/)
|
|
|
21
19
|
|
|
22
20
|
## Get started
|
|
23
21
|
|
|
24
|
-
```
|
|
22
|
+
```python
|
|
25
23
|
import matplotlib.pyplot as plt
|
|
26
24
|
import pandas as pd
|
|
27
25
|
|
|
@@ -35,13 +33,11 @@ data = pd.DataFrame(
|
|
|
35
33
|
"C": [90, 20, 70, 40],
|
|
36
34
|
}
|
|
37
35
|
)
|
|
38
|
-
x = "x"
|
|
39
|
-
y_columns = ["A", "B", "C"]
|
|
40
36
|
|
|
41
37
|
fig, ax = plt.subplots(figsize=(8, 4))
|
|
42
38
|
bumplot(
|
|
43
|
-
x=x,
|
|
44
|
-
y_columns=
|
|
39
|
+
x="x",
|
|
40
|
+
y_columns=["A", "B", "C"],
|
|
45
41
|
data=data,
|
|
46
42
|
curve_force=0.5,
|
|
47
43
|
plot_kwargs={"lw": 4},
|
|
@@ -53,7 +49,7 @@ ax.spines[["top", "right", "left", "bottom"]].set_visible(False)
|
|
|
53
49
|
ax.grid(alpha=0.4)
|
|
54
50
|
```
|
|
55
51
|
|
|
56
|
-

|
|
57
53
|
|
|
58
54
|
[See more examples](https://y-sunflower.github.io/bumplot/examples/)
|
|
59
55
|
|
|
@@ -62,3 +58,13 @@ ax.grid(alpha=0.4)
|
|
|
62
58
|
## Installation
|
|
63
59
|
|
|
64
60
|
pip install bumplot
|
|
61
|
+
|
|
62
|
+
<br><br>
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- Automatic ranking of your data (with support for both `pandas` and
|
|
67
|
+
`polars`)
|
|
68
|
+
- Accept categorical data for x-axis
|
|
69
|
+
- Infinite customization capabilities
|
|
70
|
+
- Easy control of the curvature intensity
|
|
Binary file
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import matplotlib.pyplot as plt
|
|
2
|
-
|
|
3
1
|
import narwhals as nw
|
|
4
2
|
from narwhals.typing import IntoDataFrame
|
|
5
3
|
|
|
6
4
|
|
|
7
|
-
def _get_first_n_colors(n=int) -> list[str]:
|
|
8
|
-
"""
|
|
9
|
-
Get the first n colors from matplotlib rcParams.
|
|
10
|
-
"""
|
|
11
|
-
colors: list[str] = plt.rcParams["axes.prop_cycle"].by_key()["color"][:n]
|
|
12
|
-
return colors
|
|
13
|
-
|
|
14
|
-
|
|
15
5
|
def _ranked_df(df: IntoDataFrame, x: str, y_columns: list[str]):
|
|
16
6
|
"""
|
|
17
7
|
Convert a dataframe to a ranked version of it.
|
|
@@ -20,9 +10,18 @@ def _ranked_df(df: IntoDataFrame, x: str, y_columns: list[str]):
|
|
|
20
10
|
|
|
21
11
|
df_native_ranked = (
|
|
22
12
|
df_native.unpivot(on=y_columns, index=x)
|
|
23
|
-
.with_columns(nw.col("value").rank("ordinal").over(x))
|
|
13
|
+
.with_columns(nw.col("value").rank("ordinal", descending=True).over(x))
|
|
24
14
|
.pivot(on="variable", index=x, values="value")
|
|
25
15
|
.select(nw.col(x), nw.col(y_columns))
|
|
26
16
|
)
|
|
27
17
|
|
|
28
18
|
return df_native_ranked
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _to_ordinal(n: int) -> str:
|
|
22
|
+
"""Convert number to ordinal string (1 -> '1st', 2 -> '2nd', etc.)"""
|
|
23
|
+
if 11 <= n % 100 <= 13:
|
|
24
|
+
suffix = "th"
|
|
25
|
+
else:
|
|
26
|
+
suffix = {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
|
|
27
|
+
return f"{n}{suffix}"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from collections import ChainMap
|
|
2
|
+
from itertools import cycle
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
from matplotlib.axes import Axes
|
|
6
|
+
from matplotlib.path import Path
|
|
7
|
+
import matplotlib.patches as patches
|
|
8
|
+
from matplotlib.cbook import normalize_kwargs
|
|
9
|
+
from matplotlib.collections import PathCollection
|
|
10
|
+
from matplotlib.patches import PathPatch
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from narwhals.typing import IntoDataFrame
|
|
15
|
+
|
|
16
|
+
from .bezier import bezier_curve
|
|
17
|
+
from ._utils import _ranked_df, _to_ordinal
|
|
18
|
+
from .opts import BumpOpts, _get_plot_kwargs, _get_scatter_kwargs
|
|
19
|
+
|
|
20
|
+
from typing import Any, Iterable, Tuple
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def bumplot(
|
|
24
|
+
x: str,
|
|
25
|
+
y_columns: Iterable[str | tuple[str, BumpOpts]],
|
|
26
|
+
data: IntoDataFrame,
|
|
27
|
+
curve_force: float = 1,
|
|
28
|
+
invert_y_axis: bool = True,
|
|
29
|
+
colors: Iterable[str] | None = None,
|
|
30
|
+
plot_kwargs: dict[str, Any] = {},
|
|
31
|
+
scatter_kwargs: dict[str, Any] = {},
|
|
32
|
+
ax: Axes | None = None,
|
|
33
|
+
ordinal_labels: bool = False,
|
|
34
|
+
) -> Tuple[Axes, dict[str, Tuple[PathPatch, PathCollection]]]:
|
|
35
|
+
"""
|
|
36
|
+
Creates bump plot, or bump chart, from multiple numerical
|
|
37
|
+
columns.
|
|
38
|
+
|
|
39
|
+
It requires the data to be in wide format (e.g., one column
|
|
40
|
+
per line you want to plot).
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
x: colname of the x-axis variable
|
|
44
|
+
y_columns: colnames of the y-axis variables and their plotting options.
|
|
45
|
+
data: A dataframe
|
|
46
|
+
curve_force: Smoothing factor controlling curve tightness. Higher
|
|
47
|
+
values increase curvature by moving control points further away
|
|
48
|
+
from the anchors.
|
|
49
|
+
invert_y_axis: Whether to invert y axis
|
|
50
|
+
colors: An optional list of colors
|
|
51
|
+
plot_kwargs: Additional arguments passed to `patches.PathPatch()`
|
|
52
|
+
scatter_kwargs: Additional arguments passed to `scatter()`
|
|
53
|
+
ax: The matplotlib Axes used. Default to `plt.gca()`
|
|
54
|
+
ordinal_labels: If True, converts y-axis labels to ordinal numbers (1st, 2nd, 3rd, etc.)
|
|
55
|
+
Returns:
|
|
56
|
+
The matplotlib Axes with the bump plot
|
|
57
|
+
"""
|
|
58
|
+
_plot_ax: Axes = ax if ax is not None else plt.gca()
|
|
59
|
+
colors_iterable = (
|
|
60
|
+
colors
|
|
61
|
+
if colors is not None
|
|
62
|
+
else plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
y_bumps: list[tuple[str, BumpOpts]] = [
|
|
66
|
+
(y, BumpOpts()) if isinstance(y, str) else y for y in y_columns
|
|
67
|
+
]
|
|
68
|
+
ranked: IntoDataFrame = _ranked_df(data, x=x, y_columns=[y for y, _ in y_bumps])
|
|
69
|
+
x_values_raw: np.ndarray = np.ravel(ranked.select(x).to_numpy())
|
|
70
|
+
|
|
71
|
+
if np.issubdtype(x_values_raw.dtype, np.number):
|
|
72
|
+
x_values = x_values_raw
|
|
73
|
+
x_labels = x_values_raw
|
|
74
|
+
else:
|
|
75
|
+
uniques = list(dict.fromkeys(x_values_raw)) # preserves order
|
|
76
|
+
mapping = {val: i for i, val in enumerate(uniques)}
|
|
77
|
+
x_values = np.array([mapping[val] for val in x_values_raw], dtype=int)
|
|
78
|
+
x_labels = x_values_raw
|
|
79
|
+
|
|
80
|
+
artists = {}
|
|
81
|
+
for (name, bump_opts), color in zip(y_bumps, cycle(colors_iterable)):
|
|
82
|
+
y_values: np.ndarray = np.ravel(ranked.select(name).to_numpy())
|
|
83
|
+
vertices, codes = bezier_curve(
|
|
84
|
+
x=x_values,
|
|
85
|
+
y=y_values,
|
|
86
|
+
force=curve_force,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
path: Path = Path(vertices=vertices, codes=codes)
|
|
90
|
+
patch: PathPatch = patches.PathPatch(
|
|
91
|
+
path=path,
|
|
92
|
+
facecolor="none",
|
|
93
|
+
**ChainMap(
|
|
94
|
+
_get_plot_kwargs(bump_opts),
|
|
95
|
+
normalize_kwargs(plot_kwargs, PathPatch),
|
|
96
|
+
{"edgecolor": color},
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
_plot_ax.add_patch(patch)
|
|
100
|
+
|
|
101
|
+
scatter = _plot_ax.scatter(
|
|
102
|
+
x_values,
|
|
103
|
+
y_values,
|
|
104
|
+
label=name,
|
|
105
|
+
**ChainMap(
|
|
106
|
+
_get_scatter_kwargs(bump_opts),
|
|
107
|
+
normalize_kwargs(scatter_kwargs, PathCollection),
|
|
108
|
+
{"facecolor": color},
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
artists[name] = (patch, scatter)
|
|
112
|
+
|
|
113
|
+
ticks: list[int] = list(range(1, len(y_bumps) + 1))
|
|
114
|
+
|
|
115
|
+
if invert_y_axis:
|
|
116
|
+
_plot_ax.invert_yaxis()
|
|
117
|
+
else:
|
|
118
|
+
ticks: list[int] = list(reversed(ticks))
|
|
119
|
+
|
|
120
|
+
_plot_ax.set_yticks(ticks=ticks)
|
|
121
|
+
|
|
122
|
+
labels = (
|
|
123
|
+
[_to_ordinal(tick) for tick in ticks]
|
|
124
|
+
if ordinal_labels
|
|
125
|
+
else [str(tick) for tick in ticks]
|
|
126
|
+
)
|
|
127
|
+
_plot_ax.set_yticklabels(labels)
|
|
128
|
+
|
|
129
|
+
_plot_ax.set_xticks(ticks=np.unique(x_values), labels=np.unique(x_labels))
|
|
130
|
+
|
|
131
|
+
return _plot_ax, artists
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from typing import Any, TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BumpOpts(TypedDict, total=False):
|
|
5
|
+
"""
|
|
6
|
+
Typed dictionary describing high-level styling options for a bump plot.
|
|
7
|
+
|
|
8
|
+
This type represents a user-facing, backend-agnostic set of visual options
|
|
9
|
+
that can be applied to both line (plot) and marker (scatter) artists.
|
|
10
|
+
|
|
11
|
+
All fields are optional. Keys are validated at runtime when constructed via
|
|
12
|
+
`opts()` to ensure only supported options are provided.
|
|
13
|
+
|
|
14
|
+
The options are later translated into Matplotlib-specific keyword arguments
|
|
15
|
+
via `_get_plot_kwargs()` and `_get_scatter_kwargs()`.
|
|
16
|
+
|
|
17
|
+
Notes
|
|
18
|
+
-----
|
|
19
|
+
- Keys prefixed with `line_` apply to line artists.
|
|
20
|
+
- Keys prefixed with `marker_` apply to scatter/marker artists.
|
|
21
|
+
- Some options (e.g. `clip_on` and `zorder`) apply to both.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
line_alpha: float
|
|
25
|
+
line_color: str
|
|
26
|
+
line_style: str
|
|
27
|
+
line_width: float
|
|
28
|
+
|
|
29
|
+
marker: str
|
|
30
|
+
marker_size: float
|
|
31
|
+
marker_alpha: float
|
|
32
|
+
marker_facecolor: str
|
|
33
|
+
marker_edgecolor: str
|
|
34
|
+
marker_edgewidth: float
|
|
35
|
+
|
|
36
|
+
clip_on: bool
|
|
37
|
+
zorder: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
PLOT_MAPPINGS: dict[str, str] = {
|
|
41
|
+
"line_alpha": "alpha",
|
|
42
|
+
"line_color": "edgecolor",
|
|
43
|
+
"line_style": "linestyle",
|
|
44
|
+
"line_width": "linewidth",
|
|
45
|
+
"clip_on": "clip_on",
|
|
46
|
+
"zorder": "zorder",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
SCATTER_MAPPINGS: dict[str, str] = {
|
|
50
|
+
"marker": "marker",
|
|
51
|
+
"marker_alpha": "alpha",
|
|
52
|
+
"marker_edgecolor": "edgecolor",
|
|
53
|
+
"marker_edgewidth": "linewidth",
|
|
54
|
+
"marker_facecolor": "facecolor",
|
|
55
|
+
"marker_size": "s",
|
|
56
|
+
"clip_on": "clip_on",
|
|
57
|
+
"zorder": "zorder",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# TODO: For >=Python 3.11 (https://docs.python.org/3/library/typing.html#typing.Unpack)
|
|
62
|
+
def opts(**kwargs: Any) -> BumpOpts:
|
|
63
|
+
"""
|
|
64
|
+
Construct a validated `BumpOpts` mapping from keyword arguments.
|
|
65
|
+
|
|
66
|
+
This function enforces that all provided keyword arguments correspond to
|
|
67
|
+
supported `BumpOpts` fields. Any unexpected keys result in a `TypeError`.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
**kwargs: Unpack[BumpOpts]
|
|
72
|
+
Keyword arguments corresponding to fields defined on `BumpOpts`.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
BumpOpts
|
|
77
|
+
A typed dictionary containing the validated bump plot options.
|
|
78
|
+
|
|
79
|
+
Raises
|
|
80
|
+
------
|
|
81
|
+
TypeError
|
|
82
|
+
If one or more unsupported keyword arguments are provided.
|
|
83
|
+
|
|
84
|
+
Notes
|
|
85
|
+
-----
|
|
86
|
+
This function exists to provide runtime validation for `BumpOpts`,
|
|
87
|
+
which is otherwise only enforced statically by type checkers.
|
|
88
|
+
"""
|
|
89
|
+
unsupported_keys = kwargs.keys() - BumpOpts.__annotations__
|
|
90
|
+
if unsupported_keys:
|
|
91
|
+
unsupported_repr = ", ".join(map(repr, sorted(unsupported_keys)))
|
|
92
|
+
msg = f"BumpOpts got unexpected keyword argument(s): {unsupported_repr}"
|
|
93
|
+
raise TypeError(msg)
|
|
94
|
+
|
|
95
|
+
return BumpOpts(**kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def opts_from_color(color, /, **kwargs: Any) -> BumpOpts:
|
|
99
|
+
"""
|
|
100
|
+
Construct a `BumpOpts` mapping using a single color applied consistently
|
|
101
|
+
across line and marker components.
|
|
102
|
+
|
|
103
|
+
The provided color is applied to:
|
|
104
|
+
- `line_color`
|
|
105
|
+
- `marker_facecolor`
|
|
106
|
+
- `marker_edgecolor`
|
|
107
|
+
|
|
108
|
+
Any explicitly provided keyword arguments take precedence over the color-based
|
|
109
|
+
defaults.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
color
|
|
114
|
+
The color to apply to line and marker components.
|
|
115
|
+
**kwargs
|
|
116
|
+
Additional or overriding bump plot options (passed to `opts`).
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
BumpOpts
|
|
121
|
+
A validated bump plot options mapping.
|
|
122
|
+
|
|
123
|
+
See Also
|
|
124
|
+
--------
|
|
125
|
+
opts: Performs keyword validation and construction.
|
|
126
|
+
"""
|
|
127
|
+
overrides = ["line_color", "marker_facecolor", "marker_edgecolor"]
|
|
128
|
+
new_kwargs = {k: color for k in overrides} | kwargs
|
|
129
|
+
return opts(**new_kwargs)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _get_plot_kwargs(kwargs: BumpOpts, /) -> dict[str, Any]:
|
|
133
|
+
"""
|
|
134
|
+
Extract Matplotlib `plot` keyword arguments from a `BumpOpts` mapping.
|
|
135
|
+
|
|
136
|
+
This function translates bump-plot-specific option names into the equivalent
|
|
137
|
+
Matplotlib `Axes.plot` keyword arguments using `bumplot.opts.PLOT_MAPPINGS`.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
kwargs: Unpack[BumpOpts]
|
|
142
|
+
A `BumpOpts` mapping containing zero or more plot-related options.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
dict[str, Any]
|
|
147
|
+
A dictionary of Matplotlib-compatible keyword arguments suitable for
|
|
148
|
+
`Axes.patches.PathPatch`.
|
|
149
|
+
|
|
150
|
+
Notes
|
|
151
|
+
-----
|
|
152
|
+
- Only keys present in both `kwargs` and `PLOT_MAPPINGS` are included.
|
|
153
|
+
"""
|
|
154
|
+
plot_kwargs = {}
|
|
155
|
+
for bump_arg, mpl_arg in PLOT_MAPPINGS.items():
|
|
156
|
+
try:
|
|
157
|
+
plot_kwargs[mpl_arg] = kwargs[bump_arg] # type: ignore[invalid-key]
|
|
158
|
+
except KeyError:
|
|
159
|
+
pass
|
|
160
|
+
return plot_kwargs
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _get_scatter_kwargs(kwargs: BumpOpts, /) -> dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Extract Matplotlib `scatter` keyword arguments from a `BumpOpts` mapping.
|
|
166
|
+
|
|
167
|
+
This function translates bump-plot-specific option names into the equivalent
|
|
168
|
+
Matplotlib `Axes.scatter` keyword arguments using `bumplot.opts.SCATTER_MAPPINGS`.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
kwargs: Unpack[BumpOpts]
|
|
173
|
+
A `BumpOpts` mapping containing zero or more plot-related options.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
dict[str, Any]
|
|
178
|
+
A dictionary of Matplotlib-compatible keyword arguments suitable for
|
|
179
|
+
`Axes.scatter`.
|
|
180
|
+
|
|
181
|
+
Notes
|
|
182
|
+
-----
|
|
183
|
+
- Only keys present in both `kwargs` and `SCATTER_MAPPINGS` are included.
|
|
184
|
+
"""
|
|
185
|
+
scatter_kwargs = {}
|
|
186
|
+
for bump_arg, mpl_arg in SCATTER_MAPPINGS.items():
|
|
187
|
+
try:
|
|
188
|
+
scatter_kwargs[mpl_arg] = kwargs[bump_arg] # type: ignore[invalid-key]
|
|
189
|
+
except KeyError:
|
|
190
|
+
pass
|
|
191
|
+
return scatter_kwargs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bumplot
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Easily create and customize bump charts in Python
|
|
5
5
|
Author-email: Joseph Barbier <joseph.barbierdarnal@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,10 +8,10 @@ Project-URL: Homepage, https://y-sunflower.github.io/bumplot/
|
|
|
8
8
|
Project-URL: Issues, https://github.com/y-sunflower/bumplot/issues
|
|
9
9
|
Project-URL: Documentation, https://y-sunflower.github.io/bumplot/
|
|
10
10
|
Project-URL: Repository, https://github.com/y-sunflower/bumplot
|
|
11
|
-
Keywords: bump,bump plot,bump charts,matplotlib,data,visualization
|
|
11
|
+
Keywords: bump,bump plot,bump charts,matplotlib,data,visualization,bezier,curve
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Development Status ::
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
@@ -19,12 +19,10 @@ Requires-Dist: matplotlib>=3.9.4
|
|
|
19
19
|
Requires-Dist: narwhals>=2.0.0
|
|
20
20
|
Dynamic: license-file
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# `bumplot`: easily create and customize bump charts in Python
|
|
23
23
|
|
|
24
24
|
<!-- Automatically generated, uses README.qmd to modify README.md -->
|
|
25
25
|
|
|
26
|
-
# `bumplot`: easily create and customize bump charts in Python
|
|
27
|
-
|
|
28
26
|
<img src="https://github.com/JosephBARBIERDARNAL/static/blob/main/python-libs/bumplot/image.png?raw=true" alt="bumplot logo" align="right" width="180px"/>
|
|
29
27
|
|
|
30
28
|
`bumplot` is a small Python package made to facilitate the creation of
|
|
@@ -42,7 +40,7 @@ documentation](https://y-sunflower.github.io/bumplot/)
|
|
|
42
40
|
|
|
43
41
|
## Get started
|
|
44
42
|
|
|
45
|
-
```
|
|
43
|
+
```python
|
|
46
44
|
import matplotlib.pyplot as plt
|
|
47
45
|
import pandas as pd
|
|
48
46
|
|
|
@@ -56,13 +54,11 @@ data = pd.DataFrame(
|
|
|
56
54
|
"C": [90, 20, 70, 40],
|
|
57
55
|
}
|
|
58
56
|
)
|
|
59
|
-
x = "x"
|
|
60
|
-
y_columns = ["A", "B", "C"]
|
|
61
57
|
|
|
62
58
|
fig, ax = plt.subplots(figsize=(8, 4))
|
|
63
59
|
bumplot(
|
|
64
|
-
x=x,
|
|
65
|
-
y_columns=
|
|
60
|
+
x="x",
|
|
61
|
+
y_columns=["A", "B", "C"],
|
|
66
62
|
data=data,
|
|
67
63
|
curve_force=0.5,
|
|
68
64
|
plot_kwargs={"lw": 4},
|
|
@@ -74,7 +70,7 @@ ax.spines[["top", "right", "left", "bottom"]].set_visible(False)
|
|
|
74
70
|
ax.grid(alpha=0.4)
|
|
75
71
|
```
|
|
76
72
|
|
|
77
|
-

|
|
78
74
|
|
|
79
75
|
[See more examples](https://y-sunflower.github.io/bumplot/examples/)
|
|
80
76
|
|
|
@@ -83,3 +79,13 @@ ax.grid(alpha=0.4)
|
|
|
83
79
|
## Installation
|
|
84
80
|
|
|
85
81
|
pip install bumplot
|
|
82
|
+
|
|
83
|
+
<br><br>
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
- Automatic ranking of your data (with support for both `pandas` and
|
|
88
|
+
`polars`)
|
|
89
|
+
- Accept categorical data for x-axis
|
|
90
|
+
- Infinite customization capabilities
|
|
91
|
+
- Easy control of the curvature intensity
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
.gitignore
|
|
3
3
|
.pre-commit-config.yaml
|
|
4
4
|
LICENSE
|
|
5
|
-
Makefile
|
|
6
5
|
README.md
|
|
7
6
|
coverage-badge.svg
|
|
7
|
+
justfile
|
|
8
8
|
mkdocs.yaml
|
|
9
9
|
pyproject.toml
|
|
10
10
|
.github/workflows/doc.yaml
|
|
@@ -12,11 +12,12 @@ pyproject.toml
|
|
|
12
12
|
.github/workflows/pypi.yaml
|
|
13
13
|
.github/workflows/tests.yaml
|
|
14
14
|
.github/workflows/type.yaml
|
|
15
|
-
README_files/figure-commonmark/cell-
|
|
15
|
+
README_files/figure-commonmark/cell-3-output-1.png
|
|
16
16
|
bumplot/__init__.py
|
|
17
17
|
bumplot/_utils.py
|
|
18
18
|
bumplot/bezier.py
|
|
19
19
|
bumplot/main.py
|
|
20
|
+
bumplot/opts.py
|
|
20
21
|
bumplot.egg-info/PKG-INFO
|
|
21
22
|
bumplot.egg-info/SOURCES.txt
|
|
22
23
|
bumplot.egg-info/dependency_links.txt
|
|
@@ -24,12 +25,15 @@ bumplot.egg-info/requires.txt
|
|
|
24
25
|
bumplot.egg-info/top_level.txt
|
|
25
26
|
docs/.gitignore
|
|
26
27
|
docs/README.qmd
|
|
28
|
+
docs/advanced-usage.md
|
|
27
29
|
docs/contributing.md
|
|
28
30
|
docs/examples.md
|
|
29
31
|
docs/index.md
|
|
32
|
+
docs/data/water-africa.csv
|
|
30
33
|
docs/reference/bezier.md
|
|
31
34
|
docs/reference/bumplot.md
|
|
32
35
|
docs/stylesheets/style.css
|
|
33
36
|
overrides/partials/footer.html
|
|
34
37
|
tests/__init__.py
|
|
35
|
-
tests/test_main.py
|
|
38
|
+
tests/test_main.py
|
|
39
|
+
tests/test_opts.py
|