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.
Files changed (177) hide show
  1. figrecipe/__init__.py +106 -973
  2. figrecipe/_api/__init__.py +48 -0
  3. figrecipe/_api/_extract.py +108 -0
  4. figrecipe/_api/_notebook.py +61 -0
  5. figrecipe/_api/_panel.py +46 -0
  6. figrecipe/_api/_save.py +191 -0
  7. figrecipe/_api/_seaborn_proxy.py +34 -0
  8. figrecipe/_api/_style_manager.py +153 -0
  9. figrecipe/_api/_subplots.py +333 -0
  10. figrecipe/_api/_validate.py +82 -0
  11. figrecipe/_dev/__init__.py +2 -93
  12. figrecipe/_dev/_plotters.py +76 -0
  13. figrecipe/_dev/_run_demos.py +56 -0
  14. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  15. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  16. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  17. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  18. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  19. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  20. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  21. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  22. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  23. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  24. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  25. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  26. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  27. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  28. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  29. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  30. figrecipe/_editor/__init__.py +57 -9
  31. figrecipe/_editor/_bbox/__init__.py +43 -0
  32. figrecipe/_editor/_bbox/_collections.py +177 -0
  33. figrecipe/_editor/_bbox/_elements.py +159 -0
  34. figrecipe/_editor/_bbox/_extract.py +256 -0
  35. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  36. figrecipe/_editor/_bbox/_extract_text.py +342 -0
  37. figrecipe/_editor/_bbox/_lines.py +173 -0
  38. figrecipe/_editor/_bbox/_transforms.py +146 -0
  39. figrecipe/_editor/_flask_app.py +68 -1039
  40. figrecipe/_editor/_helpers.py +242 -0
  41. figrecipe/_editor/_hitmap/__init__.py +76 -0
  42. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  43. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  44. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  45. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  46. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  47. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  48. figrecipe/_editor/_hitmap/_colors.py +181 -0
  49. figrecipe/_editor/_hitmap/_detect.py +137 -0
  50. figrecipe/_editor/_hitmap/_restore.py +154 -0
  51. figrecipe/_editor/_hitmap_main.py +182 -0
  52. figrecipe/_editor/_preferences.py +135 -0
  53. figrecipe/_editor/_render_overrides.py +480 -0
  54. figrecipe/_editor/_renderer.py +35 -185
  55. figrecipe/_editor/_routes_axis.py +453 -0
  56. figrecipe/_editor/_routes_core.py +284 -0
  57. figrecipe/_editor/_routes_element.py +317 -0
  58. figrecipe/_editor/_routes_style.py +223 -0
  59. figrecipe/_editor/_templates/__init__.py +78 -1
  60. figrecipe/_editor/_templates/_html.py +109 -13
  61. figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
  62. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  63. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  64. figrecipe/_editor/_templates/_scripts/_core.py +436 -0
  65. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  66. figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
  67. figrecipe/_editor/_templates/_scripts/_files.py +195 -0
  68. figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
  69. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  70. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  71. figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
  72. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  73. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  74. figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
  75. figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
  76. figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
  77. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  78. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  79. figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
  80. figrecipe/_editor/_templates/_styles/__init__.py +69 -0
  81. figrecipe/_editor/_templates/_styles/_base.py +64 -0
  82. figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
  83. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  84. figrecipe/_editor/_templates/_styles/_controls.py +265 -0
  85. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  86. figrecipe/_editor/_templates/_styles/_forms.py +126 -0
  87. figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
  88. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  89. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  90. figrecipe/_editor/_templates/_styles/_modals.py +98 -0
  91. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  92. figrecipe/_editor/_templates/_styles/_preview.py +225 -0
  93. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  94. figrecipe/_params/_DECORATION_METHODS.py +6 -0
  95. figrecipe/_recorder.py +35 -106
  96. figrecipe/_recorder_utils.py +124 -0
  97. figrecipe/_reproducer/__init__.py +18 -0
  98. figrecipe/_reproducer/_core.py +498 -0
  99. figrecipe/_reproducer/_custom_plots.py +279 -0
  100. figrecipe/_reproducer/_seaborn.py +100 -0
  101. figrecipe/_reproducer/_violin.py +186 -0
  102. figrecipe/_signatures/_kwargs.py +273 -0
  103. figrecipe/_signatures/_loader.py +21 -423
  104. figrecipe/_signatures/_parsing.py +147 -0
  105. figrecipe/_wrappers/_axes.py +119 -910
  106. figrecipe/_wrappers/_axes_helpers.py +136 -0
  107. figrecipe/_wrappers/_axes_plots.py +418 -0
  108. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  109. figrecipe/_wrappers/_figure.py +162 -0
  110. figrecipe/_wrappers/_panel_labels.py +127 -0
  111. figrecipe/_wrappers/_plot_helpers.py +143 -0
  112. figrecipe/_wrappers/_violin_helpers.py +180 -0
  113. figrecipe/styles/__init__.py +8 -6
  114. figrecipe/styles/_dotdict.py +72 -0
  115. figrecipe/styles/_finalize.py +134 -0
  116. figrecipe/styles/_fonts.py +77 -0
  117. figrecipe/styles/_kwargs_converter.py +178 -0
  118. figrecipe/styles/_plot_styles.py +209 -0
  119. figrecipe/styles/_style_applier.py +32 -478
  120. figrecipe/styles/_style_loader.py +16 -192
  121. figrecipe/styles/_themes.py +151 -0
  122. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  123. figrecipe/styles/presets/SCITEX.yaml +29 -24
  124. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
  125. figrecipe-0.7.4.dist-info/RECORD +188 -0
  126. figrecipe/_editor/_bbox.py +0 -978
  127. figrecipe/_editor/_hitmap.py +0 -937
  128. figrecipe/_editor/_templates/_scripts.py +0 -2778
  129. figrecipe/_editor/_templates/_styles.py +0 -1326
  130. figrecipe/_reproducer.py +0 -975
  131. figrecipe-0.6.0.dist-info/RECORD +0 -90
  132. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  133. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  134. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  135. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  136. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  137. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  138. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  139. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  140. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  141. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  142. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  143. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  144. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  145. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  146. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  147. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  148. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  149. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  150. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  151. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  152. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  153. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  154. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  155. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  156. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  157. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  158. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  159. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  160. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  161. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  162. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  163. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  164. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  165. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  166. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  167. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  168. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  169. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  170. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  171. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  172. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  173. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  174. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  175. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  176. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
  177. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """View mode JavaScript for the figure editor.
4
+
5
+ This module contains the JavaScript code for:
6
+ - View mode management (all/selected)
7
+ - Property filtering by element type
8
+ - Section visibility control
9
+ """
10
+
11
+ SCRIPTS_VIEW_MODE = """
12
+ // ===== VIEW MODE MANAGEMENT =====
13
+ // Note: viewMode variable is declared in _core.py
14
+
15
+ // Set view mode (all or selected)
16
+ function setViewMode(mode) {
17
+ viewMode = mode;
18
+
19
+ // Update toggle buttons (legacy)
20
+ const btnAll = document.getElementById('btn-show-all');
21
+ const btnSelected = document.getElementById('btn-show-selected');
22
+ if (btnAll) btnAll.classList.toggle('active', mode === 'all');
23
+ if (btnSelected) btnSelected.classList.toggle('active', mode === 'selected');
24
+
25
+ // Update controls sections class
26
+ const controlsSections = document.querySelector('.controls-sections');
27
+ controlsSections.classList.toggle('filter-mode', mode === 'selected');
28
+
29
+ // Update selection hint
30
+ const hint = document.getElementById('selection-hint');
31
+ if (mode === 'selected') {
32
+ if (selectedElement) {
33
+ hint.textContent = `Showing: ${selectedElement.type}`;
34
+ hint.style.color = 'var(--accent-color)';
35
+ // Hide all style sections - only show call properties
36
+ hideAllStyleSections();
37
+ } else {
38
+ hint.textContent = '';
39
+ hint.style.color = '';
40
+ // Show all when no selection in filter mode
41
+ showAllProperties();
42
+ }
43
+ } else {
44
+ hint.textContent = '';
45
+ showAllProperties();
46
+ }
47
+ }
48
+
49
+ // Hide all style sections (for Selected mode - only show call properties)
50
+ function hideAllStyleSections() {
51
+ const sections = document.querySelectorAll('.section[data-element-types]');
52
+ sections.forEach(section => {
53
+ section.classList.add('section-hidden');
54
+ section.classList.remove('section-visible');
55
+ });
56
+ }
57
+
58
+ // Filter properties by element type
59
+ function filterPropertiesByElementType(elementType) {
60
+ const sections = document.querySelectorAll('.section[data-element-types]');
61
+
62
+ sections.forEach(section => {
63
+ const types = section.getAttribute('data-element-types').split(',');
64
+ const isGlobal = types.includes('global');
65
+ const matches = isGlobal || types.includes(elementType);
66
+
67
+ section.classList.toggle('section-hidden', !matches);
68
+ section.classList.toggle('section-visible', matches);
69
+
70
+ // If section matches, filter individual form-rows within it
71
+ if (matches && !isGlobal) {
72
+ const formRows = section.querySelectorAll('.form-row[data-element-types]');
73
+ formRows.forEach(row => {
74
+ const rowTypes = row.getAttribute('data-element-types').split(',');
75
+ const rowMatches = rowTypes.includes(elementType);
76
+ row.classList.toggle('field-hidden', !rowMatches);
77
+ });
78
+
79
+ // Open matching sections
80
+ section.setAttribute('open', '');
81
+ }
82
+ });
83
+
84
+ // Update hint
85
+ const hint = document.getElementById('selection-hint');
86
+ hint.textContent = `Showing: ${elementType}`;
87
+ hint.style.color = 'var(--accent-color)';
88
+ }
89
+
90
+ // Show all properties (remove filtering)
91
+ function showAllProperties() {
92
+ const sections = document.querySelectorAll('.section[data-element-types]');
93
+
94
+ sections.forEach(section => {
95
+ section.classList.remove('section-hidden', 'section-visible');
96
+
97
+ const formRows = section.querySelectorAll('.form-row[data-element-types]');
98
+ formRows.forEach(row => {
99
+ row.classList.remove('field-hidden');
100
+ });
101
+ });
102
+ }
103
+ """
104
+
105
+ __all__ = ["SCRIPTS_VIEW_MODE"]
106
+
107
+ # EOF
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Zoom and pan JavaScript for the figure editor."""
4
+
5
+ SCRIPTS_ZOOM = """
6
+ // ==================== ZOOM/PAN FUNCTIONS ====================
7
+
8
+ function initializeZoomPan() {
9
+ const wrapper = document.getElementById('preview-wrapper');
10
+ const container = document.getElementById('zoom-container');
11
+
12
+ if (!wrapper || !container) return;
13
+
14
+ // Zoom buttons
15
+ document.getElementById('btn-zoom-in')?.addEventListener('click', () => setZoom(zoomLevel + ZOOM_STEP));
16
+ document.getElementById('btn-zoom-out')?.addEventListener('click', () => setZoom(zoomLevel - ZOOM_STEP));
17
+ document.getElementById('btn-zoom-reset')?.addEventListener('click', () => setZoom(1.0));
18
+ document.getElementById('btn-zoom-fit')?.addEventListener('click', zoomToFit);
19
+
20
+ // Mouse wheel zoom
21
+ wrapper.addEventListener('wheel', (e) => {
22
+ if (e.ctrlKey || e.metaKey) {
23
+ e.preventDefault();
24
+ const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
25
+ setZoom(zoomLevel + delta);
26
+ }
27
+ }, { passive: false });
28
+
29
+ // Pan with middle mouse, alt+drag, or left-click on empty area when zoomed
30
+ wrapper.addEventListener('mousedown', (e) => {
31
+ // Middle mouse or Alt+drag always pans
32
+ if (e.button === 1 || (e.button === 0 && e.altKey)) {
33
+ e.preventDefault();
34
+ startPan(e);
35
+ return;
36
+ }
37
+ // Left-click when zoomed > 100% and clicking on background (not on elements)
38
+ if (e.button === 0 && zoomLevel > 1.0) {
39
+ const target = e.target;
40
+ // Only pan if clicking on wrapper/container background, not on canvas elements
41
+ if (target.id === 'preview-wrapper' || target.classList.contains('zoom-container') ||
42
+ target.tagName === 'svg' || target.id === 'preview-image') {
43
+ // Don't pan if clicking on hitmap regions (they have data attributes)
44
+ const hitRegion = document.elementFromPoint(e.clientX, e.clientY);
45
+ if (!hitRegion || !hitRegion.closest('.hit-region')) {
46
+ e.preventDefault();
47
+ startPan(e);
48
+ }
49
+ }
50
+ }
51
+ });
52
+
53
+ wrapper.addEventListener('mousemove', (e) => {
54
+ if (isPanning) {
55
+ doPan(e);
56
+ }
57
+ });
58
+
59
+ wrapper.addEventListener('mouseup', endPan);
60
+ wrapper.addEventListener('mouseleave', endPan);
61
+
62
+ // Keyboard shortcuts for zoom
63
+ document.addEventListener('keydown', (e) => {
64
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
65
+
66
+ if (e.key === '+' || e.key === '=') {
67
+ e.preventDefault();
68
+ setZoom(zoomLevel + ZOOM_STEP);
69
+ } else if (e.key === '-' || e.key === '_') {
70
+ e.preventDefault();
71
+ setZoom(zoomLevel - ZOOM_STEP);
72
+ } else if (e.key === '0') {
73
+ e.preventDefault();
74
+ setZoom(1.0);
75
+ } else if (e.key === 'f' || e.key === 'F') {
76
+ e.preventDefault();
77
+ zoomToFit();
78
+ }
79
+ });
80
+
81
+ // Initialize fit to view
82
+ setTimeout(zoomToFit, 200);
83
+ }
84
+
85
+ function setZoom(newLevel) {
86
+ zoomLevel = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, newLevel));
87
+
88
+ const container = document.getElementById('zoom-container');
89
+ const wrapper = document.getElementById('preview-wrapper');
90
+ const img = document.getElementById('preview-image');
91
+
92
+ if (container && wrapper) {
93
+ container.style.transform = `scale(${zoomLevel})`;
94
+
95
+ // Update container size to enable proper scrolling
96
+ // Transform scale doesn't change layout size, so we set explicit dimensions
97
+ if (img) {
98
+ // Use rendered dimensions if naturalWidth not available
99
+ const imgWidth = img.naturalWidth || img.width || img.clientWidth;
100
+ const imgHeight = img.naturalHeight || img.height || img.clientHeight;
101
+
102
+ if (imgWidth && imgHeight) {
103
+ const scaledWidth = imgWidth * zoomLevel;
104
+ const scaledHeight = imgHeight * zoomLevel;
105
+
106
+ // Set container dimensions for scroll area calculation
107
+ container.style.width = `${imgWidth}px`;
108
+ container.style.height = `${imgHeight}px`;
109
+ container.style.minWidth = `${scaledWidth}px`;
110
+ container.style.minHeight = `${scaledHeight}px`;
111
+ }
112
+ }
113
+
114
+ // Update wrapper class for cursor hint
115
+ if (zoomLevel > 1.0) {
116
+ wrapper.classList.add('zoomed-in');
117
+ } else {
118
+ wrapper.classList.remove('zoomed-in');
119
+ // Reset scroll position when not zoomed
120
+ wrapper.scrollLeft = 0;
121
+ wrapper.scrollTop = 0;
122
+ }
123
+ }
124
+
125
+ // Update zoom level display
126
+ const levelDisplay = document.getElementById('zoom-level');
127
+ if (levelDisplay) {
128
+ levelDisplay.textContent = Math.round(zoomLevel * 100) + '%';
129
+ }
130
+ }
131
+
132
+ function zoomToFit() {
133
+ const wrapper = document.getElementById('preview-wrapper');
134
+ const img = document.getElementById('preview-image');
135
+
136
+ if (!wrapper || !img || !img.naturalWidth) return;
137
+
138
+ const wrapperRect = wrapper.getBoundingClientRect();
139
+ const padding = 40;
140
+
141
+ const scaleX = (wrapperRect.width - padding) / img.naturalWidth;
142
+ const scaleY = (wrapperRect.height - padding) / img.naturalHeight;
143
+
144
+ setZoom(Math.min(scaleX, scaleY, 1.0));
145
+ }
146
+
147
+ function startPan(e) {
148
+ const wrapper = document.getElementById('preview-wrapper');
149
+ isPanning = true;
150
+ panStartX = e.clientX;
151
+ panStartY = e.clientY;
152
+ scrollStartX = wrapper.scrollLeft;
153
+ scrollStartY = wrapper.scrollTop;
154
+ wrapper.classList.add('panning');
155
+ }
156
+
157
+ function doPan(e) {
158
+ if (!isPanning) return;
159
+
160
+ const wrapper = document.getElementById('preview-wrapper');
161
+ const dx = e.clientX - panStartX;
162
+ const dy = e.clientY - panStartY;
163
+
164
+ wrapper.scrollLeft = scrollStartX - dx;
165
+ wrapper.scrollTop = scrollStartY - dy;
166
+ }
167
+
168
+ function endPan() {
169
+ if (isPanning) {
170
+ const wrapper = document.getElementById('preview-wrapper');
171
+ wrapper.classList.remove('panning');
172
+ isPanning = false;
173
+ }
174
+ }
175
+
176
+ // ==================== END ZOOM/PAN ====================
177
+ """
178
+
179
+ __all__ = ["SCRIPTS_ZOOM"]
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """CSS styles for the figure editor.
4
+
5
+ This package contains modular CSS styles organized by component:
6
+ - base: CSS variables, reset, container
7
+ - preview: Preview panel, branding, zoom controls
8
+ - hitmap: Hitregion overlay shapes
9
+ - selection: Selection overlay shapes
10
+ - dynamic_props: Dynamic call properties panel
11
+ - controls: Controls panel, tabs, sections
12
+ - forms: Form elements and inputs
13
+ - buttons: Button styles, download dropdown
14
+ - color_input: Color picker components
15
+ - labels: Axis type toggle, label inputs, legend
16
+ - overlays: Ruler, grid, column overlays
17
+ - modals: Shortcuts modal, kbd styling
18
+ - inspector: Element inspector overlay
19
+ """
20
+
21
+ from ._base import STYLES_BASE
22
+ from ._buttons import STYLES_BUTTONS
23
+ from ._color_input import STYLES_COLOR_INPUT
24
+ from ._controls import STYLES_CONTROLS
25
+ from ._dynamic_props import STYLES_DYNAMIC_PROPS
26
+ from ._forms import STYLES_FORMS
27
+ from ._hitmap import STYLES_HITMAP
28
+ from ._inspector import STYLES_INSPECTOR
29
+ from ._labels import STYLES_LABELS
30
+ from ._modals import STYLES_MODALS
31
+ from ._overlays import STYLES_OVERLAYS
32
+ from ._preview import STYLES_PREVIEW
33
+ from ._selection import STYLES_SELECTION
34
+
35
+ # Combined STYLES constant for backward compatibility
36
+ STYLES = (
37
+ STYLES_BASE
38
+ + STYLES_PREVIEW
39
+ + STYLES_HITMAP
40
+ + STYLES_SELECTION
41
+ + STYLES_DYNAMIC_PROPS
42
+ + STYLES_CONTROLS
43
+ + STYLES_FORMS
44
+ + STYLES_BUTTONS
45
+ + STYLES_COLOR_INPUT
46
+ + STYLES_LABELS
47
+ + STYLES_OVERLAYS
48
+ + STYLES_MODALS
49
+ + STYLES_INSPECTOR
50
+ )
51
+
52
+ __all__ = [
53
+ "STYLES",
54
+ "STYLES_BASE",
55
+ "STYLES_PREVIEW",
56
+ "STYLES_HITMAP",
57
+ "STYLES_SELECTION",
58
+ "STYLES_DYNAMIC_PROPS",
59
+ "STYLES_CONTROLS",
60
+ "STYLES_FORMS",
61
+ "STYLES_BUTTONS",
62
+ "STYLES_COLOR_INPUT",
63
+ "STYLES_LABELS",
64
+ "STYLES_OVERLAYS",
65
+ "STYLES_MODALS",
66
+ "STYLES_INSPECTOR",
67
+ ]
68
+
69
+ # EOF
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Base CSS styles for the figure editor.
4
+
5
+ This module contains CSS for:
6
+ - CSS variables (light and dark theme)
7
+ - Reset and base styles
8
+ - Main container layout
9
+ """
10
+
11
+ STYLES_BASE = """
12
+ /* CSS Variables for theming */
13
+ :root {
14
+ --bg-primary: #ffffff;
15
+ --bg-secondary: #f5f5f5;
16
+ --bg-tertiary: #e8e8e8;
17
+ --text-primary: #1a1a1a;
18
+ --text-secondary: #666666;
19
+ --border-color: #d0d0d0;
20
+ --accent-color: #2563eb;
21
+ --accent-hover: #1d4ed8;
22
+ --success-color: #10b981;
23
+ --error-color: #ef4444;
24
+ --selection-color: rgba(37, 99, 235, 0.3);
25
+ }
26
+
27
+ [data-theme="dark"] {
28
+ --bg-primary: #1a1a1a;
29
+ --bg-secondary: #252525;
30
+ --bg-tertiary: #333333;
31
+ --text-primary: #e8e8e8;
32
+ --text-secondary: #a0a0a0;
33
+ --border-color: #404040;
34
+ --accent-color: #3b82f6;
35
+ --accent-hover: #60a5fa;
36
+ --selection-color: rgba(59, 130, 246, 0.3);
37
+ }
38
+
39
+ /* Reset and base */
40
+ * {
41
+ box-sizing: border-box;
42
+ margin: 0;
43
+ padding: 0;
44
+ }
45
+
46
+ body {
47
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
48
+ font-size: 14px;
49
+ background: var(--bg-primary);
50
+ color: var(--text-primary);
51
+ height: 100vh;
52
+ overflow: hidden;
53
+ }
54
+
55
+ /* Main container */
56
+ .editor-container {
57
+ display: flex;
58
+ height: 100vh;
59
+ }
60
+ """
61
+
62
+ __all__ = ["STYLES_BASE"]
63
+
64
+ # EOF
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Button CSS styles for the figure editor.
4
+
5
+ This module contains CSS for:
6
+ - Base button styles
7
+ - Primary, secondary, warning buttons
8
+ - Theme toggle and style info
9
+ - Download dropdown
10
+ """
11
+
12
+ STYLES_BUTTONS = """
13
+ /* Buttons */
14
+ button {
15
+ padding: 8px 16px;
16
+ border: 1px solid var(--border-color);
17
+ border-radius: 4px;
18
+ background: var(--bg-primary);
19
+ color: var(--text-primary);
20
+ font-size: 13px;
21
+ cursor: pointer;
22
+ transition: all 0.2s;
23
+ }
24
+
25
+ button:hover {
26
+ background: var(--bg-secondary);
27
+ }
28
+
29
+ .btn-primary {
30
+ background: var(--accent-color);
31
+ border-color: var(--accent-color);
32
+ color: white;
33
+ }
34
+
35
+ .btn-primary:hover {
36
+ background: var(--accent-hover);
37
+ border-color: var(--accent-hover);
38
+ }
39
+
40
+ .btn-secondary {
41
+ background: var(--bg-secondary);
42
+ }
43
+
44
+ .btn-warning {
45
+ background: #f59e0b;
46
+ border-color: #f59e0b;
47
+ color: white;
48
+ }
49
+
50
+ .btn-warning:hover {
51
+ background: #d97706;
52
+ border-color: #d97706;
53
+ }
54
+
55
+ .style-info {
56
+ padding: 8px 16px;
57
+ background: var(--bg-secondary);
58
+ border-bottom: 1px solid var(--border-color);
59
+ font-size: 12px;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 8px;
63
+ flex-wrap: wrap;
64
+ }
65
+
66
+ .style-label {
67
+ color: var(--text-secondary);
68
+ }
69
+
70
+ .style-name {
71
+ color: var(--accent-color);
72
+ font-weight: 600;
73
+ font-family: monospace;
74
+ }
75
+
76
+ .theme-selector {
77
+ padding: 4px 8px;
78
+ border: 1px solid var(--border-color);
79
+ border-radius: 4px;
80
+ background: var(--bg-primary);
81
+ color: var(--accent-color);
82
+ font-weight: 600;
83
+ font-family: monospace;
84
+ font-size: 12px;
85
+ cursor: pointer;
86
+ }
87
+
88
+ .theme-selector:hover {
89
+ border-color: var(--accent-color);
90
+ }
91
+
92
+ .theme-selector:focus {
93
+ outline: none;
94
+ border-color: var(--accent-color);
95
+ box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
96
+ }
97
+
98
+ .theme-actions {
99
+ display: flex;
100
+ gap: 4px;
101
+ margin-left: auto;
102
+ }
103
+
104
+ .btn-small {
105
+ padding: 4px 8px;
106
+ font-size: 11px;
107
+ border-radius: 3px;
108
+ }
109
+
110
+ /* Split dropdown for download button */
111
+ .download-dropdown {
112
+ position: relative;
113
+ display: flex;
114
+ }
115
+
116
+ .download-main {
117
+ border-radius: 4px 0 0 4px;
118
+ padding: 8px 16px;
119
+ font-weight: 500;
120
+ }
121
+
122
+ .download-toggle {
123
+ border-radius: 0 4px 4px 0;
124
+ padding: 8px 8px;
125
+ border-left: 1px solid rgba(255, 255, 255, 0.2);
126
+ font-size: 10px;
127
+ }
128
+
129
+ .download-menu {
130
+ display: none;
131
+ position: absolute;
132
+ top: 100%;
133
+ left: 0;
134
+ right: 0;
135
+ background: var(--bg-primary);
136
+ border: 1px solid var(--border-color);
137
+ border-radius: 4px;
138
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
139
+ z-index: 100;
140
+ margin-top: 4px;
141
+ overflow: hidden;
142
+ }
143
+
144
+ .download-menu.open {
145
+ display: block;
146
+ }
147
+
148
+ .download-option {
149
+ display: block;
150
+ width: 100%;
151
+ padding: 10px 16px;
152
+ text-align: left;
153
+ border: none;
154
+ background: transparent;
155
+ cursor: pointer;
156
+ font-size: 13px;
157
+ border-radius: 0;
158
+ }
159
+
160
+ .download-option:hover {
161
+ background: var(--bg-secondary);
162
+ }
163
+
164
+ .download-option.active {
165
+ background: var(--accent-color);
166
+ color: white;
167
+ }
168
+
169
+ .download-option.active:hover {
170
+ background: var(--accent-hover);
171
+ }
172
+
173
+ /* Legacy download buttons (kept for compatibility) */
174
+ .download-buttons {
175
+ display: flex;
176
+ gap: 8px;
177
+ }
178
+
179
+ .download-buttons button {
180
+ flex: 1;
181
+ }
182
+
183
+ /* Theme toggle */
184
+ .theme-toggle {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 8px;
188
+ cursor: pointer;
189
+ font-size: 13px;
190
+ }
191
+
192
+ .theme-toggle input {
193
+ width: 18px;
194
+ height: 18px;
195
+ }
196
+
197
+ /* Loading state */
198
+ .loading {
199
+ opacity: 0.6;
200
+ pointer-events: none;
201
+ }
202
+ """
203
+
204
+ __all__ = ["STYLES_BUTTONS"]
205
+
206
+ # EOF