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,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
|