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,115 @@
1
+ """処理の履歴(provenance)を日本語の説明にする。履歴パネルと「方法」の文で表記を揃えるため、ここに1つにする。"""
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ from graphica.core.fit_models import fit_type_label
5
+
6
+ if TYPE_CHECKING:
7
+ from graphica.core.dataset import Dataset
8
+ from graphica.models.project import ProjectModel
9
+
10
+
11
+ def describe_operation(provenance: dict[str, Any] | None) -> str:
12
+ """1つの処理を要約した文(履歴パネルの項目にも、方法の文の1文にも使える)。"""
13
+ if not provenance:
14
+ return "不明な操作"
15
+ operation = provenance.get('operation')
16
+ params: dict[str, Any] = provenance.get('params') or {}
17
+
18
+ if operation == 'savgol':
19
+ return (
20
+ f"Savitzky-Golayフィルタ(window={params.get('window_length')}, "
21
+ f"polyorder={params.get('polyorder')}, deriv={params.get('deriv')})"
22
+ )
23
+ if operation and operation.startswith('baseline_'):
24
+ method = operation[len('baseline_'):]
25
+ method_label = {
26
+ 'als': 'ALS法', 'polynomial': '多項式法',
27
+ 'rubberband': 'ラバーバンド法', 'manual': '手動点指定',
28
+ }.get(method, method)
29
+ return f"ベースライン補正({method_label})"
30
+ if operation == 'normalize':
31
+ mode = params.get('mode', '')
32
+ if mode == '特定X値での強度基準':
33
+ return f"規格化(X={params.get('reference_x')}での値基準)"
34
+ return f"規格化({mode})" if mode else "規格化"
35
+ if operation == 'resample':
36
+ return f"共通X格子へのリサンプリング/補間(手法: {params.get('method')})"
37
+ if operation == 'arithmetic':
38
+ error_note = "、誤差を伝播" if params.get('error_propagated') else ""
39
+ return f"データセット間演算({params.get('operation_symbol')}{error_note})"
40
+ if operation == 'mean_sd':
41
+ return f"複数データセットの平均±SD生成({params.get('n_source')}件、手法: {params.get('method')})"
42
+ if operation == 'cumulative_integral':
43
+ method_labels: dict[Any, str] = {'trapezoid': '台形則', 'simpson': 'Simpson則'}
44
+ method_label = method_labels.get(params.get('method'), params.get('method'))
45
+ return f"累積積分({method_label})"
46
+ if operation == 'average_duplicate_x':
47
+ return (
48
+ f"重複X値の平均化({params.get('n_duplicate_groups')}グループ、"
49
+ f"{params.get('n_points_in')}点 → {params.get('n_points_out')}点)"
50
+ )
51
+ if operation == 'xaxis_alignment':
52
+ shift = params.get('shift')
53
+ shift_text = f"{shift:+.4g}" if isinstance(shift, (int, float)) else "不明"
54
+ return f"X軸アライメント(相互相関、シフト量: {shift_text})"
55
+ if operation == 'histogram':
56
+ density_text = "確率密度" if params.get('density') else "度数"
57
+ return f"ヒストグラム(列: {params.get('column')}、{density_text}、ビン: {params.get('bins')})"
58
+ if operation == 'kde':
59
+ return f"カーネル密度推定(列: {params.get('column')}、評価点数: {params.get('n_points')})"
60
+ if operation in ('curve_fit', 'batch_curve_fit'):
61
+ fit_type = fit_type_label(params, '不明')
62
+ r_squared = params.get('r_squared')
63
+ r2_text = f", R²={r_squared:.4f}" if isinstance(r_squared, (int, float)) else ""
64
+ return f"カーブフィット({fit_type}{r2_text})"
65
+ if operation == 'multi_peak_fit':
66
+ return f"多峰分離フィット({params.get('component_type', '不明')} x{params.get('n_components', '?')})"
67
+ if operation == '2d_slice':
68
+ start, end = params.get('start'), params.get('end')
69
+ axis_labels: dict[Any, str] = {'x': 'X軸方向', 'y': 'Y軸方向', 'distance': '斜め方向'}
70
+ axis_label = axis_labels.get(params.get('axis_kind'), params.get('axis_kind'))
71
+ if start and end:
72
+ return (
73
+ f"2Dマップからの1Dスライス抽出({axis_label}、"
74
+ f"始点=({start[0]:.4g}, {start[1]:.4g}), 終点=({end[0]:.4g}, {end[1]:.4g}))"
75
+ )
76
+ return f"2Dマップからの1Dスライス抽出({axis_label})"
77
+ return operation or "不明な操作"
78
+
79
+
80
+ def generate_methods_text(dataset: "Dataset", project: "ProjectModel") -> str:
81
+ """祖先から順に辿って「方法」の文にする。元データか、親が消されていればそこで止める(循環も止める)。"""
82
+ chain = []
83
+ visited = set()
84
+ current: "Dataset | None" = dataset
85
+ while current is not None and current.provenance and current.dataset_id not in visited:
86
+ visited.add(current.dataset_id)
87
+ chain.append(current.provenance)
88
+ source_ids = current.provenance.get('source_dataset_ids') or []
89
+ if len(source_ids) != 1:
90
+ # 親が複数(データセット間の演算など)なら一本の文にならないので、ここで止めて親の名前を並べる
91
+ break
92
+ source_id = source_ids[0]
93
+ current = next((ds for ds in project.datasets if ds.dataset_id == source_id), None)
94
+
95
+ if not chain:
96
+ return f"「{dataset.name}」は処理履歴を持たない元データです。"
97
+
98
+ chain.reverse() # 古い処理から順に
99
+ steps = [describe_operation(prov) for prov in chain]
100
+
101
+ if len(steps) == 1:
102
+ body = steps[0]
103
+ else:
104
+ body = "、続いて".join(steps[:-1]) + f"を行った上で、{steps[-1]}"
105
+
106
+ root_provenance = chain[0]
107
+ root_source_names = root_provenance.get('source_dataset_names') or []
108
+ if len(root_source_names) > 1:
109
+ origin_text = "、".join(f"「{name}」" for name in root_source_names) + "を元データとして"
110
+ elif root_source_names:
111
+ origin_text = f"元データ「{root_source_names[0]}」に対し"
112
+ else:
113
+ origin_text = ""
114
+
115
+ return f"{origin_text}{body}を実施した(出力データセット: 「{dataset.name}」)。"
@@ -0,0 +1,126 @@
1
+ """
2
+ 名前付きの色の登録簿(名前1つに色1つ)。同じ物質をどのプロジェクトでも同じ色で描くためのもの。
3
+ 配色パレット(core/color_palettes.py、順序付きの色のリスト)と混ぜると、パレット管理の
4
+ 「アクティブなパレット」の意味が壊れるので、QSettings のキーから別にしている。
5
+ """
6
+ import json
7
+ import re
8
+ from typing import Any
9
+
10
+ # 並び順がポップアップの表示順なので、辞書ではなく JSON の配列で保存する。
11
+ NAMED_COLORS_SETTINGS_KEY = "named_colors_json"
12
+
13
+ MAX_NAME_LENGTH = 60
14
+ POPUP_LIMIT = 5 # これを超えた分は「すべての登録色...」から選ぶ
15
+
16
+ _HEX_RE = re.compile(r'^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$')
17
+
18
+
19
+ class NamedColorError(ValueError):
20
+ """メッセージはそのまま利用者に表示する。"""
21
+
22
+
23
+ def normalize_color(value: Any) -> str:
24
+ """`#rgb` / `#rrggbb` を小文字の `#rrggbb` にする。QColor を使わないのは Qt 非依存にするため。"""
25
+ text = (value or "").strip()
26
+ if not _HEX_RE.match(text):
27
+ raise NamedColorError(f"色コードが不正です: {value!r}(例: #1f77b4)")
28
+ text = text.lower()
29
+ if len(text) == 4:
30
+ text = "#" + "".join(ch * 2 for ch in text[1:])
31
+ return text
32
+
33
+
34
+ def normalize_name(value: Any) -> str:
35
+ name = (value or "").strip()
36
+ if not name:
37
+ raise NamedColorError("登録名を入力してください。")
38
+ if len(name) > MAX_NAME_LENGTH:
39
+ raise NamedColorError(f"登録名が長すぎます({MAX_NAME_LENGTH}文字まで)。")
40
+ return name
41
+
42
+
43
+ def load_named_colors(settings: Any) -> list[dict[str, str]]:
44
+ """壊れたエントリは1件ずつ捨てる。設定が壊れても起動できなくなるよりは、登録が一部消える方がよい。"""
45
+ raw = settings.value(NAMED_COLORS_SETTINGS_KEY, "")
46
+ if not raw:
47
+ return []
48
+ try:
49
+ data = json.loads(raw) if isinstance(raw, str) else raw
50
+ except (ValueError, TypeError):
51
+ return []
52
+ if not isinstance(data, list):
53
+ return []
54
+
55
+ entries = []
56
+ seen = set()
57
+ for item in data:
58
+ if not isinstance(item, dict):
59
+ continue
60
+ try:
61
+ name = normalize_name(item.get("name"))
62
+ color = normalize_color(item.get("color"))
63
+ except NamedColorError:
64
+ continue
65
+ if name in seen:
66
+ continue
67
+ seen.add(name)
68
+ entries.append({"name": name, "color": color})
69
+ return entries
70
+
71
+
72
+ def save_named_colors(settings: Any, entries: list[dict[str, str]]) -> None:
73
+ payload = [{"name": e["name"], "color": e["color"]} for e in entries]
74
+ settings.setValue(NAMED_COLORS_SETTINGS_KEY, json.dumps(payload, ensure_ascii=False))
75
+
76
+
77
+ def find_index_by_name(entries: list[dict[str, str]], name: str) -> int:
78
+ target = (name or "").strip()
79
+ for index, entry in enumerate(entries):
80
+ if entry["name"] == target:
81
+ return index
82
+ return -1
83
+
84
+
85
+ # 編集関数はどれも、引数のリストを変えずに新しいリストを返す。
86
+
87
+
88
+ def add_named_color(entries: list[dict[str, str]], name: object, color: object) -> list[dict[str, str]]:
89
+ # 名前と色が1対1でないと、名前から色が決まらないので重複は禁止。
90
+ name = normalize_name(name)
91
+ color = normalize_color(color)
92
+ if find_index_by_name(entries, name) != -1:
93
+ raise NamedColorError(f"「{name}」は既に登録されています。")
94
+ return list(entries) + [{"name": name, "color": color}]
95
+
96
+
97
+ def update_named_color(entries: list[dict[str, str]], index: int, name: object, color: object) -> list[dict[str, str]]:
98
+ if not 0 <= index < len(entries):
99
+ raise NamedColorError("編集対象が選択されていません。")
100
+ name = normalize_name(name)
101
+ color = normalize_color(color)
102
+ existing = find_index_by_name(entries, name)
103
+ if existing != -1 and existing != index:
104
+ raise NamedColorError(f"「{name}」は既に登録されています。")
105
+ updated = list(entries)
106
+ updated[index] = {"name": name, "color": color}
107
+ return updated
108
+
109
+
110
+ def remove_named_color(entries: list[dict[str, str]], index: int) -> list[dict[str, str]]:
111
+ if not 0 <= index < len(entries):
112
+ raise NamedColorError("削除対象が選択されていません。")
113
+ updated = list(entries)
114
+ del updated[index]
115
+ return updated
116
+
117
+
118
+ def move_named_color(entries: list[dict[str, str]], index: int, offset: int) -> list[dict[str, str]]:
119
+ if not 0 <= index < len(entries):
120
+ raise NamedColorError("移動対象が選択されていません。")
121
+ new_index = index + offset
122
+ if not 0 <= new_index < len(entries):
123
+ return list(entries)
124
+ updated = list(entries)
125
+ updated.insert(new_index, updated.pop(index))
126
+ return updated