graphica-plot 2.0.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.
- graphica/Graphica.ico +0 -0
- graphica/__init__.py +1 -0
- graphica/__main__.py +111 -0
- graphica/assets/__init__.py +0 -0
- graphica/assets/icons/__init__.py +0 -0
- graphica/assets/icons/arrow-right.svg +21 -0
- graphica/assets/icons/bold.svg +20 -0
- graphica/assets/icons/calculator.svg +26 -0
- graphica/assets/icons/chart-histogram.svg +24 -0
- graphica/assets/icons/chart-line.svg +20 -0
- graphica/assets/icons/chevron-down.svg +19 -0
- graphica/assets/icons/chevron-right.svg +19 -0
- graphica/assets/icons/color-swatch.svg +22 -0
- graphica/assets/icons/column-insert-right.svg +21 -0
- graphica/assets/icons/column-remove.svg +21 -0
- graphica/assets/icons/copy.svg +20 -0
- graphica/assets/icons/download.svg +21 -0
- graphica/assets/icons/edit.svg +21 -0
- graphica/assets/icons/eye-off.svg +21 -0
- graphica/assets/icons/eye.svg +20 -0
- graphica/assets/icons/file-plus.svg +22 -0
- graphica/assets/icons/folder-plus.svg +21 -0
- graphica/assets/icons/folder.svg +19 -0
- graphica/assets/icons/highlight.svg +22 -0
- graphica/assets/icons/history.svg +20 -0
- graphica/assets/icons/italic.svg +21 -0
- graphica/assets/icons/layout-grid.svg +22 -0
- graphica/assets/icons/math-function.svg +22 -0
- graphica/assets/icons/message-2.svg +21 -0
- graphica/assets/icons/mountain.svg +20 -0
- graphica/assets/icons/palette.svg +22 -0
- graphica/assets/icons/pointer.svg +19 -0
- graphica/assets/icons/refresh.svg +20 -0
- graphica/assets/icons/row-insert-bottom.svg +21 -0
- graphica/assets/icons/row-remove.svg +21 -0
- graphica/assets/icons/search.svg +20 -0
- graphica/assets/icons/select-all.svg +35 -0
- graphica/assets/icons/subscript.svg +20 -0
- graphica/assets/icons/superscript.svg +20 -0
- graphica/assets/icons/table.svg +21 -0
- graphica/assets/icons/trash.svg +23 -0
- graphica/assets/icons/typography.svg +23 -0
- graphica/assets/icons/x.svg +20 -0
- graphica/core/__init__.py +0 -0
- graphica/core/analysis.py +1278 -0
- graphica/core/app_paths.py +35 -0
- graphica/core/axis_settings.py +128 -0
- graphica/core/caption_export.py +47 -0
- graphica/core/color_palettes.py +56 -0
- graphica/core/commands.py +226 -0
- graphica/core/cvd_simulation.py +49 -0
- graphica/core/dataset.py +443 -0
- graphica/core/diagnostics.py +105 -0
- graphica/core/excel_utils.py +58 -0
- graphica/core/fit_models.py +379 -0
- graphica/core/grid_data.py +128 -0
- graphica/core/i18n.py +44 -0
- graphica/core/json_utils.py +27 -0
- graphica/core/label_utils.py +21 -0
- graphica/core/methods_text.py +115 -0
- graphica/core/named_colors.py +126 -0
- graphica/core/plugin_api.py +515 -0
- graphica/core/plugin_context.py +167 -0
- graphica/core/plugin_install.py +82 -0
- graphica/core/plugin_manifest.py +67 -0
- graphica/core/plugin_testing.py +213 -0
- graphica/core/plugin_types.py +118 -0
- graphica/core/provenance.py +19 -0
- graphica/core/report_export.py +51 -0
- graphica/core/safe_eval.py +194 -0
- graphica/core/script_export.py +272 -0
- graphica/core/translations_en.py +530 -0
- graphica/core/unit_conversion.py +51 -0
- graphica/core/update_check.py +43 -0
- graphica/core/version.py +14 -0
- graphica/gui/__init__.py +0 -0
- graphica/gui/app_settings.py +119 -0
- graphica/gui/axis_bindings.py +173 -0
- graphica/gui/binding.py +135 -0
- graphica/gui/builders/__init__.py +1 -0
- graphica/gui/builders/axis_panel.py +431 -0
- graphica/gui/builders/canvas_area.py +176 -0
- graphica/gui/builders/common.py +120 -0
- graphica/gui/builders/dataset_panel.py +433 -0
- graphica/gui/builders/property_sections.py +208 -0
- graphica/gui/canvas.py +519 -0
- graphica/gui/color_history.py +47 -0
- graphica/gui/color_picker_widget.py +198 -0
- graphica/gui/crash_handler.py +77 -0
- graphica/gui/cvd_preview.py +23 -0
- graphica/gui/data_editor.py +761 -0
- graphica/gui/data_import_flow.py +355 -0
- graphica/gui/dataset_bindings.py +107 -0
- graphica/gui/dataset_style_icon.py +80 -0
- graphica/gui/datasets/__init__.py +1 -0
- graphica/gui/datasets/actions_menu.py +114 -0
- graphica/gui/datasets/colors.py +218 -0
- graphica/gui/datasets/fitting.py +70 -0
- graphica/gui/datasets/host.py +162 -0
- graphica/gui/datasets/operations/__init__.py +1 -0
- graphica/gui/datasets/operations/fitting.py +468 -0
- graphica/gui/datasets/operations/peaks.py +109 -0
- graphica/gui/datasets/operations/processing.py +659 -0
- graphica/gui/datasets/operations/runner.py +148 -0
- graphica/gui/datasets/operations/transfer.py +227 -0
- graphica/gui/datasets/order.py +310 -0
- graphica/gui/datasets/overlays.py +74 -0
- graphica/gui/datasets/peaks.py +21 -0
- graphica/gui/datasets/plugin_runs.py +99 -0
- graphica/gui/datasets/processing.py +68 -0
- graphica/gui/datasets/property_panel.py +538 -0
- graphica/gui/datasets/transfer.py +36 -0
- graphica/gui/detached_canvas_window.py +22 -0
- graphica/gui/dialogs/__init__.py +137 -0
- graphica/gui/dialogs/analysis.py +1322 -0
- graphica/gui/dialogs/app.py +1015 -0
- graphica/gui/dialogs/appearance.py +851 -0
- graphica/gui/dialogs/data_edit.py +577 -0
- graphica/gui/dialogs/data_import.py +679 -0
- graphica/gui/dialogs/export.py +433 -0
- graphica/gui/dock_layout.py +176 -0
- graphica/gui/export_preview_panel.py +321 -0
- graphica/gui/export_settings.py +16 -0
- graphica/gui/file_association.py +97 -0
- graphica/gui/icon_utils.py +44 -0
- graphica/gui/main_app_window.py +210 -0
- graphica/gui/main_window.py +1151 -0
- graphica/gui/mathtext_preview.py +168 -0
- graphica/gui/menu_bar.py +320 -0
- graphica/gui/minimap_widget.py +151 -0
- graphica/gui/mixins/__init__.py +0 -0
- graphica/gui/mixins/export_mixin.py +520 -0
- graphica/gui/mixins/help_mixin.py +127 -0
- graphica/gui/mixins/project_io_mixin.py +289 -0
- graphica/gui/mixins/quick_access_mixin.py +194 -0
- graphica/gui/mixins/ui_setup_mixin.py +206 -0
- graphica/gui/notify.py +61 -0
- graphica/gui/panels/__init__.py +43 -0
- graphica/gui/panels/axis_settings.py +605 -0
- graphica/gui/panels/dataset_tree.py +424 -0
- graphica/gui/plot_type_drawers.py +131 -0
- graphica/gui/plugin_context.py +119 -0
- graphica/gui/project_files.py +361 -0
- graphica/gui/provenance_panel.py +65 -0
- graphica/gui/rendering/__init__.py +1 -0
- graphica/gui/rendering/annotations.py +177 -0
- graphica/gui/rendering/appearance.py +368 -0
- graphica/gui/rendering/common.py +316 -0
- graphica/gui/rendering/data_1d.py +376 -0
- graphica/gui/rendering/data_2d.py +66 -0
- graphica/gui/residual_panel.py +62 -0
- graphica/gui/resources.py +16 -0
- graphica/gui/single_instance.py +141 -0
- graphica/gui/splash.py +83 -0
- graphica/gui/task_runner.py +41 -0
- graphica/gui/theme.py +864 -0
- graphica/gui/tools/__init__.py +56 -0
- graphica/gui/tools/annotation.py +275 -0
- graphica/gui/tools/cursor.py +259 -0
- graphica/gui/tools/layout_edit.py +285 -0
- graphica/gui/tools/manager.py +78 -0
- graphica/gui/tools/peak_placement.py +132 -0
- graphica/gui/tools/range_select.py +191 -0
- graphica/gui/tools/region_highlight.py +222 -0
- graphica/gui/tools/slice_extraction.py +200 -0
- graphica/gui/widget_translation.py +46 -0
- graphica/gui/workers.py +280 -0
- graphica/models/__init__.py +0 -0
- graphica/models/project.py +197 -0
- graphica/plugin/__init__.py +20 -0
- graphica/plugin/testing.py +57 -0
- graphica/sample_data/__init__.py +0 -0
- graphica/sample_data/cooling_curve_sample.csv +42 -0
- graphica/ui_main_window.py +581 -0
- graphica_plot-2.0.0.dist-info/METADATA +317 -0
- graphica_plot-2.0.0.dist-info/RECORD +181 -0
- graphica_plot-2.0.0.dist-info/WHEEL +5 -0
- graphica_plot-2.0.0.dist-info/entry_points.txt +2 -0
- graphica_plot-2.0.0.dist-info/licenses/LICENSE +30 -0
- graphica_plot-2.0.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +91 -0
- graphica_plot-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""データセットの色: 色の変更、自動配色、名前付きの色の適用、配色パレットの管理。"""
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import matplotlib as mpl
|
|
5
|
+
from PySide6.QtWidgets import (QDialog)
|
|
6
|
+
|
|
7
|
+
from graphica.core.i18n import tr
|
|
8
|
+
from graphica.gui import notify
|
|
9
|
+
from graphica.core.color_palettes import BUILTIN_PALETTES, default_color_cycle
|
|
10
|
+
from graphica.core.named_colors import POPUP_LIMIT, load_named_colors
|
|
11
|
+
from graphica.gui import app_settings
|
|
12
|
+
from graphica.gui.dialogs import (ColorPaletteDialog)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# カラーマップからの自動配色で選ばせる候補(順序のある系列に向く、知覚的に均一なもの中心)。
|
|
18
|
+
RECOMMENDED_COLORMAPS = ['viridis', 'plasma', 'cividis', 'coolwarm', 'turbo', 'rainbow']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ColorController:
|
|
22
|
+
"""データセットの色と配色パレット。"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, host):
|
|
25
|
+
self._host = host
|
|
26
|
+
|
|
27
|
+
def on_color_changed(self, new_color):
|
|
28
|
+
"""色欄で選んだ色を選択中のデータセットすべてに当てる(Undo 1回分)。"""
|
|
29
|
+
selected_datasets = self._host.selected_datasets()
|
|
30
|
+
if not selected_datasets:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
is_batch = len(selected_datasets) > 1
|
|
34
|
+
if is_batch:
|
|
35
|
+
self._host.undo_stack.beginMacro(f"データセットの色を一括変更 ({len(selected_datasets)}件)")
|
|
36
|
+
for dataset in selected_datasets:
|
|
37
|
+
self._host.push_property_change(
|
|
38
|
+
dataset,
|
|
39
|
+
{'color': dataset.color},
|
|
40
|
+
{'color': new_color},
|
|
41
|
+
description="データセットの色変更"
|
|
42
|
+
)
|
|
43
|
+
if is_batch:
|
|
44
|
+
self._host.undo_stack.endMacro()
|
|
45
|
+
|
|
46
|
+
def on_gradient_color2_changed(self, new_color):
|
|
47
|
+
"""グラデーションの終端色を選択中のデータセットすべてに当てる(Undo 1回分)。"""
|
|
48
|
+
selected_datasets = self._host.selected_datasets()
|
|
49
|
+
if not selected_datasets:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
is_batch = len(selected_datasets) > 1
|
|
53
|
+
if is_batch:
|
|
54
|
+
self._host.undo_stack.beginMacro(f"グラデーション終端色を一括変更 ({len(selected_datasets)}件)")
|
|
55
|
+
for dataset in selected_datasets:
|
|
56
|
+
self._host.push_property_change(
|
|
57
|
+
dataset,
|
|
58
|
+
{'gradient_color2': dataset.gradient_color2},
|
|
59
|
+
{'gradient_color2': new_color},
|
|
60
|
+
description="グラデーション終端色の変更"
|
|
61
|
+
)
|
|
62
|
+
if is_batch:
|
|
63
|
+
self._host.undo_stack.endMacro()
|
|
64
|
+
|
|
65
|
+
def auto_assign_colors(self):
|
|
66
|
+
"""選択中のデータセットに、今のパレットの色を順に割り当てる。"""
|
|
67
|
+
selected_datasets = self._host.selected_datasets()
|
|
68
|
+
if not selected_datasets:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
color_cycle = self._host.active_color_cycle()
|
|
72
|
+
|
|
73
|
+
is_batch = len(selected_datasets) > 1
|
|
74
|
+
if is_batch:
|
|
75
|
+
self._host.undo_stack.beginMacro(f"配色の自動割り当て ({len(selected_datasets)}件)")
|
|
76
|
+
for i, dataset in enumerate(selected_datasets):
|
|
77
|
+
new_color = color_cycle[i % len(color_cycle)]
|
|
78
|
+
self._host.push_property_change(
|
|
79
|
+
dataset,
|
|
80
|
+
{'color': dataset.color},
|
|
81
|
+
{'color': new_color},
|
|
82
|
+
description="配色の自動割り当て"
|
|
83
|
+
)
|
|
84
|
+
if is_batch:
|
|
85
|
+
self._host.undo_stack.endMacro()
|
|
86
|
+
|
|
87
|
+
def populate_named_color_menu(self):
|
|
88
|
+
"""
|
|
89
|
+
「登録した色を適用」の中身を作り直す(登録はいつでも変わるので開くたびに)。
|
|
90
|
+
登録が無いときは、空のメニューではなく無効な案内項目を1つ出す。
|
|
91
|
+
"""
|
|
92
|
+
menu = getattr(self._host.parent_widget, '_named_color_apply_menu', None)
|
|
93
|
+
if menu is None:
|
|
94
|
+
return
|
|
95
|
+
menu.clear()
|
|
96
|
+
entries = load_named_colors(self._host.settings)
|
|
97
|
+
if not entries:
|
|
98
|
+
empty_action = menu.addAction(tr("(登録がありません)"))
|
|
99
|
+
empty_action.setEnabled(False)
|
|
100
|
+
return
|
|
101
|
+
# 色欄のポップアップと同じく、並べるのは先頭 POPUP_LIMIT 件まで。
|
|
102
|
+
for entry in entries[:POPUP_LIMIT]:
|
|
103
|
+
action = menu.addAction(
|
|
104
|
+
_named_color_menu_icon(entry["color"]),
|
|
105
|
+
f'{entry["name"]} {entry["color"]}')
|
|
106
|
+
action.triggered.connect(
|
|
107
|
+
lambda checked=False, c=entry["color"], n=entry["name"]:
|
|
108
|
+
self.apply_named_color_to_selection(c, n))
|
|
109
|
+
if len(entries) > POPUP_LIMIT:
|
|
110
|
+
more_action = menu.addAction(f"すべての登録色... ({len(entries)}件)")
|
|
111
|
+
more_action.triggered.connect(self.apply_named_color_from_list)
|
|
112
|
+
|
|
113
|
+
def apply_named_color_from_list(self):
|
|
114
|
+
"""登録が POPUP_LIMIT 件を超えたときに、検索欄付きの一覧から選んで適用する。"""
|
|
115
|
+
from graphica.gui.dialogs import NamedColorPickerDialog
|
|
116
|
+
dialog = NamedColorPickerDialog(self._host.settings, self._host.parent_widget, title="登録色を適用")
|
|
117
|
+
if dialog.exec() != dialog.DialogCode.Accepted:
|
|
118
|
+
return
|
|
119
|
+
entry = dialog.selected_entry()
|
|
120
|
+
if entry:
|
|
121
|
+
self.apply_named_color_to_selection(entry["color"], entry["name"])
|
|
122
|
+
|
|
123
|
+
def apply_named_color_to_selection(self, color, name):
|
|
124
|
+
"""登録した色を選択中のデータセットに当てる(Undo 1回分)。"""
|
|
125
|
+
selected_datasets = self._host.selected_datasets()
|
|
126
|
+
if not selected_datasets:
|
|
127
|
+
self._host.show_status("データセットを選択してください。", 4000)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# 変わらないものを先に除く。空のマクロでも Qt は Undo を1回分積んでしまう。
|
|
131
|
+
targets = [ds for ds in selected_datasets if ds.color != color]
|
|
132
|
+
if not targets:
|
|
133
|
+
self._host.show_status(f"選択中のデータセットは既に「{name}」の色です。", 4000)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
is_batch = len(targets) > 1
|
|
137
|
+
if is_batch:
|
|
138
|
+
self._host.undo_stack.beginMacro(f"「{name}」の色を適用 ({len(targets)}件)")
|
|
139
|
+
for dataset in targets:
|
|
140
|
+
self._host.push_property_change(
|
|
141
|
+
dataset,
|
|
142
|
+
{'color': dataset.color},
|
|
143
|
+
{'color': color},
|
|
144
|
+
description=f"「{name}」の色を適用",
|
|
145
|
+
)
|
|
146
|
+
if is_batch:
|
|
147
|
+
self._host.undo_stack.endMacro()
|
|
148
|
+
|
|
149
|
+
def auto_assign_colors_from_colormap(self):
|
|
150
|
+
"""選択中のデータセットに、カラーマップから等間隔に取った色を割り当てる(順序のある系列向け)。"""
|
|
151
|
+
selected_datasets = self._host.selected_datasets()
|
|
152
|
+
if not selected_datasets:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
cmap_name, ok = notify.get_item(
|
|
156
|
+
self._host.parent_widget, "カラーマップから自動配色", "使用するカラーマップを選択してください:",
|
|
157
|
+
RECOMMENDED_COLORMAPS, 0, False
|
|
158
|
+
)
|
|
159
|
+
if not ok:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
cmap = mpl.colormaps[cmap_name]
|
|
163
|
+
n = len(selected_datasets)
|
|
164
|
+
# 1件ならカラーマップの中央の色
|
|
165
|
+
positions = [0.5] if n == 1 else [i / (n - 1) for i in range(n)]
|
|
166
|
+
new_colors = [mpl.colors.to_hex(cmap(p)) for p in positions]
|
|
167
|
+
|
|
168
|
+
is_batch = n > 1
|
|
169
|
+
if is_batch:
|
|
170
|
+
self._host.undo_stack.beginMacro(f"カラーマップからの配色 ({n}件)")
|
|
171
|
+
for dataset, new_color in zip(selected_datasets, new_colors):
|
|
172
|
+
self._host.push_property_change(
|
|
173
|
+
dataset, {'color': dataset.color}, {'color': new_color},
|
|
174
|
+
description="カラーマップからの配色"
|
|
175
|
+
)
|
|
176
|
+
if is_batch:
|
|
177
|
+
self._host.undo_stack.endMacro()
|
|
178
|
+
|
|
179
|
+
def load_palettes(self):
|
|
180
|
+
raw = app_settings.CUSTOM_COLOR_PALETTES.read(self._host.settings)
|
|
181
|
+
if not raw:
|
|
182
|
+
return {}
|
|
183
|
+
try:
|
|
184
|
+
return json.loads(raw)
|
|
185
|
+
except (TypeError, ValueError):
|
|
186
|
+
logger.warning("カスタム配色パレットの読み込みに失敗しました。空として扱います。")
|
|
187
|
+
return {}
|
|
188
|
+
|
|
189
|
+
def save_palettes(self, palettes: dict):
|
|
190
|
+
app_settings.CUSTOM_COLOR_PALETTES.write(self._host.settings, json.dumps(palettes))
|
|
191
|
+
|
|
192
|
+
def active_color_cycle(self):
|
|
193
|
+
"""今のパレットの色。未設定や空なら matplotlib の既定の色の並び。"""
|
|
194
|
+
active_name = app_settings.ACTIVE_COLOR_PALETTE.read(
|
|
195
|
+
self._host.settings, default=ColorPaletteDialog.DEFAULT_PALETTE_NAME)
|
|
196
|
+
if active_name in BUILTIN_PALETTES:
|
|
197
|
+
return BUILTIN_PALETTES[active_name]
|
|
198
|
+
palettes = self.load_palettes()
|
|
199
|
+
if active_name != ColorPaletteDialog.DEFAULT_PALETTE_NAME and palettes.get(active_name):
|
|
200
|
+
return palettes[active_name]
|
|
201
|
+
return default_color_cycle()
|
|
202
|
+
|
|
203
|
+
def manage_palettes(self):
|
|
204
|
+
palettes = self.load_palettes()
|
|
205
|
+
active_name = app_settings.ACTIVE_COLOR_PALETTE.read(
|
|
206
|
+
self._host.settings, default=ColorPaletteDialog.DEFAULT_PALETTE_NAME)
|
|
207
|
+
|
|
208
|
+
dialog = ColorPaletteDialog(palettes, active_name, self._host.parent_widget)
|
|
209
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
210
|
+
new_palettes, new_active_name = dialog.get_result()
|
|
211
|
+
self.save_palettes(new_palettes)
|
|
212
|
+
app_settings.ACTIVE_COLOR_PALETTE.write(self._host.settings, new_active_name)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _named_color_menu_icon(color_name, size=16):
|
|
216
|
+
"""「登録した色を適用」メニューの色見本(色欄のポップアップと同じ描き方)。"""
|
|
217
|
+
from graphica.gui.color_picker_widget import _color_icon
|
|
218
|
+
return _color_icon(color_name, size=size)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""曲線フィット(単発・一括・多峰分離)と、フィット結果の再表示・注釈への焼き込みのメニューの入口。
|
|
2
|
+
|
|
3
|
+
本体は gui/datasets/operations/fitting.py。計算は裏で動き、終わったら結果を足す。
|
|
4
|
+
"""
|
|
5
|
+
from graphica.gui.datasets.operations.fitting import (BATCH_FIT_RUNNER, FIT_RESULT_WINDOW, FIT_RUNNER,
|
|
6
|
+
FITTING_OPERATIONS, MULTI_PEAK_FIT_RUNNER, RUNNER_NAMES,
|
|
7
|
+
burn_operation, stop_runner)
|
|
8
|
+
from graphica.gui.datasets.operations.runner import OperationState, run_operation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FittingController:
|
|
12
|
+
|
|
13
|
+
def __init__(self, host):
|
|
14
|
+
self._host = host
|
|
15
|
+
self._state = OperationState()
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def result_dialog(self):
|
|
19
|
+
"""結果は非モーダルで出し、グラフと見比べられるようにする。"""
|
|
20
|
+
return self._state.result_windows.get(FIT_RESULT_WINDOW)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def fit_runner(self):
|
|
24
|
+
return self._state.runners.get(FIT_RUNNER)
|
|
25
|
+
|
|
26
|
+
@fit_runner.setter
|
|
27
|
+
def fit_runner(self, runner):
|
|
28
|
+
self._state.runners[FIT_RUNNER] = runner
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def batch_fit_runner(self):
|
|
32
|
+
return self._state.runners.get(BATCH_FIT_RUNNER)
|
|
33
|
+
|
|
34
|
+
@batch_fit_runner.setter
|
|
35
|
+
def batch_fit_runner(self, runner):
|
|
36
|
+
self._state.runners[BATCH_FIT_RUNNER] = runner
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def multi_peak_fit_runner(self):
|
|
40
|
+
return self._state.runners.get(MULTI_PEAK_FIT_RUNNER)
|
|
41
|
+
|
|
42
|
+
@multi_peak_fit_runner.setter
|
|
43
|
+
def multi_peak_fit_runner(self, runner):
|
|
44
|
+
self._state.runners[MULTI_PEAK_FIT_RUNNER] = runner
|
|
45
|
+
|
|
46
|
+
def shutdown(self):
|
|
47
|
+
"""タブを閉じる前に、実行中の計算を待って片付ける(curve_fit 自体は中断できない)。"""
|
|
48
|
+
for name in RUNNER_NAMES:
|
|
49
|
+
runner = self._state.runners.get(name)
|
|
50
|
+
if runner is not None:
|
|
51
|
+
stop_runner(runner)
|
|
52
|
+
self._state.runners[name] = None
|
|
53
|
+
|
|
54
|
+
def _run(self, operation):
|
|
55
|
+
run_operation(operation, self._host, self._state)
|
|
56
|
+
|
|
57
|
+
def fit_current_dataset(self):
|
|
58
|
+
self._run(FITTING_OPERATIONS["fit_current_dataset"])
|
|
59
|
+
|
|
60
|
+
def batch_fit_selected(self):
|
|
61
|
+
self._run(FITTING_OPERATIONS["batch_fit_selected"])
|
|
62
|
+
|
|
63
|
+
def multi_peak_fit_current_dataset(self):
|
|
64
|
+
self._run(FITTING_OPERATIONS["multi_peak_fit_current_dataset"])
|
|
65
|
+
|
|
66
|
+
def show_fit_result(self):
|
|
67
|
+
self._run(FITTING_OPERATIONS["show_fit_result"])
|
|
68
|
+
|
|
69
|
+
def burn_fit_result_annotation(self, dataset, fit_result):
|
|
70
|
+
self._run(burn_operation(dataset, fit_result))
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""機能ごとのクラスが本体(PlotterApp の1タブ)に頼むことの窓口。"""
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
|
|
4
|
+
from graphica.core.axis_settings import axis_setting
|
|
5
|
+
from graphica.core.commands import SetAnnotationsCommand
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatasetHost:
|
|
9
|
+
"""機能ごとのクラスが PlotterApp の内部に直接触らないよう、使う操作だけをここに集める。"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, app):
|
|
12
|
+
self._app = app
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def parent_widget(self):
|
|
16
|
+
return self._app
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def undo_stack(self):
|
|
20
|
+
return self._app.undo_stack
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def undo_macro(self, text, enabled=True):
|
|
24
|
+
"""中の操作を Undo 1回分にまとめる(enabled=False なら何もしない)。"""
|
|
25
|
+
if enabled:
|
|
26
|
+
self._app.undo_stack.beginMacro(text)
|
|
27
|
+
try:
|
|
28
|
+
yield
|
|
29
|
+
finally:
|
|
30
|
+
if enabled:
|
|
31
|
+
self._app.undo_stack.endMacro()
|
|
32
|
+
|
|
33
|
+
def current_dataset(self):
|
|
34
|
+
return self._app.current_dataset()
|
|
35
|
+
|
|
36
|
+
def add_derived_dataset(self, dataset, source, description):
|
|
37
|
+
"""source と同じフォルダにデータセットを足して描き直す(Undo できる。今の選択は変えない)。"""
|
|
38
|
+
source_item = self._app.dataset_tree_item(source)
|
|
39
|
+
folder = source_item.parent() if source_item else None
|
|
40
|
+
|
|
41
|
+
def add():
|
|
42
|
+
self._app.dataset_order.append(dataset, folder)
|
|
43
|
+
self._app.redraw()
|
|
44
|
+
|
|
45
|
+
self._app.push_dataset_additions(add, [dataset], description)
|
|
46
|
+
|
|
47
|
+
def add_annotation(self, axis_index, annotation, description):
|
|
48
|
+
self._app.add_annotation(axis_index, annotation, description)
|
|
49
|
+
|
|
50
|
+
def annotations(self, axis_index):
|
|
51
|
+
"""その軸の注釈(リストはコピー)。"""
|
|
52
|
+
return list(axis_setting(self._app.project.all_plot_settings[axis_index], 'annotations'))
|
|
53
|
+
|
|
54
|
+
def add_annotations_to_active_axis(self, annotations, description):
|
|
55
|
+
"""今の軸に注釈をまとめて足す(Undo 1回分)。"""
|
|
56
|
+
project = self._app.project
|
|
57
|
+
index = project.active_axis_index
|
|
58
|
+
if index >= len(project.all_plot_settings):
|
|
59
|
+
return
|
|
60
|
+
old_annotations = self.annotations(index)
|
|
61
|
+
self._app.undo_stack.push(SetAnnotationsCommand(
|
|
62
|
+
project, index, old_annotations, old_annotations + list(annotations),
|
|
63
|
+
self._app.redraw_appearance, description=description))
|
|
64
|
+
|
|
65
|
+
def axis_y_span(self, axis_index):
|
|
66
|
+
"""その軸の今の Y の表示幅。軸が無ければ None。"""
|
|
67
|
+
axes = self._app.canvas.all_axes
|
|
68
|
+
if not 0 <= axis_index < len(axes):
|
|
69
|
+
return None
|
|
70
|
+
y_lo, y_hi = axes[axis_index].get_ylim()
|
|
71
|
+
return y_hi - y_lo
|
|
72
|
+
|
|
73
|
+
def selected_datasets(self):
|
|
74
|
+
return self._app.selected_datasets()
|
|
75
|
+
|
|
76
|
+
def target_folder_for_new_dataset(self):
|
|
77
|
+
"""ツリーで選ばれているのがフォルダならそのフォルダ(新しいデータセットの置き場所)、それ以外は None。"""
|
|
78
|
+
return self._app.target_folder_for_new_dataset()
|
|
79
|
+
|
|
80
|
+
def add_datasets_to_folder(self, datasets, folder, description):
|
|
81
|
+
"""まとめて足してから1回だけ描き直す(1件ずつ描き直すと件数分のフル再描画になる)。Undo 1回で消える。"""
|
|
82
|
+
def add():
|
|
83
|
+
for dataset in datasets:
|
|
84
|
+
self._app.dataset_order.append(dataset, folder)
|
|
85
|
+
self._app.redraw()
|
|
86
|
+
|
|
87
|
+
self._app.push_dataset_additions(add, list(datasets), description)
|
|
88
|
+
|
|
89
|
+
def axis_count(self):
|
|
90
|
+
return len(self._app.project.all_plot_settings)
|
|
91
|
+
|
|
92
|
+
def show_status(self, text, msecs=3000):
|
|
93
|
+
self._app.statusBar().showMessage(text, msecs)
|
|
94
|
+
|
|
95
|
+
def set_fit_button_enabled(self, enabled):
|
|
96
|
+
self._app.fit_curve_button.setEnabled(enabled)
|
|
97
|
+
|
|
98
|
+
def set_multi_peak_fit_button_enabled(self, enabled):
|
|
99
|
+
self._app.multi_peak_fit_button.setEnabled(enabled)
|
|
100
|
+
|
|
101
|
+
def pending_peak_guesses(self):
|
|
102
|
+
"""グラフ上のクリックで置いた多峰フィットの初期値(のコピー)。"""
|
|
103
|
+
return list(self._app.pending_peak_guesses())
|
|
104
|
+
|
|
105
|
+
def finish_peak_placement(self):
|
|
106
|
+
"""置いた初期値を消し、ピーク配置モードを抜ける。"""
|
|
107
|
+
self._app.clear_pending_peak_guesses()
|
|
108
|
+
if getattr(self._app, 'peak_placement_mode_enabled', False):
|
|
109
|
+
self._app.peak_placement_action.setChecked(False)
|
|
110
|
+
self._app.toggle_peak_placement_mode(False)
|
|
111
|
+
|
|
112
|
+
def datasets(self):
|
|
113
|
+
"""タブの全データセット(リストはコピー)。"""
|
|
114
|
+
return list(self._app.project.datasets)
|
|
115
|
+
|
|
116
|
+
def add_dataset(self, dataset, parent_folder=None, select=True):
|
|
117
|
+
"""ツリーと描画に足す(Undo の対象にはしない)。"""
|
|
118
|
+
return self._app.add_dataset(dataset, parent_folder, select=select)
|
|
119
|
+
|
|
120
|
+
def add_dataset_with_undo(self, dataset, parent_folder=None, description="データセットの追加"):
|
|
121
|
+
self._app.add_dataset_with_undo(dataset, parent_folder=parent_folder, description=description)
|
|
122
|
+
|
|
123
|
+
def redraw(self):
|
|
124
|
+
self._app.redraw()
|
|
125
|
+
|
|
126
|
+
def refresh_ui_state(self):
|
|
127
|
+
"""選択中のデータセットに合わせてパネルとメニューの状態を更新する。"""
|
|
128
|
+
self._app.property_panel.update_ui_state()
|
|
129
|
+
|
|
130
|
+
def active_color_cycle(self):
|
|
131
|
+
return self._app.colors.active_color_cycle()
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def project(self):
|
|
135
|
+
"""文書全体。読むためだけに使う(書き換えは host の操作を通す)。"""
|
|
136
|
+
return self._app.project
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def settings(self):
|
|
140
|
+
"""アプリ全体の設定(QSettings)。"""
|
|
141
|
+
return self._app.settings
|
|
142
|
+
|
|
143
|
+
def push_property_change(self, dataset, old_values, new_values, description, skip_if_unchanged=True):
|
|
144
|
+
"""データセットの属性の変更を Undo できる形で積む(skip_if_unchanged なら変化が無いときは何もしない)。"""
|
|
145
|
+
self._app.push_dataset_property_command(dataset, old_values, new_values, description,
|
|
146
|
+
skip_if_unchanged=skip_if_unchanged)
|
|
147
|
+
|
|
148
|
+
def remove_datasets(self, datasets, description=None):
|
|
149
|
+
"""確認なしで Undo できる形で消す。"""
|
|
150
|
+
items = [item for dataset in datasets
|
|
151
|
+
if (item := self._app.dataset_tree_item(dataset)) is not None]
|
|
152
|
+
self._app.remove_dataset_items_with_undo(items, description=description)
|
|
153
|
+
|
|
154
|
+
def sibling_tabs(self):
|
|
155
|
+
"""ほかのタブの (タブの見出し, そのタブの DatasetHost)。タブの窓に入っていなければ空。"""
|
|
156
|
+
from graphica.gui.main_app_window import MainAppWindow # main_app_window がこのパッケージを読み込むので遅らせる
|
|
157
|
+
top = self._app.window()
|
|
158
|
+
if not isinstance(top, MainAppWindow):
|
|
159
|
+
return []
|
|
160
|
+
tabs = top.tab_widget
|
|
161
|
+
return [(tabs.tabText(i), tabs.widget(i).dataset_host)
|
|
162
|
+
for i in range(tabs.count()) if tabs.widget(i) is not self._app]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""データセットの処理の定義(操作の本体)と、その流れを担う実行役。"""
|