apmtools 0.2.54__tar.gz → 0.2.55__tar.gz
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.
- {apmtools-0.2.54 → apmtools-0.2.55}/PKG-INFO +1 -1
- {apmtools-0.2.54 → apmtools-0.2.55}/pyproject.toml +1 -1
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/classes.py +12 -1
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/plot_app.py +17 -3
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/ui.py +129 -30
- {apmtools-0.2.54 → apmtools-0.2.55}/.gitignore +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/LICENSE +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/README.md +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/__init__.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/data_processing.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/functions.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/legacy.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/plot_configs.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/__init__.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/appearance.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/color_editor.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/columns.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/config.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/data.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/elements.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/export.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/filter_config.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/histogram_multi.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/matplotlib_renderer.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/multi_axis.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/multi_y.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/plotly_renderer.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/shiny/summary.py +0 -0
- {apmtools-0.2.54 → apmtools-0.2.55}/src/apmtools/units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: apmtools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.55
|
|
4
4
|
Summary: A collection of tools for processing air pollution monitoring data
|
|
5
5
|
Project-URL: Homepage, https://github.com/federlorenz/apmtools
|
|
6
6
|
Author-email: Federico Lorenzetti <lorenzetti.federico@gmail.com>
|
|
@@ -1011,7 +1011,7 @@ class Group(pd.DataFrame):
|
|
|
1011
1011
|
|
|
1012
1012
|
class Summary(pd.DataFrame):
|
|
1013
1013
|
|
|
1014
|
-
_metadata = ['metadata', 'plots']
|
|
1014
|
+
_metadata = ['metadata', 'plots', 'variable', 'description']
|
|
1015
1015
|
|
|
1016
1016
|
@property
|
|
1017
1017
|
def _constructor(self):
|
|
@@ -1030,6 +1030,17 @@ class Summary(pd.DataFrame):
|
|
|
1030
1030
|
raise TypeError("Summary.plot must be None or a list")
|
|
1031
1031
|
super().__setattr__(name, value)
|
|
1032
1032
|
|
|
1033
|
+
def __setstate__(self, state):
|
|
1034
|
+
"""Restore DataFrame state and default metadata absent in older pickles."""
|
|
1035
|
+
super().__setstate__(state)
|
|
1036
|
+
# Some older pickles contain an instance-level _metadata list, which
|
|
1037
|
+
# masks the corrected class declaration and breaks future copies.
|
|
1038
|
+
self.__dict__.pop("_metadata", None)
|
|
1039
|
+
for name, default in (("plots", None), ("variable", None),
|
|
1040
|
+
("description", ""), ("metadata", set())):
|
|
1041
|
+
if name not in self.__dict__:
|
|
1042
|
+
object.__setattr__(self, name, default)
|
|
1043
|
+
|
|
1033
1044
|
def __finalize__(self, other, method=None, **kwargs):
|
|
1034
1045
|
if isinstance(other, Summary):
|
|
1035
1046
|
self.variable = getattr(other, "variable", None)
|
|
@@ -83,12 +83,26 @@ class PlotApp:
|
|
|
83
83
|
import shinyswatch
|
|
84
84
|
|
|
85
85
|
app_ui = ui.page_fluid(
|
|
86
|
+
ui.tags.button("Hide toolbars", type="button", class_="btn btn-sm btn-outline-secondary my-0", id="standalone-toolbar-collapse-toggle", **{"aria-expanded": "true"}),
|
|
86
87
|
ui.div(
|
|
87
|
-
ui.input_action_button("open_dataset", "Open Dataset", class_="btn-primary"),
|
|
88
|
+
ui.input_action_button("open_dataset", "Open Dataset", class_="btn-outline-primary"),
|
|
89
|
+
ui.div(id="standalone-save-dataset-slot"),
|
|
88
90
|
ui.output_text("dataset_status", inline=True),
|
|
89
|
-
class_="
|
|
91
|
+
ui.div(shinyswatch.theme_picker_ui(), class_="summary-app-theme ms-auto"),
|
|
92
|
+
class_="d-flex align-items-center gap-1 my-0 standalone-dataset-toolbar",
|
|
90
93
|
),
|
|
91
|
-
ui.
|
|
94
|
+
ui.tags.script("""
|
|
95
|
+
(() => {
|
|
96
|
+
const move = () => {
|
|
97
|
+
const slot = document.getElementById('standalone-save-dataset-slot');
|
|
98
|
+
const button = document.querySelector('#summary-dataset-toolbar button');
|
|
99
|
+
if (slot && button && button.parentElement !== slot) slot.appendChild(button);
|
|
100
|
+
};
|
|
101
|
+
new MutationObserver(move).observe(document.body, {childList:true, subtree:true});
|
|
102
|
+
document.addEventListener('DOMContentLoaded', move);
|
|
103
|
+
move();
|
|
104
|
+
})();
|
|
105
|
+
"""),
|
|
92
106
|
ui.output_ui("dataset_workspace"),
|
|
93
107
|
title="PlotApp",
|
|
94
108
|
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from .filter_config import filter_config_fields as _filter_config_fields
|
|
4
4
|
import threading
|
|
5
|
+
import json
|
|
5
6
|
from datetime import date, datetime, time, timedelta
|
|
6
7
|
from collections.abc import Sequence
|
|
7
8
|
import numpy as np
|
|
@@ -1528,6 +1529,21 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
1528
1529
|
panel_bounds = {name: _pad_axis_bounds(bounds) for name, bounds in panel_bounds.items()}
|
|
1529
1530
|
limits = {key: _format_panel_bounds(panel_bounds, key) for key in limits}
|
|
1530
1531
|
for field, value in limits.items():
|
|
1532
|
+
# A restored plot's explicit axis bounds are authoritative.
|
|
1533
|
+
# The preparation observer fires after the saved inputs mount;
|
|
1534
|
+
# overwriting them here caused the saved limits to flash and
|
|
1535
|
+
# then revert to automatically calculated limits. Continue
|
|
1536
|
+
# recalculating defaults when the user changes X/Y variables.
|
|
1537
|
+
# Restoration is asynchronous: the group, aggregation and
|
|
1538
|
+
# plot selectors can briefly disagree with the saved values
|
|
1539
|
+
# while mounting. That is NOT a user edit and must never
|
|
1540
|
+
# erase a persisted bound. Release the guard only when an
|
|
1541
|
+
# actual X/Y selection change has been observed after the
|
|
1542
|
+
# saved selectors were fully initialized.
|
|
1543
|
+
if (not axis_selection_changed
|
|
1544
|
+
and field in initial_filters
|
|
1545
|
+
and str(initial_filters[field]).strip()):
|
|
1546
|
+
continue
|
|
1531
1547
|
if backend == "matplotlib":
|
|
1532
1548
|
ui.update_text_area(field, value=value)
|
|
1533
1549
|
else:
|
|
@@ -1653,6 +1669,12 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
1653
1669
|
@reactive.effect
|
|
1654
1670
|
@reactive.event(input.plot_type, ignore_init=True)
|
|
1655
1671
|
def reset_on_plot_type_change():
|
|
1672
|
+
# During a restored tab's initial mount, Shiny can report a
|
|
1673
|
+
# transient plot type. Do not clear persisted axis bounds (or
|
|
1674
|
+
# other appearance settings) until the restored selectors have
|
|
1675
|
+
# settled and a real plot-type change can be observed.
|
|
1676
|
+
if initial_filters and not _axis_selection_armed[0]:
|
|
1677
|
+
return
|
|
1656
1678
|
# Reset every plot-specific selection when the plot type changes.
|
|
1657
1679
|
for _i in range(max_subset_stages):
|
|
1658
1680
|
ui.update_select(f"subset_column_{_i}", selected="")
|
|
@@ -1781,6 +1803,16 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
1781
1803
|
labels = [txt.get_text() for leg in legends for txt in leg.get_texts()]
|
|
1782
1804
|
excluded = ("Y variable", "Grouping", "Legend", "All observations")
|
|
1783
1805
|
labels = [label for label in labels if label.strip() and label not in excluded]
|
|
1806
|
+
# A faceted plot can temporarily expose no legend while Shiny
|
|
1807
|
+
# swaps axes (or when the panel variable consumes the only
|
|
1808
|
+
# grouping dimension). Do not clear the editor identities in
|
|
1809
|
+
# that transient state: remounting the text inputs on every
|
|
1810
|
+
# panel transition can disconnect their reactive watchers.
|
|
1811
|
+
with reactive.isolate():
|
|
1812
|
+
panel_mode = (backend == "matplotlib" and
|
|
1813
|
+
bool(_optional_input("multi_panel", False)))
|
|
1814
|
+
if panel_mode and not labels and known_keys:
|
|
1815
|
+
return
|
|
1784
1816
|
# If this is merely a relabelled rendering of the existing
|
|
1785
1817
|
# traces, do not rediscover/rename their identities.
|
|
1786
1818
|
expected = [mapping.get(key, key) for key in known_keys]
|
|
@@ -2370,17 +2402,34 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2370
2402
|
.summary-plot-container {width:100%; height:auto; aspect-ratio: 2 / 1; min-width:0; position:relative;}
|
|
2371
2403
|
.summary-plot-container > * {width:100% !important;}
|
|
2372
2404
|
.summary-plot-container .shiny-plot-output {min-height:400px;}
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
.summary-
|
|
2376
|
-
|
|
2377
|
-
.summary-toolbar-actions
|
|
2378
|
-
.summary-
|
|
2405
|
+
.summary-top-toolbar {display:flex; align-items:center; gap:0.5rem;
|
|
2406
|
+
flex-wrap:nowrap; margin-bottom:0; min-width:0;}
|
|
2407
|
+
.summary-toolbar-actions {display:flex; gap:0.4rem; flex-wrap:nowrap;
|
|
2408
|
+
align-items:center; min-width:0; overflow-x:auto;}
|
|
2409
|
+
.summary-toolbar-actions .btn {white-space:nowrap;}
|
|
2410
|
+
.summary-toolbar-right {display:flex; gap:0.4rem; align-items:center;
|
|
2411
|
+
margin-left:auto; flex-shrink:0;}
|
|
2412
|
+
.summary-app-theme {width:200px; flex-shrink:0;}
|
|
2379
2413
|
.summary-app-theme .shiny-input-container {width:100%; margin-bottom:0;}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2414
|
+
.summary-dataset-toolbar {display:flex; align-items:center; gap:0.5rem;
|
|
2415
|
+
margin-bottom:0; min-width:0;}
|
|
2416
|
+
.summary-dataset-toolbar .btn {white-space:nowrap;}
|
|
2417
|
+
.summary-dataset-toolbar .summary-app-theme {margin-left:auto;}
|
|
2418
|
+
.summary-toolbar-collapse-toggle {margin-bottom:0; padding-top:0.12rem; padding-bottom:0.12rem;}
|
|
2419
|
+
.summary-dataset-toolbar .shiny-input-container,
|
|
2420
|
+
.summary-toolbar-actions .shiny-input-container {margin-bottom:0 !important;}
|
|
2421
|
+
.summary-dataset-toolbar .btn, .summary-toolbar-actions .btn {padding:0.22rem 0.65rem; font-size:0.875rem; line-height:1.5;}
|
|
2422
|
+
.summary-dataset-toolbar .shiny-input-container,
|
|
2423
|
+
.standalone-dataset-toolbar .shiny-input-container {width:auto !important; flex:0 0 auto;}
|
|
2424
|
+
.standalone-dataset-toolbar {margin-bottom:0 !important; gap:0.35rem !important;}
|
|
2425
|
+
.standalone-dataset-toolbar .btn {padding:0.22rem 0.65rem; font-size:0.875rem; line-height:1.5;}
|
|
2426
|
+
.summary-dataset-toolbar {gap:0.35rem;}
|
|
2427
|
+
.summary-toolbar-collapsed .summary-dataset-toolbar,
|
|
2428
|
+
.summary-toolbar-collapsed .summary-toolbar-actions {display:none !important;}
|
|
2429
|
+
body:has(#standalone-toolbar-collapse-toggle) .summary-toolbar-collapse-toggle {display:none;}
|
|
2430
|
+
body:has(#standalone-toolbar-collapse-toggle).summary-toolbar-collapsed
|
|
2431
|
+
.standalone-dataset-toolbar {display:none !important;}
|
|
2432
|
+
"""),
|
|
2384
2433
|
ui.tags.script("""
|
|
2385
2434
|
(() => {
|
|
2386
2435
|
// A Plotly relayout mutates its DOM. Observing all descendants and
|
|
@@ -2418,24 +2467,34 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2418
2467
|
}).observe(document.body, {childList:true, subtree:true});
|
|
2419
2468
|
document.addEventListener('shown.bs.tab', () => requestAnimationFrame(install));
|
|
2420
2469
|
install();
|
|
2470
|
+
// Delegated listener also works when a dataset workspace is replaced.
|
|
2471
|
+
document.addEventListener('click', (event) => {
|
|
2472
|
+
const toggle = event.target.closest('#summary-toolbar-collapse-toggle, #standalone-toolbar-collapse-toggle');
|
|
2473
|
+
if (!toggle) return;
|
|
2474
|
+
const collapsed = document.body.classList.toggle('summary-toolbar-collapsed');
|
|
2475
|
+
document.querySelectorAll('#summary-toolbar-collapse-toggle, #standalone-toolbar-collapse-toggle').forEach(button => {
|
|
2476
|
+
button.textContent = collapsed ? 'Show toolbars' : 'Hide toolbars';
|
|
2477
|
+
button.setAttribute('aria-expanded', String(!collapsed));
|
|
2478
|
+
});
|
|
2479
|
+
});
|
|
2421
2480
|
})();
|
|
2422
2481
|
"""),
|
|
2423
2482
|
ui.div(
|
|
2483
|
+
ui.tags.button("Hide toolbars", type="button", class_="btn btn-sm btn-outline-secondary summary-toolbar-collapse-toggle", id="summary-toolbar-collapse-toggle", **{"aria-expanded": "true"}),
|
|
2424
2484
|
ui.div(
|
|
2425
|
-
ui.
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
),
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
class_="summary-top-toolbar",
|
|
2485
|
+
ui.input_action_button("save_dataset_pickle", "Save dataset", class_="btn-outline-primary"),
|
|
2486
|
+
ui.div(shinyswatch.theme_picker_ui(), class_="summary-app-theme") if include_theme_picker else ui.div(),
|
|
2487
|
+
class_="summary-dataset-toolbar",
|
|
2488
|
+
id="summary-dataset-toolbar",
|
|
2489
|
+
),
|
|
2490
|
+
ui.div(
|
|
2491
|
+
ui.input_action_button("add_tab", "New plot", class_="btn-primary"),
|
|
2492
|
+
ui.input_action_button("add_table", "New table", class_="btn-primary"),
|
|
2493
|
+
ui.input_action_button("retain_plot", "Save Plot", class_="btn-outline-primary"),
|
|
2494
|
+
ui.input_action_button("restore_plot", "Load Plot", class_="btn-outline-primary"),
|
|
2495
|
+
ui.input_action_button("delete_saved_plot", "Delete Plot", class_="btn-outline-danger"),
|
|
2496
|
+
ui.input_action_button("close_tab", "Close tab", class_="btn-outline-danger"),
|
|
2497
|
+
class_="summary-toolbar-actions summary-top-toolbar",
|
|
2439
2498
|
),
|
|
2440
2499
|
ui.tags.style("""
|
|
2441
2500
|
.summary-info-content {max-width: 980px; margin: 1.2rem auto 3rem;
|
|
@@ -2503,6 +2562,19 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2503
2562
|
manager.update(selected, tab_filters[selected]())
|
|
2504
2563
|
manager.retain(selected, input.retained_plot_name())
|
|
2505
2564
|
saved_revision.set(saved_revision.get() + 1)
|
|
2565
|
+
# Update the existing tab label without remounting its Shiny module.
|
|
2566
|
+
# Replacing the nav panel would destroy the live plot controls.
|
|
2567
|
+
saved_label = str(input.retained_plot_name()).strip()
|
|
2568
|
+
ui.insert_ui(
|
|
2569
|
+
ui.tags.script(
|
|
2570
|
+
"(function(){const tab=" + json.dumps(selected) +
|
|
2571
|
+
", label=" + json.dumps(saved_label) +
|
|
2572
|
+
"; const a=document.querySelector('[data-value=\"'+CSS.escape(tab)+'\"]');" +
|
|
2573
|
+
"if(a){const span=a.querySelector('span'); if(span)span.textContent=label; else a.textContent=label;}" +
|
|
2574
|
+
"})();"
|
|
2575
|
+
),
|
|
2576
|
+
selector="body", where="beforeEnd",
|
|
2577
|
+
)
|
|
2506
2578
|
ui.modal_remove()
|
|
2507
2579
|
ui.notification_show("Plot configuration retained in Summary.plots", type="message")
|
|
2508
2580
|
except Exception as exc:
|
|
@@ -2543,7 +2615,8 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2543
2615
|
ui.p("Save the current Summary, including saved plot configurations, as a pickle file."),
|
|
2544
2616
|
ui.input_text("dataset_pickle_path", "Destination path on the computer running Shiny",
|
|
2545
2617
|
value="summary.pkl", placeholder="/path/to/drive/summary.pkl"),
|
|
2546
|
-
ui.p("For a mounted drive, enter its full directory path. Existing files
|
|
2618
|
+
ui.p("For a mounted drive, enter its full directory path. Existing files can be overwritten if you confirm."),
|
|
2619
|
+
ui.input_checkbox("overwrite_dataset_pickle", "Overwrite existing file", value=False),
|
|
2547
2620
|
title="Save dataset",
|
|
2548
2621
|
footer=ui.div(ui.modal_button("Cancel"),
|
|
2549
2622
|
ui.input_action_button("confirm_save_dataset_pickle", "Save pickle", class_="btn-primary")),
|
|
@@ -2566,8 +2639,11 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2566
2639
|
raise ValueError("The filename must end in .pkl")
|
|
2567
2640
|
if not target.parent.is_dir():
|
|
2568
2641
|
raise FileNotFoundError(f"Directory does not exist: {target.parent}")
|
|
2569
|
-
|
|
2570
|
-
|
|
2642
|
+
overwrite = bool(input.overwrite_dataset_pickle())
|
|
2643
|
+
if target.exists() and not overwrite:
|
|
2644
|
+
raise FileExistsError(f"File already exists: {target}. Check Overwrite existing file to replace it.")
|
|
2645
|
+
if target.exists() and not target.is_file():
|
|
2646
|
+
raise ValueError(f"Destination is not a regular file: {target}")
|
|
2571
2647
|
# Serialize to a temporary file, then publish atomically without
|
|
2572
2648
|
# overwriting an existing file. Preserve the original on errors.
|
|
2573
2649
|
temp_path = None
|
|
@@ -2576,10 +2652,32 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2576
2652
|
prefix=".apmtools-", suffix=".tmp",
|
|
2577
2653
|
delete=False) as stream:
|
|
2578
2654
|
temp_path = Path(stream.name)
|
|
2579
|
-
|
|
2655
|
+
# The manager owns retained plot presets. Never serialize
|
|
2656
|
+
# the working plotting frame, which may have stale metadata.
|
|
2657
|
+
source = manager.summary
|
|
2658
|
+
retained = manager.saved()
|
|
2659
|
+
if not hasattr(source, "plots"):
|
|
2660
|
+
raise RuntimeError("Source Summary has no plots attribute")
|
|
2661
|
+
# Serialize an independent snapshot so that a concurrent
|
|
2662
|
+
# Shiny event cannot mutate metadata during the write.
|
|
2663
|
+
snapshot = source.copy(deep=False)
|
|
2664
|
+
snapshot.plots = retained
|
|
2665
|
+
pickle.dump(snapshot, stream, protocol=pickle.HIGHEST_PROTOCOL)
|
|
2580
2666
|
stream.flush()
|
|
2581
2667
|
os.fsync(stream.fileno())
|
|
2582
|
-
|
|
2668
|
+
# Verify what was actually written before publishing it.
|
|
2669
|
+
with temp_path.open("rb") as check_stream:
|
|
2670
|
+
restored = pickle.load(check_stream)
|
|
2671
|
+
restored_plots = getattr(restored, "plots", None)
|
|
2672
|
+
if restored_plots != retained:
|
|
2673
|
+
raise RuntimeError(
|
|
2674
|
+
"Pickle verification failed: saved plot configurations "
|
|
2675
|
+
"were not preserved; no dataset file was published"
|
|
2676
|
+
)
|
|
2677
|
+
if overwrite:
|
|
2678
|
+
os.replace(temp_path, target) # atomic replacement after verification
|
|
2679
|
+
else:
|
|
2680
|
+
os.link(temp_path, target) # never overwrite unless explicitly requested
|
|
2583
2681
|
finally:
|
|
2584
2682
|
if temp_path is not None:
|
|
2585
2683
|
temp_path.unlink(missing_ok=True)
|
|
@@ -2613,7 +2711,8 @@ if (!window.__apmColorPickerInstalled) {
|
|
|
2613
2711
|
number = next_tab.get() + 1
|
|
2614
2712
|
tab_id = f"plot_{number}"
|
|
2615
2713
|
inherited = manager.restore(preset_id, tab_id)
|
|
2616
|
-
|
|
2714
|
+
saved_name = next((p.get("name") for p in manager.saved() if p.get("id") == preset_id), None)
|
|
2715
|
+
panel = ui.nav_panel(saved_name or f"Plot {number}", plot_tab_ui(tab_id, inherited), value=tab_id)
|
|
2617
2716
|
tab_filters[tab_id] = plot_tab_server(tab_id, inherited, config_tab_id=tab_id)
|
|
2618
2717
|
register_draft(tab_id, tab_filters[tab_id])
|
|
2619
2718
|
next_tab.set(number)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|