Coverage for graphica/core/axis_settings.py: 100.0%
24 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""
21つの軸の設定(保存形式は文字列キーの辞書)のキーと既定値。
4キーを持たない古いプロジェクトは、画面に戻すときも描画するときもここの既定値で読む。
5既定値を変えると既存のプロジェクトの見た目が変わるので、変えるときは保存形式の移行として扱う。
6"""
7import copy
9from graphica.core.unit_conversion import X_AXIS_UNIT_NONE
10from typing import Any
12AXIS_SETTING_DEFAULTS = {
13 'title': '',
14 'x_label': '',
15 'y_label': '',
16 'x_label_visible': True,
17 'y_label_visible': True,
18 'y2_label': '',
20 'x_autoscale': True,
21 'x_min': 0,
22 'x_max': 1,
23 'x_log': False,
24 'x_invert': False,
25 'x_major_tick_mode': 0,
26 'x_major_tick_interval': 1,
27 'x_minor_ticks_visible': False,
28 'x_minor_tick_interval': 0.5,
29 'x_log_minor_subs': 'auto',
30 'x_log_minor_labels': False,
31 'x_tick_format_mode': 0,
32 'x_tick_decimals': -1,
33 'x_secondary_axis_source_unit': X_AXIS_UNIT_NONE,
34 'x_secondary_axis_target_unit': X_AXIS_UNIT_NONE,
36 'y_autoscale': True,
37 'y_min': 0,
38 'y_max': 1,
39 'y_log': False,
40 'y_invert': False,
41 'y_major_tick_mode': 0,
42 'y_major_tick_interval': 1,
43 'y_minor_ticks_visible': False,
44 'y_minor_tick_interval': 0.5,
45 'y_log_minor_subs': 'auto',
46 'y_log_minor_labels': False,
47 'y_tick_format_mode': 0,
48 'y_tick_decimals': -1,
50 'legend_visible': True,
51 'legend_loc': 'best',
52 'grid_visible': False,
53 'minor_grid_visible': False,
54 'x_major_grid_linestyle': '-',
55 'x_major_grid_width': 0.8,
56 'x_major_grid_alpha': 1.0,
57 'x_minor_grid_linestyle': '--',
58 'x_minor_grid_width': 0.5,
59 'x_minor_grid_alpha': 1.0,
60 'y_major_grid_linestyle': '-',
61 'y_major_grid_width': 0.8,
62 'y_major_grid_alpha': 1.0,
63 'y_minor_grid_linestyle': '--',
64 'y_minor_grid_width': 0.5,
65 'y_minor_grid_alpha': 1.0,
66 'major_tick_direction': 'out',
67 'minor_tick_direction': 'out',
68 'major_tick_direction_y2': 'out',
69 'minor_tick_direction_y2': 'out',
70 'x_ticks_visible': True,
71 'x_tick_labels_visible': True,
72 'y_ticks_visible': True,
73 'y_tick_labels_visible': True,
75 # フォントは {family, size, weight, style}。空なら画面側・描画側の既定のフォント
76 'tick_font': {},
77 'tick_color': '#000000',
78 'tick_width': 0.8,
79 # matplotlib の既定(xtick.major.size)と同じ。-1(負値)の補助目盛は「主目盛から自動」
80 'major_tick_length': 3.5,
81 'minor_tick_length': -1,
82 'axis_label_font': {},
83 'axis_label_color': '#000000',
84 'legend_font': {},
85 'legend_color': '#000000',
86 'spine_width': 1.0,
87 'spine_color': '#000000',
89 'colorbar_enabled': True,
90 'colorbar_position': 'right',
91 'colorbar_width_fraction': 0.05,
92 'colorbar_label': '',
94 # 画面の欄を持たず、操作から直接書き込まれるもの
95 'annotations': [],
96 'legend_order': [],
97 'free_rect': None,
98 'legend_position': None,
99 # 第 2 Y 軸(主の Y 軸と同じ意味)。既定は範囲・目盛りとも自動
100 'y2_autoscale': True,
101 'y2_min': 0,
102 'y2_max': 1,
103 'y2_log': False,
104 'y2_invert': False,
105 'y2_major_tick_mode': 0,
106 'y2_major_tick_interval': 1,
107 'y2_minor_ticks_visible': False,
108 'y2_minor_tick_interval': 0.5,
109}
111# 軸ごとに分ける前の共通キー。新しいキーが無いプロジェクトでは、その値を X/Y 両方に使う。
112LEGACY_FALLBACK_KEYS = {
113 'x_ticks_visible': 'ticks_visible',
114 'y_ticks_visible': 'ticks_visible',
115 'x_tick_labels_visible': 'tick_labels_visible',
116 'y_tick_labels_visible': 'tick_labels_visible',
117}
120def manual_axis_range(settings: dict[str, Any], axis_key: str) -> tuple[float, float] | None:
121 """手動で決めた範囲を小さい順で。自動スケールか、最小と最大が同じなら None。axis_key は 'x' / 'y' / 'y2'。
123 最小に大きい方を入れても範囲として使う(向きは axis_inverted で決める)。黙って無視すると自動の範囲になって紛らわしい。
124 """
125 if axis_setting(settings, f'{axis_key}_autoscale'):
126 return None
127 low, high = axis_setting(settings, f'{axis_key}_min'), axis_setting(settings, f'{axis_key}_max')
128 if low == high:
129 return None
130 return (low, high) if low < high else (high, low)
133def axis_inverted(settings: dict[str, Any], axis_key: str) -> bool:
134 """軸を大きい方から並べるか。「軸を反転」に加え、手動の範囲で最小に大きい方を入れていれば向きを入れ替える。"""
135 inverted = bool(axis_setting(settings, f'{axis_key}_invert'))
136 if not axis_setting(settings, f'{axis_key}_autoscale') \
137 and axis_setting(settings, f'{axis_key}_min') > axis_setting(settings, f'{axis_key}_max'):
138 inverted = not inverted
139 return inverted
142def axis_setting(settings: dict[str, Any], key: str) -> Any:
143 """settings の key の値。無ければ(古いキーがあればその値、それも無ければ)既定値。"""
144 if key in settings:
145 return settings[key]
146 legacy_key = LEGACY_FALLBACK_KEYS.get(key)
147 if legacy_key is not None and legacy_key in settings:
148 return settings[legacy_key]
149 # 既定値のリストや辞書を呼び出し側が書き換えても、既定値が変わらないように
150 return copy.deepcopy(AXIS_SETTING_DEFAULTS[key])