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.
Files changed (181) hide show
  1. graphica/Graphica.ico +0 -0
  2. graphica/__init__.py +1 -0
  3. graphica/__main__.py +111 -0
  4. graphica/assets/__init__.py +0 -0
  5. graphica/assets/icons/__init__.py +0 -0
  6. graphica/assets/icons/arrow-right.svg +21 -0
  7. graphica/assets/icons/bold.svg +20 -0
  8. graphica/assets/icons/calculator.svg +26 -0
  9. graphica/assets/icons/chart-histogram.svg +24 -0
  10. graphica/assets/icons/chart-line.svg +20 -0
  11. graphica/assets/icons/chevron-down.svg +19 -0
  12. graphica/assets/icons/chevron-right.svg +19 -0
  13. graphica/assets/icons/color-swatch.svg +22 -0
  14. graphica/assets/icons/column-insert-right.svg +21 -0
  15. graphica/assets/icons/column-remove.svg +21 -0
  16. graphica/assets/icons/copy.svg +20 -0
  17. graphica/assets/icons/download.svg +21 -0
  18. graphica/assets/icons/edit.svg +21 -0
  19. graphica/assets/icons/eye-off.svg +21 -0
  20. graphica/assets/icons/eye.svg +20 -0
  21. graphica/assets/icons/file-plus.svg +22 -0
  22. graphica/assets/icons/folder-plus.svg +21 -0
  23. graphica/assets/icons/folder.svg +19 -0
  24. graphica/assets/icons/highlight.svg +22 -0
  25. graphica/assets/icons/history.svg +20 -0
  26. graphica/assets/icons/italic.svg +21 -0
  27. graphica/assets/icons/layout-grid.svg +22 -0
  28. graphica/assets/icons/math-function.svg +22 -0
  29. graphica/assets/icons/message-2.svg +21 -0
  30. graphica/assets/icons/mountain.svg +20 -0
  31. graphica/assets/icons/palette.svg +22 -0
  32. graphica/assets/icons/pointer.svg +19 -0
  33. graphica/assets/icons/refresh.svg +20 -0
  34. graphica/assets/icons/row-insert-bottom.svg +21 -0
  35. graphica/assets/icons/row-remove.svg +21 -0
  36. graphica/assets/icons/search.svg +20 -0
  37. graphica/assets/icons/select-all.svg +35 -0
  38. graphica/assets/icons/subscript.svg +20 -0
  39. graphica/assets/icons/superscript.svg +20 -0
  40. graphica/assets/icons/table.svg +21 -0
  41. graphica/assets/icons/trash.svg +23 -0
  42. graphica/assets/icons/typography.svg +23 -0
  43. graphica/assets/icons/x.svg +20 -0
  44. graphica/core/__init__.py +0 -0
  45. graphica/core/analysis.py +1278 -0
  46. graphica/core/app_paths.py +35 -0
  47. graphica/core/axis_settings.py +128 -0
  48. graphica/core/caption_export.py +47 -0
  49. graphica/core/color_palettes.py +56 -0
  50. graphica/core/commands.py +226 -0
  51. graphica/core/cvd_simulation.py +49 -0
  52. graphica/core/dataset.py +443 -0
  53. graphica/core/diagnostics.py +105 -0
  54. graphica/core/excel_utils.py +58 -0
  55. graphica/core/fit_models.py +379 -0
  56. graphica/core/grid_data.py +128 -0
  57. graphica/core/i18n.py +44 -0
  58. graphica/core/json_utils.py +27 -0
  59. graphica/core/label_utils.py +21 -0
  60. graphica/core/methods_text.py +115 -0
  61. graphica/core/named_colors.py +126 -0
  62. graphica/core/plugin_api.py +515 -0
  63. graphica/core/plugin_context.py +167 -0
  64. graphica/core/plugin_install.py +82 -0
  65. graphica/core/plugin_manifest.py +67 -0
  66. graphica/core/plugin_testing.py +213 -0
  67. graphica/core/plugin_types.py +118 -0
  68. graphica/core/provenance.py +19 -0
  69. graphica/core/report_export.py +51 -0
  70. graphica/core/safe_eval.py +194 -0
  71. graphica/core/script_export.py +272 -0
  72. graphica/core/translations_en.py +530 -0
  73. graphica/core/unit_conversion.py +51 -0
  74. graphica/core/update_check.py +43 -0
  75. graphica/core/version.py +14 -0
  76. graphica/gui/__init__.py +0 -0
  77. graphica/gui/app_settings.py +119 -0
  78. graphica/gui/axis_bindings.py +173 -0
  79. graphica/gui/binding.py +135 -0
  80. graphica/gui/builders/__init__.py +1 -0
  81. graphica/gui/builders/axis_panel.py +431 -0
  82. graphica/gui/builders/canvas_area.py +176 -0
  83. graphica/gui/builders/common.py +120 -0
  84. graphica/gui/builders/dataset_panel.py +433 -0
  85. graphica/gui/builders/property_sections.py +208 -0
  86. graphica/gui/canvas.py +519 -0
  87. graphica/gui/color_history.py +47 -0
  88. graphica/gui/color_picker_widget.py +198 -0
  89. graphica/gui/crash_handler.py +77 -0
  90. graphica/gui/cvd_preview.py +23 -0
  91. graphica/gui/data_editor.py +761 -0
  92. graphica/gui/data_import_flow.py +355 -0
  93. graphica/gui/dataset_bindings.py +107 -0
  94. graphica/gui/dataset_style_icon.py +80 -0
  95. graphica/gui/datasets/__init__.py +1 -0
  96. graphica/gui/datasets/actions_menu.py +114 -0
  97. graphica/gui/datasets/colors.py +218 -0
  98. graphica/gui/datasets/fitting.py +70 -0
  99. graphica/gui/datasets/host.py +162 -0
  100. graphica/gui/datasets/operations/__init__.py +1 -0
  101. graphica/gui/datasets/operations/fitting.py +468 -0
  102. graphica/gui/datasets/operations/peaks.py +109 -0
  103. graphica/gui/datasets/operations/processing.py +659 -0
  104. graphica/gui/datasets/operations/runner.py +148 -0
  105. graphica/gui/datasets/operations/transfer.py +227 -0
  106. graphica/gui/datasets/order.py +310 -0
  107. graphica/gui/datasets/overlays.py +74 -0
  108. graphica/gui/datasets/peaks.py +21 -0
  109. graphica/gui/datasets/plugin_runs.py +99 -0
  110. graphica/gui/datasets/processing.py +68 -0
  111. graphica/gui/datasets/property_panel.py +538 -0
  112. graphica/gui/datasets/transfer.py +36 -0
  113. graphica/gui/detached_canvas_window.py +22 -0
  114. graphica/gui/dialogs/__init__.py +137 -0
  115. graphica/gui/dialogs/analysis.py +1322 -0
  116. graphica/gui/dialogs/app.py +1015 -0
  117. graphica/gui/dialogs/appearance.py +851 -0
  118. graphica/gui/dialogs/data_edit.py +577 -0
  119. graphica/gui/dialogs/data_import.py +679 -0
  120. graphica/gui/dialogs/export.py +433 -0
  121. graphica/gui/dock_layout.py +176 -0
  122. graphica/gui/export_preview_panel.py +321 -0
  123. graphica/gui/export_settings.py +16 -0
  124. graphica/gui/file_association.py +97 -0
  125. graphica/gui/icon_utils.py +44 -0
  126. graphica/gui/main_app_window.py +210 -0
  127. graphica/gui/main_window.py +1151 -0
  128. graphica/gui/mathtext_preview.py +168 -0
  129. graphica/gui/menu_bar.py +320 -0
  130. graphica/gui/minimap_widget.py +151 -0
  131. graphica/gui/mixins/__init__.py +0 -0
  132. graphica/gui/mixins/export_mixin.py +520 -0
  133. graphica/gui/mixins/help_mixin.py +127 -0
  134. graphica/gui/mixins/project_io_mixin.py +289 -0
  135. graphica/gui/mixins/quick_access_mixin.py +194 -0
  136. graphica/gui/mixins/ui_setup_mixin.py +206 -0
  137. graphica/gui/notify.py +61 -0
  138. graphica/gui/panels/__init__.py +43 -0
  139. graphica/gui/panels/axis_settings.py +605 -0
  140. graphica/gui/panels/dataset_tree.py +424 -0
  141. graphica/gui/plot_type_drawers.py +131 -0
  142. graphica/gui/plugin_context.py +119 -0
  143. graphica/gui/project_files.py +361 -0
  144. graphica/gui/provenance_panel.py +65 -0
  145. graphica/gui/rendering/__init__.py +1 -0
  146. graphica/gui/rendering/annotations.py +177 -0
  147. graphica/gui/rendering/appearance.py +368 -0
  148. graphica/gui/rendering/common.py +316 -0
  149. graphica/gui/rendering/data_1d.py +376 -0
  150. graphica/gui/rendering/data_2d.py +66 -0
  151. graphica/gui/residual_panel.py +62 -0
  152. graphica/gui/resources.py +16 -0
  153. graphica/gui/single_instance.py +141 -0
  154. graphica/gui/splash.py +83 -0
  155. graphica/gui/task_runner.py +41 -0
  156. graphica/gui/theme.py +864 -0
  157. graphica/gui/tools/__init__.py +56 -0
  158. graphica/gui/tools/annotation.py +275 -0
  159. graphica/gui/tools/cursor.py +259 -0
  160. graphica/gui/tools/layout_edit.py +285 -0
  161. graphica/gui/tools/manager.py +78 -0
  162. graphica/gui/tools/peak_placement.py +132 -0
  163. graphica/gui/tools/range_select.py +191 -0
  164. graphica/gui/tools/region_highlight.py +222 -0
  165. graphica/gui/tools/slice_extraction.py +200 -0
  166. graphica/gui/widget_translation.py +46 -0
  167. graphica/gui/workers.py +280 -0
  168. graphica/models/__init__.py +0 -0
  169. graphica/models/project.py +197 -0
  170. graphica/plugin/__init__.py +20 -0
  171. graphica/plugin/testing.py +57 -0
  172. graphica/sample_data/__init__.py +0 -0
  173. graphica/sample_data/cooling_curve_sample.csv +42 -0
  174. graphica/ui_main_window.py +581 -0
  175. graphica_plot-2.0.0.dist-info/METADATA +317 -0
  176. graphica_plot-2.0.0.dist-info/RECORD +181 -0
  177. graphica_plot-2.0.0.dist-info/WHEEL +5 -0
  178. graphica_plot-2.0.0.dist-info/entry_points.txt +2 -0
  179. graphica_plot-2.0.0.dist-info/licenses/LICENSE +30 -0
  180. graphica_plot-2.0.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +91 -0
  181. graphica_plot-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
1
+ """データセットのプロパティ欄の 7 つの節(折りたたみ・開閉の保存・見出しの出し入れ・ラベル列の幅)。"""
2
+ import json
3
+ from PySide6.QtCore import Qt
4
+ from PySide6.QtWidgets import QComboBox, QFormLayout, QLabel, QSizePolicy, QToolButton, QVBoxLayout, QWidget
5
+ from graphica.core.i18n import tr
6
+ from graphica.gui import app_settings
7
+ from graphica.gui.builders.common import DATASET_PROPERTY_SECTIONS, PLOT_TYPE_COMBO_MIN_CHARS, _svg_icon
8
+
9
+
10
+ def wrap_in_collapsible_section(app, group_box, title):
11
+ """group_box の外に開閉ボタンを付け、グループボックスごと出し入れする。見出しはボタンにだけ出す。"""
12
+ group_box.setTitle("")
13
+ # theme.py はグループボックスのタイトル用に上の余白を取る。タイトルを空にしたので、それを 0 にする目印
14
+ group_box.setProperty("collapsibleBody", True)
15
+
16
+ wrapper = QWidget()
17
+ wrapper_layout = QVBoxLayout(wrapper)
18
+ wrapper_layout.setContentsMargins(0, 0, 0, 0)
19
+ wrapper_layout.setSpacing(2)
20
+
21
+ toggle_button = QToolButton()
22
+ toggle_button.setText(title)
23
+ toggle_button.setCheckable(True)
24
+ toggle_button.setChecked(True)
25
+ toggle_button.setIcon(_svg_icon("chevron-down", size=14))
26
+ toggle_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
27
+ toggle_button.setObjectName("collapsible_section_toggle")
28
+ toggle_button.setCursor(Qt.CursorShape.PointingHandCursor)
29
+ toggle_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
30
+
31
+ def _on_toggled(checked, box=group_box, btn=toggle_button):
32
+ box.setVisible(checked)
33
+ btn.setIcon(_svg_icon("chevron-down" if checked else "chevron-right", size=14))
34
+
35
+ toggle_button.toggled.connect(_on_toggled)
36
+ # ダークモードの切り替えでアイコンを作り直すので、ボタンを集めておく
37
+ if not hasattr(app, '_collapsible_toggle_buttons'):
38
+ app._collapsible_toggle_buttons = []
39
+ app._collapsible_toggle_buttons.append(toggle_button)
40
+
41
+ wrapper_layout.addWidget(toggle_button)
42
+ wrapper_layout.addWidget(group_box)
43
+ return wrapper
44
+
45
+
46
+ def build_dataset_property_sections(app):
47
+ """データセットのプロパティ欄を DATASET_PROPERTY_SECTIONS の節に分け、節ごとに QFormLayout を持たせる。
48
+
49
+ 列幅の広がりが節の中に閉じる。Designer の行は takeRow() で移す(removeRow() はウィジェットごと破棄する)。
50
+ 平滑化のチェックだけは「平滑化の手法」の直前に置くので、ここでは移さない。
51
+ """
52
+ app._prop_sections = {}
53
+
54
+ container = QWidget()
55
+ container_layout = QVBoxLayout(container)
56
+ container_layout.setContentsMargins(0, 0, 0, 0)
57
+ container_layout.setSpacing(0)
58
+
59
+ collapsed = app._load_collapsed_property_sections()
60
+
61
+ for key, title in DATASET_PROPERTY_SECTIONS:
62
+ body = QWidget()
63
+ form = QFormLayout(body)
64
+ # 親の見出し(4px)→ 子の見出し(12px)→ 中身、の階段になるよう字下げする
65
+ form.setContentsMargins(24, 2, 0, 6)
66
+ form.setSpacing(6)
67
+
68
+ toggle_button = QToolButton()
69
+ toggle_button.setText(tr(title))
70
+ toggle_button.setCheckable(True)
71
+ toggle_button.setIcon(_svg_icon("chevron-down", size=13))
72
+ toggle_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
73
+ # 上の2つの開閉ボタンとは別の名前(theme.py で控えめに描き、2つだけであることをテストが確かめる)
74
+ toggle_button.setObjectName("property_subsection_toggle")
75
+ toggle_button.setCursor(Qt.CursorShape.PointingHandCursor)
76
+ toggle_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
77
+ # QToolButton は文字幅なので、区切りの罫線が見出しの下にしか引かれない
78
+ toggle_button.setSizePolicy(
79
+ QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
80
+ toggle_button.setChecked(key not in collapsed)
81
+ # 1本目は親の見出しのすぐ下なので、罫線が二重に見える
82
+ if key == DATASET_PROPERTY_SECTIONS[0][0]:
83
+ toggle_button.setProperty("firstSection", True)
84
+
85
+ section = QWidget()
86
+ section_layout = QVBoxLayout(section)
87
+ section_layout.setContentsMargins(0, 0, 0, 0)
88
+ section_layout.setSpacing(0)
89
+ section_layout.addWidget(toggle_button)
90
+ section_layout.addWidget(body)
91
+ body.setVisible(toggle_button.isChecked())
92
+
93
+ toggle_button.toggled.connect(
94
+ lambda checked, k=key: app._on_property_section_toggled(k, checked))
95
+
96
+ container_layout.addWidget(section)
97
+ app._prop_sections[key] = {
98
+ 'form': form, 'body': body, 'toggle': toggle_button, 'section': section,
99
+ }
100
+
101
+ # 空の formLayout_4 は生成物の構造なので残し、余白だけ潰す
102
+ for row in range(app.ui.formLayout_4.rowCount() - 1, -1, -1):
103
+ app.ui.formLayout_4.takeRow(row)
104
+ app.ui.formLayout_4.setContentsMargins(0, 0, 0, 0)
105
+ app.ui.formLayout_4.setSpacing(0)
106
+ app.ui.gridLayout_4.setContentsMargins(0, 0, 0, 0)
107
+ app.ui.gridLayout_4.addWidget(container, 1, 0, 1, 1)
108
+ app._dataset_property_sections_container = container
109
+
110
+ # 無効な親の下の子は有効にできないので、グループボックスは常に有効にして中身だけ無効にする
111
+ # (でないと、データセットが無い間は節を開閉できない)
112
+ app.ui.properties_groupbox.setEnabled(True)
113
+ app._set_dataset_property_fields_enabled(False)
114
+
115
+ style_form = app._prop_form('style')
116
+ style_form.addRow(app.ui.legend_name_label, app.ui.legend_name_edit)
117
+ style_form.addRow(app.ui.plot_type_label, app.ui.plot_type_combo)
118
+ style_form.addRow(app.ui.color_label, app.color_picker_widget)
119
+ style_form.addRow(app.ui.linestyle_label, app.ui.linestyle_combo)
120
+ style_form.addRow(app.ui.linewidth_label, app.ui.linewidth_spinbox)
121
+ style_form.addRow(app.ui.marker_label, app.ui.marker_combo)
122
+ style_form.addRow(app.ui.makersize_label, app.ui.markersize_spinbox)
123
+
124
+ app.ui.plot_type_combo.setSizeAdjustPolicy(
125
+ QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
126
+ app.ui.plot_type_combo.setMinimumContentsLength(PLOT_TYPE_COMBO_MIN_CHARS)
127
+
128
+
129
+ def prop_form(app, section_key):
130
+ return app._prop_sections[section_key]['form']
131
+
132
+
133
+ def align_form_label_columns(app, forms):
134
+ """ラベルの列幅を複数の QFormLayout で揃え、入力欄の左端を合わせる。決めた幅(px)を返す。
135
+
136
+ 隠れているラベルも数える(QFormLayout は隠れたものを外すので、出た瞬間に列幅が変わって欄がずれる)。
137
+ """
138
+ labels = []
139
+ widest = 0
140
+ for form in forms:
141
+ for row in range(form.rowCount()):
142
+ item = form.itemAt(row, QFormLayout.ItemRole.LabelRole)
143
+ if item is None or not isinstance(item.widget(), QLabel):
144
+ continue
145
+ label = item.widget()
146
+ labels.append(label)
147
+ widest = max(widest, label.sizeHint().width())
148
+
149
+ for label in labels:
150
+ label.setMinimumWidth(widest)
151
+ return widest
152
+
153
+
154
+ def set_dataset_property_fields_enabled(app, enabled):
155
+ """選択が無いときは無効にする。無効にするのは節の中身だけ(見出しまで無効にすると開閉できない)。"""
156
+ for entry in getattr(app, '_prop_sections', {}).values():
157
+ entry['body'].setEnabled(enabled)
158
+
159
+
160
+ def load_collapsed_property_sections(app):
161
+ raw = app_settings.DATASET_PROPERTY_COLLAPSED_SECTIONS.read(app.settings)
162
+ try:
163
+ keys = json.loads(raw) if isinstance(raw, str) else list(raw)
164
+ except (ValueError, TypeError):
165
+ return set()
166
+ valid = {key for key, _ in DATASET_PROPERTY_SECTIONS}
167
+ return {k for k in keys if k in valid}
168
+
169
+
170
+ def on_property_section_toggled(app, section_key, checked):
171
+ """開閉し、閉じている節を QSettings に書き戻す(一度閉じたものは次の起動でも閉じたまま)。"""
172
+ entry = app._prop_sections.get(section_key)
173
+ if entry is None:
174
+ return
175
+ entry['body'].setVisible(checked)
176
+ entry['toggle'].setIcon(
177
+ _svg_icon("chevron-down" if checked else "chevron-right", size=13))
178
+
179
+ collapsed = sorted(
180
+ key for key, _ in DATASET_PROPERTY_SECTIONS
181
+ if not app._prop_sections[key]['toggle'].isChecked()
182
+ )
183
+ app_settings.DATASET_PROPERTY_COLLAPSED_SECTIONS.write(app.settings, json.dumps(collapsed))
184
+
185
+
186
+ def update_property_section_visibility(app):
187
+ """中の行が全部隠れた節は見出しごと隠す。"""
188
+ sections = getattr(app, '_prop_sections', None)
189
+ if not sections:
190
+ return
191
+ for key, _ in DATASET_PROPERTY_SECTIONS:
192
+ entry = sections.get(key)
193
+ if entry is None:
194
+ continue
195
+ form = entry['form']
196
+ has_visible = False
197
+ for row in range(form.rowCount()):
198
+ for role in (QFormLayout.ItemRole.LabelRole,
199
+ QFormLayout.ItemRole.FieldRole,
200
+ QFormLayout.ItemRole.SpanningRole):
201
+ item = form.itemAt(row, role)
202
+ if item is not None and item.widget() is not None \
203
+ and not item.widget().isHidden():
204
+ has_visible = True
205
+ break
206
+ if has_visible:
207
+ break
208
+ entry['section'].setVisible(has_visible)