MatplotLibAPI 3.2.18__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.18 → matplotlibapi-3.2.20}/.github/workflows/ci.yml +2 -2
  2. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/BoxViolin.py +4 -2
  3. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Wordcloud.py +79 -36
  4. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/__init__.py +7 -1
  5. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/PKG-INFO +1 -1
  6. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/pyproject.toml +1 -1
  7. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/scripts/generate_sample_data.py +1 -8
  8. matplotlibapi-3.2.20/tests/__init__.py +1 -0
  9. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/conftest.py +3 -5
  10. matplotlibapi-3.2.20/tests/test_wordcloud.py +77 -0
  11. matplotlibapi-3.2.18/tests/__init__.py +0 -0
  12. matplotlibapi-3.2.18/tests/test_wordcloud.py +0 -17
  13. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/.github/workflows/python-publish.yml +0 -0
  14. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/.gitignore +0 -0
  15. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/AGENTS.md +0 -0
  16. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/LICENSE +0 -0
  17. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Area.py +0 -0
  18. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Bar.py +0 -0
  19. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Bubble.py +0 -0
  20. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Composite.py +0 -0
  21. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Heatmap.py +0 -0
  22. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Histogram.py +0 -0
  23. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Network.py +0 -0
  24. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Pie.py +0 -0
  25. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Pivot.py +0 -0
  26. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Sankey.py +0 -0
  27. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/StyleTemplate.py +0 -0
  28. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Sunburst.py +0 -0
  29. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Table.py +0 -0
  30. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Timeserie.py +0 -0
  31. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Treemap.py +0 -0
  32. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/Waffle.py +0 -0
  33. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/_typing.py +0 -0
  34. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/_visualization_utils.py +0 -0
  35. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/MatplotLibAPI/accessor.py +0 -0
  36. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/README.md +0 -0
  37. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/SECURITY.md +0 -0
  38. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/SUGGESTIONS.md +0 -0
  39. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_area.py +0 -0
  40. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_bar.py +0 -0
  41. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_box_violin.py +0 -0
  42. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_bubble.py +0 -0
  43. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_dependencies.py +0 -0
  44. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_heatmap.py +0 -0
  45. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_histogram.py +0 -0
  46. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_network.py +0 -0
  47. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_pie.py +0 -0
  48. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_pivot.py +0 -0
  49. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_sankey.py +0 -0
  50. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_smoke.py +0 -0
  51. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_sunburst.py +0 -0
  52. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_table.py +0 -0
  53. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_timeseries.py +0 -0
  54. {matplotlibapi-3.2.18 → matplotlibapi-3.2.20}/tests/test_treemap.py +0 -0
  55. {matplotlibapi-3.2.18 → 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:
@@ -131,6 +104,40 @@ def _prepare_word_frequencies(
131
104
  return words, weights
132
105
 
133
106
 
107
+ def create_circular_mask(size: int = 300, radius: Optional[int] = None) -> np.ndarray:
108
+ """Construct a binary mask with a circular opening for a word cloud.
109
+
110
+ Parameters
111
+ ----------
112
+ size : int, optional
113
+ Width and height of the mask in pixels. Defaults to ``300``.
114
+ radius : int, optional
115
+ Radius of the circular opening in pixels. Defaults to ``size // 2``.
116
+
117
+ Returns
118
+ -------
119
+ numpy.ndarray
120
+ Two-dimensional array suitable for the ``mask`` argument of
121
+ ``wordcloud.WordCloud`` where ``0`` values define the drawable region.
122
+
123
+ Raises
124
+ ------
125
+ ValueError
126
+ If ``size`` or ``radius`` are non-positive.
127
+ """
128
+ if size <= 0:
129
+ raise ValueError("size must be a positive integer.")
130
+
131
+ resolved_radius = radius if radius is not None else size // 2
132
+ if resolved_radius <= 0:
133
+ raise ValueError("radius must be a positive integer.")
134
+
135
+ center = (size - 1) / 2
136
+ x, y = np.ogrid[:size, :size]
137
+ mask_region = (x - center) ** 2 + (y - center) ** 2 > resolved_radius**2
138
+ return 255 * mask_region.astype(np.uint8)
139
+
140
+
134
141
  def _plot_words(
135
142
  ax: Axes,
136
143
  words: Sequence[str],
@@ -138,6 +145,7 @@ def _plot_words(
138
145
  style: StyleTemplate,
139
146
  title: Optional[str],
140
147
  random_state: Optional[int],
148
+ mask: Optional[np.ndarray],
141
149
  ) -> Axes:
142
150
  """Render words on the provided axes with sizes proportional to weights.
143
151
 
@@ -179,25 +187,39 @@ def _plot_words(
179
187
 
180
188
  canvas.draw()
181
189
  ax_bbox = ax.get_window_extent()
182
- width = max(int(ax_bbox.width), 1)
183
- height = max(int(ax_bbox.height), 1)
190
+ resolved_mask = create_circular_mask() if mask is None else np.asarray(mask)
191
+ if resolved_mask is not None and resolved_mask.ndim != 2:
192
+ raise ValueError("mask must be a 2D array.")
193
+
194
+ width = (
195
+ max(int(ax_bbox.width), 1)
196
+ if resolved_mask is None
197
+ else max(int(resolved_mask.shape[1]), 1)
198
+ )
199
+ height = (
200
+ max(int(ax_bbox.height), 1)
201
+ if resolved_mask is None
202
+ else max(int(resolved_mask.shape[0]), 1)
203
+ )
184
204
 
185
205
  frequency_map = {
186
206
  string_formatter(word): weight for word, weight in zip(words, weights)
187
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())]
188
210
 
189
- font_sizes = _normalize_weights(weights, base_size=style.font_size)
190
211
  wc = WordCloud(
191
212
  width=width,
192
213
  height=height,
193
214
  background_color=style.background_color,
194
215
  colormap=colormaps.get_cmap(style.palette),
195
- min_font_size=int(font_sizes.min(initial=style.font_size)),
196
- 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,
197
218
  random_state=random_state,
219
+ mask=resolved_mask,
198
220
  ).generate_from_frequencies(frequency_map)
199
221
 
200
- ax.imshow(wc, interpolation="bilinear")
222
+ ax.imshow(wc.to_array(), interpolation="bilinear")
201
223
 
202
224
  if title:
203
225
  ax.set_title(title, color=style.font_color, fontsize=style.font_size * 1.5)
@@ -213,7 +235,8 @@ def aplot_wordcloud(
213
235
  max_words: int = MAX_RESULTS,
214
236
  stopwords: Optional[Iterable[str]] = None,
215
237
  random_state: Optional[int] = None,
216
- ax: Optional[Axes] = None,
238
+ ax: Optional[Axes | np.ndarray[Any, np.dtype[Any]]] = None,
239
+ mask: Optional[np.ndarray] = None,
217
240
  ) -> Axes:
218
241
  """Plot a word cloud on the provided axes.
219
242
 
@@ -235,8 +258,11 @@ def aplot_wordcloud(
235
258
  Words to exclude from the visualization. Defaults to ``None``.
236
259
  random_state : int, optional
237
260
  Seed for word placement. Defaults to ``None``.
238
- ax : matplotlib.axes.Axes, optional
261
+ ax : matplotlib.axes.Axes or numpy.ndarray, optional
239
262
  Axes to draw on. Defaults to ``None`` which uses the current axes.
263
+ mask : numpy.ndarray, optional
264
+ Two-dimensional mask array defining the drawable region of the word cloud.
265
+ Defaults to a circular mask generated by :func:`create_circular_mask`.
240
266
 
241
267
  Returns
242
268
  -------
@@ -247,9 +273,15 @@ def aplot_wordcloud(
247
273
  ------
248
274
  AttributeError
249
275
  If required columns are missing from the DataFrame.
276
+ TypeError
277
+ If ``ax`` is not a ``matplotlib.axes.Axes`` instance.
250
278
  """
251
279
  if ax is None:
252
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.")
253
285
 
254
286
  words, weights = _prepare_word_frequencies(
255
287
  pd_df=pd_df,
@@ -259,7 +291,13 @@ def aplot_wordcloud(
259
291
  stopwords=stopwords,
260
292
  )
261
293
  return _plot_words(
262
- ax, words, weights, style=style, title=title, random_state=random_state
294
+ ax,
295
+ words,
296
+ weights,
297
+ style=style,
298
+ title=title,
299
+ random_state=random_state,
300
+ mask=mask,
263
301
  )
264
302
 
265
303
 
@@ -275,6 +313,7 @@ def fplot_wordcloud(
275
313
  figsize: Tuple[float, float] = FIG_SIZE,
276
314
  save_path: Optional[str] = None,
277
315
  savefig_kwargs: Optional[Dict[str, Any]] = None,
316
+ mask: Optional[np.ndarray] = None,
278
317
  ) -> Figure:
279
318
  """Create a new figure with a word cloud.
280
319
 
@@ -298,6 +337,9 @@ def fplot_wordcloud(
298
337
  Seed for word placement. Defaults to ``None``.
299
338
  figsize : tuple of float, optional
300
339
  Figure size. Defaults to ``FIG_SIZE``.
340
+ mask : numpy.ndarray, optional
341
+ Two-dimensional mask array defining the drawable region of the word cloud.
342
+ Defaults to a circular mask generated by :func:`create_circular_mask`.
301
343
 
302
344
  Returns
303
345
  -------
@@ -325,6 +367,7 @@ def fplot_wordcloud(
325
367
  style=style,
326
368
  title=title,
327
369
  random_state=random_state,
370
+ mask=mask,
328
371
  )
329
372
  fig.patch.set_facecolor(style.background_color)
330
373
  fig.tight_layout()
@@ -35,7 +35,12 @@ from .Timeserie import TIMESERIE_STYLE_TEMPLATE, aplot_timeserie, fplot_timeseri
35
35
  from .Sunburst import fplot_sunburst
36
36
  from .Treemap import TREEMAP_STYLE_TEMPLATE, fplot_treemap
37
37
  from .Waffle import aplot_waffle, fplot_waffle
38
- from .Wordcloud import WORDCLOUD_STYLE_TEMPLATE, aplot_wordcloud, fplot_wordcloud
38
+ from .Wordcloud import (
39
+ WORDCLOUD_STYLE_TEMPLATE,
40
+ aplot_wordcloud,
41
+ create_circular_mask,
42
+ fplot_wordcloud,
43
+ )
39
44
  from .accessor import DataFrameAccessor
40
45
 
41
46
  __all__ = [
@@ -54,6 +59,7 @@ __all__ = [
54
59
  "aplot_correlation_matrix",
55
60
  "aplot_area",
56
61
  "aplot_pie_donut",
62
+ "create_circular_mask",
57
63
  "aplot_waffle",
58
64
  "fplot_bubble",
59
65
  "fplot_network",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MatplotLibAPI
3
- Version: 3.2.18
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.18"
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,17 +0,0 @@
1
- """Tests for word cloud visualizations."""
2
-
3
- from matplotlib.figure import Figure
4
-
5
- from MatplotLibAPI.Wordcloud import 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)
File without changes
File without changes
File without changes