scitex 2.7.3__py3-none-any.whl → 2.8.1__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 (160) hide show
  1. scitex/__version__.py +1 -1
  2. scitex/dev/plt/__init__.py +0 -0
  3. scitex/dev/plt/plot_mpl_axhline.py +0 -0
  4. scitex/dev/plt/plot_mpl_axhspan.py +0 -0
  5. scitex/dev/plt/plot_mpl_axvline.py +0 -0
  6. scitex/dev/plt/plot_mpl_axvspan.py +0 -0
  7. scitex/dev/plt/plot_mpl_bar.py +0 -0
  8. scitex/dev/plt/plot_mpl_barh.py +0 -0
  9. scitex/dev/plt/plot_mpl_boxplot.py +0 -0
  10. scitex/dev/plt/plot_mpl_contour.py +0 -0
  11. scitex/dev/plt/plot_mpl_contourf.py +0 -0
  12. scitex/dev/plt/plot_mpl_errorbar.py +0 -0
  13. scitex/dev/plt/plot_mpl_eventplot.py +0 -0
  14. scitex/dev/plt/plot_mpl_fill.py +0 -0
  15. scitex/dev/plt/plot_mpl_fill_between.py +0 -0
  16. scitex/dev/plt/plot_mpl_hexbin.py +0 -0
  17. scitex/dev/plt/plot_mpl_hist.py +0 -0
  18. scitex/dev/plt/plot_mpl_hist2d.py +0 -0
  19. scitex/dev/plt/plot_mpl_imshow.py +0 -0
  20. scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
  21. scitex/dev/plt/plot_mpl_pie.py +0 -0
  22. scitex/dev/plt/plot_mpl_plot.py +0 -0
  23. scitex/dev/plt/plot_mpl_quiver.py +0 -0
  24. scitex/dev/plt/plot_mpl_scatter.py +0 -0
  25. scitex/dev/plt/plot_mpl_stackplot.py +0 -0
  26. scitex/dev/plt/plot_mpl_stem.py +0 -0
  27. scitex/dev/plt/plot_mpl_step.py +0 -0
  28. scitex/dev/plt/plot_mpl_violinplot.py +0 -0
  29. scitex/dev/plt/plot_sns_barplot.py +0 -0
  30. scitex/dev/plt/plot_sns_boxplot.py +0 -0
  31. scitex/dev/plt/plot_sns_heatmap.py +0 -0
  32. scitex/dev/plt/plot_sns_histplot.py +0 -0
  33. scitex/dev/plt/plot_sns_kdeplot.py +0 -0
  34. scitex/dev/plt/plot_sns_lineplot.py +0 -0
  35. scitex/dev/plt/plot_sns_scatterplot.py +0 -0
  36. scitex/dev/plt/plot_sns_stripplot.py +0 -0
  37. scitex/dev/plt/plot_sns_swarmplot.py +0 -0
  38. scitex/dev/plt/plot_sns_violinplot.py +0 -0
  39. scitex/dev/plt/plot_stx_bar.py +0 -0
  40. scitex/dev/plt/plot_stx_barh.py +0 -0
  41. scitex/dev/plt/plot_stx_box.py +0 -0
  42. scitex/dev/plt/plot_stx_boxplot.py +0 -0
  43. scitex/dev/plt/plot_stx_conf_mat.py +0 -0
  44. scitex/dev/plt/plot_stx_contour.py +0 -0
  45. scitex/dev/plt/plot_stx_ecdf.py +0 -0
  46. scitex/dev/plt/plot_stx_errorbar.py +0 -0
  47. scitex/dev/plt/plot_stx_fill_between.py +0 -0
  48. scitex/dev/plt/plot_stx_fillv.py +0 -0
  49. scitex/dev/plt/plot_stx_heatmap.py +0 -0
  50. scitex/dev/plt/plot_stx_image.py +0 -0
  51. scitex/dev/plt/plot_stx_imshow.py +0 -0
  52. scitex/dev/plt/plot_stx_joyplot.py +0 -0
  53. scitex/dev/plt/plot_stx_kde.py +0 -0
  54. scitex/dev/plt/plot_stx_line.py +0 -0
  55. scitex/dev/plt/plot_stx_mean_ci.py +0 -0
  56. scitex/dev/plt/plot_stx_mean_std.py +0 -0
  57. scitex/dev/plt/plot_stx_median_iqr.py +0 -0
  58. scitex/dev/plt/plot_stx_raster.py +0 -0
  59. scitex/dev/plt/plot_stx_rectangle.py +0 -0
  60. scitex/dev/plt/plot_stx_scatter.py +0 -0
  61. scitex/dev/plt/plot_stx_shaded_line.py +0 -0
  62. scitex/dev/plt/plot_stx_violin.py +0 -0
  63. scitex/dev/plt/plot_stx_violinplot.py +0 -0
  64. scitex/diagram/README.md +197 -0
  65. scitex/diagram/__init__.py +48 -0
  66. scitex/diagram/_compile.py +312 -0
  67. scitex/diagram/_diagram.py +355 -0
  68. scitex/diagram/_presets.py +173 -0
  69. scitex/diagram/_schema.py +182 -0
  70. scitex/diagram/_split.py +278 -0
  71. scitex/fig/editor/__init__.py +5 -2
  72. scitex/fig/editor/_dearpygui_editor.py +1 -1
  73. scitex/fig/editor/_mpl_editor.py +1 -1
  74. scitex/fig/editor/_qt_editor.py +1 -1
  75. scitex/fig/editor/_tkinter_editor.py +1 -1
  76. scitex/fig/editor/edit/__init__.py +50 -0
  77. scitex/fig/editor/edit/backend_detector.py +109 -0
  78. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  79. scitex/fig/editor/edit/editor_launcher.py +239 -0
  80. scitex/fig/editor/edit/manual_handler.py +53 -0
  81. scitex/fig/editor/edit/panel_loader.py +232 -0
  82. scitex/fig/editor/edit/path_resolver.py +67 -0
  83. scitex/fig/editor/flask_editor/_bbox.py +23 -0
  84. scitex/fig/editor/flask_editor/_core.py +908 -103
  85. scitex/fig/editor/flask_editor/_renderer.py +74 -0
  86. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  87. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  88. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  89. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  90. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  91. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  92. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  93. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  94. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  95. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  96. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  97. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  98. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  99. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  100. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  101. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  102. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  103. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  104. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  105. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  106. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  107. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  108. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  109. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  110. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  111. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  112. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  113. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  114. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  115. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  116. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  117. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  118. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  119. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  120. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  121. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  122. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  123. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  124. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  125. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  126. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  127. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  128. scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
  129. scitex/fig/editor/flask_editor/templates/_html.py +27 -9
  130. scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
  131. scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
  132. scitex/fig/io/_bundle.py +97 -12
  133. scitex/io/__init__.py +12 -0
  134. scitex/io/_bundle.py +69 -10
  135. scitex/io/_zip_bundle.py +439 -0
  136. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
  137. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
  138. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
  139. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
  140. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
  141. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
  142. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
  143. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
  144. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
  145. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
  146. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
  147. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
  148. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
  149. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
  150. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
  151. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
  152. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
  153. scitex/plt/io/_layered_bundle.py +0 -0
  154. scitex/schema/_plot.py +0 -0
  155. {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/METADATA +1 -1
  156. {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/RECORD +78 -22
  157. scitex/fig/editor/_edit.py +0 -751
  158. {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
  159. {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
  160. {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Global State Variables
3
+ * Central state management for the Flask figure editor
4
+ *
5
+ * Note: `overrides` is set in the HTML page via inline script before loading modules.
6
+ * This allows Flask to inject the initial data using Jinja2 templating.
7
+ *
8
+ * IMPORTANT: Variables use `var` (not `let`) to ensure they're accessible
9
+ * as globals across all script files loaded via separate <script> tags.
10
+ */
11
+
12
+ // ============================================================================
13
+ // Override Data and Element State
14
+ // ============================================================================
15
+ // `overrides` is defined in HTML before this script loads
16
+ // Fallback to empty object if not defined
17
+ if (typeof overrides === 'undefined') {
18
+ var overrides = {};
19
+ }
20
+ var traces = overrides.traces || [];
21
+ var elementBboxes = {};
22
+ var imgSize = {width: 0, height: 0};
23
+ var hoveredElement = null;
24
+ var selectedElement = null;
25
+ var debugMode = false; // Debug mode to show all hit areas
26
+ var isShowingOriginalPreview = false; // True when showing existing SVG/PNG from bundle
27
+ var originalBboxes = null; // Store original bboxes from /preview
28
+ var originalImgSize = null; // Store original img size from /preview
29
+
30
+ // Schema metadata storage
31
+ var schemaMeta = null;
32
+
33
+ // ============================================================================
34
+ // Multi-Panel State
35
+ // ============================================================================
36
+ var panelData = null; // Panel info from /preview
37
+ var currentPanelIndex = 0;
38
+ var showingPanelGrid = false;
39
+ var panelBboxesCache = {}; // Cache bboxes per panel {panelName: {bboxes, imgSize}}
40
+ var activePanelCard = null; // Currently active panel card for hover/click
41
+ var panelHoveredElement = null; // Hovered element in panel grid
42
+ var panelDebugMode = false; // Show hit regions in panel grid
43
+
44
+ // ============================================================================
45
+ // Cycle Selection State
46
+ // ============================================================================
47
+ var elementsAtCursor = []; // All elements at current cursor position
48
+ var currentCycleIndex = 0; // Current index in cycle
49
+
50
+ // ============================================================================
51
+ // Dimension Settings
52
+ // ============================================================================
53
+ var dimensionUnit = 'mm';
54
+ var MM_TO_INCH = 1 / 25.4;
55
+ var INCH_TO_MM = 25.4;
56
+
57
+ // ============================================================================
58
+ // Hitmap State
59
+ // ============================================================================
60
+ var hitmapCanvas = null;
61
+ var hitmapCtx = null;
62
+ var hitmapColorMap = {}; // Maps RGB string -> element info
63
+ var hitmapLoaded = false;
64
+ var hitmapImgSize = {width: 0, height: 0};
65
+
66
+ // ============================================================================
67
+ // Background Settings
68
+ // ============================================================================
69
+ var backgroundType = 'transparent';
70
+ var initializingBackground = true; // Flag to prevent updates during init
71
+
72
+ // ============================================================================
73
+ // Canvas Mode and Layout State
74
+ // ============================================================================
75
+ var canvasMode = 'grid'; // 'grid' or 'canvas'
76
+ var panelPositions = {}; // Store panel positions {name: {x, y, width, height}} in pixels
77
+ var panelLayoutMm = {}; // Store panel positions in mm for saving {name: {x_mm, y_mm, width_mm, height_mm}}
78
+ var canvasScale = 3; // Scale factor: pixels per mm (updated in loadPanelGrid)
79
+ var draggedPanel = null;
80
+ var dragOffset = {x: 0, y: 0};
81
+ var layoutModified = false; // Track if layout has been modified
82
+
83
+ // ============================================================================
84
+ // Element Drag State
85
+ // ============================================================================
86
+ var elementDragState = null; // {element, panelName, startPos, elementType, axId}
87
+
88
+ // Snap positions for legend alignment
89
+ var SNAP_POSITIONS = {
90
+ 'upper right': [1.0, 1.0],
91
+ 'upper left': [0.0, 1.0],
92
+ 'lower left': [0.0, 0.0],
93
+ 'lower right': [1.0, 0.0],
94
+ 'upper center': [0.5, 1.0],
95
+ 'lower center': [0.5, 0.0],
96
+ 'center left': [0.0, 0.5],
97
+ 'center right': [1.0, 0.5],
98
+ 'center': [0.5, 0.5]
99
+ };
100
+
101
+ // ============================================================================
102
+ // Resize State
103
+ // ============================================================================
104
+ var resizingPanel = null;
105
+
106
+ // ============================================================================
107
+ // Update Scheduling
108
+ // ============================================================================
109
+ var updateTimer = null;
110
+ var DEBOUNCE_DELAY = 500;
111
+
112
+ // ============================================================================
113
+ // Shortcut Mode State
114
+ // ============================================================================
115
+ var shortcutMode = null; // For multi-key shortcuts like Alt+A → L
116
+
117
+ // ============================================================================
118
+ // Canvas Zoom and View State
119
+ // ============================================================================
120
+ var canvasZoom = 1.0;
121
+
122
+ // ============================================================================
123
+ // Grid Visibility
124
+ // ============================================================================
125
+ var gridVisible = true;
126
+
127
+ // ============================================================================
128
+ // Undo/Redo State
129
+ // ============================================================================
130
+ var undoStack = [];
131
+ var redoStack = [];
132
+
133
+ // ============================================================================
134
+ // Context Menu State
135
+ // ============================================================================
136
+ var contextMenu = null;
137
+
138
+ // ============================================================================
139
+ // Auto-Update State
140
+ // ============================================================================
141
+ var autoUpdateIntervalId = null;
142
+
143
+ console.log('state.js loaded - global state variables initialized');
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Utility Functions
3
+ * Common helper functions used across the editor
4
+ */
5
+
6
+ // ============================================================================
7
+ // Theme Utilities
8
+ // ============================================================================
9
+ function isDarkMode() {
10
+ return document.body.classList.contains('dark-theme');
11
+ }
12
+
13
+ // ============================================================================
14
+ // Image Dimension Calculations
15
+ // ============================================================================
16
+ function getObjectFitContainDimensions(img) {
17
+ const containerWidth = img.clientWidth;
18
+ const containerHeight = img.clientHeight;
19
+ const imgWidth = img.naturalWidth;
20
+ const imgHeight = img.naturalHeight;
21
+
22
+ // Handle edge cases
23
+ if (!containerWidth || !containerHeight || !imgWidth || !imgHeight) {
24
+ return {
25
+ displayWidth: containerWidth,
26
+ displayHeight: containerHeight,
27
+ offsetX: 0,
28
+ offsetY: 0
29
+ };
30
+ }
31
+
32
+ // Calculate scale factor for object-fit: contain
33
+ const containerRatio = containerWidth / containerHeight;
34
+ const imgRatio = imgWidth / imgHeight;
35
+
36
+ let displayWidth, displayHeight, offsetX, offsetY;
37
+
38
+ if (imgRatio > containerRatio) {
39
+ // Image is wider than container - fit to width, letterbox top/bottom
40
+ displayWidth = containerWidth;
41
+ displayHeight = containerWidth / imgRatio;
42
+ offsetX = 0;
43
+ offsetY = (containerHeight - displayHeight) / 2;
44
+ } else {
45
+ // Image is taller than container - fit to height, letterbox left/right
46
+ displayHeight = containerHeight;
47
+ displayWidth = containerHeight * imgRatio;
48
+ offsetX = (containerWidth - displayWidth) / 2;
49
+ offsetY = 0;
50
+ }
51
+
52
+ return {displayWidth, displayHeight, offsetX, offsetY};
53
+ }
54
+
55
+ // ============================================================================
56
+ // Status Display
57
+ // ============================================================================
58
+ function setStatus(msg, isError = false) {
59
+ document.getElementById('status').textContent = msg;
60
+ document.getElementById('status').style.color = isError ? 'red' : '';
61
+
62
+ // Show/hide spinner for loading states
63
+ if (msg.toLowerCase().includes('updating') || msg.toLowerCase().includes('loading')) {
64
+ // Show global overlay (visible for both single and multi-panel views)
65
+ const globalOverlay = document.getElementById('loading-overlay');
66
+ if (globalOverlay) globalOverlay.style.display = 'flex';
67
+
68
+ // Also show local overlay if visible
69
+ const localOverlay = document.getElementById('overlay-loading');
70
+ if (localOverlay) localOverlay.style.display = 'flex';
71
+ } else {
72
+ // Hide both overlays
73
+ const globalOverlay = document.getElementById('loading-overlay');
74
+ if (globalOverlay) globalOverlay.style.display = 'none';
75
+ const localOverlay = document.getElementById('overlay-loading');
76
+ if (localOverlay) localOverlay.style.display = 'none';
77
+ }
78
+ }
79
+
80
+ // ============================================================================
81
+ // Loading State Management
82
+ // ============================================================================
83
+ function showLoading(message = 'Loading...') {
84
+ const overlay = document.getElementById('loading-overlay');
85
+ if (overlay) {
86
+ overlay.style.display = 'flex';
87
+ const text = overlay.querySelector('.loading-text');
88
+ if (text) text.textContent = message;
89
+ }
90
+ }
91
+
92
+ function hideLoading() {
93
+ const overlay = document.getElementById('loading-overlay');
94
+ if (overlay) {
95
+ overlay.style.display = 'none';
96
+ }
97
+ }
98
+
99
+ // ============================================================================
100
+ // Section Management
101
+ // ============================================================================
102
+ function toggleSection(header) {
103
+ const content = header.nextElementSibling;
104
+ content.style.display = content.style.display === 'none' ? 'block' : 'none';
105
+ }
106
+
107
+ function expandSection(sectionId) {
108
+ const section = document.getElementById(sectionId);
109
+ if (section) {
110
+ const header = section.querySelector('h3');
111
+ const content = section.querySelector('.section-content');
112
+ if (content && content.style.display === 'none') {
113
+ content.style.display = 'block';
114
+ if (header) {
115
+ header.classList.add('expanded');
116
+ }
117
+ }
118
+
119
+ // Scroll the section into view
120
+ section.scrollIntoView({behavior: 'smooth', block: 'start'});
121
+ }
122
+ }
123
+
124
+ // ============================================================================
125
+ // Scroll to Section by Element Name
126
+ // ============================================================================
127
+ function scrollToSection(elementName) {
128
+ if (!elementName) return;
129
+
130
+ // Map element names to section IDs
131
+ let sectionId = null;
132
+ const lowerName = elementName.toLowerCase();
133
+
134
+ if (lowerName.includes('legend')) {
135
+ sectionId = 'section-legend';
136
+ } else if (lowerName.includes('title')) {
137
+ sectionId = 'section-labels';
138
+ } else if (lowerName.includes('xlabel') || lowerName.includes('ylabel') || lowerName.includes('label')) {
139
+ sectionId = 'section-labels';
140
+ } else if (lowerName.includes('xaxis') || lowerName.includes('yaxis') || lowerName.includes('axis') || lowerName.includes('tick') || lowerName.includes('spine')) {
141
+ sectionId = 'section-ticks';
142
+ } else if (lowerName.includes('trace') || lowerName.includes('line') || lowerName.includes('scatter') || lowerName.includes('bar')) {
143
+ sectionId = 'section-traces';
144
+ } else if (lowerName.includes('panel') || lowerName.includes('ax_')) {
145
+ sectionId = 'section-selected';
146
+ }
147
+
148
+ if (sectionId) {
149
+ expandSection(sectionId);
150
+ }
151
+ }
152
+
153
+ // ============================================================================
154
+ // Color Synchronization
155
+ // ============================================================================
156
+ function setupColorSync(colorId, textId) {
157
+ const colorInput = document.getElementById(colorId);
158
+ const textInput = document.getElementById(textId);
159
+ if (colorInput && textInput) {
160
+ colorInput.addEventListener('input', function() {
161
+ textInput.value = this.value;
162
+ });
163
+ textInput.addEventListener('input', function() {
164
+ colorInput.value = this.value;
165
+ });
166
+ }
167
+ }
168
+
169
+ // ============================================================================
170
+ // Dimension Unit Conversion
171
+ // ============================================================================
172
+ function setDimensionUnit(unit) {
173
+ if (dimensionUnit === unit) return;
174
+
175
+ const oldUnit = dimensionUnit;
176
+ dimensionUnit = unit;
177
+
178
+ // Get current values
179
+ const widthInput = document.getElementById('fig_width');
180
+ const heightInput = document.getElementById('fig_height');
181
+
182
+ // Convert values
183
+ if (unit === 'mm') {
184
+ // inch to mm
185
+ if (widthInput.value) {
186
+ widthInput.value = (parseFloat(widthInput.value) * INCH_TO_MM).toFixed(1);
187
+ }
188
+ if (heightInput.value) {
189
+ heightInput.value = (parseFloat(heightInput.value) * INCH_TO_MM).toFixed(1);
190
+ }
191
+ } else {
192
+ // mm to inch
193
+ if (widthInput.value) {
194
+ widthInput.value = (parseFloat(widthInput.value) * MM_TO_INCH).toFixed(2);
195
+ }
196
+ if (heightInput.value) {
197
+ heightInput.value = (parseFloat(heightInput.value) * MM_TO_INCH).toFixed(2);
198
+ }
199
+ }
200
+
201
+ // Update values and labels
202
+ document.getElementById('unit_width').textContent = unit;
203
+ document.getElementById('unit_height').textContent = unit;
204
+
205
+ // Update button states
206
+ document.getElementById('btn_mm').classList.toggle('active', unit === 'mm');
207
+ document.getElementById('btn_inch').classList.toggle('active', unit === 'inch');
208
+ }
209
+
210
+ // ============================================================================
211
+ // Figure Size Conversion
212
+ // ============================================================================
213
+ function getFigSizeInches() {
214
+ const width = parseFloat(document.getElementById('fig_width').value);
215
+ const height = parseFloat(document.getElementById('fig_height').value);
216
+
217
+ if (dimensionUnit === 'mm') {
218
+ return {
219
+ width: width * MM_TO_INCH,
220
+ height: height * MM_TO_INCH
221
+ };
222
+ }
223
+ return {width, height};
224
+ }
225
+
226
+ // ============================================================================
227
+ // Significance Class Helper
228
+ // ============================================================================
229
+ function getSigClass(stars) {
230
+ if (stars >= 3) return 'sig-high';
231
+ if (stars >= 2) return 'sig-med';
232
+ if (stars >= 1) return 'sig-low';
233
+ return '';
234
+ }
235
+
236
+ // ============================================================================
237
+ // Debounce Helper
238
+ // ============================================================================
239
+ function debounce(func, delay) {
240
+ let timeoutId;
241
+ return function(...args) {
242
+ clearTimeout(timeoutId);
243
+ timeoutId = setTimeout(() => func.apply(this, args), delay);
244
+ };
245
+ }