figrecipe 0.6.0__py3-none-any.whl → 0.9.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.
- figrecipe/__init__.py +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_dev/demo_plotters/__init__.py +35 -166
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Coordinate transformation utilities for bbox extraction.
|
|
5
|
+
|
|
6
|
+
This module handles the transformation from matplotlib display coordinates
|
|
7
|
+
to image pixel coordinates for hit detection.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from matplotlib.figure import Figure
|
|
13
|
+
from matplotlib.transforms import Bbox
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def transform_bbox(
|
|
17
|
+
window_extent: Bbox,
|
|
18
|
+
fig: Figure,
|
|
19
|
+
tight_bbox: Bbox,
|
|
20
|
+
img_width: int,
|
|
21
|
+
img_height: int,
|
|
22
|
+
scale_x: float,
|
|
23
|
+
scale_y: float,
|
|
24
|
+
pad_inches: float,
|
|
25
|
+
saved_height_inches: float,
|
|
26
|
+
) -> Optional[Dict[str, float]]:
|
|
27
|
+
"""
|
|
28
|
+
Transform matplotlib window extent to image pixel coordinates.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
window_extent : Bbox
|
|
33
|
+
Bbox in display coordinates (points).
|
|
34
|
+
fig : Figure
|
|
35
|
+
Matplotlib figure.
|
|
36
|
+
tight_bbox : Bbox
|
|
37
|
+
Tight bbox of figure in inches.
|
|
38
|
+
img_width, img_height : int
|
|
39
|
+
Output image dimensions.
|
|
40
|
+
scale_x, scale_y : float
|
|
41
|
+
Scale factors from inches to pixels.
|
|
42
|
+
pad_inches : float
|
|
43
|
+
Padding added by bbox_inches='tight' (default 0.1).
|
|
44
|
+
saved_height_inches : float
|
|
45
|
+
Total saved image height including padding.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
dict or None
|
|
50
|
+
{x, y, width, height} in image pixels.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
dpi = fig.dpi
|
|
54
|
+
|
|
55
|
+
# Convert display coords to inches
|
|
56
|
+
x0_inches = window_extent.x0 / dpi
|
|
57
|
+
y0_inches = window_extent.y0 / dpi
|
|
58
|
+
x1_inches = window_extent.x1 / dpi
|
|
59
|
+
y1_inches = window_extent.y1 / dpi
|
|
60
|
+
|
|
61
|
+
# Transform to saved image coordinates
|
|
62
|
+
# Account for tight bbox origin and padding
|
|
63
|
+
x0_rel = x0_inches - tight_bbox.x0 + pad_inches
|
|
64
|
+
x1_rel = x1_inches - tight_bbox.x0 + pad_inches
|
|
65
|
+
|
|
66
|
+
# Y coordinate flip: matplotlib Y=0 at bottom, image Y=0 at top
|
|
67
|
+
y0_rel = saved_height_inches - (y1_inches - tight_bbox.y0 + pad_inches)
|
|
68
|
+
y1_rel = saved_height_inches - (y0_inches - tight_bbox.y0 + pad_inches)
|
|
69
|
+
|
|
70
|
+
# Scale to image pixels
|
|
71
|
+
x0_px = x0_rel * scale_x
|
|
72
|
+
y0_px = y0_rel * scale_y
|
|
73
|
+
x1_px = x1_rel * scale_x
|
|
74
|
+
y1_px = y1_rel * scale_y
|
|
75
|
+
|
|
76
|
+
# Clamp to bounds
|
|
77
|
+
x0_px = max(0, min(x0_px, img_width))
|
|
78
|
+
x1_px = max(0, min(x1_px, img_width))
|
|
79
|
+
y0_px = max(0, min(y0_px, img_height))
|
|
80
|
+
y1_px = max(0, min(y1_px, img_height))
|
|
81
|
+
|
|
82
|
+
width = x1_px - x0_px
|
|
83
|
+
height = y1_px - y0_px
|
|
84
|
+
|
|
85
|
+
if width <= 0 or height <= 0:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"x": float(x0_px),
|
|
90
|
+
"y": float(y0_px),
|
|
91
|
+
"width": float(width),
|
|
92
|
+
"height": float(height),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
except Exception:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def display_to_image(
|
|
100
|
+
display_x: float,
|
|
101
|
+
display_y: float,
|
|
102
|
+
fig: Figure,
|
|
103
|
+
tight_bbox: Bbox,
|
|
104
|
+
img_width: int,
|
|
105
|
+
img_height: int,
|
|
106
|
+
scale_x: float,
|
|
107
|
+
scale_y: float,
|
|
108
|
+
pad_inches: float,
|
|
109
|
+
saved_height_inches: float,
|
|
110
|
+
) -> Optional[List[float]]:
|
|
111
|
+
"""
|
|
112
|
+
Transform display coordinates to image pixel coordinates.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
list or None
|
|
117
|
+
[x, y] in image pixels.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
dpi = fig.dpi
|
|
121
|
+
|
|
122
|
+
# Convert to inches
|
|
123
|
+
x_inches = display_x / dpi
|
|
124
|
+
y_inches = display_y / dpi
|
|
125
|
+
|
|
126
|
+
# Transform to saved image coordinates with padding
|
|
127
|
+
x_rel = x_inches - tight_bbox.x0 + pad_inches
|
|
128
|
+
|
|
129
|
+
# Y coordinate flip: matplotlib Y=0 at bottom, image Y=0 at top
|
|
130
|
+
y_rel = saved_height_inches - (y_inches - tight_bbox.y0 + pad_inches)
|
|
131
|
+
|
|
132
|
+
# Scale to image pixels
|
|
133
|
+
x_px = x_rel * scale_x
|
|
134
|
+
y_px = y_rel * scale_y
|
|
135
|
+
|
|
136
|
+
# Clamp
|
|
137
|
+
x_px = max(0, min(x_px, img_width))
|
|
138
|
+
y_px = max(0, min(y_px, img_height))
|
|
139
|
+
|
|
140
|
+
return [float(x_px), float(y_px)]
|
|
141
|
+
|
|
142
|
+
except Exception:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
__all__ = ["transform_bbox", "display_to_image"]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Call-specific override application for element properties.
|
|
5
|
+
|
|
6
|
+
This is the SINGLE SOURCE OF TRUTH for applying element-level changes.
|
|
7
|
+
Both initial render and re-render use this same function through apply_overrides().
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
from matplotlib.figure import Figure
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def apply_call_overrides(
|
|
16
|
+
fig: Figure, call_overrides: Dict[str, Dict[str, Any]], record: Any
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Apply call-specific overrides to figure elements.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
fig : Figure
|
|
23
|
+
Matplotlib figure.
|
|
24
|
+
call_overrides : dict
|
|
25
|
+
Mapping from call_id to {param: value} overrides.
|
|
26
|
+
record : FigureRecord
|
|
27
|
+
Recording record to find call metadata.
|
|
28
|
+
"""
|
|
29
|
+
from matplotlib.patches import Wedge
|
|
30
|
+
|
|
31
|
+
axes_list = fig.get_axes()
|
|
32
|
+
|
|
33
|
+
# Build mapping from ax_key to axes index
|
|
34
|
+
# ax_keys are in format "ax_{row}_{col}", need to map to actual axes indices
|
|
35
|
+
ax_keys_sorted = sorted(record.axes.keys())
|
|
36
|
+
ax_key_to_index = {key: idx for idx, key in enumerate(ax_keys_sorted)}
|
|
37
|
+
|
|
38
|
+
for call_id, params in call_overrides.items():
|
|
39
|
+
# Find the call in record to get function type, ax_index, and call position
|
|
40
|
+
call_function = None
|
|
41
|
+
ax_index = None
|
|
42
|
+
ax_record_found = None
|
|
43
|
+
for ax_key, ax_record in record.axes.items():
|
|
44
|
+
for call in ax_record.calls:
|
|
45
|
+
if call.id == call_id:
|
|
46
|
+
call_function = call.function
|
|
47
|
+
ax_record_found = ax_record
|
|
48
|
+
# Use sorted key order to get correct axes index
|
|
49
|
+
ax_index = ax_key_to_index.get(ax_key, 0)
|
|
50
|
+
break
|
|
51
|
+
if call_function:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if call_function is None or ax_index is None or ax_index >= len(axes_list):
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
ax = axes_list[ax_index]
|
|
58
|
+
|
|
59
|
+
# Apply overrides based on plot type
|
|
60
|
+
for param, value in params.items():
|
|
61
|
+
if call_function in ("bar", "barh", "hist"):
|
|
62
|
+
# Bar/hist creates multiple patches per call - apply to ALL
|
|
63
|
+
_apply_bar_override(ax, ax_record_found, call_id, param, value)
|
|
64
|
+
elif call_function == "plot":
|
|
65
|
+
_apply_line_override(ax, ax_record_found, call_id, param, value)
|
|
66
|
+
elif call_function == "scatter":
|
|
67
|
+
_apply_scatter_override(ax, ax_record_found, call_id, param, value)
|
|
68
|
+
elif call_function == "pie":
|
|
69
|
+
# Pie wedges - apply to all wedges for this call
|
|
70
|
+
wedges = [p for p in ax.patches if isinstance(p, Wedge)]
|
|
71
|
+
for wedge in wedges:
|
|
72
|
+
_apply_patch_param(wedge, param, value)
|
|
73
|
+
elif call_function in ("fill_between", "fill_betweenx"):
|
|
74
|
+
_apply_fill_override(ax, ax_record_found, call_id, param, value)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _apply_bar_override(ax, ax_record, call_id, param, value):
|
|
78
|
+
"""Apply override to bar/hist patches for a specific call."""
|
|
79
|
+
from matplotlib.patches import Rectangle
|
|
80
|
+
|
|
81
|
+
rectangles = [p for p in ax.patches if isinstance(p, Rectangle)]
|
|
82
|
+
if not rectangles:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Find all bar/hist calls to determine grouping
|
|
86
|
+
bar_calls = [c for c in ax_record.calls if c.function in ("bar", "barh", "hist")]
|
|
87
|
+
if not bar_calls:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Find which call index this is
|
|
91
|
+
call_idx = next((i for i, c in enumerate(bar_calls) if c.id == call_id), None)
|
|
92
|
+
if call_idx is None:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# Distribute patches among calls
|
|
96
|
+
patches_per_call = len(rectangles) // len(bar_calls) if bar_calls else 1
|
|
97
|
+
start_idx = call_idx * patches_per_call
|
|
98
|
+
end_idx = start_idx + patches_per_call
|
|
99
|
+
|
|
100
|
+
# Apply to all patches for this call
|
|
101
|
+
for patch in rectangles[start_idx:end_idx]:
|
|
102
|
+
_apply_patch_param(patch, param, value)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _apply_line_override(ax, ax_record, call_id, param, value):
|
|
106
|
+
"""Apply override to line for a specific call."""
|
|
107
|
+
lines = [line for line in ax.get_lines() if not line.get_label().startswith("_")]
|
|
108
|
+
line_calls = [c for c in ax_record.calls if c.function == "plot"]
|
|
109
|
+
|
|
110
|
+
call_idx = next((i for i, c in enumerate(line_calls) if c.id == call_id), None)
|
|
111
|
+
if call_idx is not None and call_idx < len(lines):
|
|
112
|
+
_apply_line_param(lines[call_idx], param, value)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _apply_scatter_override(ax, ax_record, call_id, param, value):
|
|
116
|
+
"""Apply override to scatter collection for a specific call."""
|
|
117
|
+
from matplotlib.collections import PathCollection
|
|
118
|
+
|
|
119
|
+
collections = [c for c in ax.collections if isinstance(c, PathCollection)]
|
|
120
|
+
scatter_calls = [c for c in ax_record.calls if c.function == "scatter"]
|
|
121
|
+
|
|
122
|
+
call_idx = next((i for i, c in enumerate(scatter_calls) if c.id == call_id), None)
|
|
123
|
+
if call_idx is not None and call_idx < len(collections):
|
|
124
|
+
_apply_collection_param(collections[call_idx], param, value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _apply_fill_override(ax, ax_record, call_id, param, value):
|
|
128
|
+
"""Apply override to fill_between for a specific call."""
|
|
129
|
+
from matplotlib.collections import PolyCollection
|
|
130
|
+
|
|
131
|
+
fills = [c for c in ax.collections if isinstance(c, PolyCollection)]
|
|
132
|
+
fill_calls = [
|
|
133
|
+
c for c in ax_record.calls if c.function in ("fill_between", "fill_betweenx")
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
call_idx = next((i for i, c in enumerate(fill_calls) if c.id == call_id), None)
|
|
137
|
+
if call_idx is not None and call_idx < len(fills):
|
|
138
|
+
_apply_collection_param(fills[call_idx], param, value)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _apply_patch_param(patch: Any, param: str, value: Any) -> None:
|
|
142
|
+
"""Apply parameter to a patch (bar, wedge, etc.)."""
|
|
143
|
+
if param == "color":
|
|
144
|
+
patch.set_facecolor(value)
|
|
145
|
+
elif param == "edgecolor":
|
|
146
|
+
patch.set_edgecolor(value)
|
|
147
|
+
elif param == "linewidth":
|
|
148
|
+
patch.set_linewidth(value)
|
|
149
|
+
elif param == "alpha":
|
|
150
|
+
patch.set_alpha(value)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _apply_line_param(line: Any, param: str, value: Any) -> None:
|
|
154
|
+
"""Apply parameter to a line."""
|
|
155
|
+
if param == "color":
|
|
156
|
+
line.set_color(value)
|
|
157
|
+
elif param == "linewidth":
|
|
158
|
+
line.set_linewidth(value)
|
|
159
|
+
elif param == "linestyle":
|
|
160
|
+
line.set_linestyle(value)
|
|
161
|
+
elif param == "alpha":
|
|
162
|
+
line.set_alpha(value)
|
|
163
|
+
elif param == "marker":
|
|
164
|
+
line.set_marker(value)
|
|
165
|
+
elif param == "markersize":
|
|
166
|
+
line.set_markersize(value)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _apply_collection_param(coll: Any, param: str, value: Any) -> None:
|
|
170
|
+
"""Apply parameter to a collection (scatter, fill)."""
|
|
171
|
+
if param in ("color", "c"):
|
|
172
|
+
coll.set_facecolors(value)
|
|
173
|
+
elif param == "edgecolor":
|
|
174
|
+
coll.set_edgecolors(value)
|
|
175
|
+
elif param == "s":
|
|
176
|
+
coll.set_sizes([value] if not hasattr(value, "__len__") else value)
|
|
177
|
+
elif param == "alpha":
|
|
178
|
+
coll.set_alpha(value)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = ["apply_call_overrides"]
|
|
182
|
+
|
|
183
|
+
# EOF
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Plot handlers for datatable plotting functionality."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dispatch_plot(ax, plot_type, plot_data, columns):
|
|
9
|
+
"""Dispatch plot based on type and data.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
ax: Matplotlib axes object
|
|
13
|
+
plot_type: Frontend plot type name
|
|
14
|
+
plot_data: Dict mapping column names to data arrays
|
|
15
|
+
columns: List of column names in order
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
True on success
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If plot type is unknown
|
|
22
|
+
"""
|
|
23
|
+
# Map frontend names to matplotlib method names
|
|
24
|
+
method_name = plot_type
|
|
25
|
+
if plot_type == "line":
|
|
26
|
+
method_name = "plot"
|
|
27
|
+
elif plot_type == "histogram":
|
|
28
|
+
method_name = "hist"
|
|
29
|
+
|
|
30
|
+
# Get the plotting method
|
|
31
|
+
plot_method = getattr(ax, method_name, None)
|
|
32
|
+
if plot_method is None:
|
|
33
|
+
raise ValueError(f"Unknown plot type: {plot_type}")
|
|
34
|
+
|
|
35
|
+
# Prepare data arrays
|
|
36
|
+
data_arrays = [np.array(plot_data.get(c, [])) for c in columns]
|
|
37
|
+
|
|
38
|
+
# Handle decoration methods
|
|
39
|
+
if _handle_decoration(ax, method_name, data_arrays):
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
# Handle specialized plot types
|
|
43
|
+
if _handle_specialized(ax, plot_method, method_name, data_arrays, columns):
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
# Handle standard xy plots
|
|
47
|
+
_handle_standard_xy(ax, plot_method, method_name, data_arrays, columns)
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _handle_decoration(ax, method_name, data_arrays):
|
|
52
|
+
"""Handle decoration methods (scalar-based, iterate over rows)."""
|
|
53
|
+
decoration_methods = {
|
|
54
|
+
"text",
|
|
55
|
+
"annotate",
|
|
56
|
+
"arrow",
|
|
57
|
+
"axhline",
|
|
58
|
+
"axvline",
|
|
59
|
+
"axhspan",
|
|
60
|
+
"axvspan",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if method_name not in decoration_methods:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
n_rows = len(data_arrays[0]) if data_arrays else 0
|
|
67
|
+
|
|
68
|
+
for row_idx in range(n_rows):
|
|
69
|
+
row_vals = [arr[row_idx] for arr in data_arrays]
|
|
70
|
+
|
|
71
|
+
if method_name == "text" and len(row_vals) >= 2:
|
|
72
|
+
s = str(row_vals[2]) if len(row_vals) >= 3 else ""
|
|
73
|
+
ax.text(row_vals[0], row_vals[1], s)
|
|
74
|
+
|
|
75
|
+
elif method_name == "annotate":
|
|
76
|
+
if len(row_vals) >= 3:
|
|
77
|
+
ax.annotate(str(row_vals[0]), xy=(row_vals[1], row_vals[2]))
|
|
78
|
+
elif len(row_vals) == 2:
|
|
79
|
+
ax.annotate("", xy=(row_vals[0], row_vals[1]))
|
|
80
|
+
|
|
81
|
+
elif method_name == "arrow" and len(row_vals) >= 4:
|
|
82
|
+
ax.arrow(
|
|
83
|
+
row_vals[0],
|
|
84
|
+
row_vals[1],
|
|
85
|
+
row_vals[2],
|
|
86
|
+
row_vals[3],
|
|
87
|
+
head_width=0.1,
|
|
88
|
+
head_length=0.05,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
elif method_name == "axhline" and len(row_vals) >= 1:
|
|
92
|
+
ax.axhline(y=row_vals[0])
|
|
93
|
+
|
|
94
|
+
elif method_name == "axvline" and len(row_vals) >= 1:
|
|
95
|
+
ax.axvline(x=row_vals[0])
|
|
96
|
+
|
|
97
|
+
elif method_name == "axhspan" and len(row_vals) >= 2:
|
|
98
|
+
ax.axhspan(row_vals[0], row_vals[1], alpha=0.3)
|
|
99
|
+
|
|
100
|
+
elif method_name == "axvspan" and len(row_vals) >= 2:
|
|
101
|
+
ax.axvspan(row_vals[0], row_vals[1], alpha=0.3)
|
|
102
|
+
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _handle_specialized(ax, plot_method, method_name, data_arrays, columns):
|
|
107
|
+
"""Handle specialized plot types that need custom argument handling."""
|
|
108
|
+
if method_name in ("boxplot", "violinplot"):
|
|
109
|
+
if method_name == "boxplot":
|
|
110
|
+
plot_method(data_arrays, labels=columns)
|
|
111
|
+
else:
|
|
112
|
+
plot_method(data_arrays)
|
|
113
|
+
ax.set_xticks(range(1, len(columns) + 1))
|
|
114
|
+
ax.set_xticklabels(columns)
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
if method_name == "pie":
|
|
118
|
+
labels = columns[1:] if len(columns) > 1 else None
|
|
119
|
+
plot_method(data_arrays[0], labels=labels, autopct="%1.1f%%")
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
if method_name in (
|
|
123
|
+
"hist",
|
|
124
|
+
"acorr",
|
|
125
|
+
"psd",
|
|
126
|
+
"specgram",
|
|
127
|
+
"angle_spectrum",
|
|
128
|
+
"phase_spectrum",
|
|
129
|
+
"magnitude_spectrum",
|
|
130
|
+
):
|
|
131
|
+
for i, arr in enumerate(data_arrays):
|
|
132
|
+
plot_method(arr, label=columns[i])
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
if method_name in ("hist2d", "hexbin", "xcorr", "csd", "cohere"):
|
|
136
|
+
if len(data_arrays) >= 2:
|
|
137
|
+
plot_method(data_arrays[0], data_arrays[1])
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
if method_name in ("fill_between", "fill_betweenx"):
|
|
141
|
+
if len(data_arrays) >= 3:
|
|
142
|
+
plot_method(data_arrays[0], data_arrays[1], data_arrays[2], alpha=0.5)
|
|
143
|
+
elif len(data_arrays) >= 2:
|
|
144
|
+
plot_method(data_arrays[0], data_arrays[1], alpha=0.5)
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
if method_name == "errorbar" and len(data_arrays) >= 3:
|
|
148
|
+
plot_method(
|
|
149
|
+
data_arrays[0], data_arrays[1], yerr=data_arrays[2], fmt="o-", capsize=3
|
|
150
|
+
)
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
if method_name in ("imshow", "matshow"):
|
|
154
|
+
if len(data_arrays) == 1:
|
|
155
|
+
arr = data_arrays[0]
|
|
156
|
+
plot_method(arr.reshape(-1, 1) if arr.ndim == 1 else arr)
|
|
157
|
+
else:
|
|
158
|
+
plot_method(np.column_stack(data_arrays))
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
if method_name in ("contour", "contourf", "pcolor", "pcolormesh"):
|
|
162
|
+
if len(data_arrays) == 1:
|
|
163
|
+
arr = data_arrays[0]
|
|
164
|
+
plot_method(arr.reshape(-1, 1) if arr.ndim == 1 else arr)
|
|
165
|
+
else:
|
|
166
|
+
plot_method(np.column_stack(data_arrays))
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
if method_name in ("quiver", "barbs", "streamplot"):
|
|
170
|
+
if len(data_arrays) >= 4:
|
|
171
|
+
plot_method(data_arrays[0], data_arrays[1], data_arrays[2], data_arrays[3])
|
|
172
|
+
elif len(data_arrays) >= 2:
|
|
173
|
+
x = np.arange(len(data_arrays[0]))
|
|
174
|
+
y = np.arange(len(data_arrays[0]))
|
|
175
|
+
plot_method(x, y, data_arrays[0], data_arrays[1])
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
if method_name == "eventplot":
|
|
179
|
+
plot_method(data_arrays)
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _handle_standard_xy(ax, plot_method, method_name, data_arrays, columns):
|
|
186
|
+
"""Handle standard x, y plots."""
|
|
187
|
+
# Detect x and y columns
|
|
188
|
+
x_idx = None
|
|
189
|
+
y_indices = []
|
|
190
|
+
for i, col in enumerate(columns):
|
|
191
|
+
if col.endswith("_x") or col.lower() == "x":
|
|
192
|
+
x_idx = i
|
|
193
|
+
else:
|
|
194
|
+
y_indices.append(i)
|
|
195
|
+
|
|
196
|
+
if x_idx is not None and y_indices:
|
|
197
|
+
x_data = data_arrays[x_idx]
|
|
198
|
+
y_arrays = [data_arrays[i] for i in y_indices]
|
|
199
|
+
y_cols = [columns[i] for i in y_indices]
|
|
200
|
+
elif len(data_arrays) >= 2:
|
|
201
|
+
x_data = data_arrays[0]
|
|
202
|
+
y_arrays = data_arrays[1:]
|
|
203
|
+
y_cols = columns[1:]
|
|
204
|
+
else:
|
|
205
|
+
x_data = np.arange(len(data_arrays[0]))
|
|
206
|
+
y_arrays = data_arrays
|
|
207
|
+
y_cols = columns
|
|
208
|
+
|
|
209
|
+
for i, y_data in enumerate(y_arrays):
|
|
210
|
+
if method_name == "bar" and len(y_arrays) > 1:
|
|
211
|
+
width = 0.8 / len(y_arrays)
|
|
212
|
+
offset = (i - len(y_arrays) / 2 + 0.5) * width
|
|
213
|
+
plot_method(x_data + offset, y_data, width=width, label=y_cols[i])
|
|
214
|
+
elif method_name == "barh":
|
|
215
|
+
plot_method(x_data, y_data, label=y_cols[i])
|
|
216
|
+
elif method_name in (
|
|
217
|
+
"plot",
|
|
218
|
+
"scatter",
|
|
219
|
+
"step",
|
|
220
|
+
"loglog",
|
|
221
|
+
"semilogx",
|
|
222
|
+
"semilogy",
|
|
223
|
+
):
|
|
224
|
+
plot_method(x_data, y_data, label=y_cols[i])
|
|
225
|
+
elif method_name == "stem":
|
|
226
|
+
plot_method(x_data, y_data, label=y_cols[i])
|
|
227
|
+
elif method_name == "fill":
|
|
228
|
+
plot_method(x_data, y_data, alpha=0.5, label=y_cols[i])
|
|
229
|
+
elif method_name == "stairs":
|
|
230
|
+
plot_method(y_data, label=y_cols[i])
|
|
231
|
+
elif method_name == "stackplot":
|
|
232
|
+
plot_method(x_data, *y_arrays, labels=y_cols)
|
|
233
|
+
break
|
|
234
|
+
else:
|
|
235
|
+
try:
|
|
236
|
+
plot_method(x_data, y_data, label=y_cols[i])
|
|
237
|
+
except TypeError:
|
|
238
|
+
plot_method(y_data, label=y_cols[i])
|
|
239
|
+
|
|
240
|
+
# Set labels
|
|
241
|
+
if len(columns) >= 2:
|
|
242
|
+
ax.set_xlabel(columns[0])
|
|
243
|
+
if len(y_cols) == 1:
|
|
244
|
+
ax.set_ylabel(y_cols[0])
|
|
245
|
+
if len(y_cols) > 1:
|
|
246
|
+
ax.legend()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
__all__ = ["dispatch_plot"]
|