figrecipe 0.6.0__py3-none-any.whl → 0.7.4__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 +106 -973
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +46 -0
- figrecipe/_api/_save.py +191 -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/_dev/__init__.py +2 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -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 +57 -9
- 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 +256 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +342 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_flask_app.py +68 -1039
- figrecipe/_editor/_helpers.py +242 -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 +137 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +35 -185
- figrecipe/_editor/_routes_axis.py +453 -0
- figrecipe/_editor/_routes_core.py +284 -0
- figrecipe/_editor/_routes_element.py +317 -0
- figrecipe/_editor/_routes_style.py +223 -0
- figrecipe/_editor/_templates/__init__.py +78 -1
- figrecipe/_editor/_templates/_html.py +109 -13
- figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_core.py +436 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
- figrecipe/_editor/_templates/_scripts/_files.py +195 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
- figrecipe/_editor/_templates/_styles/__init__.py +69 -0
- figrecipe/_editor/_templates/_styles/_base.py +64 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_controls.py +265 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_forms.py +126 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +98 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +225 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_params/_DECORATION_METHODS.py +6 -0
- figrecipe/_recorder.py +35 -106
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +498 -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/_wrappers/_axes.py +119 -910
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_figure.py +162 -0
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -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 +32 -478
- 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 +29 -24
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
- figrecipe-0.7.4.dist-info/RECORD +188 -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/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.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Image processing for hitmap generation."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from matplotlib.image import AxesImage
|
|
8
|
+
|
|
9
|
+
from .._colors import id_to_rgb
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def process_images(
|
|
13
|
+
ax,
|
|
14
|
+
ax_idx: int,
|
|
15
|
+
element_id: int,
|
|
16
|
+
color_map: Dict[str, Any],
|
|
17
|
+
ax_info: Dict[str, Any],
|
|
18
|
+
) -> int:
|
|
19
|
+
"""Process images on an axes.
|
|
20
|
+
|
|
21
|
+
Returns updated element_id.
|
|
22
|
+
"""
|
|
23
|
+
ax_call_ids = ax_info.get("call_ids", {})
|
|
24
|
+
|
|
25
|
+
imshow_ids = list(ax_call_ids.get("imshow", []))
|
|
26
|
+
specgram_ids = list(ax_call_ids.get("specgram", []))
|
|
27
|
+
contourf_ids = list(ax_call_ids.get("contourf", []))
|
|
28
|
+
|
|
29
|
+
image_idx = 0
|
|
30
|
+
|
|
31
|
+
for i, img in enumerate(ax.images):
|
|
32
|
+
if isinstance(img, AxesImage):
|
|
33
|
+
if not img.get_visible():
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
key = f"ax{ax_idx}_image{i}"
|
|
37
|
+
|
|
38
|
+
call_id = None
|
|
39
|
+
label = f"image_{i}"
|
|
40
|
+
|
|
41
|
+
if image_idx < len(imshow_ids):
|
|
42
|
+
call_id = imshow_ids[image_idx]
|
|
43
|
+
label = call_id
|
|
44
|
+
elif image_idx < len(specgram_ids):
|
|
45
|
+
call_id = specgram_ids[image_idx]
|
|
46
|
+
label = call_id
|
|
47
|
+
elif image_idx < len(contourf_ids):
|
|
48
|
+
call_id = contourf_ids[image_idx]
|
|
49
|
+
label = call_id
|
|
50
|
+
|
|
51
|
+
image_idx += 1
|
|
52
|
+
|
|
53
|
+
color_map[key] = {
|
|
54
|
+
"id": element_id,
|
|
55
|
+
"type": "image",
|
|
56
|
+
"label": label,
|
|
57
|
+
"ax_index": ax_idx,
|
|
58
|
+
"rgb": list(id_to_rgb(element_id)),
|
|
59
|
+
"call_id": call_id,
|
|
60
|
+
}
|
|
61
|
+
element_id += 1
|
|
62
|
+
|
|
63
|
+
return element_id
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ["process_images"]
|
|
67
|
+
|
|
68
|
+
# EOF
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Line processing for hitmap generation."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from .._colors import id_to_rgb, mpl_color_to_hex, normalize_color
|
|
8
|
+
from .._detect import is_boxplot_element
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def process_lines(
|
|
12
|
+
ax,
|
|
13
|
+
ax_idx: int,
|
|
14
|
+
element_id: int,
|
|
15
|
+
original_props: Dict[str, Any],
|
|
16
|
+
color_map: Dict[str, Any],
|
|
17
|
+
ax_info: Dict[str, Any],
|
|
18
|
+
) -> int:
|
|
19
|
+
"""Process lines (traces) on an axes.
|
|
20
|
+
|
|
21
|
+
Returns updated element_id.
|
|
22
|
+
"""
|
|
23
|
+
ax_plot_types = ax_info.get("types", set())
|
|
24
|
+
ax_call_ids = ax_info.get("call_ids", {})
|
|
25
|
+
has_boxplot = "boxplot" in ax_plot_types
|
|
26
|
+
has_violin = "violinplot" in ax_plot_types
|
|
27
|
+
has_regular_plot = "plot" in ax_plot_types
|
|
28
|
+
|
|
29
|
+
boxplot_ids = list(ax_call_ids.get("boxplot", []))
|
|
30
|
+
violin_ids = list(ax_call_ids.get("violinplot", []))
|
|
31
|
+
plot_ids = list(ax_call_ids.get("plot", []))
|
|
32
|
+
|
|
33
|
+
boxplot_call_id = boxplot_ids[0] if boxplot_ids else None
|
|
34
|
+
violin_call_id = violin_ids[0] if violin_ids else None
|
|
35
|
+
regular_line_idx = 0
|
|
36
|
+
has_record = len(ax_plot_types) > 0
|
|
37
|
+
|
|
38
|
+
for i, line in enumerate(ax.get_lines()):
|
|
39
|
+
if not line.get_visible():
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
orig_label = line.get_label() or ""
|
|
43
|
+
|
|
44
|
+
if has_record:
|
|
45
|
+
if (
|
|
46
|
+
orig_label.startswith("_child")
|
|
47
|
+
and not has_boxplot
|
|
48
|
+
and not has_violin
|
|
49
|
+
and not has_regular_plot
|
|
50
|
+
):
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
xdata = line.get_xdata()
|
|
54
|
+
if len(xdata) == 0:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
key = f"ax{ax_idx}_line{i}"
|
|
58
|
+
rgb = id_to_rgb(element_id)
|
|
59
|
+
|
|
60
|
+
original_props[key] = {
|
|
61
|
+
"color": line.get_color(),
|
|
62
|
+
"markerfacecolor": line.get_markerfacecolor(),
|
|
63
|
+
"markeredgecolor": line.get_markeredgecolor(),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
line.set_color(normalize_color(rgb))
|
|
67
|
+
line.set_markerfacecolor(normalize_color(rgb))
|
|
68
|
+
line.set_markeredgecolor(normalize_color(rgb))
|
|
69
|
+
|
|
70
|
+
call_id = None
|
|
71
|
+
if has_boxplot and (is_boxplot_element(line, ax) or orig_label.startswith("_")):
|
|
72
|
+
elem_type = "boxplot"
|
|
73
|
+
label = boxplot_call_id or "boxplot"
|
|
74
|
+
call_id = boxplot_call_id
|
|
75
|
+
elif has_violin and orig_label.startswith("_"):
|
|
76
|
+
elem_type = "violin"
|
|
77
|
+
label = violin_call_id or "violin"
|
|
78
|
+
call_id = violin_call_id
|
|
79
|
+
else:
|
|
80
|
+
elem_type = "line"
|
|
81
|
+
label = orig_label if orig_label else f"line_{i}"
|
|
82
|
+
if regular_line_idx < len(plot_ids):
|
|
83
|
+
call_id = plot_ids[regular_line_idx]
|
|
84
|
+
label = call_id
|
|
85
|
+
else:
|
|
86
|
+
call_id = f"line_{ax_idx}_{regular_line_idx}"
|
|
87
|
+
if orig_label.startswith("_"):
|
|
88
|
+
label = call_id
|
|
89
|
+
regular_line_idx += 1
|
|
90
|
+
|
|
91
|
+
color_map[key] = {
|
|
92
|
+
"id": element_id,
|
|
93
|
+
"type": elem_type,
|
|
94
|
+
"label": label,
|
|
95
|
+
"ax_index": ax_idx,
|
|
96
|
+
"rgb": list(rgb),
|
|
97
|
+
"original_color": mpl_color_to_hex(original_props[key]["color"]),
|
|
98
|
+
"call_id": call_id,
|
|
99
|
+
}
|
|
100
|
+
element_id += 1
|
|
101
|
+
|
|
102
|
+
return element_id
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["process_lines"]
|
|
106
|
+
|
|
107
|
+
# EOF
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Patch processing for hitmap generation."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from matplotlib.patches import Polygon, Rectangle, Wedge
|
|
8
|
+
|
|
9
|
+
from .._colors import id_to_rgb, mpl_color_to_hex, normalize_color
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def process_patches(
|
|
13
|
+
ax,
|
|
14
|
+
ax_idx: int,
|
|
15
|
+
element_id: int,
|
|
16
|
+
original_props: Dict[str, Any],
|
|
17
|
+
color_map: Dict[str, Any],
|
|
18
|
+
ax_info: Dict[str, Any],
|
|
19
|
+
) -> int:
|
|
20
|
+
"""Process patches (bars, wedges, polygons) on an axes.
|
|
21
|
+
|
|
22
|
+
Returns updated element_id.
|
|
23
|
+
"""
|
|
24
|
+
ax_plot_types = ax_info.get("types", set())
|
|
25
|
+
ax_call_ids = ax_info.get("call_ids", {})
|
|
26
|
+
|
|
27
|
+
has_hist = "hist" in ax_plot_types
|
|
28
|
+
has_bar = "bar" in ax_plot_types
|
|
29
|
+
|
|
30
|
+
hist_ids = list(ax_call_ids.get("hist", []))
|
|
31
|
+
bar_ids = list(ax_call_ids.get("bar", []))
|
|
32
|
+
pie_ids = list(ax_call_ids.get("pie", []))
|
|
33
|
+
|
|
34
|
+
if has_hist and hist_ids:
|
|
35
|
+
rect_call_id = hist_ids[0]
|
|
36
|
+
rect_type = "hist"
|
|
37
|
+
elif has_bar and bar_ids:
|
|
38
|
+
rect_call_id = bar_ids[0]
|
|
39
|
+
rect_type = "bar"
|
|
40
|
+
else:
|
|
41
|
+
rect_call_id = f"bar_{ax_idx}"
|
|
42
|
+
rect_type = "bar"
|
|
43
|
+
|
|
44
|
+
for i, patch in enumerate(ax.patches):
|
|
45
|
+
if isinstance(patch, Rectangle):
|
|
46
|
+
element_id = _process_rectangle(
|
|
47
|
+
patch,
|
|
48
|
+
i,
|
|
49
|
+
ax_idx,
|
|
50
|
+
element_id,
|
|
51
|
+
original_props,
|
|
52
|
+
color_map,
|
|
53
|
+
rect_call_id,
|
|
54
|
+
rect_type,
|
|
55
|
+
)
|
|
56
|
+
elif isinstance(patch, Wedge):
|
|
57
|
+
element_id = _process_wedge(
|
|
58
|
+
patch, i, ax_idx, element_id, original_props, color_map, pie_ids
|
|
59
|
+
)
|
|
60
|
+
elif isinstance(patch, Polygon):
|
|
61
|
+
element_id = _process_polygon(
|
|
62
|
+
patch, i, ax_idx, element_id, original_props, color_map
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return element_id
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _process_rectangle(
|
|
69
|
+
patch, i, ax_idx, element_id, original_props, color_map, rect_call_id, rect_type
|
|
70
|
+
):
|
|
71
|
+
"""Process Rectangle patch (bars, histogram bins)."""
|
|
72
|
+
if not patch.get_visible():
|
|
73
|
+
return element_id
|
|
74
|
+
if patch.get_width() == 1.0 and patch.get_height() == 1.0:
|
|
75
|
+
return element_id
|
|
76
|
+
|
|
77
|
+
key = f"ax{ax_idx}_bar{i}"
|
|
78
|
+
rgb = id_to_rgb(element_id)
|
|
79
|
+
|
|
80
|
+
original_props[key] = {
|
|
81
|
+
"facecolor": patch.get_facecolor(),
|
|
82
|
+
"edgecolor": patch.get_edgecolor(),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
patch.set_facecolor(normalize_color(rgb))
|
|
86
|
+
patch.set_edgecolor(normalize_color(rgb))
|
|
87
|
+
|
|
88
|
+
label = rect_call_id or patch.get_label() or f"bar_{i}"
|
|
89
|
+
|
|
90
|
+
color_map[key] = {
|
|
91
|
+
"id": element_id,
|
|
92
|
+
"type": rect_type,
|
|
93
|
+
"label": label,
|
|
94
|
+
"ax_index": ax_idx,
|
|
95
|
+
"rgb": list(rgb),
|
|
96
|
+
"original_color": mpl_color_to_hex(original_props[key]["facecolor"]),
|
|
97
|
+
"call_id": rect_call_id,
|
|
98
|
+
}
|
|
99
|
+
return element_id + 1
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _process_wedge(patch, i, ax_idx, element_id, original_props, color_map, pie_ids):
|
|
103
|
+
"""Process Wedge patch (pie chart slices)."""
|
|
104
|
+
if not patch.get_visible():
|
|
105
|
+
return element_id
|
|
106
|
+
|
|
107
|
+
key = f"ax{ax_idx}_wedge{i}"
|
|
108
|
+
rgb = id_to_rgb(element_id)
|
|
109
|
+
|
|
110
|
+
original_props[key] = {
|
|
111
|
+
"facecolor": patch.get_facecolor(),
|
|
112
|
+
"edgecolor": patch.get_edgecolor(),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
patch.set_facecolor(normalize_color(rgb))
|
|
116
|
+
patch.set_edgecolor(normalize_color(rgb))
|
|
117
|
+
|
|
118
|
+
call_id = pie_ids[0] if pie_ids else None
|
|
119
|
+
label = call_id or f"wedge_{i}"
|
|
120
|
+
|
|
121
|
+
color_map[key] = {
|
|
122
|
+
"id": element_id,
|
|
123
|
+
"type": "pie",
|
|
124
|
+
"label": label,
|
|
125
|
+
"ax_index": ax_idx,
|
|
126
|
+
"rgb": list(rgb),
|
|
127
|
+
"original_color": mpl_color_to_hex(original_props[key]["facecolor"]),
|
|
128
|
+
"call_id": call_id,
|
|
129
|
+
}
|
|
130
|
+
return element_id + 1
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _process_polygon(patch, i, ax_idx, element_id, original_props, color_map):
|
|
134
|
+
"""Process Polygon patch (fill areas)."""
|
|
135
|
+
if not patch.get_visible():
|
|
136
|
+
return element_id
|
|
137
|
+
|
|
138
|
+
key = f"ax{ax_idx}_polygon{i}"
|
|
139
|
+
rgb = id_to_rgb(element_id)
|
|
140
|
+
|
|
141
|
+
original_props[key] = {
|
|
142
|
+
"facecolor": patch.get_facecolor(),
|
|
143
|
+
"edgecolor": patch.get_edgecolor(),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
patch.set_facecolor(normalize_color(rgb))
|
|
147
|
+
patch.set_edgecolor(normalize_color(rgb))
|
|
148
|
+
|
|
149
|
+
color_map[key] = {
|
|
150
|
+
"id": element_id,
|
|
151
|
+
"type": "fill",
|
|
152
|
+
"label": f"fill_{i}",
|
|
153
|
+
"ax_index": ax_idx,
|
|
154
|
+
"rgb": list(rgb),
|
|
155
|
+
"original_color": mpl_color_to_hex(original_props[key]["facecolor"]),
|
|
156
|
+
"call_id": None,
|
|
157
|
+
}
|
|
158
|
+
return element_id + 1
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
__all__ = ["process_patches"]
|
|
162
|
+
|
|
163
|
+
# EOF
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Text and legend processing for hitmap generation."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from .._colors import id_to_rgb, normalize_color
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def process_text(
|
|
11
|
+
ax,
|
|
12
|
+
ax_idx: int,
|
|
13
|
+
element_id: int,
|
|
14
|
+
original_props: Dict[str, Any],
|
|
15
|
+
color_map: Dict[str, Any],
|
|
16
|
+
) -> int:
|
|
17
|
+
"""Process text elements (title, labels) on an axes.
|
|
18
|
+
|
|
19
|
+
Returns updated element_id.
|
|
20
|
+
"""
|
|
21
|
+
# Title
|
|
22
|
+
title = ax.get_title()
|
|
23
|
+
if title:
|
|
24
|
+
key = f"ax{ax_idx}_title"
|
|
25
|
+
rgb = id_to_rgb(element_id)
|
|
26
|
+
title_obj = ax.title
|
|
27
|
+
|
|
28
|
+
original_props[key] = {"color": title_obj.get_color()}
|
|
29
|
+
title_obj.set_color(normalize_color(rgb))
|
|
30
|
+
|
|
31
|
+
color_map[key] = {
|
|
32
|
+
"id": element_id,
|
|
33
|
+
"type": "title",
|
|
34
|
+
"label": "title",
|
|
35
|
+
"ax_index": ax_idx,
|
|
36
|
+
"rgb": list(rgb),
|
|
37
|
+
}
|
|
38
|
+
element_id += 1
|
|
39
|
+
|
|
40
|
+
# X label
|
|
41
|
+
xlabel = ax.get_xlabel()
|
|
42
|
+
if xlabel:
|
|
43
|
+
key = f"ax{ax_idx}_xlabel"
|
|
44
|
+
rgb = id_to_rgb(element_id)
|
|
45
|
+
xlabel_obj = ax.xaxis.label
|
|
46
|
+
|
|
47
|
+
original_props[key] = {"color": xlabel_obj.get_color()}
|
|
48
|
+
xlabel_obj.set_color(normalize_color(rgb))
|
|
49
|
+
|
|
50
|
+
color_map[key] = {
|
|
51
|
+
"id": element_id,
|
|
52
|
+
"type": "xlabel",
|
|
53
|
+
"label": "xlabel",
|
|
54
|
+
"ax_index": ax_idx,
|
|
55
|
+
"rgb": list(rgb),
|
|
56
|
+
}
|
|
57
|
+
element_id += 1
|
|
58
|
+
|
|
59
|
+
# Y label
|
|
60
|
+
ylabel = ax.get_ylabel()
|
|
61
|
+
if ylabel:
|
|
62
|
+
key = f"ax{ax_idx}_ylabel"
|
|
63
|
+
rgb = id_to_rgb(element_id)
|
|
64
|
+
ylabel_obj = ax.yaxis.label
|
|
65
|
+
|
|
66
|
+
original_props[key] = {"color": ylabel_obj.get_color()}
|
|
67
|
+
ylabel_obj.set_color(normalize_color(rgb))
|
|
68
|
+
|
|
69
|
+
color_map[key] = {
|
|
70
|
+
"id": element_id,
|
|
71
|
+
"type": "ylabel",
|
|
72
|
+
"label": "ylabel",
|
|
73
|
+
"ax_index": ax_idx,
|
|
74
|
+
"rgb": list(rgb),
|
|
75
|
+
}
|
|
76
|
+
element_id += 1
|
|
77
|
+
|
|
78
|
+
return element_id
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def process_legend(
|
|
82
|
+
ax,
|
|
83
|
+
ax_idx: int,
|
|
84
|
+
element_id: int,
|
|
85
|
+
original_props: Dict[str, Any],
|
|
86
|
+
color_map: Dict[str, Any],
|
|
87
|
+
) -> int:
|
|
88
|
+
"""Process legend on an axes.
|
|
89
|
+
|
|
90
|
+
Returns updated element_id.
|
|
91
|
+
"""
|
|
92
|
+
legend = ax.get_legend()
|
|
93
|
+
if legend is not None and legend.get_visible():
|
|
94
|
+
key = f"ax{ax_idx}_legend"
|
|
95
|
+
rgb = id_to_rgb(element_id)
|
|
96
|
+
|
|
97
|
+
frame = legend.get_frame()
|
|
98
|
+
original_props[key] = {
|
|
99
|
+
"facecolor": frame.get_facecolor(),
|
|
100
|
+
"edgecolor": frame.get_edgecolor(),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
frame.set_facecolor(normalize_color(rgb))
|
|
104
|
+
frame.set_edgecolor(normalize_color(rgb))
|
|
105
|
+
|
|
106
|
+
color_map[key] = {
|
|
107
|
+
"id": element_id,
|
|
108
|
+
"type": "legend",
|
|
109
|
+
"label": "legend",
|
|
110
|
+
"ax_index": ax_idx,
|
|
111
|
+
"rgb": list(rgb),
|
|
112
|
+
}
|
|
113
|
+
element_id += 1
|
|
114
|
+
|
|
115
|
+
return element_id
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def process_figure_text(
|
|
119
|
+
mpl_fig,
|
|
120
|
+
element_id: int,
|
|
121
|
+
original_props: Dict[str, Any],
|
|
122
|
+
color_map: Dict[str, Any],
|
|
123
|
+
) -> int:
|
|
124
|
+
"""Process figure-level text elements (suptitle, supxlabel, supylabel).
|
|
125
|
+
|
|
126
|
+
Returns updated element_id.
|
|
127
|
+
"""
|
|
128
|
+
# Suptitle
|
|
129
|
+
if hasattr(mpl_fig, "_suptitle") and mpl_fig._suptitle is not None:
|
|
130
|
+
suptitle_obj = mpl_fig._suptitle
|
|
131
|
+
if suptitle_obj.get_text():
|
|
132
|
+
key = "fig_suptitle"
|
|
133
|
+
rgb = id_to_rgb(element_id)
|
|
134
|
+
|
|
135
|
+
original_props[key] = {"color": suptitle_obj.get_color()}
|
|
136
|
+
suptitle_obj.set_color(normalize_color(rgb))
|
|
137
|
+
|
|
138
|
+
color_map[key] = {
|
|
139
|
+
"id": element_id,
|
|
140
|
+
"type": "suptitle",
|
|
141
|
+
"label": "suptitle",
|
|
142
|
+
"ax_index": -1,
|
|
143
|
+
"rgb": list(rgb),
|
|
144
|
+
}
|
|
145
|
+
element_id += 1
|
|
146
|
+
|
|
147
|
+
# Supxlabel
|
|
148
|
+
if hasattr(mpl_fig, "_supxlabel") and mpl_fig._supxlabel is not None:
|
|
149
|
+
supxlabel_obj = mpl_fig._supxlabel
|
|
150
|
+
if supxlabel_obj.get_text():
|
|
151
|
+
key = "fig_supxlabel"
|
|
152
|
+
rgb = id_to_rgb(element_id)
|
|
153
|
+
|
|
154
|
+
original_props[key] = {"color": supxlabel_obj.get_color()}
|
|
155
|
+
supxlabel_obj.set_color(normalize_color(rgb))
|
|
156
|
+
|
|
157
|
+
color_map[key] = {
|
|
158
|
+
"id": element_id,
|
|
159
|
+
"type": "supxlabel",
|
|
160
|
+
"label": "supxlabel",
|
|
161
|
+
"ax_index": -1,
|
|
162
|
+
"rgb": list(rgb),
|
|
163
|
+
}
|
|
164
|
+
element_id += 1
|
|
165
|
+
|
|
166
|
+
# Supylabel
|
|
167
|
+
if hasattr(mpl_fig, "_supylabel") and mpl_fig._supylabel is not None:
|
|
168
|
+
supylabel_obj = mpl_fig._supylabel
|
|
169
|
+
if supylabel_obj.get_text():
|
|
170
|
+
key = "fig_supylabel"
|
|
171
|
+
rgb = id_to_rgb(element_id)
|
|
172
|
+
|
|
173
|
+
original_props[key] = {"color": supylabel_obj.get_color()}
|
|
174
|
+
supylabel_obj.set_color(normalize_color(rgb))
|
|
175
|
+
|
|
176
|
+
color_map[key] = {
|
|
177
|
+
"id": element_id,
|
|
178
|
+
"type": "supylabel",
|
|
179
|
+
"label": "supylabel",
|
|
180
|
+
"ax_index": -1,
|
|
181
|
+
"rgb": list(rgb),
|
|
182
|
+
}
|
|
183
|
+
element_id += 1
|
|
184
|
+
|
|
185
|
+
return element_id
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = ["process_text", "process_legend", "process_figure_text"]
|
|
189
|
+
|
|
190
|
+
# EOF
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Color utilities for hitmap generation."""
|
|
4
|
+
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
# Hand-picked distinct colors for first 12 elements (maximum visual distinction)
|
|
8
|
+
DISTINCT_COLORS = [
|
|
9
|
+
(255, 0, 0), # Red
|
|
10
|
+
(0, 200, 0), # Green
|
|
11
|
+
(0, 100, 255), # Blue
|
|
12
|
+
(255, 200, 0), # Yellow
|
|
13
|
+
(255, 0, 255), # Magenta
|
|
14
|
+
(0, 255, 255), # Cyan
|
|
15
|
+
(255, 128, 0), # Orange
|
|
16
|
+
(128, 0, 255), # Purple
|
|
17
|
+
(0, 255, 128), # Spring green
|
|
18
|
+
(255, 0, 128), # Rose
|
|
19
|
+
(128, 255, 0), # Lime
|
|
20
|
+
(0, 128, 255), # Sky blue
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Reserved colors
|
|
24
|
+
BACKGROUND_COLOR = (26, 26, 26) # Dark gray for background
|
|
25
|
+
AXES_COLOR = (64, 64, 64) # Medium gray for non-selectable axes elements
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def hsv_to_rgb(h: float, s: float, v: float) -> Tuple[int, int, int]:
|
|
29
|
+
"""Convert HSV to RGB (0-255 range).
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
h : float
|
|
34
|
+
Hue (0-1).
|
|
35
|
+
s : float
|
|
36
|
+
Saturation (0-1).
|
|
37
|
+
v : float
|
|
38
|
+
Value (0-1).
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
tuple
|
|
43
|
+
RGB tuple (0-255 range).
|
|
44
|
+
"""
|
|
45
|
+
if s == 0:
|
|
46
|
+
r = g = b = int(v * 255)
|
|
47
|
+
return (r, g, b)
|
|
48
|
+
|
|
49
|
+
i = int(h * 6)
|
|
50
|
+
f = h * 6 - i
|
|
51
|
+
p = v * (1 - s)
|
|
52
|
+
q = v * (1 - s * f)
|
|
53
|
+
t = v * (1 - s * (1 - f))
|
|
54
|
+
|
|
55
|
+
i %= 6
|
|
56
|
+
if i == 0:
|
|
57
|
+
r, g, b = v, t, p
|
|
58
|
+
elif i == 1:
|
|
59
|
+
r, g, b = q, v, p
|
|
60
|
+
elif i == 2:
|
|
61
|
+
r, g, b = p, v, t
|
|
62
|
+
elif i == 3:
|
|
63
|
+
r, g, b = p, q, v
|
|
64
|
+
elif i == 4:
|
|
65
|
+
r, g, b = t, p, v
|
|
66
|
+
else:
|
|
67
|
+
r, g, b = v, p, q
|
|
68
|
+
|
|
69
|
+
return (int(r * 255), int(g * 255), int(b * 255))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def id_to_rgb(element_id: int) -> Tuple[int, int, int]:
|
|
73
|
+
"""Convert element ID to unique RGB color.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
element_id : int
|
|
78
|
+
Unique element identifier (1-based).
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
tuple of int
|
|
83
|
+
RGB color tuple (0-255 range).
|
|
84
|
+
|
|
85
|
+
Notes
|
|
86
|
+
-----
|
|
87
|
+
- ID 0 is reserved for background
|
|
88
|
+
- IDs 1-12 use hand-picked distinct colors
|
|
89
|
+
- IDs 13+ use HSV-based generation
|
|
90
|
+
"""
|
|
91
|
+
if element_id <= 0:
|
|
92
|
+
return BACKGROUND_COLOR
|
|
93
|
+
|
|
94
|
+
if element_id <= len(DISTINCT_COLORS):
|
|
95
|
+
return DISTINCT_COLORS[element_id - 1]
|
|
96
|
+
|
|
97
|
+
# HSV-based generation for IDs > 12
|
|
98
|
+
golden_ratio = 0.618033988749895
|
|
99
|
+
hue = ((element_id - len(DISTINCT_COLORS)) * golden_ratio) % 1.0
|
|
100
|
+
saturation = 0.7 + (element_id % 3) * 0.1
|
|
101
|
+
value = 0.75 + (element_id % 4) * 0.0625
|
|
102
|
+
|
|
103
|
+
return hsv_to_rgb(hue, saturation, value)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def rgb_to_id(rgb: Tuple[int, int, int]) -> int:
|
|
107
|
+
"""Convert RGB color back to element ID.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
rgb : tuple of int
|
|
112
|
+
RGB color tuple.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
int
|
|
117
|
+
Element ID, or 0 if background/unknown.
|
|
118
|
+
"""
|
|
119
|
+
if rgb == BACKGROUND_COLOR:
|
|
120
|
+
return 0
|
|
121
|
+
if rgb == AXES_COLOR:
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
# Check hand-picked colors
|
|
125
|
+
if rgb in DISTINCT_COLORS:
|
|
126
|
+
return DISTINCT_COLORS.index(rgb) + 1
|
|
127
|
+
|
|
128
|
+
# For HSV-generated colors, search
|
|
129
|
+
for test_id in range(len(DISTINCT_COLORS) + 1, 1000):
|
|
130
|
+
if id_to_rgb(test_id) == rgb:
|
|
131
|
+
return test_id
|
|
132
|
+
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def normalize_color(rgb: Tuple[int, int, int]) -> Tuple[float, float, float]:
|
|
137
|
+
"""Normalize RGB from 0-255 to 0-1 range."""
|
|
138
|
+
return (rgb[0] / 255, rgb[1] / 255, rgb[2] / 255)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def mpl_color_to_hex(color) -> str:
|
|
142
|
+
"""Convert matplotlib color to hex string.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
color : color
|
|
147
|
+
Matplotlib color (RGBA tuple, hex string, named color).
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
str
|
|
152
|
+
Hex color string (e.g., '#FF0000').
|
|
153
|
+
"""
|
|
154
|
+
import matplotlib.colors as mcolors
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
if hasattr(color, "__iter__") and not isinstance(color, str):
|
|
158
|
+
color = tuple(color)
|
|
159
|
+
if len(color) >= 3:
|
|
160
|
+
if all(isinstance(c, (int, float)) for c in color[:3]):
|
|
161
|
+
if all(c <= 1.0 for c in color[:3]):
|
|
162
|
+
return mcolors.to_hex(color[:3])
|
|
163
|
+
else:
|
|
164
|
+
return mcolors.to_hex(tuple(c / 255 for c in color[:3]))
|
|
165
|
+
return mcolors.to_hex(color)
|
|
166
|
+
except Exception:
|
|
167
|
+
return "#888888"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
__all__ = [
|
|
171
|
+
"DISTINCT_COLORS",
|
|
172
|
+
"BACKGROUND_COLOR",
|
|
173
|
+
"AXES_COLOR",
|
|
174
|
+
"hsv_to_rgb",
|
|
175
|
+
"id_to_rgb",
|
|
176
|
+
"rgb_to_id",
|
|
177
|
+
"normalize_color",
|
|
178
|
+
"mpl_color_to_hex",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
# EOF
|