MatplotLibAPI 3.2.19__tar.gz → 3.2.20__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 (55) hide show
  1. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/.github/workflows/ci.yml +2 -2
  2. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/BoxViolin.py +4 -2
  3. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Wordcloud.py +13 -33
  4. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/PKG-INFO +1 -1
  5. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/pyproject.toml +1 -1
  6. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/scripts/generate_sample_data.py +1 -8
  7. matplotlibapi-3.2.20/tests/__init__.py +1 -0
  8. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/conftest.py +3 -5
  9. matplotlibapi-3.2.20/tests/test_wordcloud.py +77 -0
  10. matplotlibapi-3.2.19/tests/__init__.py +0 -0
  11. matplotlibapi-3.2.19/tests/test_wordcloud.py +0 -38
  12. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/.github/workflows/python-publish.yml +0 -0
  13. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/.gitignore +0 -0
  14. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/AGENTS.md +0 -0
  15. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/LICENSE +0 -0
  16. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Area.py +0 -0
  17. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Bar.py +0 -0
  18. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Bubble.py +0 -0
  19. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Composite.py +0 -0
  20. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Heatmap.py +0 -0
  21. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Histogram.py +0 -0
  22. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Network.py +0 -0
  23. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Pie.py +0 -0
  24. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Pivot.py +0 -0
  25. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Sankey.py +0 -0
  26. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/StyleTemplate.py +0 -0
  27. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Sunburst.py +0 -0
  28. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Table.py +0 -0
  29. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Timeserie.py +0 -0
  30. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Treemap.py +0 -0
  31. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/Waffle.py +0 -0
  32. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/__init__.py +0 -0
  33. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/_typing.py +0 -0
  34. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/_visualization_utils.py +0 -0
  35. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/MatplotLibAPI/accessor.py +0 -0
  36. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/README.md +0 -0
  37. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/SECURITY.md +0 -0
  38. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/SUGGESTIONS.md +0 -0
  39. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_area.py +0 -0
  40. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_bar.py +0 -0
  41. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_box_violin.py +0 -0
  42. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_bubble.py +0 -0
  43. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_dependencies.py +0 -0
  44. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_heatmap.py +0 -0
  45. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_histogram.py +0 -0
  46. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_network.py +0 -0
  47. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_pie.py +0 -0
  48. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_pivot.py +0 -0
  49. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_sankey.py +0 -0
  50. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_smoke.py +0 -0
  51. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_sunburst.py +0 -0
  52. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_table.py +0 -0
  53. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_timeseries.py +0 -0
  54. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_treemap.py +0 -0
  55. {matplotlibapi-3.2.19 → matplotlibapi-3.2.20}/tests/test_waffle.py +0 -0
@@ -31,12 +31,12 @@ jobs:
31
31
 
32
32
  - name: Run style checks
33
33
  run: |
34
- pydocstyle MatplotLibAPI
34
+ pydocstyle .
35
35
  black --check .
36
36
 
37
37
  - name: Run static type analysis
38
38
  run: |
39
- pyright MatplotLibAPI
39
+ pyright .
40
40
 
41
41
  - name: Run tests with coverage
42
42
  run: |
@@ -33,10 +33,12 @@ def aplot_box_violin(
33
33
  validate_dataframe(pd_df, cols=cols)
34
34
  plot_ax = _get_axis(ax)
35
35
 
36
+ common_kwargs = {"data": pd_df, "x": by, "y": column, "palette": style.palette}
37
+
36
38
  if violin:
37
- sns.violinplot(data=pd_df, x=by, y=column, palette=style.palette, ax=plot_ax)
39
+ sns.violinplot(**common_kwargs, hue=by, legend=False, ax=plot_ax)
38
40
  else:
39
- sns.boxplot(data=pd_df, x=by, y=column, palette=style.palette, ax=plot_ax)
41
+ sns.boxplot(**common_kwargs, hue=by, legend=False, ax=plot_ax)
40
42
 
41
43
  plot_ax.set_facecolor(style.background_color)
42
44
  plot_ax.set_ylabel(string_formatter(column))
@@ -25,33 +25,6 @@ WORDCLOUD_STYLE_TEMPLATE = StyleTemplate(
25
25
  )
26
26
 
27
27
 
28
- def _normalize_weights(weights: Sequence[float], base_size: int) -> np.ndarray:
29
- """Normalize weights to a range of font sizes.
30
-
31
- Parameters
32
- ----------
33
- weights : Sequence[float]
34
- Sequence of weights representing word importance.
35
- base_size : int
36
- Base font size used as the lower bound for scaling.
37
-
38
- Returns
39
- -------
40
- numpy.ndarray
41
- Array of font sizes corresponding to the provided weights.
42
- """
43
- numeric_weights = np.asarray(weights, dtype=float)
44
- if numeric_weights.size == 0:
45
- return np.array([], dtype=float)
46
- min_weight = numeric_weights.min()
47
- max_weight = numeric_weights.max()
48
- if min_weight == max_weight:
49
- return np.full_like(numeric_weights, fill_value=base_size, dtype=float)
50
-
51
- min_size, max_size = base_size, base_size * 4
52
- return np.interp(numeric_weights, (min_weight, max_weight), (min_size, max_size))
53
-
54
-
55
28
  def _filter_stopwords(
56
29
  words: Iterable[str], stopwords: Optional[Iterable[str]]
57
30
  ) -> np.ndarray:
@@ -232,20 +205,21 @@ def _plot_words(
232
205
  frequency_map = {
233
206
  string_formatter(word): weight for word, weight in zip(words, weights)
234
207
  }
208
+ min_font_size = style.font_mapping[min(style.font_mapping.keys())]
209
+ max_font_size = style.font_mapping[max(style.font_mapping.keys())]
235
210
 
236
- font_sizes = _normalize_weights(weights, base_size=style.font_size)
237
211
  wc = WordCloud(
238
212
  width=width,
239
213
  height=height,
240
214
  background_color=style.background_color,
241
215
  colormap=colormaps.get_cmap(style.palette),
242
- min_font_size=int(font_sizes.min(initial=style.font_size)),
243
- max_font_size=int(font_sizes.max(initial=style.font_size * 4)),
216
+ min_font_size=min_font_size,
217
+ max_font_size=max_font_size,
244
218
  random_state=random_state,
245
219
  mask=resolved_mask,
246
220
  ).generate_from_frequencies(frequency_map)
247
221
 
248
- ax.imshow(wc, interpolation="bilinear")
222
+ ax.imshow(wc.to_array(), interpolation="bilinear")
249
223
 
250
224
  if title:
251
225
  ax.set_title(title, color=style.font_color, fontsize=style.font_size * 1.5)
@@ -261,7 +235,7 @@ def aplot_wordcloud(
261
235
  max_words: int = MAX_RESULTS,
262
236
  stopwords: Optional[Iterable[str]] = None,
263
237
  random_state: Optional[int] = None,
264
- ax: Optional[Axes] = None,
238
+ ax: Optional[Axes | np.ndarray[Any, np.dtype[Any]]] = None,
265
239
  mask: Optional[np.ndarray] = None,
266
240
  ) -> Axes:
267
241
  """Plot a word cloud on the provided axes.
@@ -284,7 +258,7 @@ def aplot_wordcloud(
284
258
  Words to exclude from the visualization. Defaults to ``None``.
285
259
  random_state : int, optional
286
260
  Seed for word placement. Defaults to ``None``.
287
- ax : matplotlib.axes.Axes, optional
261
+ ax : matplotlib.axes.Axes or numpy.ndarray, optional
288
262
  Axes to draw on. Defaults to ``None`` which uses the current axes.
289
263
  mask : numpy.ndarray, optional
290
264
  Two-dimensional mask array defining the drawable region of the word cloud.
@@ -299,9 +273,15 @@ def aplot_wordcloud(
299
273
  ------
300
274
  AttributeError
301
275
  If required columns are missing from the DataFrame.
276
+ TypeError
277
+ If ``ax`` is not a ``matplotlib.axes.Axes`` instance.
302
278
  """
303
279
  if ax is None:
304
280
  ax = cast(Axes, plt.gca())
281
+ elif isinstance(ax, np.ndarray):
282
+ raise TypeError("ax must be a single matplotlib Axes instance, not an array.")
283
+ elif not isinstance(ax, Axes):
284
+ raise TypeError("ax must be a matplotlib Axes instance.")
305
285
 
306
286
  words, weights = _prepare_word_frequencies(
307
287
  pd_df=pd_df,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MatplotLibAPI
3
- Version: 3.2.19
3
+ Version: 3.2.20
4
4
  License-File: LICENSE
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: kaleido
@@ -3,7 +3,7 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
  [project]
5
5
  name = "MatplotLibAPI"
6
- version = "3.2.19"
6
+ version = "3.2.20"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.8"
9
9
  dependencies = [
@@ -97,7 +97,7 @@ def generate_treemap_data():
97
97
  }
98
98
  df = pd.DataFrame(data)
99
99
  # For treemap, we need a path-like structure. We will create it here.
100
- df["path"] = df["parent"] + "/" + df["location"]
100
+ df["path"] = df["parent"].str.cat(df["location"], sep="/")
101
101
  df["path"] = df["path"].str.lstrip("/")
102
102
  df.to_csv("data/treemap.csv", index=False)
103
103
 
@@ -169,7 +169,6 @@ def generate_wordcloud_data():
169
169
 
170
170
  def generate_bar_data():
171
171
  """Generate and save sample data for bar and stacked bar charts."""
172
-
173
172
  data = {
174
173
  "product": ["Gadget", "Gadget", "Gadget", "Widget", "Widget", "Widget"],
175
174
  "region": ["North", "South", "West", "North", "South", "West"],
@@ -181,7 +180,6 @@ def generate_bar_data():
181
180
 
182
181
  def generate_histogram_data():
183
182
  """Generate and save sample data for histogram and KDE plots."""
184
-
185
183
  data = {
186
184
  "waiting_time_minutes": [
187
185
  5,
@@ -212,7 +210,6 @@ def generate_histogram_data():
212
210
 
213
211
  def generate_box_violin_data():
214
212
  """Generate and save sample data for box and violin plots."""
215
-
216
213
  data = {
217
214
  "department": [
218
215
  "Engineering",
@@ -243,7 +240,6 @@ def generate_box_violin_data():
243
240
 
244
241
  def generate_heatmap_and_correlation_data():
245
242
  """Generate and save sample data for heatmaps and correlation matrices."""
246
-
247
243
  data = {
248
244
  "month": ["Jan", "Jan", "Feb", "Feb", "Mar", "Mar", "Apr", "Apr"],
249
245
  "channel": [
@@ -269,7 +265,6 @@ def generate_heatmap_and_correlation_data():
269
265
 
270
266
  def generate_area_data():
271
267
  """Generate and save sample data for area charts."""
272
-
273
268
  data = {
274
269
  "quarter": ["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"],
275
270
  "segment": [
@@ -290,7 +285,6 @@ def generate_area_data():
290
285
 
291
286
  def generate_pie_waffle_data():
292
287
  """Generate and save sample data for pie, donut, and waffle charts."""
293
-
294
288
  data = {
295
289
  "device": ["Desktop", "Mobile", "Tablet", "Other"],
296
290
  "sessions": [5200, 8900, 1300, 600],
@@ -302,7 +296,6 @@ def generate_pie_waffle_data():
302
296
 
303
297
  def generate_sankey_data():
304
298
  """Generate and save sample data for Sankey diagrams."""
305
-
306
299
  data = {
307
300
  "source": ["Homepage", "Homepage", "Landing Page", "Landing Page", "Cart"],
308
301
  "target": ["Landing Page", "Product", "Product", "Signup", "Checkout"],
@@ -0,0 +1 @@
1
+ """Public test for MatplotLibAPI."""
@@ -2,7 +2,7 @@
2
2
 
3
3
  import os
4
4
  from pathlib import Path
5
- from typing import Any, Callable
5
+ from typing import Any, Callable, Generator
6
6
 
7
7
  import pandas as pd
8
8
  import pytest
@@ -13,7 +13,6 @@ from scripts import generate_sample_data
13
13
  @pytest.fixture(scope="session")
14
14
  def sample_data_dir(tmp_path_factory: pytest.TempPathFactory) -> Path:
15
15
  """Generate sample CSV files in an isolated directory for tests."""
16
-
17
16
  base_dir = tmp_path_factory.mktemp("sample_data")
18
17
  cwd = os.getcwd()
19
18
  os.chdir(base_dir)
@@ -28,7 +27,7 @@ def sample_data_dir(tmp_path_factory: pytest.TempPathFactory) -> Path:
28
27
  @pytest.fixture(scope="session")
29
28
  def load_sample_df(
30
29
  sample_data_dir: Path,
31
- ) -> Callable[[str, Any], pd.DataFrame]:
30
+ ) -> Callable[..., pd.DataFrame]:
32
31
  """Return a loader that reads generated sample CSVs into dataframes."""
33
32
 
34
33
  def _loader(filename: str, **kwargs: Any) -> pd.DataFrame:
@@ -38,9 +37,8 @@ def load_sample_df(
38
37
 
39
38
 
40
39
  @pytest.fixture(autouse=True)
41
- def close_matplotlib_figures() -> None:
40
+ def close_matplotlib_figures() -> Generator[None, None, None]:
42
41
  """Close any matplotlib figures created during a test."""
43
-
44
42
  yield
45
43
 
46
44
  import matplotlib.pyplot as plt
@@ -0,0 +1,77 @@
1
+ """Tests for word cloud visualizations."""
2
+
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.figure import Figure
5
+ import numpy as np
6
+
7
+ from MatplotLibAPI.Wordcloud import (
8
+ aplot_wordcloud,
9
+ create_circular_mask,
10
+ fplot_wordcloud,
11
+ )
12
+
13
+
14
+ def test_fplot_wordcloud(load_sample_df):
15
+ """Render a word cloud figure from sample data."""
16
+
17
+ df = load_sample_df("wordcloud.csv")
18
+
19
+ fig = fplot_wordcloud(
20
+ pd_df=df, text_column="country", weight_column="population", random_state=42
21
+ )
22
+
23
+ assert isinstance(fig, Figure)
24
+ default_mask = create_circular_mask(size=300)
25
+ image = fig.axes[0].images[0].get_array()
26
+ assert image is not None
27
+ assert tuple(image.shape[:2]) == default_mask.shape
28
+
29
+
30
+ def test_fplot_wordcloud_with_mask(load_sample_df):
31
+ """Render a word cloud using a circular mask to constrain placement."""
32
+
33
+ df = load_sample_df("wordcloud.csv")
34
+ mask = create_circular_mask(size=200)
35
+
36
+ fig = fplot_wordcloud(
37
+ pd_df=df,
38
+ text_column="country",
39
+ weight_column="population",
40
+ random_state=0,
41
+ mask=mask,
42
+ )
43
+
44
+ image = fig.axes[0].images[0].get_array()
45
+ assert image is not None
46
+ assert tuple(image.shape[:2]) == mask.shape
47
+
48
+
49
+ def test_aplot_wordcloud(load_sample_df):
50
+ """Render a word cloud onto an existing axes object."""
51
+
52
+ df = load_sample_df("wordcloud.csv")
53
+ fig, ax = plt.subplots()
54
+
55
+ result_ax = aplot_wordcloud(
56
+ ax=ax,
57
+ pd_df=df,
58
+ text_column="country",
59
+ weight_column="population",
60
+ random_state=42,
61
+ )
62
+
63
+ assert result_ax is not None
64
+ assert ax is result_ax
65
+ image = result_ax.images[0].get_array()
66
+ assert image is not None
67
+ assert image.shape[0] > 0 and image.shape[1] > 0
68
+
69
+
70
+ def test_create_circular_mask():
71
+ """Verify circular mask generation."""
72
+
73
+ mask = create_circular_mask(size=100, radius=40)
74
+ assert mask.shape == (100, 100)
75
+ assert mask.dtype == np.uint8
76
+ assert np.sum(mask == 0) > 0
77
+ assert np.sum(mask == 255) > 0
File without changes
@@ -1,38 +0,0 @@
1
- """Tests for word cloud visualizations."""
2
-
3
- from matplotlib.figure import Figure
4
-
5
- from MatplotLibAPI.Wordcloud import create_circular_mask, fplot_wordcloud
6
-
7
-
8
- def test_fplot_wordcloud(load_sample_df):
9
- """Render a word cloud figure from sample data."""
10
-
11
- df = load_sample_df("wordcloud.csv")
12
-
13
- fig = fplot_wordcloud(
14
- pd_df=df, text_column="country", weight_column="population", random_state=42
15
- )
16
-
17
- assert isinstance(fig, Figure)
18
- default_mask = create_circular_mask()
19
- image = fig.axes[0].images[0].get_array()
20
- assert tuple(image.shape[:2]) == default_mask.shape
21
-
22
-
23
- def test_fplot_wordcloud_with_mask(load_sample_df):
24
- """Render a word cloud using a circular mask to constrain placement."""
25
-
26
- df = load_sample_df("wordcloud.csv")
27
- mask = create_circular_mask(size=200)
28
-
29
- fig = fplot_wordcloud(
30
- pd_df=df,
31
- text_column="country",
32
- weight_column="population",
33
- random_state=0,
34
- mask=mask,
35
- )
36
-
37
- image = fig.axes[0].images[0].get_array()
38
- assert tuple(image.shape[:2]) == mask.shape
File without changes
File without changes
File without changes