scitex 2.4.3__py3-none-any.whl → 2.5.0__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 (45) hide show
  1. scitex/__version__.py +1 -1
  2. scitex/io/_load.py +5 -0
  3. scitex/io/_load_modules/_canvas.py +171 -0
  4. scitex/io/_save.py +8 -0
  5. scitex/io/_save_modules/_canvas.py +356 -0
  6. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +77 -22
  7. scitex/plt/docs/FIGURE_ARCHITECTURE.md +257 -0
  8. scitex/plt/utils/__init__.py +10 -0
  9. scitex/plt/utils/_collect_figure_metadata.py +14 -12
  10. scitex/plt/utils/_csv_column_naming.py +237 -0
  11. scitex/session/_decorator.py +13 -1
  12. scitex/vis/README.md +246 -615
  13. scitex/vis/__init__.py +138 -78
  14. scitex/vis/canvas.py +423 -0
  15. scitex/vis/docs/CANVAS_ARCHITECTURE.md +307 -0
  16. scitex/vis/editor/__init__.py +1 -1
  17. scitex/vis/editor/_dearpygui_editor.py +1830 -0
  18. scitex/vis/editor/_defaults.py +40 -1
  19. scitex/vis/editor/_edit.py +54 -18
  20. scitex/vis/editor/_flask_editor.py +37 -0
  21. scitex/vis/editor/_qt_editor.py +865 -0
  22. scitex/vis/editor/flask_editor/__init__.py +21 -0
  23. scitex/vis/editor/flask_editor/bbox.py +216 -0
  24. scitex/vis/editor/flask_editor/core.py +152 -0
  25. scitex/vis/editor/flask_editor/plotter.py +130 -0
  26. scitex/vis/editor/flask_editor/renderer.py +184 -0
  27. scitex/vis/editor/flask_editor/templates/__init__.py +33 -0
  28. scitex/vis/editor/flask_editor/templates/html.py +295 -0
  29. scitex/vis/editor/flask_editor/templates/scripts.py +614 -0
  30. scitex/vis/editor/flask_editor/templates/styles.py +549 -0
  31. scitex/vis/editor/flask_editor/utils.py +81 -0
  32. scitex/vis/io/__init__.py +84 -21
  33. scitex/vis/io/canvas.py +226 -0
  34. scitex/vis/io/data.py +204 -0
  35. scitex/vis/io/directory.py +202 -0
  36. scitex/vis/io/export.py +460 -0
  37. scitex/vis/io/panel.py +424 -0
  38. {scitex-2.4.3.dist-info → scitex-2.5.0.dist-info}/METADATA +9 -2
  39. {scitex-2.4.3.dist-info → scitex-2.5.0.dist-info}/RECORD +42 -21
  40. scitex/vis/DJANGO_INTEGRATION.md +0 -677
  41. scitex/vis/editor/_web_editor.py +0 -1440
  42. scitex/vis/tmp.txt +0 -239
  43. {scitex-2.4.3.dist-info → scitex-2.5.0.dist-info}/WHEEL +0 -0
  44. {scitex-2.4.3.dist-info → scitex-2.5.0.dist-info}/entry_points.txt +0 -0
  45. {scitex-2.4.3.dist-info → scitex-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- # Timestamp: "2025-12-01 13:30:00 (ywatanabe)"
3
+ # Timestamp: "2025-12-08 18:45:00 (ywatanabe)"
4
4
  # File: ./src/scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py
5
5
 
6
6
  """CSV formatter for matplotlib plot() calls."""
@@ -12,6 +12,48 @@ import numpy as np
12
12
  import pandas as pd
13
13
  import xarray as xr
14
14
 
15
+ from scitex.plt.utils._csv_column_naming import get_csv_column_name
16
+
17
+
18
+ def _parse_tracking_id(id: str) -> tuple:
19
+ """Parse tracking ID to extract axes position and trace index.
20
+
21
+ Parameters
22
+ ----------
23
+ id : str
24
+ Tracking ID like "ax_00_plot_0" or "plot_0"
25
+
26
+ Returns
27
+ -------
28
+ tuple
29
+ (ax_row, ax_col, trace_index)
30
+ """
31
+ ax_row, ax_col, trace_index = 0, 0, 0
32
+
33
+ if id.startswith('ax_'):
34
+ parts = id.split('_')
35
+ if len(parts) >= 2:
36
+ ax_pos = parts[1]
37
+ if len(ax_pos) >= 2:
38
+ try:
39
+ ax_row = int(ax_pos[0])
40
+ ax_col = int(ax_pos[1])
41
+ except ValueError:
42
+ pass
43
+ # Extract trace index from the rest (e.g., "plot_0" -> 0)
44
+ if len(parts) >= 4 and parts[2] == 'plot':
45
+ try:
46
+ trace_index = int(parts[3])
47
+ except ValueError:
48
+ pass
49
+ elif id.startswith('plot_'):
50
+ try:
51
+ trace_index = int(id.split('_')[1])
52
+ except (ValueError, IndexError):
53
+ pass
54
+
55
+ return ax_row, ax_col, trace_index
56
+
15
57
 
16
58
  def _format_plot(
17
59
  id: str,
@@ -30,7 +72,7 @@ def _format_plot(
30
72
  Parameters
31
73
  ----------
32
74
  id : str
33
- Identifier prefix for the output columns (e.g., "ax_00").
75
+ Identifier prefix for the output columns (e.g., "ax_00_plot_0").
34
76
  tracked_dict : dict or None
35
77
  Dictionary containing tracked data. May include:
36
78
  - 'plot_df': Pre-formatted DataFrame from wrapper
@@ -41,25 +83,40 @@ def _format_plot(
41
83
  Returns
42
84
  -------
43
85
  pd.DataFrame
44
- Formatted data with columns prefixed by id.
45
- For 1D data: {id}_plot_x, {id}_plot_y
46
- For 2D data: {id}_plot_x00, {id}_plot_y00, {id}_plot_x01, ...
86
+ Formatted data with columns using single source of truth naming.
87
+ For 1D data: ax_00_plot_0_plot_x, ax_00_plot_0_plot_y
47
88
  """
48
89
  # Check if tracked_dict is empty or not a dictionary
49
90
  if not tracked_dict or not isinstance(tracked_dict, dict):
50
91
  return pd.DataFrame()
51
92
 
93
+ # Parse the tracking ID to get axes position and trace index
94
+ ax_row, ax_col, trace_index = _parse_tracking_id(id)
95
+
52
96
  # For stx_line, we expect a 'plot_df' key
53
97
  if 'plot_df' in tracked_dict:
54
98
  plot_df = tracked_dict['plot_df']
55
99
  if isinstance(plot_df, pd.DataFrame):
56
- # Add the id prefix to all columns
57
- return plot_df.add_prefix(f"{id}_")
100
+ # Rename columns using single source of truth
101
+ renamed = {}
102
+ for col in plot_df.columns:
103
+ if col == 'plot_x':
104
+ renamed[col] = get_csv_column_name('plot_x', ax_row, ax_col, trace_index=trace_index)
105
+ elif col == 'plot_y':
106
+ renamed[col] = get_csv_column_name('plot_y', ax_row, ax_col, trace_index=trace_index)
107
+ else:
108
+ # For other columns, just prefix with id
109
+ renamed[col] = f"{id}_{col}"
110
+ return plot_df.rename(columns=renamed)
58
111
 
59
112
  # Handle raw args from __getattr__ proxied calls
60
113
  if 'args' in tracked_dict:
61
114
  args = tracked_dict['args']
62
115
  if isinstance(args, tuple) and len(args) > 0:
116
+ # Get column names from single source of truth
117
+ x_col = get_csv_column_name('plot_x', ax_row, ax_col, trace_index=trace_index)
118
+ y_col = get_csv_column_name('plot_y', ax_row, ax_col, trace_index=trace_index)
119
+
63
120
  # Handle single argument: plot(y) or plot(data_2d)
64
121
  if len(args) == 1:
65
122
  args_value = args[0]
@@ -72,14 +129,14 @@ def _format_plot(
72
129
  # 2D array: extract x and y columns
73
130
  if hasattr(args_value, 'ndim') and args_value.ndim == 2:
74
131
  x, y = args_value[:, 0], args_value[:, 1]
75
- df = pd.DataFrame({f"{id}_plot_x": x, f"{id}_plot_y": y})
132
+ df = pd.DataFrame({x_col: x, y_col: y})
76
133
  return df
77
134
 
78
135
  # 1D array: generate x from indices (common case: plot(y))
79
136
  elif hasattr(args_value, 'ndim') and args_value.ndim == 1:
80
137
  x = np.arange(len(args_value))
81
138
  y = args_value
82
- df = pd.DataFrame({f"{id}_plot_x": x, f"{id}_plot_y": y})
139
+ df = pd.DataFrame({x_col: x, y_col: y})
83
140
  return df
84
141
 
85
142
  # Handle two arguments: plot(x, y)
@@ -94,22 +151,20 @@ def _format_plot(
94
151
  if hasattr(y, 'ndim') and y.ndim == 2:
95
152
  out = OrderedDict()
96
153
  for ii in range(y.shape[1]):
97
- out[f"{id}_plot_x{ii:02d}"] = x
98
- out[f"{id}_plot_y{ii:02d}"] = y[:, ii]
154
+ x_col_i = get_csv_column_name(f'plot_x{ii:02d}', ax_row, ax_col, trace_index=trace_index)
155
+ y_col_i = get_csv_column_name(f'plot_y{ii:02d}', ax_row, ax_col, trace_index=trace_index)
156
+ out[x_col_i] = x
157
+ out[y_col_i] = y[:, ii]
99
158
  df = pd.DataFrame(out)
100
159
  return df
101
160
 
102
161
  # Handle DataFrame y
103
162
  if isinstance(y_arg, pd.DataFrame):
104
- df = pd.DataFrame(
105
- {
106
- f"{id}_plot_x": x,
107
- **{
108
- f"{id}_plot_y{ii:02d}": np.array(y_arg[col])
109
- for ii, col in enumerate(y_arg.columns)
110
- },
111
- }
112
- )
163
+ result = {x_col: x}
164
+ for ii, col in enumerate(y_arg.columns):
165
+ y_col_i = get_csv_column_name(f'plot_y{ii:02d}', ax_row, ax_col, trace_index=trace_index)
166
+ result[y_col_i] = np.array(y_arg[col])
167
+ df = pd.DataFrame(result)
113
168
  return df
114
169
 
115
170
  # Handle 1D arrays (most common case: plot(x, y))
@@ -117,11 +172,11 @@ def _format_plot(
117
172
  # Flatten x if needed
118
173
  x_flat = np.ravel(x)
119
174
  y_flat = np.ravel(y)
120
- df = pd.DataFrame({f"{id}_plot_x": x_flat, f"{id}_plot_y": y_flat})
175
+ df = pd.DataFrame({x_col: x_flat, y_col: y_flat})
121
176
  return df
122
177
 
123
178
  # Fallback for list-like y
124
- df = pd.DataFrame({f"{id}_plot_x": np.ravel(x), f"{id}_plot_y": np.ravel(y)})
179
+ df = pd.DataFrame({x_col: np.ravel(x), y_col: np.ravel(y)})
125
180
  return df
126
181
 
127
182
  # Default empty DataFrame if we can't process the input
@@ -0,0 +1,257 @@
1
+ <!-- ---
2
+ !-- Timestamp: 2025-12-08 16:05:55
3
+ !-- Author: ywatanabe
4
+ !-- File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/docs/FIGURE_ARCHITECTURE.md
5
+ !-- --- -->
6
+
7
+ # Figure Architecture for scitex.plt
8
+
9
+ ## Terminology
10
+
11
+ | Term | Meaning | In Code |
12
+ |------------|------------------------------|--------------------------------|
13
+ | **Figure** | A matplotlib figure object | `fig, ax = stx.plt.subplots()` |
14
+ | **Axes** | A single subplot/axes | `ax.plot(x, y)` |
15
+ | **Panel** | Used in `scitex.vis` context | See CANVAS_ARCHITECTURE.md |
16
+
17
+ ## Output Format
18
+
19
+ `stx.plt` outputs **3 files per figure**:
20
+
21
+ ```
22
+ output_dir/
23
+ ├── 01_plot.png # Rendered image
24
+ ├── 01_plot.json # Metadata (dimensions, axes, traces, styles)
25
+ └── 01_plot.csv # Raw data (columns referenced in JSON)
26
+ ```
27
+
28
+ ### Save Patterns
29
+
30
+ **Flat (default):**
31
+ ```python
32
+ fig.savefig("./01_plot.png")
33
+ # Creates: 01_plot.png, 01_plot.json, 01_plot.csv
34
+ ```
35
+
36
+ **Organized by extension:**
37
+ ```python
38
+ fig.savefig("./png/01_plot.png")
39
+ # Creates: png/01_plot.png, json/01_plot.json, csv/01_plot.csv
40
+ ```
41
+
42
+ ## JSON Schema (panel.json)
43
+
44
+ ```json
45
+ {
46
+ "metadata_version": "1.1.0",
47
+ "scitex": {
48
+ "version": "2.4.3",
49
+ "created_at": "2025-12-08T15:40:58.453762",
50
+ "created_with": "scitex.plt.subplots (mm-control)",
51
+ "mode": "publication",
52
+ "axes_size_mm": [40, 28],
53
+ "position_in_grid": [0, 0],
54
+ "style_mm": {
55
+ "axis_thickness_mm": 0.2,
56
+ "tick_length_mm": 0.8,
57
+ "tick_thickness_mm": 0.2,
58
+ "trace_thickness_mm": 0.2,
59
+ "marker_size_mm": 0.8,
60
+ "axis_font_size_pt": 7,
61
+ "tick_font_size_pt": 7,
62
+ "title_font_size_pt": 8,
63
+ "legend_font_size_pt": 6,
64
+ "font_family": "Arial",
65
+ "n_ticks": 4
66
+ }
67
+ },
68
+ "matplotlib": {
69
+ "version": "3.10.3"
70
+ },
71
+ "id": "01_plot",
72
+ "dimensions": {
73
+ "figure_size_mm": [80.0, 68.0],
74
+ "figure_size_inch": [3.15, 2.68],
75
+ "figure_size_px": [944, 803],
76
+ "axes_size_mm": [40.0, 28.0],
77
+ "axes_size_inch": [1.57, 1.10],
78
+ "axes_size_px": [472, 330],
79
+ "axes_position": [0.25, 0.29, 0.5, 0.41],
80
+ "dpi": 300
81
+ },
82
+ "margins_mm": {
83
+ "left": 20.0,
84
+ "bottom": 20.0,
85
+ "right": 20.0,
86
+ "top": 20.0
87
+ },
88
+ "axes_bbox_px": {
89
+ "x0": 236,
90
+ "y0": 236,
91
+ "x1": 708,
92
+ "y1": 566,
93
+ "width": 472,
94
+ "height": 330
95
+ },
96
+ "axes_bbox_mm": {
97
+ "x0": 20.0,
98
+ "y0": 20.0,
99
+ "x1": 60.0,
100
+ "y1": 48.0,
101
+ "width": 40.0,
102
+ "height": 28.0
103
+ },
104
+ "axes": {
105
+ "x": {
106
+ "label": "Time",
107
+ "unit": "s",
108
+ "scale": "linear",
109
+ "lim": [-0.31, 6.60],
110
+ "n_ticks": 4
111
+ },
112
+ "y": {
113
+ "label": "Amplitude",
114
+ "unit": "a.u.",
115
+ "scale": "linear",
116
+ "lim": [-1.10, 1.10],
117
+ "n_ticks": 4
118
+ }
119
+ },
120
+ "title": "ax.plot(x, y)",
121
+ "plot_type": "line",
122
+ "method": "plot",
123
+ "traces": [
124
+ {
125
+ "id": "sine",
126
+ "label": "sin(x)",
127
+ "color": "#0000ff",
128
+ "linestyle": "-",
129
+ "linewidth": 0.57,
130
+ "csv_columns": {
131
+ "x": "ax_00_sine_plot_x",
132
+ "y": "ax_00_sine_plot_y"
133
+ }
134
+ },
135
+ {
136
+ "id": "cosine",
137
+ "label": "cos(x)",
138
+ "color": "#ff0000",
139
+ "linestyle": "--",
140
+ "linewidth": 0.57,
141
+ "csv_columns": {
142
+ "x": "ax_00_cosine_plot_x",
143
+ "y": "ax_00_cosine_plot_y"
144
+ }
145
+ }
146
+ ],
147
+ "legend": {
148
+ "visible": true,
149
+ "loc": 0,
150
+ "frameon": false,
151
+ "labels": ["sin(x)", "cos(x)"]
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## CSV Format
157
+
158
+ Column naming convention: `ax_{ax_idx}_{trace_id}_{method}_{dim}`
159
+
160
+ ```csv
161
+ ax_00_sine_plot_x,ax_00_sine_plot_y,ax_00_cosine_plot_x,ax_00_cosine_plot_y
162
+ 0.0,0.0,0.0,1.0
163
+ 0.063,0.063,0.063,0.998
164
+ 0.127,0.127,0.127,0.992
165
+ ...
166
+ ```
167
+
168
+ ## Key Features
169
+
170
+ ### Publication Mode
171
+
172
+ ```python
173
+ fig, ax = stx.plt.subplots(
174
+ fig_mm={"width": 80, "height": 68},
175
+ axes_mm={"width": 40, "height": 28},
176
+ mode="publication"
177
+ )
178
+ ```
179
+
180
+ - All dimensions in **millimeters** for publication standards
181
+ - Consistent styling across figures
182
+ - Automatic metadata embedding
183
+
184
+ ### Automatic Export
185
+
186
+ On `fig.savefig()`:
187
+ 1. PNG/PDF/SVG rendered
188
+ 2. JSON metadata exported
189
+ 3. CSV data exported
190
+
191
+ ### Trace Tracking
192
+
193
+ All plotting calls are tracked:
194
+
195
+ ```python
196
+ ax.plot(x, y, id="sine", label="sin(x)") # Tracked
197
+ ax.scatter(x, y, id="points") # Tracked
198
+ ax.stx_line(x, y, id="trace") # Tracked
199
+ ```
200
+
201
+ ## Supported Plot Methods
202
+
203
+ ### Standard Matplotlib
204
+ - `plot`, `scatter`, `bar`, `barh`
205
+ - `hist`, `hist2d`, `hexbin`
206
+ - `boxplot`, `violinplot`
207
+ - `fill_between`, `fill_betweenx`
208
+ - `errorbar`, `contour`, `contourf`
209
+ - `imshow`, `matshow`, `pie`
210
+ - `quiver`, `streamplot`
211
+ - `stem`, `step`, `eventplot`
212
+
213
+ ### SciTeX Extensions
214
+ - `stx_line`, `stx_shaded_line`
215
+ - `stx_mean_std`, `stx_mean_ci`, `stx_median_iqr`
216
+ - `stx_kde`, `stx_ecdf`
217
+ - `stx_box`, `stx_violin`
218
+ - `stx_bar`, `stx_barh`
219
+ - `stx_scatter`, `stx_scatter_hist`
220
+ - `stx_heatmap`, `stx_conf_mat`
221
+ - `stx_image`, `stx_imshow`
222
+ - `stx_fillv`, `stx_contour`
223
+ - `stx_raster`, `stx_joyplot`
224
+ - `stx_rectangle`
225
+
226
+ ### Seaborn Integration
227
+ - `sns_lineplot`, `sns_scatterplot`
228
+ - `sns_barplot`, `sns_boxplot`, `sns_violinplot`
229
+ - `sns_stripplot`, `sns_swarmplot`
230
+ - `sns_histplot`, `sns_kdeplot`
231
+ - `sns_heatmap`, `sns_jointplot`, `sns_pairplot`
232
+
233
+ ## Integration with scitex.vis
234
+
235
+ stx.plt outputs can be used as `scitex` type panels in a canvas:
236
+
237
+ ```
238
+ canvas/panels/panel_a/
239
+ ├── panel.json # Renamed from 01_plot.json
240
+ ├── panel.csv # Renamed from 01_plot.csv
241
+ └── panel.png # Renamed from 01_plot.png
242
+ ```
243
+
244
+ The JSON structure is compatible - canvas.json references the panel data via relative paths with hash verification.
245
+
246
+ ## Summary
247
+
248
+ | Aspect | Description |
249
+ |-------------|------------------------------------|
250
+ | Output | PNG + JSON + CSV per figure |
251
+ | Units | Millimeters for publication |
252
+ | Tracking | All traces tracked with IDs |
253
+ | Metadata | Dimensions, styles, axes info |
254
+ | Data | CSV with column references in JSON |
255
+ | Integration | Direct use as canvas panels |
256
+
257
+ <!-- EOF -->
@@ -22,6 +22,12 @@ from ._figure_from_axes_mm import (
22
22
  )
23
23
  from ._units import inch_to_mm, mm_to_inch, mm_to_pt, pt_to_mm
24
24
  from ._collect_figure_metadata import collect_figure_metadata
25
+ from ._csv_column_naming import (
26
+ get_csv_column_name,
27
+ get_csv_column_prefix,
28
+ parse_csv_column_name,
29
+ sanitize_trace_id,
30
+ )
25
31
 
26
32
  __all__ = [
27
33
  "HistogramBinManager",
@@ -47,6 +53,8 @@ __all__ = [
47
53
  "cross_ref",
48
54
  "enhance_scitex_save_with_captions",
49
55
  "export_captions",
56
+ "get_csv_column_name",
57
+ "get_csv_column_prefix",
50
58
  "get_dimension_info",
51
59
  "get_scitex_config",
52
60
  "histogram_bin_manager",
@@ -57,9 +65,11 @@ __all__ = [
57
65
  "mk_patches",
58
66
  "mm_to_inch",
59
67
  "mm_to_pt",
68
+ "parse_csv_column_name",
60
69
  "print_dimension_info",
61
70
  "pt_to_mm",
62
71
  "quick_caption",
72
+ "sanitize_trace_id",
63
73
  "save_with_caption",
64
74
  "view_dimensions",
65
75
  ]
@@ -349,14 +349,15 @@ def _extract_traces(ax) -> list:
349
349
  and csv_columns mapping
350
350
  """
351
351
  import matplotlib.colors as mcolors
352
+ from ._csv_column_naming import get_csv_column_name, sanitize_trace_id
352
353
 
353
354
  traces = []
354
355
 
355
356
  # Get axes position for CSV column naming
356
- ax_pos = "00" # Default for single axes
357
+ ax_row, ax_col = 0, 0 # Default for single axes
357
358
  if hasattr(ax, '_scitex_metadata') and 'position_in_grid' in ax._scitex_metadata:
358
359
  pos = ax._scitex_metadata['position_in_grid']
359
- ax_pos = f"{pos[0]}{pos[1]}"
360
+ ax_row, ax_col = pos[0], pos[1]
360
361
 
361
362
  for i, line in enumerate(ax.lines):
362
363
  trace = {}
@@ -369,14 +370,16 @@ def _extract_traces(ax) -> list:
369
370
  label = line.get_label()
370
371
 
371
372
  # Determine trace_id for CSV column matching
373
+ # Use index-based ID to match CSV export (single source of truth)
374
+ trace_id_for_csv = None # Will use trace_index in get_csv_column_name
375
+
376
+ # Store display id/label separately
372
377
  if scitex_id:
373
- trace_id = scitex_id
378
+ trace["id"] = scitex_id
374
379
  elif not label.startswith('_'):
375
- trace_id = label
380
+ trace["id"] = label
376
381
  else:
377
- trace_id = f"line_{i}"
378
-
379
- trace["id"] = trace_id
382
+ trace["id"] = f"line_{i}"
380
383
 
381
384
  # Label (for legend) - use label if not internal
382
385
  if not label.startswith('_'):
@@ -404,12 +407,11 @@ def _extract_traces(ax) -> list:
404
407
  trace["marker"] = marker
405
408
  trace["markersize"] = line.get_markersize()
406
409
 
407
- # CSV column mapping - this is how we'll reconstruct from CSV
408
- # Format matches what _export_as_csv generates: ax_{row}{col}_{id}_plot_x/y
409
- # The id should match the id= kwarg passed to ax.plot()
410
+ # CSV column mapping - use single source of truth
411
+ # Uses trace_index to match what _export_as_csv generates
410
412
  trace["csv_columns"] = {
411
- "x": f"ax_{ax_pos}_{trace_id}_plot_x",
412
- "y": f"ax_{ax_pos}_{trace_id}_plot_y",
413
+ "x": get_csv_column_name("plot_x", ax_row, ax_col, trace_index=i),
414
+ "y": get_csv_column_name("plot_y", ax_row, ax_col, trace_index=i),
413
415
  }
414
416
 
415
417
  traces.append(trace)