bumplot 0.1.1__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.
Files changed (44) hide show
  1. {bumplot-0.1.1 → bumplot-0.2.1}/.github/workflows/tests.yaml +1 -2
  2. {bumplot-0.1.1 → bumplot-0.2.1}/.gitignore +2 -1
  3. {bumplot-0.1.1 → bumplot-0.2.1}/PKG-INFO +2 -2
  4. bumplot-0.2.1/bumplot/__init__.py +5 -0
  5. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot/_utils.py +9 -10
  6. bumplot-0.2.1/bumplot/main.py +131 -0
  7. bumplot-0.2.1/bumplot/opts.py +191 -0
  8. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot.egg-info/PKG-INFO +2 -2
  9. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot.egg-info/SOURCES.txt +6 -2
  10. bumplot-0.2.1/docs/advanced-usage.md +231 -0
  11. {bumplot-0.1.1 → bumplot-0.2.1}/docs/contributing.md +4 -2
  12. bumplot-0.2.1/docs/data/water-africa.csv +7 -0
  13. {bumplot-0.1.1 → bumplot-0.2.1}/docs/examples.md +59 -20
  14. {bumplot-0.1.1 → bumplot-0.2.1}/docs/index.md +9 -4
  15. bumplot-0.1.1/Makefile → bumplot-0.2.1/justfile +0 -2
  16. {bumplot-0.1.1 → bumplot-0.2.1}/mkdocs.yaml +1 -0
  17. {bumplot-0.1.1 → bumplot-0.2.1}/pyproject.toml +5 -2
  18. bumplot-0.2.1/tests/test_main.py +196 -0
  19. bumplot-0.2.1/tests/test_opts.py +108 -0
  20. bumplot-0.1.1/bumplot/__init__.py +0 -4
  21. bumplot-0.1.1/bumplot/main.py +0 -104
  22. bumplot-0.1.1/tests/test_main.py +0 -86
  23. {bumplot-0.1.1 → bumplot-0.2.1}/.coverage +0 -0
  24. {bumplot-0.1.1 → bumplot-0.2.1}/.github/workflows/doc.yaml +0 -0
  25. {bumplot-0.1.1 → bumplot-0.2.1}/.github/workflows/lint.yaml +0 -0
  26. {bumplot-0.1.1 → bumplot-0.2.1}/.github/workflows/pypi.yaml +0 -0
  27. {bumplot-0.1.1 → bumplot-0.2.1}/.github/workflows/type.yaml +0 -0
  28. {bumplot-0.1.1 → bumplot-0.2.1}/.pre-commit-config.yaml +0 -0
  29. {bumplot-0.1.1 → bumplot-0.2.1}/LICENSE +0 -0
  30. {bumplot-0.1.1 → bumplot-0.2.1}/README.md +0 -0
  31. {bumplot-0.1.1 → bumplot-0.2.1}/README_files/figure-commonmark/cell-3-output-1.png +0 -0
  32. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot/bezier.py +0 -0
  33. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot.egg-info/dependency_links.txt +0 -0
  34. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot.egg-info/requires.txt +0 -0
  35. {bumplot-0.1.1 → bumplot-0.2.1}/bumplot.egg-info/top_level.txt +0 -0
  36. {bumplot-0.1.1 → bumplot-0.2.1}/coverage-badge.svg +0 -0
  37. {bumplot-0.1.1 → bumplot-0.2.1}/docs/.gitignore +0 -0
  38. {bumplot-0.1.1 → bumplot-0.2.1}/docs/README.qmd +0 -0
  39. {bumplot-0.1.1 → bumplot-0.2.1}/docs/reference/bezier.md +0 -0
  40. {bumplot-0.1.1 → bumplot-0.2.1}/docs/reference/bumplot.md +0 -0
  41. {bumplot-0.1.1 → bumplot-0.2.1}/docs/stylesheets/style.css +0 -0
  42. {bumplot-0.1.1 → bumplot-0.2.1}/overrides/partials/footer.html +0 -0
  43. {bumplot-0.1.1 → bumplot-0.2.1}/setup.cfg +0 -0
  44. {bumplot-0.1.1 → bumplot-0.2.1}/tests/__init__.py +0 -0
@@ -10,8 +10,7 @@ jobs:
10
10
  strategy:
11
11
  matrix:
12
12
  python-version:
13
- - "3.11"
14
- - "3.12"
13
+ - "3.10"
15
14
  - "3.13"
16
15
  env:
17
16
  UV_PYTHON: ${{ matrix.python-version }}
@@ -20,4 +20,5 @@ env/
20
20
 
21
21
  *.zip
22
22
  sandbox/
23
- scripts/release.sh
23
+ release.sh
24
+ .claude/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bumplot
3
- Version: 0.1.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
@@ -11,7 +11,7 @@ Project-URL: Repository, https://github.com/y-sunflower/bumplot
11
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 :: 4 - Beta
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
@@ -0,0 +1,5 @@
1
+ from .main import bumplot
2
+ from .opts import opts, opts_from_color
3
+
4
+ __version__ = "0.2.1"
5
+ __all__ = ["bumplot", "opts", "opts_from_color"]
@@ -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.
@@ -26,3 +16,12 @@ def _ranked_df(df: IntoDataFrame, x: str, y_columns: list[str]):
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.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
@@ -11,7 +11,7 @@ Project-URL: Repository, https://github.com/y-sunflower/bumplot
11
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 :: 4 - Beta
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
@@ -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
@@ -17,6 +17,7 @@ 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
@@ -0,0 +1,231 @@
1
+ `bumplot` is designed to integrate seamlessly with matplotlib and allow you to control virtually everything.
2
+
3
+ Here you will find examples that show how to use `bumplot` in more complex, but also more realistic, cases based on real data.
4
+
5
+ These examples are also designed to be easy to reproduce: you should be able to simply copy and paste the code onto your computer.
6
+
7
+ <br>
8
+ <br>
9
+
10
+ ## Water sources in Africa
11
+
12
+ ```py
13
+ # mkdocs: render
14
+ import pandas as pd
15
+ import matplotlib.pyplot as plt
16
+ from pyfonts import set_default_font, load_google_font
17
+
18
+ from bumplot import bumplot, opts_from_color
19
+
20
+ url = "https://raw.githubusercontent.com/y-sunflower/bumplot/main/docs/data/water-africa.csv"
21
+ df = pd.read_csv(url)
22
+
23
+ font = load_google_font("Poppins")
24
+ set_default_font(font)
25
+
26
+ highlight = {
27
+ "Zambia": opts_from_color("#2a9d8f", zorder=2, line_width=3, marker_size=100),
28
+ "Tanzania": opts_from_color("#bb3e03", zorder=2, line_width=3, marker_size=100),
29
+ }
30
+ countries = [name for name in df.columns if not (name.startswith("decade"))]
31
+
32
+ fig, ax = plt.subplots(figsize=(12, 5))
33
+ _, bump_artists = bumplot(
34
+ x="decade",
35
+ y_columns=[(name, highlight.get(name, {})) for name in countries],
36
+ data=df,
37
+ curve_force=0.7,
38
+ ordinal_labels=True,
39
+ colors=["lightgray"]
40
+ )
41
+ ax.spines[["top", "right", "left", "bottom"]].set_visible(False)
42
+ ax.margins(x=0.01)
43
+ ax.tick_params(size=0)
44
+ ax.tick_params(axis="x", labelsize=14, pad=10)
45
+ ax.set_xticks(ax.get_xticks(), labels=[round(tick) for tick in ax.get_xticks()])
46
+
47
+ font_bold = load_google_font("Poppins", weight="bold")
48
+ for name, (_, scatter) in bump_artists.items():
49
+ _, last_y = scatter.get_offsets()[-1]
50
+ ax.text(
51
+ x=1,
52
+ y=last_y,
53
+ s=name,
54
+ size=11,
55
+ color=scatter.get_facecolor(),
56
+ va="center",
57
+ ha="left",
58
+ font=font_bold,
59
+ transform=ax.get_yaxis_transform(),
60
+ )
61
+
62
+ fig.text(
63
+ x=0.5,
64
+ y=0.98,
65
+ s="# of water sources installations in africa".upper(),
66
+ size=18,
67
+ ha="center",
68
+ va="top",
69
+ font=font_bold,
70
+ )
71
+ fig.text(
72
+ x=0.5,
73
+ y=0.91,
74
+ s="Ranking of each country based on the total number of water source installations for each decade",
75
+ size=10,
76
+ ha="center",
77
+ va="top",
78
+ color="grey",
79
+ font=load_google_font("Poppins", italic=True),
80
+ )
81
+ fig.text(
82
+ x=0.12,
83
+ y=0,
84
+ s="Made with bumplot\nGraphic: Joseph Barbier",
85
+ va="top",
86
+ color="grey",
87
+ font=load_google_font("Poppins", italic=True),
88
+ size=10,
89
+ )
90
+ ```
91
+
92
+ <br>
93
+ <br>
94
+
95
+ ## Global CO₂ Emissions Rankings
96
+
97
+ ```py
98
+ # mkdocs: render
99
+ import pandas as pd
100
+ import matplotlib.pyplot as plt
101
+ from pyfonts import set_default_font, load_google_font
102
+
103
+ from bumplot import bumplot, opts_from_color
104
+
105
+ url = "https://raw.githubusercontent.com/owid/co2-data/master/owid-co2-data.csv"
106
+ df_raw = pd.read_csv(url)
107
+ countries = [
108
+ "United States",
109
+ "China",
110
+ "India",
111
+ "Russia",
112
+ "Japan",
113
+ "Germany",
114
+ "Iran",
115
+ "South Korea",
116
+ "Saudi Arabia",
117
+ "Indonesia",
118
+ ]
119
+ years = [1930, 1940, 1950, 1960, 1970, 1980, 1990, 2000, 2010, 2020]
120
+ df = (
121
+ df_raw[df_raw["country"].isin(countries) & df_raw["year"].isin(years)][
122
+ ["year", "country", "co2"]
123
+ ]
124
+ .dropna()
125
+ .pivot(index="year", columns="country", values="co2")
126
+ .reset_index()
127
+ )
128
+
129
+ font = load_google_font("Inter")
130
+ set_default_font(font)
131
+
132
+ highlight = {
133
+ "China": opts_from_color(
134
+ "#ae2012",
135
+ marker_size=100,
136
+ marker_edgewidth=2,
137
+ zorder=10,
138
+ ),
139
+ "United States": opts_from_color(
140
+ "#005f73",
141
+ marker_size=100,
142
+ marker_edgewidth=2,
143
+ zorder=10,
144
+ ),
145
+ }
146
+
147
+ countries_to_plot = [col for col in df.columns if col != "year"]
148
+
149
+ fig, ax = plt.subplots(figsize=(10, 7))
150
+ _, bump_artists = bumplot(
151
+ x="year",
152
+ y_columns=[(name, highlight.get(name, {})) for name in countries_to_plot],
153
+ data=df,
154
+ curve_force=0.7,
155
+ ordinal_labels=True,
156
+ colors=["#D1D5DB"],
157
+ scatter_kwargs={
158
+ "edgecolor": "#D1D5DB",
159
+ "s": 40,
160
+ "linewidth": 1,
161
+ "facecolor": "#F3F4F6",
162
+ },
163
+ plot_kwargs={"linewidth": 1.5},
164
+ )
165
+
166
+ ax.spines[["top", "right", "left", "bottom"]].set_visible(False)
167
+ ax.tick_params(axis="x", labelsize=11, pad=4, colors="#4a4e69")
168
+ ax.tick_params(size=0)
169
+
170
+ font_medium = load_google_font("Inter", weight="medium")
171
+ for country in countries:
172
+ if country in bump_artists:
173
+ _, scatter = bump_artists[country]
174
+ _, last_y = scatter.get_offsets()[-1]
175
+
176
+ color = "#ae2012" if country == "China" else "#4a4e69"
177
+ weight = "bold" if country == "China" else "regular"
178
+ font_label = load_google_font("Inter", weight=weight)
179
+
180
+ ax.text(
181
+ x=1,
182
+ y=last_y,
183
+ s=country,
184
+ size=11,
185
+ color=color,
186
+ va="center",
187
+ ha="left",
188
+ font=font_label,
189
+ transform=ax.get_yaxis_transform(),
190
+ )
191
+
192
+ font_bold = load_google_font("Inter", weight="bold")
193
+ fig.text(
194
+ x=0.5,
195
+ y=0.97,
196
+ s="The Rise of China's CO₂ Emissions",
197
+ size=20,
198
+ ha="center",
199
+ va="top",
200
+ font=font_bold,
201
+ color="#111827",
202
+ )
203
+ fig.text(
204
+ x=0.5,
205
+ y=0.92,
206
+ s="Ranking of top 10 emitting countries, 1930–2020",
207
+ size=11,
208
+ ha="center",
209
+ va="top",
210
+ color="#4a4e69",
211
+ )
212
+ fig.text(
213
+ x=0.1,
214
+ y=0.04,
215
+ s="Source: Our World in Data · CO₂ emissions dataset",
216
+ va="bottom",
217
+ ha="left",
218
+ color="#4a4e69",
219
+ size=8,
220
+ )
221
+ fig.text(
222
+ x=0.9,
223
+ y=0.04,
224
+ s="Made with bumplot",
225
+ va="bottom",
226
+ ha="right",
227
+ color="#4a4e69",
228
+ font=font_bold,
229
+ size=8,
230
+ )
231
+ ```
@@ -37,16 +37,18 @@ You can now make changes to the package and start coding!
37
37
 
38
38
  ### Run the test
39
39
 
40
+ It's recommended to use [just](https://github.com/casey/just).
41
+
40
42
  - Test that everything works correctly by running:
41
43
 
42
44
  ```bash
43
- make test
45
+ just test
44
46
  ```
45
47
 
46
48
  ### Preview documentation locally
47
49
 
48
50
  ```bash
49
- make preview
51
+ just preview
50
52
  ```
51
53
 
52
54
  ## Push changes