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,316 @@
|
|
|
1
|
+
"""描画で共有する定数と補助(色・目盛り・ウォーターフォールの並び・統計ラベルなど)。canvas からも同じ名前で読める。"""
|
|
2
|
+
import logging
|
|
3
|
+
import matplotlib.ticker as ticker
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from graphica.core.analysis import sample_standard_deviation
|
|
7
|
+
from graphica.core.axis_settings import AXIS_SETTING_DEFAULTS, axis_setting
|
|
8
|
+
from graphica.gui.theme import DARK_TOKENS, LIGHT_TOKENS
|
|
9
|
+
from typing import NamedTuple
|
|
10
|
+
|
|
11
|
+
# 注釈を「まだ一度も描いていない」を表す番兵。None だとキーが None のときに一致してしまう
|
|
12
|
+
_NO_KEY = object()
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# 目盛りの本数の上限(細かすぎる間隔で描画が固まらないように)。matplotlib の MAXTICKS=1000 に丸め誤差で届かない値
|
|
17
|
+
MAX_TICKS_PER_AXIS = 500
|
|
18
|
+
|
|
19
|
+
# ウォーターフォールのトレースと背景の zorder の範囲。目盛(約2.01)と枠線(2.5)より下に収めて、隠さないようにする
|
|
20
|
+
WATERFALL_ZORDER_BASE = 0.1
|
|
21
|
+
WATERFALL_ZORDER_TOP = 1.9
|
|
22
|
+
|
|
23
|
+
# 立体風の縮小の下限。段が多いと振幅が潰れる・反転するので 0.2 倍で止める
|
|
24
|
+
WATERFALL_DEPTH_SHRINK_MIN_SCALE = 0.2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# 軸の位置(図に対する割合)がこれより動かなければ収まったとみなす。1500 画素の図で 0.0002 画素
|
|
28
|
+
_LAYOUT_SETTLED_TOLERANCE = 1e-7
|
|
29
|
+
_LAYOUT_MAX_PASSES = 15
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fit_tight_layout(fig):
|
|
33
|
+
"""tight_layout を、軸の位置が動かなくなるまで繰り返す。
|
|
34
|
+
|
|
35
|
+
tight_layout は今の配置から計算し直すので、注釈や目盛りの文字がはみ出す図では 1 回で収まらず、
|
|
36
|
+
描くたびに少しずつ動く(描いた回数で見た目が変わる)。例外はそのまま上げる。
|
|
37
|
+
"""
|
|
38
|
+
def positions():
|
|
39
|
+
return [bound for ax in fig.axes for bound in ax.get_position().bounds]
|
|
40
|
+
|
|
41
|
+
before = positions()
|
|
42
|
+
for _ in range(_LAYOUT_MAX_PASSES):
|
|
43
|
+
fig.tight_layout()
|
|
44
|
+
after = positions()
|
|
45
|
+
if len(after) == len(before) and all(abs(a - b) <= _LAYOUT_SETTLED_TOLERANCE
|
|
46
|
+
for a, b in zip(after, before)):
|
|
47
|
+
return
|
|
48
|
+
before = after
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _waterfall_depth_scale(w_idx, enabled, shrink_ratio):
|
|
52
|
+
"""段 w_idx のトレースの Y に掛ける倍率。奥ほど縮めて立体風にする(無効なら 1.0)。"""
|
|
53
|
+
if not enabled:
|
|
54
|
+
return 1.0
|
|
55
|
+
return max(WATERFALL_DEPTH_SHRINK_MIN_SCALE, 1.0 - shrink_ratio * w_idx)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _WaterfallLayout(NamedTuple):
|
|
59
|
+
index: dict # dataset_id -> 段(0 が一番手前)
|
|
60
|
+
count: int
|
|
61
|
+
baseline: float # 奥のトレースを隠す背景を敷く下端
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _waterfall_layout(datasets):
|
|
65
|
+
"""
|
|
66
|
+
ウォーターフォールを有効にしたデータセットに、並び順で段を振る。非表示のものも数に入れる
|
|
67
|
+
(隠しても後ろの段が繰り上がって図が変わらないように)。下端は、ずらした後の全トレースの最小値から少し下。
|
|
68
|
+
"""
|
|
69
|
+
waterfall_datasets = [ds for ds in datasets if ds.waterfall_enabled]
|
|
70
|
+
baseline = 0.0
|
|
71
|
+
shifted_mins, shifted_maxs = [], []
|
|
72
|
+
for i, wds in enumerate(waterfall_datasets):
|
|
73
|
+
if len(wds.y_data) == 0:
|
|
74
|
+
continue
|
|
75
|
+
depth_scale = _waterfall_depth_scale(
|
|
76
|
+
i, wds.waterfall_depth_shrink_enabled, wds.waterfall_depth_shrink_ratio)
|
|
77
|
+
y_shift = i * wds.waterfall_offset_y
|
|
78
|
+
shifted_mins.append(float(np.nanmin(wds.y_data)) * depth_scale + y_shift)
|
|
79
|
+
shifted_maxs.append(float(np.nanmax(wds.y_data)) * depth_scale + y_shift)
|
|
80
|
+
if shifted_mins:
|
|
81
|
+
y_min_all, y_max_all = min(shifted_mins), max(shifted_maxs)
|
|
82
|
+
margin = (y_max_all - y_min_all) * 0.05 if y_max_all > y_min_all else 1.0
|
|
83
|
+
baseline = y_min_all - margin
|
|
84
|
+
return _WaterfallLayout(
|
|
85
|
+
{ds.dataset_id: i for i, ds in enumerate(waterfall_datasets)}, len(waterfall_datasets), baseline)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _AxisStyle(NamedTuple):
|
|
89
|
+
tick_font: dict
|
|
90
|
+
label_font: dict
|
|
91
|
+
tick_color: str
|
|
92
|
+
label_color: str
|
|
93
|
+
spine_width: float
|
|
94
|
+
spine_color: str
|
|
95
|
+
tick_width: float
|
|
96
|
+
major_tick_length: float
|
|
97
|
+
minor_tick_length: float
|
|
98
|
+
|
|
99
|
+
# 対数軸の補助目盛りの設定値 -> LogLocator の subs(補助目盛りを打つ仮数)。'auto' は表示の桁数に応じて間引く
|
|
100
|
+
_LOG_MINOR_SUBS_PRESETS = {
|
|
101
|
+
'auto': 'auto',
|
|
102
|
+
'all': (2, 3, 4, 5, 6, 7, 8, 9),
|
|
103
|
+
'few': (2, 5),
|
|
104
|
+
'one': (5,),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# 矢印注釈の形 -> matplotlib の arrowstyle。キーの無い古い注釈は 'single'
|
|
108
|
+
_ARROW_STYLE_MAP = {
|
|
109
|
+
'single': '->',
|
|
110
|
+
'double': '<->',
|
|
111
|
+
'bracket': ']-[',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# 拡大図の置き場所 -> 軸の中での左下の位置(軸に対する割合)
|
|
115
|
+
_INSET_CORNER_ORIGINS = {
|
|
116
|
+
'右上': (0.55, 0.55),
|
|
117
|
+
'左上': (0.05, 0.55),
|
|
118
|
+
'右下': (0.55, 0.05),
|
|
119
|
+
'左下': (0.05, 0.05),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# 'Line' の点がこれを超えたら、表示では LTTB で約 TARGET 点に間引く(線の形を保つ方法なので、点の疎密が情報の散布図には使わない)
|
|
123
|
+
LTTB_DOWNSAMPLE_THRESHOLD = 20000
|
|
124
|
+
LTTB_DOWNSAMPLE_TARGET_POINTS = 3000
|
|
125
|
+
|
|
126
|
+
# 2Dマップの1軸あたりの点数の上限。超えたら均等に間引く(エクスポートにも効く)
|
|
127
|
+
GRID_2D_MAX_DISPLAY_POINTS_PER_AXIS = 500
|
|
128
|
+
|
|
129
|
+
# 領域ハイライトに色・透明度が無いときの値。region_highlight_mixin.py が書き込む既定値と揃えておく
|
|
130
|
+
REGION_HIGHLIGHT_DEFAULT_COLOR = '#F2A72B'
|
|
131
|
+
REGION_HIGHLIGHT_DEFAULT_ALPHA = 0.18
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _apply_legend_order(lines, labels, order):
|
|
135
|
+
"""凡例を order(ラベルの並び)の順にする。order に無いラベルは元の順のまま最後に付ける。"""
|
|
136
|
+
if not order:
|
|
137
|
+
return lines, labels
|
|
138
|
+
order_index = {name: i for i, name in enumerate(order)}
|
|
139
|
+
indices = sorted(
|
|
140
|
+
range(len(labels)),
|
|
141
|
+
key=lambda i: (0, order_index[labels[i]]) if labels[i] in order_index else (1, i)
|
|
142
|
+
)
|
|
143
|
+
return [lines[i] for i in indices], [labels[i] for i in indices]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _apply_nan_policy(x_data, y_data, policy):
|
|
147
|
+
"""
|
|
148
|
+
欠損値の方針を描く直前の点列に当てる(データ自体は変えない)。
|
|
149
|
+
'gap' は何もしない(matplotlib が NaN で線を切る)。'ffill' は直前の値で埋める(先頭の NaN は残る)。
|
|
150
|
+
'drop' は X か Y が欠けた点を除いて前後をつなぐ。知らない値は 'gap' と同じ。
|
|
151
|
+
"""
|
|
152
|
+
if policy == 'ffill':
|
|
153
|
+
return (
|
|
154
|
+
pd.Series(x_data).ffill().to_numpy(),
|
|
155
|
+
pd.Series(y_data).ffill().to_numpy(),
|
|
156
|
+
)
|
|
157
|
+
if policy == 'drop':
|
|
158
|
+
# np.isnan は日時(datetime64)を扱えないので pandas で判定する
|
|
159
|
+
x_series = pd.Series(x_data)
|
|
160
|
+
y_series = pd.Series(y_data)
|
|
161
|
+
valid = (~x_series.isna()) & (~y_series.isna())
|
|
162
|
+
return x_series[valid].to_numpy(), y_series[valid].to_numpy()
|
|
163
|
+
return x_data, y_data
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# 統計値ラベルの見出し
|
|
167
|
+
STAT_LABEL_TITLES = {
|
|
168
|
+
'r_squared': 'R²',
|
|
169
|
+
'mean': '平均',
|
|
170
|
+
'std': '標準偏差',
|
|
171
|
+
'max': '最大値',
|
|
172
|
+
'min': '最小値',
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _legend_position_from_settings(settings):
|
|
177
|
+
"""
|
|
178
|
+
settings['legend_position'](ドラッグで動かした凡例の位置、[x, y])を
|
|
179
|
+
legend の loc に渡せるタプルにする。未設定・壊れた値は None。
|
|
180
|
+
"""
|
|
181
|
+
position = axis_setting(settings, 'legend_position')
|
|
182
|
+
if not isinstance(position, (list, tuple)) or len(position) != 2:
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
x, y = float(position[0]), float(position[1])
|
|
186
|
+
except (TypeError, ValueError):
|
|
187
|
+
return None
|
|
188
|
+
if not (np.isfinite(x) and np.isfinite(y)):
|
|
189
|
+
return None
|
|
190
|
+
return (x, y)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _compute_stat_label_text(dataset, stat):
|
|
194
|
+
"""
|
|
195
|
+
統計値ラベルの文字列を、描くたびにデータとフィット結果から計算する(データを変えると追従する)。
|
|
196
|
+
データセットが消えた・まだ計算できないときは、何を待っているかが分かる文字列にする。
|
|
197
|
+
"""
|
|
198
|
+
title = STAT_LABEL_TITLES.get(stat, stat)
|
|
199
|
+
if dataset is None:
|
|
200
|
+
return f"{title} = (データセットなし)"
|
|
201
|
+
|
|
202
|
+
if stat == 'r_squared':
|
|
203
|
+
fit_result = dataset.fit_result
|
|
204
|
+
if not fit_result or fit_result.get('r_squared') is None:
|
|
205
|
+
return f"{title} = (フィット未実行)"
|
|
206
|
+
return f"{title} = {fit_result['r_squared']:.4g}"
|
|
207
|
+
|
|
208
|
+
y = np.asarray(dataset.y_data, dtype=float)
|
|
209
|
+
y = y[~np.isnan(y)]
|
|
210
|
+
if len(y) == 0:
|
|
211
|
+
return f"{title} = (データなし)"
|
|
212
|
+
if stat == 'mean':
|
|
213
|
+
value = np.mean(y)
|
|
214
|
+
elif stat == 'std':
|
|
215
|
+
if len(y) < 2:
|
|
216
|
+
return f"{title} = (データ不足)"
|
|
217
|
+
value = sample_standard_deviation(y)
|
|
218
|
+
elif stat == 'max':
|
|
219
|
+
value = np.max(y)
|
|
220
|
+
elif stat == 'min':
|
|
221
|
+
value = np.min(y)
|
|
222
|
+
else:
|
|
223
|
+
return f"{title} = (未対応の統計値)"
|
|
224
|
+
return f"{title} = {value:.4g}"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _safe_multiple_locator(interval, axis_min, axis_max):
|
|
228
|
+
"""間隔 interval の MultipleLocator。今の範囲で目盛りが多すぎるなら MAX_TICKS_PER_AXIS 本相当まで粗くする。"""
|
|
229
|
+
axis_range = abs(axis_max - axis_min)
|
|
230
|
+
if axis_range > 0 and interval > 0:
|
|
231
|
+
estimated_ticks = axis_range / interval
|
|
232
|
+
if estimated_ticks > MAX_TICKS_PER_AXIS:
|
|
233
|
+
adjusted_interval = axis_range / MAX_TICKS_PER_AXIS
|
|
234
|
+
logger.warning(
|
|
235
|
+
"目盛り間隔 %.6g は軸範囲に対して細かすぎるため、%.6g に調整しました。",
|
|
236
|
+
interval, adjusted_interval
|
|
237
|
+
)
|
|
238
|
+
interval = adjusted_interval
|
|
239
|
+
return ticker.MultipleLocator(interval)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _sci_each_formatter():
|
|
243
|
+
"""目盛りごとに指数で書く(例: 1.0×10^10)。"""
|
|
244
|
+
def _fmt(value, pos=None):
|
|
245
|
+
if value == 0:
|
|
246
|
+
return "0"
|
|
247
|
+
exponent = int(np.floor(np.log10(abs(value))))
|
|
248
|
+
mantissa = value / (10 ** exponent)
|
|
249
|
+
if abs(mantissa) >= 9.995: # 丸めると 10.0 になるので桁を上げる
|
|
250
|
+
mantissa /= 10
|
|
251
|
+
exponent += 1
|
|
252
|
+
return rf"${mantissa:.1f}\times10^{{{exponent}}}$"
|
|
253
|
+
return ticker.FuncFormatter(_fmt)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _apply_tick_format_mode(axis, mode):
|
|
257
|
+
"""
|
|
258
|
+
mode: 0=自動(matplotlib のまま) / 1=指数を軸端にまとめる(×10^n) /
|
|
259
|
+
2=目盛りごとに指数 / 3=常に小数で書く
|
|
260
|
+
"""
|
|
261
|
+
if mode == 1:
|
|
262
|
+
formatter = ticker.ScalarFormatter(useMathText=True)
|
|
263
|
+
formatter.set_powerlimits((0, 0))
|
|
264
|
+
axis.set_major_formatter(formatter)
|
|
265
|
+
elif mode == 2:
|
|
266
|
+
axis.set_major_formatter(_sci_each_formatter())
|
|
267
|
+
elif mode == 3:
|
|
268
|
+
formatter = ticker.ScalarFormatter(useOffset=False, useMathText=True)
|
|
269
|
+
formatter.set_scientific(False)
|
|
270
|
+
axis.set_major_formatter(formatter)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _apply_tick_decimal_places(axis, decimals):
|
|
274
|
+
"""小数点以下の桁数を指定したら、指数表記の設定より優先して固定小数点で書く。負値・None は自動(何もしない)。"""
|
|
275
|
+
if decimals is None or decimals < 0:
|
|
276
|
+
return
|
|
277
|
+
axis.set_major_formatter(ticker.FormatStrFormatter(f'%.{decimals}f'))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# 目盛線の長さは軸の大きさに対する割合ではなく pt。文字や線の太さも pt なので、割合にすると
|
|
281
|
+
# 大きく書き出したときや多パネル図で目盛りだけ長さがちぐはぐになる
|
|
282
|
+
DEFAULT_MAJOR_TICK_LENGTH = AXIS_SETTING_DEFAULTS['major_tick_length']
|
|
283
|
+
# 補助目盛が「自動」のときの主目盛に対する長さ(matplotlib の既定 2.0 / 3.5 と同じ)
|
|
284
|
+
MINOR_TICK_LENGTH_RATIO = 2.0 / 3.5
|
|
285
|
+
# 欄で補助目盛の長さ「自動」を表す値。判定は「負値なら自動」なので、この値に頼るのは欄だけ
|
|
286
|
+
MINOR_TICK_LENGTH_AUTO = -0.5
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _resolve_tick_lengths(settings):
|
|
290
|
+
"""(主目盛, 補助目盛) の長さを pt で返す。補助目盛が未指定・負値なら主目盛 × MINOR_TICK_LENGTH_RATIO。"""
|
|
291
|
+
major = axis_setting(settings, 'major_tick_length')
|
|
292
|
+
if major is None or major < 0:
|
|
293
|
+
major = DEFAULT_MAJOR_TICK_LENGTH
|
|
294
|
+
minor = axis_setting(settings, 'minor_tick_length')
|
|
295
|
+
if minor is None or minor < 0:
|
|
296
|
+
minor = major * MINOR_TICK_LENGTH_RATIO
|
|
297
|
+
return major, minor
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# グラフの配色は gui/theme.py のトークンから取る。Figure も Axes も surface にするのは、キャンバスを囲む
|
|
301
|
+
# Qt 側の余白(surface)との間に色の継ぎ目を作らないため
|
|
302
|
+
DARK_FIGURE_FACECOLOR = DARK_TOKENS['surface']
|
|
303
|
+
DARK_AXES_FACECOLOR = DARK_TOKENS['surface']
|
|
304
|
+
DARK_TEXT_COLOR = DARK_TOKENS['text_primary']
|
|
305
|
+
LIGHT_FIGURE_FACECOLOR = LIGHT_TOKENS['surface']
|
|
306
|
+
LIGHT_AXES_FACECOLOR = LIGHT_TOKENS['surface']
|
|
307
|
+
LIGHT_TEXT_COLOR = LIGHT_TOKENS['text_primary']
|
|
308
|
+
|
|
309
|
+
# 凡例は軸の背景と同化しないよう、一段違う面色と濃い枠線にする
|
|
310
|
+
DARK_LEGEND_FACECOLOR = DARK_TOKENS['surface_2']
|
|
311
|
+
DARK_LEGEND_EDGECOLOR = DARK_TOKENS['border_strong']
|
|
312
|
+
LIGHT_LEGEND_FACECOLOR = LIGHT_TOKENS['surface_2']
|
|
313
|
+
LIGHT_LEGEND_EDGECOLOR = LIGHT_TOKENS['border_strong']
|
|
314
|
+
|
|
315
|
+
DARK_GRID_COLOR = DARK_TOKENS['border_strong']
|
|
316
|
+
LIGHT_GRID_COLOR = LIGHT_TOKENS['border_strong']
|