acadplot 0.1.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.
acadplot/__init__.py ADDED
@@ -0,0 +1,70 @@
1
+ from .styles import (
2
+ available_fonts,
3
+ available_layouts,
4
+ available_text_colors,
5
+ available_themes,
6
+ configure_plot_style,
7
+ despine,
8
+ figure_size,
9
+ format_axes,
10
+ get_current_style,
11
+ use_style,
12
+ )
13
+ from .draw import draw, draw_bar
14
+ from .line import plot_line
15
+ from .bar import plot_bar, plot_grouped_bar, plot_stacked_bar
16
+ from .charts import plot_box, plot_errorbar, plot_heatmap, plot_scatter
17
+ from .utils import (
18
+ annotate_points,
19
+ available_patterns,
20
+ blend_color,
21
+ colors,
22
+ format_legend,
23
+ get_output_dir,
24
+ markers,
25
+ new_alpha,
26
+ panel_labels,
27
+ patterns,
28
+ save,
29
+ save_all,
30
+ set_output_dir,
31
+ theme_preview,
32
+ )
33
+
34
+ __version__ = "0.1.0"
35
+ __all__ = [
36
+ "plot_line",
37
+ "plot_bar",
38
+ "plot_grouped_bar",
39
+ "plot_stacked_bar",
40
+ "plot_scatter",
41
+ "plot_errorbar",
42
+ "plot_box",
43
+ "plot_heatmap",
44
+ "draw",
45
+ "draw_bar",
46
+ "configure_plot_style",
47
+ "available_fonts",
48
+ "available_layouts",
49
+ "available_text_colors",
50
+ "available_themes",
51
+ "figure_size",
52
+ "format_axes",
53
+ "format_legend",
54
+ "despine",
55
+ "get_current_style",
56
+ "use_style",
57
+ "save",
58
+ "save_all",
59
+ "set_output_dir",
60
+ "get_output_dir",
61
+ "panel_labels",
62
+ "annotate_points",
63
+ "theme_preview",
64
+ "available_patterns",
65
+ "colors",
66
+ "markers",
67
+ "patterns",
68
+ "new_alpha",
69
+ "blend_color",
70
+ ]
acadplot/bar.py ADDED
@@ -0,0 +1,428 @@
1
+ from typing import List, Optional, Sequence, Tuple
2
+
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.colors import to_rgba
5
+
6
+ from .draw import draw_bar, resolve_color_key, resolve_pattern_key
7
+ from .styles import (
8
+ apply_axis_style,
9
+ apply_grid,
10
+ configure_plot_style,
11
+ get_current_style,
12
+ )
13
+ from .utils import new_alpha, save, styled_legend
14
+
15
+
16
+ def _prepare_axes(ax, fig_size):
17
+ if ax is None:
18
+ return plt.subplots(figsize=fig_size)
19
+ return ax.figure, ax
20
+
21
+
22
+ def _resolve_text_sizes(style, font_size, label_size, tick_size, legend_size):
23
+ font_size_override = font_size
24
+ if font_size is None:
25
+ font_size = float(style["font_size"])
26
+ if label_size is None:
27
+ label_size = (
28
+ font_size_override
29
+ if font_size_override is not None
30
+ else float(style["label_size"])
31
+ )
32
+ if tick_size is None:
33
+ tick_size = (
34
+ font_size_override
35
+ if font_size_override is not None
36
+ else float(style["tick_size"])
37
+ )
38
+ if legend_size is None:
39
+ legend_size = (
40
+ font_size_override
41
+ if font_size_override is not None
42
+ else float(style["legend_size"])
43
+ )
44
+ return font_size, label_size, tick_size, legend_size
45
+
46
+
47
+ def _apply_bar_axis_style(ax, tick_size: float) -> None:
48
+ ax.tick_params(axis="both", labelsize=tick_size)
49
+ apply_axis_style(ax)
50
+ ax.tick_params(axis="x", length=0)
51
+
52
+
53
+ def _parse_bar(bar):
54
+ if len(bar) == 5:
55
+ x, y, color_key, pattern_key, bar_label = bar
56
+ return x, y, color_key, pattern_key, bar_label
57
+ if len(bar) == 4:
58
+ x, y, color_key, bar_label = bar
59
+ return x, y, color_key, None, bar_label
60
+ if len(bar) == 3:
61
+ x, y, bar_label = bar
62
+ return x, y, None, None, bar_label
63
+ raise ValueError("Bar entries must be (x, y, color, label) or (x, y, label).")
64
+
65
+
66
+ def _parse_group_bar(entry):
67
+ if len(entry) == 4:
68
+ value, color_key, pattern_key, bar_label = entry
69
+ return value, color_key, pattern_key, bar_label
70
+ if len(entry) == 3:
71
+ value, color_key, bar_label = entry
72
+ return value, color_key, None, bar_label
73
+ if len(entry) == 2:
74
+ value, bar_label = entry
75
+ return value, None, None, bar_label
76
+ raise ValueError(
77
+ "Grouped bar entries must be (value, color, label) or (value, label)."
78
+ )
79
+
80
+
81
+ def _parse_stack(stack):
82
+ if len(stack) == 4:
83
+ values, color_key, pattern_key, stack_label = stack
84
+ return values, color_key, pattern_key, stack_label
85
+ if len(stack) == 3:
86
+ values, color_key, stack_label = stack
87
+ return values, color_key, None, stack_label
88
+ if len(stack) == 2:
89
+ values, stack_label = stack
90
+ return values, None, None, stack_label
91
+ raise ValueError("Stack entries must be (values, color, label) or (values, label).")
92
+
93
+
94
+ def _resolve_pattern(patterns, index: int, label: str, explicit_pattern):
95
+ if explicit_pattern is not None:
96
+ return resolve_pattern_key(explicit_pattern)
97
+ if patterns is None:
98
+ return None
99
+ if isinstance(patterns, dict):
100
+ return resolve_pattern_key(patterns.get(label))
101
+ if not patterns:
102
+ return None
103
+ return resolve_pattern_key(patterns[index % len(patterns)])
104
+
105
+
106
+ def _add_legend(
107
+ ax,
108
+ location: str,
109
+ legend_size: float,
110
+ ncols: int,
111
+ columnspacing: float,
112
+ legend_outside: bool | str,
113
+ ):
114
+ return styled_legend(
115
+ ax,
116
+ location,
117
+ legend_size=legend_size,
118
+ ncols=ncols,
119
+ columnspacing=columnspacing,
120
+ legend_outside=legend_outside,
121
+ )
122
+
123
+
124
+ def plot_bar(
125
+ bars: List[Tuple],
126
+ location: str,
127
+ fig_size: Optional[Tuple[float, float]] = None,
128
+ label: Tuple[str, str] = ("x-label", "y-label"),
129
+ ax=None,
130
+ xticklabels: Optional[List[str]] = None,
131
+ rotation: float = 0.0,
132
+ font_size: Optional[float] = None,
133
+ label_size: Optional[float] = None,
134
+ tick_size: Optional[float] = None,
135
+ legend_size: Optional[float] = None,
136
+ ncols: int = 1,
137
+ columnspacing: float = 0.5,
138
+ bar_width: float = 0.35,
139
+ patterns: Optional[Sequence[str | int] | dict[str, str | int]] = None,
140
+ grid: Optional[str] = None,
141
+ fname: Optional[str] = "bar_plot.pdf",
142
+ legend_outside: bool | str = False,
143
+ ):
144
+ """Plot a bar chart with a legend.
145
+
146
+ Args:
147
+ bars (List[Tuple]): List of bars, each defined by x positions, y values,
148
+ optional color name/index/raw Matplotlib color, and label. Omit color
149
+ to use the active theme palette cycle.
150
+ location (str): Location of the legend.
151
+ fig_size (Tuple[float, float], optional): Figure size. Defaults to the active style.
152
+ label (Tuple[str, str], optional): Labels for the x and y axes. Defaults to ("x-label", "y-label").
153
+ ax (Optional[plt.Axes], optional): Axes to plot on. Creates new if None. Defaults to None.
154
+ xticklabels (Optional[List[str]], optional): Labels for x-axis ticks. Defaults to None.
155
+ rotation (float, optional): Rotation angle for x-tick labels. Defaults to 0.0.
156
+ font_size (float, optional): Base font size for labels, ticks, and legend. Defaults to the active style.
157
+ label_size (float, optional): Axis label size. Defaults to font_size or the active style.
158
+ tick_size (float, optional): Tick label size. Defaults to font_size or the active style.
159
+ legend_size (float, optional): Legend text size. Defaults to font_size or the active style.
160
+ ncols (int, optional): Number of columns in the legend. Defaults to 1.
161
+ columnspacing (float, optional): Spacing between legend columns. Defaults to 0.5.
162
+ bar_width (float, optional): Width of the bars. Defaults to 0.35.
163
+ grid (str, optional): Grid preset: "major-y", "major", "major-minor", or "none".
164
+ fname (Optional[str], optional): Filename to save the plot. Defaults to "bar_plot.pdf".
165
+ """
166
+ style = get_current_style()
167
+ if fig_size is None:
168
+ fig_size = style["fig_size"]
169
+ font_size, label_size, tick_size, legend_size = _resolve_text_sizes(
170
+ style,
171
+ font_size,
172
+ label_size,
173
+ tick_size,
174
+ legend_size,
175
+ )
176
+
177
+ fig, ax = _prepare_axes(ax, fig_size)
178
+
179
+ ax.set_prop_cycle(color=list(style["palette"]))
180
+ ax.set_xlabel(label[0], fontsize=label_size)
181
+ ax.set_ylabel(label[1], fontsize=label_size)
182
+ apply_grid(ax, grid or str(style["bar_grid"]))
183
+
184
+ for bar_idx, bar in enumerate(bars):
185
+ x, y, color_key, pattern_key, bar_label = _parse_bar(bar)
186
+ pattern = _resolve_pattern(patterns, bar_idx, bar_label, pattern_key)
187
+ draw_bar(ax, x, y, color_key, bar_label, bar_width, pattern)
188
+
189
+ _add_legend(ax, location, legend_size, ncols, columnspacing, legend_outside)
190
+
191
+ if xticklabels is not None and bars:
192
+ ax.set_xticks(bars[0][0])
193
+ ax.set_xticklabels(xticklabels, rotation=rotation, fontsize=tick_size)
194
+
195
+ _apply_bar_axis_style(ax, tick_size)
196
+
197
+ if fname:
198
+ save(fig, fname, close=False)
199
+
200
+ return fig, ax
201
+
202
+
203
+ def plot_grouped_bar(
204
+ groups: List[Tuple[str, List[Tuple]]],
205
+ location: str,
206
+ fig_size: Optional[Tuple[float, float]] = None,
207
+ label: Tuple[str, str] = ("x-label", "y-label"),
208
+ ax=None,
209
+ rotation: float = 0.0,
210
+ font_size: Optional[float] = None,
211
+ label_size: Optional[float] = None,
212
+ tick_size: Optional[float] = None,
213
+ legend_size: Optional[float] = None,
214
+ ncols: int = 1,
215
+ columnspacing: float = 0.5,
216
+ bar_width: float = 0.25,
217
+ patterns: Optional[Sequence[str | int] | dict[str, str | int]] = None,
218
+ grid: Optional[str] = None,
219
+ fname: Optional[str] = "grouped_bar_plot.pdf",
220
+ legend_outside: bool | str = False,
221
+ ):
222
+ """Plot a grouped bar chart with multiple bars per group.
223
+
224
+ Args:
225
+ groups (List[Tuple[str, List[Tuple]]]): List of groups, each defined by
226
+ group name and a list of (value, optional color, label) tuples. Omit
227
+ color to use the active theme palette cycle.
228
+ location (str): Location of the legend.
229
+ fig_size (Tuple[float, float], optional): Figure size. Defaults to the active style.
230
+ label (Tuple[str, str], optional): Labels for the x and y axes. Defaults to ("x-label", "y-label").
231
+ ax (Optional[plt.Axes], optional): Axes to plot on. Creates new if None. Defaults to None.
232
+ rotation (float, optional): Rotation angle for x-tick labels. Defaults to 0.0.
233
+ font_size (float, optional): Base font size for labels, ticks, and legend. Defaults to the active style.
234
+ label_size (float, optional): Axis label size. Defaults to font_size or the active style.
235
+ tick_size (float, optional): Tick label size. Defaults to font_size or the active style.
236
+ legend_size (float, optional): Legend text size. Defaults to font_size or the active style.
237
+ ncols (int, optional): Number of columns in the legend. Defaults to 1.
238
+ columnspacing (float, optional): Spacing between legend columns. Defaults to 0.5.
239
+ bar_width (float, optional): Width of each bar. Defaults to 0.25.
240
+ grid (str, optional): Grid preset: "major-y", "major", "major-minor", or "none".
241
+ fname (Optional[str], optional): Filename to save the plot. Defaults to "grouped_bar_plot.pdf".
242
+ """
243
+ style = get_current_style()
244
+ if fig_size is None:
245
+ fig_size = style["fig_size"]
246
+ font_size, label_size, tick_size, legend_size = _resolve_text_sizes(
247
+ style,
248
+ font_size,
249
+ label_size,
250
+ tick_size,
251
+ legend_size,
252
+ )
253
+
254
+ fig, ax = _prepare_axes(ax, fig_size)
255
+
256
+ ax.set_prop_cycle(color=list(style["palette"]))
257
+ ax.set_xlabel(label[0], fontsize=label_size)
258
+ ax.set_ylabel(label[1], fontsize=label_size)
259
+ apply_grid(ax, grid or str(style["bar_grid"]))
260
+
261
+ n_bars = len(groups[0][1]) if groups else 0
262
+ group_positions = range(len(groups))
263
+
264
+ for bar_idx in range(n_bars):
265
+ positions = [
266
+ pos + (bar_idx - n_bars / 2 + 0.5) * bar_width for pos in group_positions
267
+ ]
268
+ values = [group[1][bar_idx][0] for group in groups]
269
+ _, color_key, pattern_key, bar_label = _parse_group_bar(groups[0][1][bar_idx])
270
+ pattern = _resolve_pattern(patterns, bar_idx, bar_label, pattern_key)
271
+ draw_bar(ax, positions, values, color_key, bar_label, bar_width, pattern)
272
+
273
+ ax.set_xticks(list(group_positions))
274
+ ax.set_xticklabels([g[0] for g in groups], rotation=rotation, fontsize=tick_size)
275
+
276
+ _add_legend(ax, location, legend_size, ncols, columnspacing, legend_outside)
277
+ _apply_bar_axis_style(ax, tick_size)
278
+
279
+ if fname:
280
+ save(fig, fname, close=False)
281
+
282
+ return fig, ax
283
+
284
+
285
+ def plot_stacked_bar(
286
+ categories: List[str],
287
+ stacks: List[Tuple],
288
+ location: str,
289
+ fig_size: Optional[Tuple[float, float]] = None,
290
+ label: Tuple[str, str] = ("x-label", "y-label"),
291
+ ax=None,
292
+ rotation: float = 0.0,
293
+ font_size: Optional[float] = None,
294
+ label_size: Optional[float] = None,
295
+ tick_size: Optional[float] = None,
296
+ legend_size: Optional[float] = None,
297
+ ncols: int = 1,
298
+ columnspacing: float = 0.5,
299
+ bar_width: float = 0.35,
300
+ patterns: Optional[Sequence[str | int] | dict[str, str | int]] = None,
301
+ grid: Optional[str] = None,
302
+ fname: Optional[str] = "stacked_bar_plot.pdf",
303
+ legend_outside: bool | str = False,
304
+ ):
305
+ """Plot a stacked bar chart.
306
+
307
+ Args:
308
+ categories (List[str]): Category labels for the x-axis.
309
+ stacks (List[Tuple]): List of stacks, each defined by values, optional
310
+ color name/index/raw Matplotlib color, and label. Omit color to use
311
+ the active theme palette cycle.
312
+ location (str): Location of the legend.
313
+ fig_size (Tuple[float, float], optional): Figure size. Defaults to the active style.
314
+ label (Tuple[str, str], optional): Labels for the x and y axes. Defaults to ("x-label", "y-label").
315
+ ax (Optional[plt.Axes], optional): Axes to plot on. Creates new if None. Defaults to None.
316
+ rotation (float, optional): Rotation angle for x-tick labels. Defaults to 0.0.
317
+ font_size (float, optional): Base font size for labels, ticks, and legend. Defaults to the active style.
318
+ label_size (float, optional): Axis label size. Defaults to font_size or the active style.
319
+ tick_size (float, optional): Tick label size. Defaults to font_size or the active style.
320
+ legend_size (float, optional): Legend text size. Defaults to font_size or the active style.
321
+ ncols (int, optional): Number of columns in the legend. Defaults to 1.
322
+ columnspacing (float, optional): Spacing between legend columns. Defaults to 0.5.
323
+ bar_width (float, optional): Width of the bars. Defaults to 0.35.
324
+ grid (str, optional): Grid preset: "major-y", "major", "major-minor", or "none".
325
+ fname (Optional[str], optional): Filename to save the plot. Defaults to "stacked_bar_plot.pdf".
326
+ """
327
+ style = get_current_style()
328
+ if fig_size is None:
329
+ fig_size = style["fig_size"]
330
+ font_size, label_size, tick_size, legend_size = _resolve_text_sizes(
331
+ style,
332
+ font_size,
333
+ label_size,
334
+ tick_size,
335
+ legend_size,
336
+ )
337
+
338
+ fig, ax = _prepare_axes(ax, fig_size)
339
+
340
+ ax.set_prop_cycle(color=list(style["palette"]))
341
+ ax.set_xlabel(label[0], fontsize=label_size)
342
+ ax.set_ylabel(label[1], fontsize=label_size)
343
+ apply_grid(ax, grid or str(style["bar_grid"]))
344
+
345
+ x_positions = range(len(categories))
346
+ bottoms = [0.0] * len(categories)
347
+
348
+ for stack_idx, stack in enumerate(stacks):
349
+ values, color_key, pattern_key, stack_label = _parse_stack(stack)
350
+ color = resolve_color_key(color_key)
351
+ pattern = _resolve_pattern(patterns, stack_idx, stack_label, pattern_key)
352
+ bar_alpha = float(style["bar_alpha"])
353
+ bar_edge_color = str(style["bar_edge_color"])
354
+ color_kwargs = {"edgecolor": bar_edge_color}
355
+ if color is not None:
356
+ color_kwargs["facecolor"] = new_alpha(to_rgba(color), bar_alpha)
357
+ container = ax.bar(
358
+ x_positions,
359
+ values,
360
+ bar_width,
361
+ bottom=bottoms,
362
+ linewidth=float(style["bar_edge_width"]),
363
+ hatch=pattern,
364
+ label=stack_label,
365
+ zorder=3,
366
+ **color_kwargs,
367
+ )
368
+ for patch in container.patches:
369
+ if color is None:
370
+ patch.set_facecolor(
371
+ new_alpha(to_rgba(patch.get_facecolor()), bar_alpha)
372
+ )
373
+ patch.set_edgecolor(bar_edge_color)
374
+ patch.set_alpha(None)
375
+ bottoms = [bottom + value for bottom, value in zip(bottoms, values)]
376
+
377
+ ax.set_xticks(list(x_positions))
378
+ ax.set_xticklabels(categories, rotation=rotation, fontsize=tick_size)
379
+
380
+ _add_legend(ax, location, legend_size, ncols, columnspacing, legend_outside)
381
+ _apply_bar_axis_style(ax, tick_size)
382
+
383
+ if fname:
384
+ save(fig, fname, close=False)
385
+
386
+ return fig, ax
387
+
388
+
389
+ if __name__ == "__main__":
390
+ configure_plot_style()
391
+
392
+ bar_data: List[Tuple[List[float], List[float], str | int, str]] = [
393
+ ([0, 1, 2, 3], [10, 20, 15, 25], "blue", "Bar A"),
394
+ ([0, 1, 2, 3], [8, 15, 12, 20], "orange", "Bar B"),
395
+ ]
396
+ plot_bar(
397
+ bar_data,
398
+ "upper left",
399
+ xticklabels=["A", "B", "C", "D"],
400
+ fname="examples/bar_plot.png",
401
+ font_size=8,
402
+ )
403
+
404
+ grouped_data: List[Tuple[str, List[Tuple[float, str | int, str]]]] = [
405
+ ("Model X", [(10, "blue", "Train"), (8, "orange", "Val")]),
406
+ ("Model Y", [(15, "blue", "Train"), (12, "orange", "Val")]),
407
+ ("Model Z", [(12, "blue", "Train"), (14, "orange", "Val")]),
408
+ ]
409
+ plot_grouped_bar(
410
+ grouped_data,
411
+ "upper left",
412
+ fname="examples/grouped_bar_plot.png",
413
+ font_size=8,
414
+ )
415
+
416
+ categories = ["Dataset A", "Dataset B", "Dataset C"]
417
+ stacked_data: List[Tuple[List[float], str | int, str]] = [
418
+ ([10, 20, 15], "blue", "Method A"),
419
+ ([5, 10, 8], "orange", "Method B"),
420
+ ([3, 5, 4], "green", "Method C"),
421
+ ]
422
+ plot_stacked_bar(
423
+ categories,
424
+ stacked_data,
425
+ "upper left",
426
+ fname="examples/stacked_bar_plot.png",
427
+ font_size=8,
428
+ )