MatplotLibAPI 3.2.21__py3-none-any.whl → 4.0.0__py3-none-any.whl

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 (51) hide show
  1. MatplotLibAPI/__init__.py +4 -86
  2. MatplotLibAPI/accessor.py +519 -196
  3. MatplotLibAPI/area.py +177 -0
  4. MatplotLibAPI/bar.py +185 -0
  5. MatplotLibAPI/base_plot.py +88 -0
  6. MatplotLibAPI/box_violin.py +180 -0
  7. MatplotLibAPI/bubble.py +568 -0
  8. MatplotLibAPI/{Composite.py → composite.py} +127 -106
  9. MatplotLibAPI/heatmap.py +223 -0
  10. MatplotLibAPI/histogram.py +170 -0
  11. MatplotLibAPI/mcp/__init__.py +17 -0
  12. MatplotLibAPI/mcp/metadata.py +90 -0
  13. MatplotLibAPI/mcp/renderers.py +45 -0
  14. MatplotLibAPI/mcp_server.py +626 -0
  15. MatplotLibAPI/network/__init__.py +28 -0
  16. MatplotLibAPI/network/constants.py +22 -0
  17. MatplotLibAPI/network/core.py +1360 -0
  18. MatplotLibAPI/network/plot.py +597 -0
  19. MatplotLibAPI/network/scaling.py +56 -0
  20. MatplotLibAPI/pie.py +154 -0
  21. MatplotLibAPI/pivot.py +274 -0
  22. MatplotLibAPI/sankey.py +99 -0
  23. MatplotLibAPI/{StyleTemplate.py → style_template.py} +27 -22
  24. MatplotLibAPI/sunburst.py +139 -0
  25. MatplotLibAPI/{Table.py → table.py} +112 -87
  26. MatplotLibAPI/{Timeserie.py → timeserie.py} +98 -42
  27. MatplotLibAPI/{Treemap.py → treemap.py} +43 -55
  28. MatplotLibAPI/typing.py +12 -0
  29. MatplotLibAPI/{_visualization_utils.py → utils.py} +7 -13
  30. MatplotLibAPI/waffle.py +173 -0
  31. MatplotLibAPI/word_cloud.py +489 -0
  32. {matplotlibapi-3.2.21.dist-info → matplotlibapi-4.0.0.dist-info}/METADATA +98 -9
  33. matplotlibapi-4.0.0.dist-info/RECORD +36 -0
  34. {matplotlibapi-3.2.21.dist-info → matplotlibapi-4.0.0.dist-info}/WHEEL +1 -1
  35. matplotlibapi-4.0.0.dist-info/entry_points.txt +2 -0
  36. MatplotLibAPI/Area.py +0 -80
  37. MatplotLibAPI/Bar.py +0 -83
  38. MatplotLibAPI/BoxViolin.py +0 -75
  39. MatplotLibAPI/Bubble.py +0 -458
  40. MatplotLibAPI/Heatmap.py +0 -121
  41. MatplotLibAPI/Histogram.py +0 -73
  42. MatplotLibAPI/Network.py +0 -989
  43. MatplotLibAPI/Pie.py +0 -70
  44. MatplotLibAPI/Pivot.py +0 -134
  45. MatplotLibAPI/Sankey.py +0 -46
  46. MatplotLibAPI/Sunburst.py +0 -89
  47. MatplotLibAPI/Waffle.py +0 -86
  48. MatplotLibAPI/Wordcloud.py +0 -373
  49. MatplotLibAPI/_typing.py +0 -17
  50. matplotlibapi-3.2.21.dist-info/RECORD +0 -26
  51. {matplotlibapi-3.2.21.dist-info → matplotlibapi-4.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,489 @@
1
+ """Word cloud plotting utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, Union
6
+
7
+ from matplotlib.transforms import BboxBase
8
+ import numpy as np
9
+ import pandas as pd
10
+ import matplotlib.pyplot as plt
11
+ from matplotlib import colormaps
12
+ from matplotlib.axes import Axes
13
+ from matplotlib.backend_bases import FigureCanvasBase
14
+ from matplotlib.figure import Figure, SubFigure
15
+ from wordcloud import WordCloud
16
+
17
+ from .base_plot import BasePlot
18
+
19
+ from .utils import _get_axis
20
+
21
+ from .style_template import (
22
+ FIG_SIZE,
23
+ MAX_RESULTS,
24
+ TITLE_SCALE_FACTOR,
25
+ WORDCLOUD_STYLE_TEMPLATE,
26
+ StyleTemplate,
27
+ string_formatter,
28
+ validate_dataframe,
29
+ )
30
+
31
+
32
+ def _filter_stopwords(
33
+ words: Iterable[str], stopwords: Optional[Iterable[str]]
34
+ ) -> np.ndarray:
35
+ """Remove stopwords from a sequence of words.
36
+
37
+ Parameters
38
+ ----------
39
+ words : Iterable[str]
40
+ Words to filter.
41
+ stopwords : Iterable[str], optional
42
+ Collection of stopwords to exclude. Defaults to ``None``.
43
+
44
+ Returns
45
+ -------
46
+ numpy.ndarray
47
+ Filtered words.
48
+ """
49
+ if stopwords is None:
50
+ return np.array(list(words))
51
+
52
+ stop_set: set[str] = {word.lower() for word in stopwords}
53
+ return np.array([word for word in words if word.lower() not in stop_set])
54
+
55
+
56
+ def from_pandas_nodelist(
57
+ nodes_df: pd.DataFrame,
58
+ node_col: str = "node",
59
+ weight_col: str = "weight",
60
+ ):
61
+ """Create SankeyData from a DataFrame."""
62
+ validate_dataframe(nodes_df, cols=[node_col, weight_col])
63
+ return nodes_df[[node_col, weight_col]].set_index(node_col)[weight_col].to_dict()
64
+
65
+
66
+ def _prepare_word_frequencies(
67
+ pd_df: pd.DataFrame,
68
+ text_column: str,
69
+ weight_column: str,
70
+ max_words: int,
71
+ stopwords: Optional[Iterable[str]],
72
+ ) -> Tuple[list[str], list[float]]:
73
+ """Aggregate and filter word frequencies.
74
+
75
+ Parameters
76
+ ----------
77
+ pd_df : pandas.DataFrame
78
+ Input DataFrame containing word data.
79
+ text_column : str
80
+ Column containing words or phrases.
81
+ weight_column : str, optional
82
+ Column containing numeric weights. Defaults to ``None``.
83
+ max_words : int
84
+ Maximum number of words to include.
85
+ stopwords : Iterable[str], optional
86
+ Words to exclude from the visualization. Defaults to ``None``.
87
+
88
+ Returns
89
+ -------
90
+ tuple of list
91
+ Lists of filtered words and their corresponding weights.
92
+
93
+ Raises
94
+ ------
95
+ AttributeError
96
+ If required columns are missing from the DataFrame.
97
+ """
98
+ validate_dataframe(pd_df, cols=[text_column], sort_by=weight_column)
99
+ word_frequencies = from_pandas_nodelist(
100
+ pd_df, node_col=text_column, weight_col=weight_column
101
+ )
102
+ words: np.ndarray = np.asarray(list(word_frequencies.keys()), dtype=np.str_)
103
+ weights: np.ndarray = np.asarray(list(word_frequencies.values()), dtype=np.float64)
104
+
105
+ filtered_words = _filter_stopwords(words, stopwords)
106
+ mask: np.ndarray = np.asarray(np.isin(words, filtered_words), dtype=bool)
107
+ filtered_weights: np.ndarray = np.asarray(weights[mask], dtype=np.float64)
108
+
109
+ sorted_indices = np.argsort(filtered_weights)[::-1]
110
+ sorted_words = filtered_words[sorted_indices][:max_words].tolist()
111
+ sorted_weights = filtered_weights[sorted_indices][:max_words].tolist()
112
+
113
+ return sorted_words, sorted_weights
114
+
115
+
116
+ def _create_circular_mask(size: int = 300, radius: Optional[int] = None) -> np.ndarray:
117
+ """Construct a binary mask with a circular opening for a word cloud.
118
+
119
+ Parameters
120
+ ----------
121
+ size : int, optional
122
+ Width and height of the mask in pixels. Defaults to ``300``.
123
+ radius : int, optional
124
+ Radius of the circular opening in pixels. Defaults to ``size // 2``.
125
+
126
+ Returns
127
+ -------
128
+ numpy.ndarray
129
+ Two-dimensional array suitable for the ``mask`` argument of
130
+ ``wordcloud.WordCloud`` where ``0`` values define the drawable region.
131
+
132
+ Raises
133
+ ------
134
+ ValueError
135
+ If ``size`` or ``radius`` are non-positive.
136
+ """
137
+ if size <= 0:
138
+ raise ValueError("size must be a positive integer.")
139
+
140
+ resolved_radius: int = radius if radius is not None else size // 2
141
+ if resolved_radius <= 0:
142
+ raise ValueError("radius must be a positive integer.")
143
+
144
+ center: float = (size - 1) / 2
145
+ x, y = np.ogrid[:size, :size]
146
+ mask_region = (x - center) ** 2 + (y - center) ** 2 > resolved_radius**2
147
+ return 255 * mask_region.astype(np.uint8)
148
+
149
+
150
+ def _plot_words(
151
+ ax: Axes,
152
+ words: Sequence[str],
153
+ weights: Sequence[float],
154
+ style: StyleTemplate,
155
+ title: Optional[str],
156
+ random_state: Optional[int],
157
+ mask: Optional[np.ndarray],
158
+ ) -> Axes:
159
+ """Render words on the provided axes with sizes proportional to weights.
160
+
161
+ Parameters
162
+ ----------
163
+ ax : matplotlib.axes.Axes
164
+ Axes on which to draw.
165
+ words : Sequence[str]
166
+ Words to render.
167
+ weights : Sequence[float]
168
+ Corresponding weights for sizing.
169
+ style : StyleTemplate
170
+ Style configuration for the plot.
171
+ title : str, optional
172
+ Title of the plot. Defaults to ``None``.
173
+ random_state : int, optional
174
+ Seed for reproducible placement. Defaults to ``None``.
175
+
176
+ Returns
177
+ -------
178
+ matplotlib.axes.Axes
179
+ Axes containing the rendered word cloud.
180
+ """
181
+ ax.set_facecolor(style.background_color)
182
+ ax.axis("off")
183
+
184
+ if not words:
185
+ if title:
186
+ ax.set_title(
187
+ title,
188
+ color=style.font_color,
189
+ fontsize=style.font_size * TITLE_SCALE_FACTOR * 0.75,
190
+ )
191
+ return ax
192
+
193
+ fig_raw = ax.figure
194
+ if not isinstance(fig_raw, (Figure, SubFigure)):
195
+ raise RuntimeError("Axes is not associated with a Figure.")
196
+
197
+ fig_obj: Union[Figure, SubFigure] = fig_raw
198
+ canvas: FigureCanvasBase = fig_obj.canvas
199
+ if canvas is None:
200
+ raise RuntimeError("Figure does not have an attached canvas.")
201
+
202
+ canvas.draw()
203
+ ax_bbox: BboxBase = ax.get_window_extent()
204
+
205
+ if mask is None:
206
+ mask_dimension: int = max(int(ax_bbox.width), int(ax_bbox.height), 1)
207
+ resolved_mask: np.ndarray = _create_circular_mask(size=mask_dimension)
208
+ else:
209
+ resolved_mask: np.ndarray = np.asarray(mask, dtype=np.uint8)
210
+
211
+ if resolved_mask.ndim != 2:
212
+ raise ValueError("mask must be a 2D array.")
213
+
214
+ height, width = resolved_mask.shape
215
+
216
+ frequency_map: Dict[str, float] = {
217
+ string_formatter(word): float(weight) for word, weight in zip(words, weights)
218
+ }
219
+ if frequency_map and max(frequency_map.values()) <= 0:
220
+ frequency_map = {word: 1.0 for word in frequency_map}
221
+
222
+ max_font_size = int(style.font_mapping[max(style.font_mapping.keys())] * 20)
223
+
224
+ wc: WordCloud = WordCloud(
225
+ width=width,
226
+ height=height,
227
+ background_color=style.background_color,
228
+ colormap=colormaps.get_cmap(style.palette),
229
+ # min_font_size=min_font_size,
230
+ # max_font_size=max_font_size,
231
+ random_state=random_state,
232
+ mask=resolved_mask,
233
+ ).generate_from_frequencies(frequency_map, max_font_size=max_font_size)
234
+
235
+ ax.imshow(wc.to_array(), interpolation="bilinear")
236
+
237
+ if title:
238
+ ax.set_title(
239
+ title,
240
+ color=style.font_color,
241
+ fontsize=style.font_size * TITLE_SCALE_FACTOR * 0.75,
242
+ )
243
+ return ax
244
+
245
+
246
+ class WordCloudPlot(BasePlot):
247
+ """Represent a word-cloud plot builder.
248
+
249
+ Methods
250
+ -------
251
+ aplot
252
+ Plot the word cloud on an existing Matplotlib axes.
253
+ fplot
254
+ Plot the word cloud on a new Matplotlib figure.
255
+ """
256
+
257
+ def __init__(self, pd_df: pd.DataFrame, text_column: str, weight_column: str):
258
+ validate_dataframe(pd_df, cols=[text_column], sort_by=weight_column)
259
+ super().__init__(pd_df=pd_df)
260
+ self.text_column = text_column
261
+ self.weight_column = weight_column
262
+
263
+ def aplot(
264
+ self,
265
+ title: Optional[str] = None,
266
+ style: StyleTemplate = WORDCLOUD_STYLE_TEMPLATE,
267
+ max_words: int = MAX_RESULTS,
268
+ stopwords: Optional[Iterable[str]] = None,
269
+ random_state: Optional[int] = None,
270
+ ax: Optional[Axes] = None,
271
+ mask: Optional[np.ndarray] = None,
272
+ **kwargs: Any,
273
+ ) -> Axes:
274
+ """Plot the configured word cloud on existing axes.
275
+
276
+ Parameters
277
+ ----------
278
+ title : str, optional
279
+ Plot title.
280
+ style : StyleTemplate, optional
281
+ Style configuration. The default is ``WORDCLOUD_STYLE_TEMPLATE``.
282
+ max_words : int, optional
283
+ Maximum number of words to include. The default is ``MAX_RESULTS``.
284
+ stopwords : Iterable[str], optional
285
+ Words to exclude from the cloud.
286
+ random_state : int, optional
287
+ Random seed used by word-cloud placement.
288
+ ax : Axes, optional
289
+ Matplotlib axes to draw on. If None, use the current axes.
290
+ mask : np.ndarray, optional
291
+ Binary mask controlling drawable pixels.
292
+ **kwargs : Any
293
+ Additional keyword arguments reserved for compatibility.
294
+
295
+ Returns
296
+ -------
297
+ Axes
298
+ Matplotlib axes containing the rendered word cloud.
299
+ """
300
+ words, weights = _prepare_word_frequencies(
301
+ pd_df=self._obj,
302
+ text_column=self.text_column,
303
+ weight_column=self.weight_column,
304
+ max_words=max_words,
305
+ stopwords=stopwords,
306
+ )
307
+ plot_ax = _get_axis(ax)
308
+ return _plot_words(
309
+ plot_ax,
310
+ words,
311
+ weights,
312
+ style=style,
313
+ title=title,
314
+ random_state=random_state,
315
+ mask=mask,
316
+ )
317
+
318
+ def fplot(
319
+ self,
320
+ title: Optional[str] = None,
321
+ style: StyleTemplate = WORDCLOUD_STYLE_TEMPLATE,
322
+ max_words: int = MAX_RESULTS,
323
+ stopwords: Optional[Iterable[str]] = None,
324
+ random_state: Optional[int] = None,
325
+ figsize: Tuple[float, float] = FIG_SIZE,
326
+ mask: Optional[np.ndarray] = None,
327
+ ) -> Figure:
328
+ """Plot the configured word cloud on a new figure.
329
+
330
+ Parameters
331
+ ----------
332
+ title : str, optional
333
+ Plot title.
334
+ style : StyleTemplate, optional
335
+ Style configuration. The default is ``WORDCLOUD_STYLE_TEMPLATE``.
336
+ max_words : int, optional
337
+ Maximum number of words to include. The default is ``MAX_RESULTS``.
338
+ stopwords : Iterable[str], optional
339
+ Words to exclude from the cloud.
340
+ random_state : int, optional
341
+ Random seed used by word-cloud placement.
342
+ figsize : tuple[float, float], optional
343
+ Figure size. The default is ``FIG_SIZE``.
344
+ mask : np.ndarray, optional
345
+ Binary mask controlling drawable pixels.
346
+
347
+ Returns
348
+ -------
349
+ Figure
350
+ Matplotlib figure containing the rendered word cloud.
351
+ """
352
+ fig = Figure(
353
+ figsize=figsize,
354
+ facecolor=style.background_color,
355
+ edgecolor=style.background_color,
356
+ )
357
+ ax = Axes(fig=fig, facecolor=style.background_color)
358
+ self.aplot(
359
+ title=title,
360
+ style=style,
361
+ max_words=max_words,
362
+ stopwords=stopwords,
363
+ random_state=random_state,
364
+ ax=ax,
365
+ mask=mask,
366
+ )
367
+ return fig
368
+
369
+
370
+ def aplot_wordcloud(
371
+ pd_df: pd.DataFrame,
372
+ text_column: str,
373
+ weight_column: str,
374
+ title: Optional[str] = None,
375
+ style: StyleTemplate = WORDCLOUD_STYLE_TEMPLATE,
376
+ max_words: int = MAX_RESULTS,
377
+ stopwords: Optional[Iterable[str]] = None,
378
+ random_state: Optional[int] = None,
379
+ ax: Optional[Axes] = None,
380
+ mask: Optional[np.ndarray] = None,
381
+ **kwargs: Any,
382
+ ) -> Axes:
383
+ """Plot a word cloud on the provided axes.
384
+
385
+ Parameters
386
+ ----------
387
+ pd_df : pandas.DataFrame
388
+ DataFrame containing the words to visualize.
389
+ text_column : str
390
+ Column containing words or phrases.
391
+ weight_column : str, optional
392
+ Column containing numeric weights. Defaults to ``None`` for equal weights.
393
+ title : str, optional
394
+ Plot title. Defaults to ``None``.
395
+ style : StyleTemplate, optional
396
+ Styling options. Defaults to ``WORDCLOUD_STYLE_TEMPLATE``.
397
+ max_words : int, optional
398
+ Maximum number of words to display. Defaults to ``MAX_RESULTS``.
399
+ stopwords : Iterable[str], optional
400
+ Words to exclude from the visualization. Defaults to ``None``.
401
+ random_state : int, optional
402
+ Seed for word placement. Defaults to ``None``.
403
+ ax : matplotlib.axes.Axes or numpy.ndarray, optional
404
+ Axes to draw on. Defaults to ``None`` which uses the current axes.
405
+ mask : numpy.ndarray, optional
406
+ Two-dimensional mask array defining the drawable region of the word cloud.
407
+ Defaults to a circular mask generated by :func:`create_circular_mask`.
408
+
409
+ Returns
410
+ -------
411
+ matplotlib.axes.Axes
412
+ Axes containing the rendered word cloud.
413
+
414
+ Raises
415
+ ------
416
+ AttributeError
417
+ If required columns are missing from the DataFrame.
418
+ TypeError
419
+ If ``ax`` is not a ``matplotlib.axes.Axes`` instance.
420
+ """
421
+ return WordCloudPlot(
422
+ pd_df=pd_df,
423
+ text_column=text_column,
424
+ weight_column=weight_column,
425
+ ).aplot(
426
+ title=title,
427
+ style=style,
428
+ max_words=max_words,
429
+ stopwords=stopwords,
430
+ random_state=random_state,
431
+ ax=ax,
432
+ mask=mask,
433
+ **kwargs,
434
+ )
435
+
436
+
437
+ def fplot_wordcloud(
438
+ pd_df: pd.DataFrame,
439
+ text_column: str,
440
+ weight_column: str,
441
+ title: Optional[str] = None,
442
+ style: StyleTemplate = WORDCLOUD_STYLE_TEMPLATE,
443
+ max_words: int = MAX_RESULTS,
444
+ stopwords: Optional[Iterable[str]] = None,
445
+ random_state: Optional[int] = None,
446
+ figsize: Tuple[float, float] = FIG_SIZE,
447
+ mask: Optional[np.ndarray] = None,
448
+ ) -> Figure:
449
+ """Return a new figure containing a word cloud.
450
+
451
+ Parameters
452
+ ----------
453
+ pd_df : pd.DataFrame
454
+ DataFrame containing text and optional weight columns.
455
+ text_column : str
456
+ Column containing text terms.
457
+ weight_column : str, optional
458
+ Column containing numeric weights. The default is None.
459
+ title : str, optional
460
+ Plot title. The default is None.
461
+ style : StyleTemplate, optional
462
+ Style configuration. The default is ``WORDCLOUD_STYLE_TEMPLATE``.
463
+ max_words : int, optional
464
+ Maximum number of words to include. The default is ``MAX_RESULTS``.
465
+ stopwords : Iterable[str], optional
466
+ Words to exclude from the cloud. The default is None.
467
+ random_state : int, optional
468
+ Random seed used by word cloud layout. The default is None.
469
+ figsize : tuple[float, float], optional
470
+ Size of the created figure. The default is ``FIG_SIZE``.
471
+ mask : np.ndarray, optional
472
+ Optional 2D mask limiting word cloud shape. The default is None.
473
+
474
+ Returns
475
+ -------
476
+ Figure
477
+ Matplotlib figure containing the word cloud.
478
+ """
479
+ return WordCloudPlot(
480
+ pd_df=pd_df, text_column=text_column, weight_column=weight_column
481
+ ).fplot(
482
+ title=title,
483
+ style=style,
484
+ max_words=max_words,
485
+ stopwords=stopwords,
486
+ random_state=random_state,
487
+ figsize=figsize,
488
+ mask=mask,
489
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MatplotLibAPI
3
- Version: 3.2.21
3
+ Version: 4.0.0
4
4
  License-File: LICENSE
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: kaleido
@@ -21,6 +21,8 @@ Requires-Dist: pytest; extra == 'dev'
21
21
  Requires-Dist: pytest-cov; extra == 'dev'
22
22
  Requires-Dist: pytest-pydocstyle; extra == 'dev'
23
23
  Requires-Dist: tomli; extra == 'dev'
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp>=1.0.0; extra == 'mcp'
24
26
  Description-Content-Type: text/markdown
25
27
 
26
28
  # MatplotLibAPI
@@ -47,23 +49,98 @@ This will create a `data` directory with CSV files for each plot type.
47
49
 
48
50
  ## Usage
49
51
 
50
- Here's a simple example of how to create a bubble chart using MatplotLibAPI with a sample CSV file:
52
+ Here's a simple example of how to create a bubble chart using the object-based API with a sample CSV file:
51
53
 
52
54
  ```python
53
55
  import pandas as pd
54
56
  import matplotlib.pyplot as plt
55
- from MatplotLibAPI import fplot_bubble
57
+ from MatplotLibAPI.bubble import Bubble
56
58
 
57
59
  # Load the sample data
58
60
  df = pd.read_csv('data/bubble.csv')
59
61
 
60
- # Generate the bubble chart
61
- fig = fplot_bubble(df, label='country', x='gdp_per_capita', y='population', z='population', title='Country Statistics')
62
+ # Build and render the bubble chart
63
+ fig = Bubble(
64
+ pd_df=df,
65
+ label='country',
66
+ x='gdp_per_capita',
67
+ y='population',
68
+ z='population',
69
+ ).fplot(title='Country Statistics')
62
70
 
63
71
  # Display the plot
64
72
  plt.show()
65
73
  ```
66
74
 
75
+
76
+ ## MCP Integration
77
+
78
+ You can run a dedicated MCP server that exposes MatplotLibAPI plotting tools for LLM agents.
79
+
80
+ 1. Install MCP dependencies:
81
+
82
+ ```bash
83
+ pip install -e .[mcp]
84
+ ```
85
+
86
+ 2. Start the MCP server over stdio:
87
+
88
+ ```bash
89
+ matplotlibapi-mcp
90
+ ```
91
+
92
+ ### Tool surface
93
+
94
+ The MCP server provides these tools:
95
+
96
+ - `plot_bubble`: dedicated bubble-chart rendering.
97
+ - `plot_network`: dedicated network-chart rendering.
98
+ - `plot_module`: generic module renderer for `bar`, `histogram`, `box_violin`, `heatmap`, `correlation_matrix`, `area`, `pie`, `waffle`, `sankey`, `table`, `timeserie`, `wordcloud`, `treemap`, and `sunburst`.
99
+ - Explicit module endpoints such as `plot_bar`, `plot_heatmap`, `plot_sankey`, `plot_treemap`, and others for direct LLM tool selection with no module-dispatch step.
100
+ - `describe_plot_modules`: discoverability endpoint that returns supported modules, shared input contract, parameter hints, and dedicated-tool mapping.
101
+
102
+ All rendering tools accept either:
103
+
104
+ - `csv_path`: filesystem path to a CSV file, or
105
+ - `table`: in-memory records (`list[dict]`).
106
+
107
+ All rendering tools return PNG bytes (octet payload) for downstream transport.
108
+
109
+ For LLM orchestration, explicit endpoints are generally easier to select and ground (for example `plot_heatmap` rather than `plot_module` + `plot_module="heatmap"`). Keep `plot_module` for dynamic clients that need one generic surface.
110
+
111
+ ### Discoverability-first workflow
112
+
113
+ For dynamic clients and agent exploration, use this sequence:
114
+
115
+ 1. Call `describe_plot_modules`.
116
+ 2. Select a module from `supported_plot_modules`.
117
+ 3. Read recommended keys from `parameter_hints[module_name]`.
118
+ 4. Call `plot_module` with `params` + `csv_path` or `table`.
119
+
120
+ If a module key is invalid, `plot_module` returns a clear error with supported values.
121
+
122
+ ### Example generic call
123
+
124
+ Example payload for `plot_module` with in-memory table records:
125
+
126
+ ```json
127
+ {
128
+ "plot_module": "heatmap",
129
+ "table": [
130
+ {"month": "Jan", "channel": "Email", "engagements": 120},
131
+ {"month": "Jan", "channel": "Social", "engagements": 200}
132
+ ],
133
+ "params": {
134
+ "x": "month",
135
+ "y": "channel",
136
+ "value": "engagements",
137
+ "title": "Engagement Heatmap"
138
+ }
139
+ }
140
+ ```
141
+
142
+ The package exposes a single MCP entry point: `matplotlibapi-mcp`.
143
+
67
144
  ## Plot Types
68
145
 
69
146
  The library supports the following plot types:
@@ -92,10 +169,16 @@ This repository includes a `data` directory with sample CSV files for each plot
92
169
 
93
170
  ```python
94
171
  import pandas as pd
95
- from MatplotLibAPI import fplot_bubble
172
+ from MatplotLibAPI.bubble import Bubble
96
173
 
97
174
  df = pd.read_csv('data/bubble.csv')
98
- fig = fplot_bubble(df, label='country', x='gdp_per_capita', y='life_expectancy', z='population')
175
+ fig = Bubble(
176
+ pd_df=df,
177
+ label='country',
178
+ x='gdp_per_capita',
179
+ y='life_expectancy',
180
+ z='population',
181
+ ).fplot()
99
182
  fig.show()
100
183
  ```
101
184
 
@@ -103,10 +186,16 @@ fig.show()
103
186
 
104
187
  ```python
105
188
  import pandas as pd
106
- from MatplotLibAPI import fplot_network
189
+ from MatplotLibAPI.network import NetworkGraph
107
190
 
108
191
  df = pd.read_csv('data/network.csv')
109
- fig = fplot_network(df)
192
+ graph = NetworkGraph.from_pandas_edgelist(
193
+ df,
194
+ source='city_a',
195
+ target='city_b',
196
+ edge_weight_col='distance_km',
197
+ )
198
+ fig = graph.fplot(title='City Network', edge_weight_col='distance_km')
110
199
  fig.show()
111
200
  ```
112
201
 
@@ -0,0 +1,36 @@
1
+ MatplotLibAPI/__init__.py,sha256=RRT8uq3WQT16lUVs4DgwmL3pCeK6Jo6nOu8LIZFjMH4,200
2
+ MatplotLibAPI/accessor.py,sha256=XfPQrDqYBmNiGRHwD8kPZ0m-O8qU4FWZRT0OHsMFhzY,64092
3
+ MatplotLibAPI/area.py,sha256=DZXmn8vZIaRiPlZq0CIhwXtBQ8vgyNBq1bqEern4Wqo,4714
4
+ MatplotLibAPI/bar.py,sha256=zh5mcCP9FYjzi9WDlO9D6x9tuYpHN195j9Pf6_jSjrA,5107
5
+ MatplotLibAPI/base_plot.py,sha256=ubbjfk5cRr7YvlEzZ9wiZ_V_KsbUV_S5pmirHxD9fR8,2621
6
+ MatplotLibAPI/box_violin.py,sha256=xsa-hNV0hp2zjPg4_bLjBp0lMOezBxTTOtvmooJF65M,4849
7
+ MatplotLibAPI/bubble.py,sha256=-BLEclKxVpagW3dk8qwEruemehJmy-kALttatgcMILI,17035
8
+ MatplotLibAPI/composite.py,sha256=vhPSWpXDzVKcy5BZAgyvtKOKEukepgtZzDAU473Tv1E,9531
9
+ MatplotLibAPI/heatmap.py,sha256=pXmB5GzU8Gg8JRoIoMc3ZzSej80GgYO3m7j8PKZV7JQ,5855
10
+ MatplotLibAPI/histogram.py,sha256=k8MpTR76c8FpKMyNOsq3oVHIj2Wwj73hSpeXQmJonik,4743
11
+ MatplotLibAPI/mcp_server.py,sha256=hjyW7PipY78rLb5uBU5s9tf3bYRM4GBcR91MzMSX794,19192
12
+ MatplotLibAPI/pie.py,sha256=VGgTJoPu42JYqXlvI9WC4QB7IEXmHDj3BXDPjRzqyGc,4605
13
+ MatplotLibAPI/pivot.py,sha256=rk5wQYxPzSb6gkvtuTmDc9ZpaNThoeSl03BCQ4azOWw,7098
14
+ MatplotLibAPI/sankey.py,sha256=OtQWzzQP9iPJAWylFnuKnmNFEkjHnzw7zyMQBUl9RIM,3022
15
+ MatplotLibAPI/style_template.py,sha256=AFihe6twpMLYc_4-o0mhEvVMitTRtdAayZB-ECzUSsA,8498
16
+ MatplotLibAPI/sunburst.py,sha256=9UPzov1H9hUHQZ1DtH9pVMjpbeSnnIdRVPpr7majgt8,3767
17
+ MatplotLibAPI/table.py,sha256=LAzsVPZ4cpbqqaQ-mBAI-2X715hkFA9bhV6LbRaNBXw,7086
18
+ MatplotLibAPI/timeserie.py,sha256=iE_MIDwQ19dWgxoxa0kd27DdfBe5__XsxrDp5qk1W-k,11899
19
+ MatplotLibAPI/treemap.py,sha256=jmaBMlMbP9DY7ox49q1ispuK16Fz0lBNvuyZOrrwcFk,4903
20
+ MatplotLibAPI/typing.py,sha256=zZSz7cpqPSEIrryUEW5C1_QFjEjBeeAVjBu5jktskAk,441
21
+ MatplotLibAPI/utils.py,sha256=SZvDGsm1pt9rp1JxeXkQ4wUmOj6b3b04U2KiBmFwwJ4,1792
22
+ MatplotLibAPI/waffle.py,sha256=KXnJ5A_4_9bICt1MAj9ExwlmB9EV_W34HRuXFAV99W8,5247
23
+ MatplotLibAPI/word_cloud.py,sha256=qMBLFL1E5vGsclaz5ty-CxaD60aOQwmvaQiDkuroHwQ,15420
24
+ MatplotLibAPI/mcp/__init__.py,sha256=KzF8wOB2dQrAs7i1YMqQWyLnpu0vO8OS7HGyFIGkyoA,434
25
+ MatplotLibAPI/mcp/metadata.py,sha256=G0SJSG4XX5fZQy5F2Ii3FVOM1uprFcBvbGpK7BxMDrg,2880
26
+ MatplotLibAPI/mcp/renderers.py,sha256=mB7B9ytr1ahxkK8MSIFQKtQ3nz5jYmX2JgKhQUe0W4k,1293
27
+ MatplotLibAPI/network/__init__.py,sha256=jyef46y8-3u7hVhUHfFVEvYx65jSptyIbuJP6VE_f2Y,728
28
+ MatplotLibAPI/network/constants.py,sha256=ugU0w1del8XFa-fzHaiKIGXQVhVj4TUyeYEtyDc68JM,466
29
+ MatplotLibAPI/network/core.py,sha256=6h5eR6fwkIbO_GjDjO_Y4fBwbnvTR51jEn5d0-7qjxU,45759
30
+ MatplotLibAPI/network/plot.py,sha256=bktXpQuJRriqhCzELYS9HU1NlsM99nqpSOgww6WRVTk,20435
31
+ MatplotLibAPI/network/scaling.py,sha256=gdbusWHN-yKYWJjSZ0viqM5S31tZfLH8hojoc2fML2c,1632
32
+ matplotlibapi-4.0.0.dist-info/METADATA,sha256=EVMDbThu5oKgPI3iuPiqSx3H_wP_Yw9PGBgGoFbWG4E,8451
33
+ matplotlibapi-4.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
34
+ matplotlibapi-4.0.0.dist-info/entry_points.txt,sha256=x_X45-YKZXmwaEiyYqQiPVHFJ5Rj66qwNesDDq3-rWg,68
35
+ matplotlibapi-4.0.0.dist-info/licenses/LICENSE,sha256=hMErKLb6YZR3lRR5zr-vxeFkvY69QAaafgSpZ5-P1dQ,1067
36
+ matplotlibapi-4.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.29.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ matplotlibapi-mcp = MatplotLibAPI.mcp_server:main