figrecipe 0.5.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 +220 -819
- 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 +29 -0
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/demo_plotters/__init__.py +64 -0
- 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/bar_categorical/plot_bar.py +25 -0
- figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
- figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
- figrecipe/_editor/__init__.py +278 -0
- 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 +258 -0
- 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/_overrides.py +318 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +199 -0
- 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 +152 -0
- figrecipe/_editor/_templates/_html.py +502 -0
- 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 +33 -0
- figrecipe/_params/_PLOTTING_METHODS.py +58 -0
- figrecipe/_params/__init__.py +9 -0
- figrecipe/_recorder.py +92 -110
- 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/_seaborn.py +14 -9
- figrecipe/_serializer.py +2 -2
- figrecipe/_signatures/README.md +68 -0
- figrecipe/_signatures/__init__.py +12 -2
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +114 -57
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +6 -4
- figrecipe/_utils/_crop.py +10 -4
- figrecipe/_utils/_image_diff.py +37 -33
- figrecipe/_utils/_numpy_io.py +0 -1
- figrecipe/_utils/_units.py +11 -3
- figrecipe/_validator.py +12 -3
- figrecipe/_wrappers/_axes.py +193 -170
- 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 +277 -18
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/plt.py +0 -1
- figrecipe/pyplot.py +2 -1
- figrecipe/styles/__init__.py +12 -11
- 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 +60 -202
- figrecipe/styles/_style_loader.py +73 -121
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
- figrecipe/styles/presets/SCITEX.yaml +181 -0
- figrecipe-0.7.4.dist-info/METADATA +429 -0
- figrecipe-0.7.4.dist-info/RECORD +188 -0
- figrecipe/_reproducer.py +0 -358
- figrecipe-0.5.0.dist-info/METADATA +0 -336
- figrecipe-0.5.0.dist-info/RECORD +0 -26
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Color handling JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Color presets (SCITEX, matplotlib, CSS)
|
|
7
|
+
- Color conversion utilities
|
|
8
|
+
- Color input widget creation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_COLORS = """
|
|
12
|
+
// ===== COLOR HANDLING =====
|
|
13
|
+
|
|
14
|
+
// Color presets from SCITEX theme (priority 1 - highest)
|
|
15
|
+
const COLOR_PRESETS = {
|
|
16
|
+
'blue': { hex: '#0080c0', rgb: [0, 128, 192] },
|
|
17
|
+
'red': { hex: '#ff4632', rgb: [255, 70, 50] },
|
|
18
|
+
'green': { hex: '#14b414', rgb: [20, 180, 20] },
|
|
19
|
+
'yellow': { hex: '#e6a014', rgb: [230, 160, 20] },
|
|
20
|
+
'purple': { hex: '#c832ff', rgb: [200, 50, 255] },
|
|
21
|
+
'lightblue': { hex: '#14c8c8', rgb: [20, 200, 200] },
|
|
22
|
+
'orange': { hex: '#e45e32', rgb: [228, 94, 50] },
|
|
23
|
+
'pink': { hex: '#ff96c8', rgb: [255, 150, 200] },
|
|
24
|
+
'black': { hex: '#000000', rgb: [0, 0, 0] },
|
|
25
|
+
'white': { hex: '#ffffff', rgb: [255, 255, 255] },
|
|
26
|
+
'gray': { hex: '#808080', rgb: [128, 128, 128] }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Matplotlib single-letter colors (priority 2)
|
|
30
|
+
const MATPLOTLIB_SINGLE = {
|
|
31
|
+
'b': { hex: '#1f77b4', rgb: [31, 119, 180] },
|
|
32
|
+
'g': { hex: '#2ca02c', rgb: [44, 160, 44] },
|
|
33
|
+
'r': { hex: '#d62728', rgb: [214, 39, 40] },
|
|
34
|
+
'c': { hex: '#17becf', rgb: [23, 190, 207] },
|
|
35
|
+
'm': { hex: '#9467bd', rgb: [148, 103, 189] },
|
|
36
|
+
'y': { hex: '#bcbd22', rgb: [188, 189, 34] },
|
|
37
|
+
'k': { hex: '#000000', rgb: [0, 0, 0] },
|
|
38
|
+
'w': { hex: '#ffffff', rgb: [255, 255, 255] }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Common matplotlib/CSS named colors (priority 3)
|
|
42
|
+
const MATPLOTLIB_NAMED = {
|
|
43
|
+
'aqua': '#00ffff', 'coral': '#ff7f50', 'crimson': '#dc143c',
|
|
44
|
+
'cyan': '#00ffff', 'gold': '#ffd700', 'indigo': '#4b0082',
|
|
45
|
+
'lime': '#00ff00', 'magenta': '#ff00ff', 'maroon': '#800000',
|
|
46
|
+
'navy': '#000080', 'olive': '#808000', 'salmon': '#fa8072',
|
|
47
|
+
'silver': '#c0c0c0', 'teal': '#008080', 'tomato': '#ff6347',
|
|
48
|
+
'turquoise': '#40e0d0', 'violet': '#ee82ee'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Check if a field is a color field (single color, not list)
|
|
52
|
+
function isColorField(key, sigInfo) {
|
|
53
|
+
// 'colors' (plural) is a list of colors - handle separately
|
|
54
|
+
if (key.toLowerCase() === 'colors') return false;
|
|
55
|
+
const colorKeywords = ['color', 'facecolor', 'edgecolor', 'markerfacecolor', 'markeredgecolor', 'c'];
|
|
56
|
+
if (colorKeywords.includes(key.toLowerCase())) return true;
|
|
57
|
+
if (sigInfo?.type && sigInfo.type.toLowerCase().includes('color')) return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if a field is a color list field
|
|
62
|
+
function isColorListField(key, value) {
|
|
63
|
+
if (key.toLowerCase() === 'colors' && Array.isArray(value)) return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Convert color to RGB string for display
|
|
68
|
+
function colorToRGB(color) {
|
|
69
|
+
if (!color) return '';
|
|
70
|
+
if (typeof color === 'string' && color.match(/^rgb/i)) return color;
|
|
71
|
+
if (typeof color === 'string' && color.startsWith('#')) {
|
|
72
|
+
const hex = color.slice(1);
|
|
73
|
+
if (hex.length === 3) {
|
|
74
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
75
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
76
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
77
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
78
|
+
} else if (hex.length === 6) {
|
|
79
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
80
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
81
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
82
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return color;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Convert color to hex for color picker
|
|
89
|
+
function colorToHex(color) {
|
|
90
|
+
return resolveColorToHex(color);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Find preset color matching input (name, hex, RGB array, or RGB string)
|
|
94
|
+
function findPresetColor(input) {
|
|
95
|
+
if (!input) return null;
|
|
96
|
+
|
|
97
|
+
// Handle array input [r, g, b] where values are 0-1
|
|
98
|
+
if (Array.isArray(input) && input.length >= 3) {
|
|
99
|
+
const r = Math.round(parseFloat(input[0]) * 255);
|
|
100
|
+
const g = Math.round(parseFloat(input[1]) * 255);
|
|
101
|
+
const b = Math.round(parseFloat(input[2]) * 255);
|
|
102
|
+
// Check against preset RGB values
|
|
103
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
104
|
+
const [pr, pg, pb] = preset.rgb;
|
|
105
|
+
if (Math.abs(r - pr) <= 1 && Math.abs(g - pg) <= 1 && Math.abs(b - pb) <= 1) {
|
|
106
|
+
return { name, ...preset };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const [name, preset] of Object.entries(MATPLOTLIB_SINGLE)) {
|
|
110
|
+
const [pr, pg, pb] = preset.rgb;
|
|
111
|
+
if (Math.abs(r - pr) <= 1 && Math.abs(g - pg) <= 1 && Math.abs(b - pb) <= 1) {
|
|
112
|
+
return { name, ...preset };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const inputLower = typeof input === 'string' ? input.toLowerCase().trim() : '';
|
|
119
|
+
|
|
120
|
+
// Check preset names
|
|
121
|
+
if (COLOR_PRESETS[inputLower]) return { name: inputLower, ...COLOR_PRESETS[inputLower] };
|
|
122
|
+
if (MATPLOTLIB_SINGLE[inputLower]) return { name: inputLower, ...MATPLOTLIB_SINGLE[inputLower] };
|
|
123
|
+
|
|
124
|
+
// Check hex values
|
|
125
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
126
|
+
if (preset.hex.toLowerCase() === inputLower) return { name, ...preset };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check RGB string format like "rgb(0, 128, 192)"
|
|
130
|
+
const rgbMatch = inputLower.match(/rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)/);
|
|
131
|
+
if (rgbMatch) {
|
|
132
|
+
const r = parseInt(rgbMatch[1]);
|
|
133
|
+
const g = parseInt(rgbMatch[2]);
|
|
134
|
+
const b = parseInt(rgbMatch[3]);
|
|
135
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
136
|
+
const [pr, pg, pb] = preset.rgb;
|
|
137
|
+
if (Math.abs(r - pr) <= 1 && Math.abs(g - pg) <= 1 && Math.abs(b - pb) <= 1) {
|
|
138
|
+
return { name, ...preset };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Find preset by hex value
|
|
147
|
+
function findPresetByHex(hexValue) {
|
|
148
|
+
if (!hexValue) return null;
|
|
149
|
+
const hex = hexValue.toLowerCase();
|
|
150
|
+
|
|
151
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
152
|
+
if (preset.hex.toLowerCase() === hex) return { name, ...preset };
|
|
153
|
+
}
|
|
154
|
+
for (const [name, preset] of Object.entries(MATPLOTLIB_SINGLE)) {
|
|
155
|
+
if (preset.hex.toLowerCase() === hex) return { name, ...preset };
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Resolve color to hex using priority: theme > matplotlib > CSS
|
|
161
|
+
function resolveColorToHex(input) {
|
|
162
|
+
if (!input) return '#000000';
|
|
163
|
+
|
|
164
|
+
if (typeof input === 'string' && input.startsWith('#')) {
|
|
165
|
+
return input.length === 4 ?
|
|
166
|
+
'#' + input[1] + input[1] + input[2] + input[2] + input[3] + input[3] :
|
|
167
|
+
input;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (typeof input === 'string' && input.startsWith('(')) {
|
|
171
|
+
const match = input.match(/\\(([\\d.]+),\\s*([\\d.]+),\\s*([\\d.]+)/);
|
|
172
|
+
if (match) {
|
|
173
|
+
const r = Math.round(parseFloat(match[1]) * 255);
|
|
174
|
+
const g = Math.round(parseFloat(match[2]) * 255);
|
|
175
|
+
const b = Math.round(parseFloat(match[3]) * 255);
|
|
176
|
+
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle array format [r, g, b] where values are 0-1
|
|
181
|
+
if (Array.isArray(input) && input.length >= 3) {
|
|
182
|
+
const r = Math.round(parseFloat(input[0]) * 255);
|
|
183
|
+
const g = Math.round(parseFloat(input[1]) * 255);
|
|
184
|
+
const b = Math.round(parseFloat(input[2]) * 255);
|
|
185
|
+
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Ensure input is a string before calling toLowerCase
|
|
189
|
+
if (typeof input !== 'string') return '#000000';
|
|
190
|
+
|
|
191
|
+
const inputLower = input.toLowerCase().trim();
|
|
192
|
+
if (COLOR_PRESETS[inputLower]) return COLOR_PRESETS[inputLower].hex;
|
|
193
|
+
if (MATPLOTLIB_SINGLE[inputLower]) return MATPLOTLIB_SINGLE[inputLower].hex;
|
|
194
|
+
if (MATPLOTLIB_NAMED[inputLower]) return MATPLOTLIB_NAMED[inputLower];
|
|
195
|
+
|
|
196
|
+
return '#000000';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Format color for display
|
|
200
|
+
function formatColorDisplay(value) {
|
|
201
|
+
if (!value) return '';
|
|
202
|
+
const preset = findPresetColor(value);
|
|
203
|
+
if (preset) return preset.name;
|
|
204
|
+
|
|
205
|
+
// Convert to hex first, then to clean RGB display
|
|
206
|
+
const hex = resolveColorToHex(value);
|
|
207
|
+
if (hex && hex.startsWith('#') && hex.length === 7) {
|
|
208
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
209
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
210
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
211
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Convert hex to RGB tuple string
|
|
217
|
+
function hexToRGBTuple(hex) {
|
|
218
|
+
if (!hex || !hex.startsWith('#')) return null;
|
|
219
|
+
const h = hex.slice(1);
|
|
220
|
+
if (h.length !== 6) return null;
|
|
221
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
222
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
223
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
224
|
+
return `(${r.toFixed(3)}, ${g.toFixed(3)}, ${b.toFixed(3)})`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Create color input with preset dropdown and picker
|
|
228
|
+
function createColorInput(callId, key, value) {
|
|
229
|
+
const wrapper = document.createElement('div');
|
|
230
|
+
wrapper.className = 'color-input-wrapper';
|
|
231
|
+
|
|
232
|
+
const swatch = document.createElement('div');
|
|
233
|
+
swatch.className = 'color-swatch';
|
|
234
|
+
|
|
235
|
+
const select = document.createElement('select');
|
|
236
|
+
select.className = 'color-select';
|
|
237
|
+
|
|
238
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
239
|
+
const opt = document.createElement('option');
|
|
240
|
+
opt.value = name;
|
|
241
|
+
opt.textContent = name;
|
|
242
|
+
select.appendChild(opt);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const separator = document.createElement('option');
|
|
246
|
+
separator.disabled = true;
|
|
247
|
+
separator.textContent = '───────────';
|
|
248
|
+
select.appendChild(separator);
|
|
249
|
+
|
|
250
|
+
const customOpt = document.createElement('option');
|
|
251
|
+
customOpt.value = '__custom__';
|
|
252
|
+
customOpt.textContent = 'Custom...';
|
|
253
|
+
select.appendChild(customOpt);
|
|
254
|
+
|
|
255
|
+
const customInput = document.createElement('input');
|
|
256
|
+
customInput.type = 'text';
|
|
257
|
+
customInput.className = 'color-custom-input';
|
|
258
|
+
customInput.placeholder = '#rrggbb or name';
|
|
259
|
+
customInput.style.display = 'none';
|
|
260
|
+
|
|
261
|
+
// rgbDisplay removed - dropdown now shows RGB format directly
|
|
262
|
+
|
|
263
|
+
const colorPicker = document.createElement('input');
|
|
264
|
+
colorPicker.type = 'color';
|
|
265
|
+
colorPicker.className = 'color-picker-hidden';
|
|
266
|
+
|
|
267
|
+
function updateDisplay(colorValue) {
|
|
268
|
+
const hex = resolveColorToHex(colorValue);
|
|
269
|
+
swatch.style.backgroundColor = hex;
|
|
270
|
+
colorPicker.value = hex;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const initialPreset = findPresetColor(value);
|
|
274
|
+
if (initialPreset) {
|
|
275
|
+
select.value = initialPreset.name;
|
|
276
|
+
} else if (value) {
|
|
277
|
+
// Store hex internally, display RGB for users
|
|
278
|
+
const hexValue = resolveColorToHex(value);
|
|
279
|
+
const currentOpt = document.createElement('option');
|
|
280
|
+
currentOpt.value = hexValue;
|
|
281
|
+
currentOpt.textContent = formatColorDisplay(value);
|
|
282
|
+
select.insertBefore(currentOpt, separator);
|
|
283
|
+
select.value = hexValue;
|
|
284
|
+
}
|
|
285
|
+
updateDisplay(value || 'blue');
|
|
286
|
+
|
|
287
|
+
select.addEventListener('change', function() {
|
|
288
|
+
if (this.value === '__custom__') {
|
|
289
|
+
customInput.style.display = '';
|
|
290
|
+
customInput.focus();
|
|
291
|
+
swatch.style.display = 'none';
|
|
292
|
+
} else {
|
|
293
|
+
customInput.style.display = 'none';
|
|
294
|
+
swatch.style.display = '';
|
|
295
|
+
updateDisplay(this.value);
|
|
296
|
+
handleDynamicParamChange(callId, key, { value: this.value });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
customInput.addEventListener('keydown', function(e) {
|
|
301
|
+
if (e.key === 'Enter') {
|
|
302
|
+
const inputValue = this.value.trim();
|
|
303
|
+
if (inputValue) {
|
|
304
|
+
swatch.style.display = '';
|
|
305
|
+
const preset = findPresetColor(inputValue);
|
|
306
|
+
if (preset) {
|
|
307
|
+
select.value = preset.name;
|
|
308
|
+
} else {
|
|
309
|
+
let existingOpt = Array.from(select.options).find(o => o.value === inputValue);
|
|
310
|
+
if (!existingOpt) {
|
|
311
|
+
const newOpt = document.createElement('option');
|
|
312
|
+
newOpt.value = inputValue;
|
|
313
|
+
newOpt.textContent = inputValue;
|
|
314
|
+
select.insertBefore(newOpt, separator);
|
|
315
|
+
}
|
|
316
|
+
select.value = inputValue;
|
|
317
|
+
}
|
|
318
|
+
customInput.style.display = 'none';
|
|
319
|
+
updateDisplay(inputValue);
|
|
320
|
+
handleDynamicParamChange(callId, key, { value: inputValue });
|
|
321
|
+
}
|
|
322
|
+
} else if (e.key === 'Escape') {
|
|
323
|
+
customInput.style.display = 'none';
|
|
324
|
+
if (select.value === '__custom__') select.selectedIndex = 0;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
swatch.addEventListener('click', () => colorPicker.click());
|
|
329
|
+
|
|
330
|
+
colorPicker.addEventListener('change', function() {
|
|
331
|
+
const pickedColor = this.value;
|
|
332
|
+
const preset = findPresetColor(pickedColor);
|
|
333
|
+
if (preset) {
|
|
334
|
+
select.value = preset.name;
|
|
335
|
+
} else {
|
|
336
|
+
const rgbTuple = hexToRGBTuple(pickedColor);
|
|
337
|
+
let existingOpt = Array.from(select.options).find(o => o.value === pickedColor || o.value === rgbTuple);
|
|
338
|
+
if (!existingOpt) {
|
|
339
|
+
const newOpt = document.createElement('option');
|
|
340
|
+
newOpt.value = rgbTuple || pickedColor;
|
|
341
|
+
newOpt.textContent = rgbTuple || pickedColor;
|
|
342
|
+
select.insertBefore(newOpt, separator);
|
|
343
|
+
}
|
|
344
|
+
select.value = rgbTuple || pickedColor;
|
|
345
|
+
}
|
|
346
|
+
swatch.style.display = '';
|
|
347
|
+
customInput.style.display = 'none';
|
|
348
|
+
updateDisplay(select.value);
|
|
349
|
+
handleDynamicParamChange(callId, key, { value: select.value });
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
wrapper.appendChild(swatch);
|
|
353
|
+
wrapper.appendChild(select);
|
|
354
|
+
wrapper.appendChild(customInput);
|
|
355
|
+
wrapper.appendChild(colorPicker);
|
|
356
|
+
|
|
357
|
+
return wrapper;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Create color list input for arrays of colors (e.g., pie chart colors)
|
|
361
|
+
function createColorListInput(callId, key, colorArray) {
|
|
362
|
+
const wrapper = document.createElement('div');
|
|
363
|
+
wrapper.className = 'color-list-wrapper';
|
|
364
|
+
|
|
365
|
+
if (!Array.isArray(colorArray) || colorArray.length === 0) {
|
|
366
|
+
wrapper.textContent = 'No colors';
|
|
367
|
+
return wrapper;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Create a color swatch + dropdown for each color in the list
|
|
371
|
+
colorArray.forEach((color, index) => {
|
|
372
|
+
const itemWrapper = document.createElement('div');
|
|
373
|
+
itemWrapper.className = 'color-list-item';
|
|
374
|
+
|
|
375
|
+
const indexLabel = document.createElement('span');
|
|
376
|
+
indexLabel.className = 'color-list-index';
|
|
377
|
+
indexLabel.textContent = `${index + 1}:`;
|
|
378
|
+
|
|
379
|
+
const swatch = document.createElement('div');
|
|
380
|
+
swatch.className = 'color-swatch color-swatch-small';
|
|
381
|
+
const hex = resolveColorToHex(color);
|
|
382
|
+
swatch.style.backgroundColor = hex;
|
|
383
|
+
|
|
384
|
+
const select = document.createElement('select');
|
|
385
|
+
select.className = 'color-select color-select-small';
|
|
386
|
+
select.dataset.index = index;
|
|
387
|
+
|
|
388
|
+
// Add preset color options
|
|
389
|
+
for (const [name, preset] of Object.entries(COLOR_PRESETS)) {
|
|
390
|
+
const opt = document.createElement('option');
|
|
391
|
+
opt.value = name;
|
|
392
|
+
opt.textContent = name;
|
|
393
|
+
select.appendChild(opt);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Set current value
|
|
397
|
+
const preset = findPresetColor(color);
|
|
398
|
+
if (preset) {
|
|
399
|
+
select.value = preset.name;
|
|
400
|
+
} else {
|
|
401
|
+
// Add current color as option
|
|
402
|
+
const currentOpt = document.createElement('option');
|
|
403
|
+
currentOpt.value = hex;
|
|
404
|
+
currentOpt.textContent = formatColorDisplay(color);
|
|
405
|
+
select.insertBefore(currentOpt, select.firstChild);
|
|
406
|
+
select.value = hex;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Handle color change
|
|
410
|
+
select.addEventListener('change', function() {
|
|
411
|
+
const newColor = this.value;
|
|
412
|
+
swatch.style.backgroundColor = resolveColorToHex(newColor);
|
|
413
|
+
|
|
414
|
+
// Update the colors array and send to backend
|
|
415
|
+
const newColors = [...colorArray];
|
|
416
|
+
newColors[index] = newColor;
|
|
417
|
+
|
|
418
|
+
// Send the entire updated array to the backend
|
|
419
|
+
handleColorListChange(callId, key, newColors);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
itemWrapper.appendChild(indexLabel);
|
|
423
|
+
itemWrapper.appendChild(swatch);
|
|
424
|
+
itemWrapper.appendChild(select);
|
|
425
|
+
wrapper.appendChild(itemWrapper);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return wrapper;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Handle color list change - update entire array
|
|
432
|
+
async function handleColorListChange(callId, key, colorsArray) {
|
|
433
|
+
// Normalize all colors to hex format for consistency
|
|
434
|
+
const normalizedColors = colorsArray.map(color => {
|
|
435
|
+
// If it's already a preset name, use it directly
|
|
436
|
+
if (typeof color === 'string' && COLOR_PRESETS[color.toLowerCase()]) {
|
|
437
|
+
return color;
|
|
438
|
+
}
|
|
439
|
+
// Otherwise convert to hex
|
|
440
|
+
return resolveColorToHex(color);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
console.log(`Color list change: ${callId}.${key} = [${normalizedColors.join(', ')}]`);
|
|
444
|
+
|
|
445
|
+
document.body.classList.add('loading');
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const response = await fetch('/update_call', {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
headers: { 'Content-Type': 'application/json' },
|
|
451
|
+
body: JSON.stringify({ call_id: callId, param: key, value: normalizedColors })
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const data = await response.json();
|
|
455
|
+
|
|
456
|
+
if (data.success) {
|
|
457
|
+
const img = document.getElementById('preview-image');
|
|
458
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
459
|
+
|
|
460
|
+
if (data.img_size) {
|
|
461
|
+
currentImgWidth = data.img_size.width;
|
|
462
|
+
currentImgHeight = data.img_size.height;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
currentBboxes = data.bboxes;
|
|
466
|
+
loadHitmap();
|
|
467
|
+
updateHitRegions();
|
|
468
|
+
|
|
469
|
+
if (callsData[callId]) {
|
|
470
|
+
callsData[callId].kwargs[key] = colorsArray;
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
alert('Update failed: ' + data.error);
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
alert('Update failed: ' + error.message);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
document.body.classList.remove('loading');
|
|
480
|
+
}
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
__all__ = ["SCRIPTS_COLORS"]
|
|
484
|
+
|
|
485
|
+
# EOF
|