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,368 @@
|
|
|
1
|
+
"""1 つの軸の外観の設定。_AxisStyle を共有する決まった順の手順で、あとの手順は前の手順が作った状態を読む。"""
|
|
2
|
+
import matplotlib.dates as mdates
|
|
3
|
+
import matplotlib.ticker as ticker
|
|
4
|
+
import numpy as np
|
|
5
|
+
from graphica.core.axis_settings import axis_setting
|
|
6
|
+
from graphica.core.unit_conversion import X_AXIS_UNIT_LABELS, X_AXIS_UNIT_NONE, convert_x_axis_unit
|
|
7
|
+
from graphica.gui.mathtext_preview import families_for_texts, font_kwargs_for_text
|
|
8
|
+
from graphica.gui.rendering.common import (
|
|
9
|
+
DARK_AXES_FACECOLOR, DARK_GRID_COLOR, DARK_LEGEND_EDGECOLOR, DARK_LEGEND_FACECOLOR, LIGHT_AXES_FACECOLOR,
|
|
10
|
+
LIGHT_GRID_COLOR, LIGHT_LEGEND_EDGECOLOR, LIGHT_LEGEND_FACECOLOR, _AxisStyle, _LOG_MINOR_SUBS_PRESETS,
|
|
11
|
+
_apply_legend_order, _apply_tick_decimal_places, _apply_tick_format_mode, _legend_position_from_settings,
|
|
12
|
+
_resolve_tick_lengths, _safe_multiple_locator)
|
|
13
|
+
from matplotlib.font_manager import FontProperties
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def apply_appearance(canvas, ax, axis_index, settings):
|
|
17
|
+
"""その軸に見た目の設定を当てる。matplotlib の状態に依存するので、下の順番は変えない。"""
|
|
18
|
+
secondary_ax = canvas.all_secondary_axes[axis_index]
|
|
19
|
+
ax.set_facecolor(DARK_AXES_FACECOLOR if canvas.dark_mode else LIGHT_AXES_FACECOLOR)
|
|
20
|
+
|
|
21
|
+
is_date_x = axis_index < len(canvas.axis_is_date_x) and canvas.axis_is_date_x[axis_index]
|
|
22
|
+
# 文字列カテゴリ軸では、範囲・対数などの数値向けの設定はカテゴリの位置と噛み合わないので使わない
|
|
23
|
+
is_category_x = axis_index < len(canvas.axis_is_category_x) and canvas.axis_is_category_x[axis_index]
|
|
24
|
+
|
|
25
|
+
canvas._apply_limits_and_scale(ax, settings, is_category_x)
|
|
26
|
+
canvas._apply_tick_locators(ax, settings, is_date_x, is_category_x)
|
|
27
|
+
style = canvas._axis_text_and_line_style(settings)
|
|
28
|
+
canvas._apply_titles_and_labels(ax, settings, style)
|
|
29
|
+
canvas._apply_spines_and_tick_marks(ax, settings, style)
|
|
30
|
+
canvas._apply_legend(ax, secondary_ax, settings)
|
|
31
|
+
canvas._apply_grid(ax, settings)
|
|
32
|
+
canvas._apply_secondary_y_axis(ax, secondary_ax, settings, style)
|
|
33
|
+
canvas._apply_unit_conversion_x_axis(ax, settings, style, is_date_x, is_category_x)
|
|
34
|
+
canvas._apply_colorbar(ax, axis_index, settings, style)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def apply_limits_and_scale(canvas, ax, settings, is_category_x):
|
|
38
|
+
if is_category_x or axis_setting(settings, 'x_autoscale'): ax.autoscale(enable=True, axis='x', tight=True)
|
|
39
|
+
else:
|
|
40
|
+
min_val, max_val = axis_setting(settings, 'x_min'), axis_setting(settings, 'x_max')
|
|
41
|
+
if min_val < max_val: ax.set_xlim(min_val, max_val)
|
|
42
|
+
|
|
43
|
+
if axis_setting(settings, 'y_autoscale'): ax.autoscale(enable=True, axis='y', tight=True)
|
|
44
|
+
else:
|
|
45
|
+
min_val, max_val = axis_setting(settings, 'y_min'), axis_setting(settings, 'y_max')
|
|
46
|
+
if min_val < max_val: ax.set_ylim(min_val, max_val)
|
|
47
|
+
|
|
48
|
+
if not is_category_x:
|
|
49
|
+
# set_xscale は同じ 'linear' でも Locator/Formatter を既定に戻してしまう。
|
|
50
|
+
# カテゴリ軸では matplotlib が付けたカテゴリ用のものを残したいので呼ばない
|
|
51
|
+
ax.set_xscale('log' if axis_setting(settings, 'x_log') else 'linear')
|
|
52
|
+
ax.xaxis.set_inverted(axis_setting(settings, 'x_invert'))
|
|
53
|
+
ax.set_yscale('log' if axis_setting(settings, 'y_log') else 'linear')
|
|
54
|
+
ax.yaxis.set_inverted(axis_setting(settings, 'y_invert'))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def apply_tick_locators(canvas, ax, settings, is_date_x, is_category_x):
|
|
58
|
+
x_min_lim, x_max_lim = ax.get_xlim()
|
|
59
|
+
y_min_lim, y_max_lim = ax.get_ylim()
|
|
60
|
+
|
|
61
|
+
if is_date_x:
|
|
62
|
+
# 日時の軸は範囲に合わせて年・月・日・時刻の間隔と表記を自動で選ぶ(間隔の手動指定は使わない)
|
|
63
|
+
date_locator = mdates.AutoDateLocator()
|
|
64
|
+
ax.xaxis.set_major_locator(date_locator)
|
|
65
|
+
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(date_locator))
|
|
66
|
+
elif is_category_x:
|
|
67
|
+
# matplotlib が付けたカテゴリ用の目盛りのまま(数値用で上書きするとラベルが崩れる)
|
|
68
|
+
pass
|
|
69
|
+
elif axis_setting(settings, 'x_major_tick_mode') == 0: ax.xaxis.set_major_locator(ticker.AutoLocator())
|
|
70
|
+
else:
|
|
71
|
+
interval = axis_setting(settings, 'x_major_tick_interval')
|
|
72
|
+
if interval > 0: ax.xaxis.set_major_locator(_safe_multiple_locator(interval, x_min_lim, x_max_lim))
|
|
73
|
+
|
|
74
|
+
if axis_setting(settings, 'y_major_tick_mode') == 0: ax.yaxis.set_major_locator(ticker.AutoLocator())
|
|
75
|
+
else:
|
|
76
|
+
interval = axis_setting(settings, 'y_major_tick_interval')
|
|
77
|
+
if interval > 0: ax.yaxis.set_major_locator(_safe_multiple_locator(interval, y_min_lim, y_max_lim))
|
|
78
|
+
|
|
79
|
+
if is_date_x or is_category_x:
|
|
80
|
+
ax.xaxis.set_minor_locator(ticker.NullLocator())
|
|
81
|
+
elif axis_setting(settings, 'x_minor_ticks_visible'):
|
|
82
|
+
if axis_setting(settings, 'x_log'):
|
|
83
|
+
# 対数軸は間隔指定の MultipleLocator ではなく LogLocator にする
|
|
84
|
+
subs = _LOG_MINOR_SUBS_PRESETS.get(axis_setting(settings, 'x_log_minor_subs'), 'auto')
|
|
85
|
+
ax.xaxis.set_minor_locator(ticker.LogLocator(base=10.0, subs=subs))
|
|
86
|
+
ax.xaxis.set_minor_formatter(
|
|
87
|
+
ticker.LogFormatterSciNotation(base=10.0, labelOnlyBase=False)
|
|
88
|
+
if axis_setting(settings, 'x_log_minor_labels') else ticker.NullFormatter()
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
interval = axis_setting(settings, 'x_minor_tick_interval')
|
|
92
|
+
if interval > 0: ax.xaxis.set_minor_locator(_safe_multiple_locator(interval, x_min_lim, x_max_lim))
|
|
93
|
+
else: ax.xaxis.set_minor_locator(ticker.NullLocator())
|
|
94
|
+
|
|
95
|
+
if axis_setting(settings, 'y_minor_ticks_visible'):
|
|
96
|
+
if axis_setting(settings, 'y_log'):
|
|
97
|
+
subs = _LOG_MINOR_SUBS_PRESETS.get(axis_setting(settings, 'y_log_minor_subs'), 'auto')
|
|
98
|
+
ax.yaxis.set_minor_locator(ticker.LogLocator(base=10.0, subs=subs))
|
|
99
|
+
ax.yaxis.set_minor_formatter(
|
|
100
|
+
ticker.LogFormatterSciNotation(base=10.0, labelOnlyBase=False)
|
|
101
|
+
if axis_setting(settings, 'y_log_minor_labels') else ticker.NullFormatter()
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
interval = axis_setting(settings, 'y_minor_tick_interval')
|
|
105
|
+
if interval > 0: ax.yaxis.set_minor_locator(_safe_multiple_locator(interval, y_min_lim, y_max_lim))
|
|
106
|
+
else: ax.yaxis.set_minor_locator(ticker.NullLocator())
|
|
107
|
+
|
|
108
|
+
# 日付・カテゴリの X 軸は専用の表記を付けてあるので、数値の軸だけ
|
|
109
|
+
if not is_date_x and not is_category_x:
|
|
110
|
+
_apply_tick_format_mode(ax.xaxis, axis_setting(settings, 'x_tick_format_mode'))
|
|
111
|
+
_apply_tick_decimal_places(ax.xaxis, axis_setting(settings, 'x_tick_decimals'))
|
|
112
|
+
_apply_tick_format_mode(ax.yaxis, axis_setting(settings, 'y_tick_format_mode'))
|
|
113
|
+
_apply_tick_decimal_places(ax.yaxis, axis_setting(settings, 'y_tick_decimals'))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def axis_text_and_line_style(canvas, settings):
|
|
117
|
+
"""文字と線の見た目のうち、軸・第2軸・カラーバーで共通に使うもの(ダークモードの色の読み替え後)。"""
|
|
118
|
+
major_tick_length, minor_tick_length = _resolve_tick_lengths(settings)
|
|
119
|
+
return _AxisStyle(
|
|
120
|
+
tick_font=axis_setting(settings, 'tick_font'),
|
|
121
|
+
label_font=axis_setting(settings, 'axis_label_font'),
|
|
122
|
+
tick_color=canvas._effective_text_color(axis_setting(settings, 'tick_color')),
|
|
123
|
+
label_color=canvas._effective_text_color(axis_setting(settings, 'axis_label_color')),
|
|
124
|
+
spine_width=axis_setting(settings, 'spine_width'),
|
|
125
|
+
spine_color=canvas._effective_text_color(axis_setting(settings, 'spine_color')),
|
|
126
|
+
tick_width=axis_setting(settings, 'tick_width'),
|
|
127
|
+
major_tick_length=major_tick_length,
|
|
128
|
+
minor_tick_length=minor_tick_length,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def apply_titles_and_labels(canvas, ax, settings, style):
|
|
133
|
+
title = axis_setting(settings, 'title')
|
|
134
|
+
ax.set_title(title, **font_kwargs_for_text(title, style.label_font), color=style.label_color)
|
|
135
|
+
# 非表示にしても文字列は消さない(表示に戻したときに打ち直さなくて済むように)
|
|
136
|
+
x_label_text = axis_setting(settings, 'x_label') if axis_setting(settings, 'x_label_visible') else ''
|
|
137
|
+
y_label_text = axis_setting(settings, 'y_label') if axis_setting(settings, 'y_label_visible') else ''
|
|
138
|
+
ax.set_xlabel(x_label_text, **font_kwargs_for_text(x_label_text, style.label_font), color=style.label_color)
|
|
139
|
+
ax.set_ylabel(y_label_text, **font_kwargs_for_text(y_label_text, style.label_font), color=style.label_color)
|
|
140
|
+
# タイトルのクリックで、その軸を編集対象にする
|
|
141
|
+
ax.title.set_picker(5)
|
|
142
|
+
|
|
143
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
|
144
|
+
label.set(**style.tick_font)
|
|
145
|
+
label.set_color(style.tick_color)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def apply_spines_and_tick_marks(canvas, ax, settings, style):
|
|
149
|
+
for spine in ax.spines.values():
|
|
150
|
+
spine.set_linewidth(style.spine_width)
|
|
151
|
+
spine.set_color(style.spine_color)
|
|
152
|
+
|
|
153
|
+
# 表示/非表示は毎回 True/False を明示する。tick_params の値は ax.cla() をまたいで残るので、
|
|
154
|
+
# 隠すときだけ指定すると表示に戻せない。軸の共有で内側の目盛数値を隠すのは呼び出し側が後で行う
|
|
155
|
+
major_dir = axis_setting(settings, 'major_tick_direction')
|
|
156
|
+
minor_dir = axis_setting(settings, 'minor_tick_direction')
|
|
157
|
+
x_ticks_visible = axis_setting(settings, 'x_ticks_visible')
|
|
158
|
+
y_ticks_visible = axis_setting(settings, 'y_ticks_visible')
|
|
159
|
+
x_tick_labels_visible = axis_setting(settings, 'x_tick_labels_visible')
|
|
160
|
+
y_tick_labels_visible = axis_setting(settings, 'y_tick_labels_visible')
|
|
161
|
+
|
|
162
|
+
ax.tick_params(axis='x', which='major', width=style.tick_width, length=style.major_tick_length, color=style.spine_color, labelcolor=style.tick_color,
|
|
163
|
+
direction=major_dir, bottom=x_ticks_visible, labelbottom=x_tick_labels_visible)
|
|
164
|
+
ax.tick_params(axis='x', which='minor', width=style.tick_width * 0.75, length=style.minor_tick_length, color=style.spine_color,
|
|
165
|
+
direction=minor_dir, bottom=x_ticks_visible, labelbottom=x_tick_labels_visible)
|
|
166
|
+
ax.tick_params(axis='y', which='major', width=style.tick_width, length=style.major_tick_length, color=style.spine_color, labelcolor=style.tick_color,
|
|
167
|
+
direction=major_dir, left=y_ticks_visible, labelleft=y_tick_labels_visible)
|
|
168
|
+
ax.tick_params(axis='y', which='minor', width=style.tick_width * 0.75, length=style.minor_tick_length, color=style.spine_color,
|
|
169
|
+
direction=minor_dir, left=y_ticks_visible, labelleft=y_tick_labels_visible)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def apply_legend(canvas, ax, secondary_ax, settings):
|
|
173
|
+
lines_primary, labels_primary, lines_secondary, labels_secondary = [], [], [], []
|
|
174
|
+
if axis_setting(settings, 'legend_visible'):
|
|
175
|
+
lines_primary, labels_primary = ax.get_legend_handles_labels()
|
|
176
|
+
if secondary_ax:
|
|
177
|
+
lines_secondary, labels_secondary = secondary_ax.get_legend_handles_labels()
|
|
178
|
+
has_primary_data = bool(lines_primary)
|
|
179
|
+
has_secondary_data = bool(lines_secondary)
|
|
180
|
+
if not (has_primary_data or has_secondary_data):
|
|
181
|
+
if ax.get_legend() is not None: ax.get_legend().remove()
|
|
182
|
+
if secondary_ax and secondary_ax.get_legend() is not None: secondary_ax.get_legend().remove()
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
loc_code = axis_setting(settings, 'legend_loc')
|
|
186
|
+
# ドラッグで動かした位置(軸の左下 (0, 0)・右上 (1, 1) での凡例の左下)があれば、位置の選択より優先する
|
|
187
|
+
dragged_position = _legend_position_from_settings(settings)
|
|
188
|
+
if dragged_position is not None:
|
|
189
|
+
loc_code = dragged_position
|
|
190
|
+
legend_font_dict = axis_setting(settings, 'legend_font')
|
|
191
|
+
legend_color = canvas._effective_text_color(axis_setting(settings, 'legend_color'))
|
|
192
|
+
legend_font_prop = FontProperties(
|
|
193
|
+
family=families_for_texts(labels_primary + labels_secondary, legend_font_dict.get('family')),
|
|
194
|
+
size=legend_font_dict.get('size'),
|
|
195
|
+
weight=legend_font_dict.get('weight'),
|
|
196
|
+
style=legend_font_dict.get('style'),
|
|
197
|
+
stretch=legend_font_dict.get('stretch')
|
|
198
|
+
)
|
|
199
|
+
# 凡例の並びは描画順とは別に指定できる
|
|
200
|
+
legend_order = axis_setting(settings, 'legend_order')
|
|
201
|
+
legend_obj = None
|
|
202
|
+
if secondary_ax and has_primary_data and has_secondary_data:
|
|
203
|
+
combined_lines, combined_labels = _apply_legend_order(
|
|
204
|
+
lines_primary + lines_secondary, labels_primary + labels_secondary, legend_order
|
|
205
|
+
)
|
|
206
|
+
legend_obj = ax.legend(combined_lines, combined_labels, loc=loc_code, prop=legend_font_prop)
|
|
207
|
+
elif has_primary_data:
|
|
208
|
+
ordered_lines, ordered_labels = _apply_legend_order(lines_primary, labels_primary, legend_order)
|
|
209
|
+
legend_obj = ax.legend(ordered_lines, ordered_labels, loc=loc_code, prop=legend_font_prop)
|
|
210
|
+
elif secondary_ax and has_secondary_data:
|
|
211
|
+
ordered_lines, ordered_labels = _apply_legend_order(lines_secondary, labels_secondary, legend_order)
|
|
212
|
+
legend_obj = secondary_ax.legend(ordered_lines, ordered_labels, loc=loc_code, prop=legend_font_prop)
|
|
213
|
+
|
|
214
|
+
if legend_obj:
|
|
215
|
+
for text in legend_obj.get_texts(): text.set_color(legend_color)
|
|
216
|
+
frame = legend_obj.get_frame()
|
|
217
|
+
if canvas.dark_mode:
|
|
218
|
+
frame.set_facecolor(DARK_LEGEND_FACECOLOR)
|
|
219
|
+
frame.set_edgecolor(DARK_LEGEND_EDGECOLOR)
|
|
220
|
+
else:
|
|
221
|
+
frame.set_facecolor(LIGHT_LEGEND_FACECOLOR)
|
|
222
|
+
frame.set_edgecolor(LIGHT_LEGEND_EDGECOLOR)
|
|
223
|
+
frame.set_alpha(0.92)
|
|
224
|
+
# 離した位置が legend._loc に入り、PlotterApp がそれを設定へ保存する
|
|
225
|
+
# (matplotlib 3.x に Legend.draggable() は無い)
|
|
226
|
+
legend_obj.set_draggable(True, update='loc')
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def apply_grid(canvas, ax, settings):
|
|
230
|
+
"""ax.grid() は指定した which/axis にしか効かないので、X/Y × 主/補助 を個別に呼ぶ。"""
|
|
231
|
+
if not axis_setting(settings, 'grid_visible'):
|
|
232
|
+
ax.grid(False, which='both')
|
|
233
|
+
return
|
|
234
|
+
grid_color = DARK_GRID_COLOR if canvas.dark_mode else LIGHT_GRID_COLOR
|
|
235
|
+
for grid_axis in ('x', 'y'):
|
|
236
|
+
ax.grid(
|
|
237
|
+
True, which='major', axis=grid_axis,
|
|
238
|
+
linestyle=axis_setting(settings, f'{grid_axis}_major_grid_linestyle'),
|
|
239
|
+
linewidth=axis_setting(settings, f'{grid_axis}_major_grid_width'),
|
|
240
|
+
alpha=axis_setting(settings, f'{grid_axis}_major_grid_alpha'),
|
|
241
|
+
color=grid_color,
|
|
242
|
+
)
|
|
243
|
+
if axis_setting(settings, 'minor_grid_visible'):
|
|
244
|
+
ax.grid(
|
|
245
|
+
True, which='minor', axis=grid_axis,
|
|
246
|
+
linestyle=axis_setting(settings, f'{grid_axis}_minor_grid_linestyle'),
|
|
247
|
+
linewidth=axis_setting(settings, f'{grid_axis}_minor_grid_width'),
|
|
248
|
+
alpha=axis_setting(settings, f'{grid_axis}_minor_grid_alpha'),
|
|
249
|
+
color=grid_color,
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
ax.grid(False, which='minor', axis=grid_axis)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _apply_secondary_y_limits_and_ticks(secondary_ax, settings):
|
|
256
|
+
"""主の Y 軸と同じ手順(範囲 → 対数 → 反転 → 目盛り)。既定(自動・線形・目盛り自動)では何も上書きしない。"""
|
|
257
|
+
if axis_setting(settings, 'y2_autoscale'):
|
|
258
|
+
secondary_ax.autoscale(enable=True, axis='y', tight=True)
|
|
259
|
+
else:
|
|
260
|
+
min_val, max_val = axis_setting(settings, 'y2_min'), axis_setting(settings, 'y2_max')
|
|
261
|
+
if min_val < max_val:
|
|
262
|
+
secondary_ax.set_ylim(min_val, max_val)
|
|
263
|
+
is_log = axis_setting(settings, 'y2_log')
|
|
264
|
+
# set_yscale は同じ値でも目盛りの Locator を既定に戻すので、変わるときだけ呼ぶ
|
|
265
|
+
if secondary_ax.get_yscale() != ('log' if is_log else 'linear'):
|
|
266
|
+
secondary_ax.set_yscale('log' if is_log else 'linear')
|
|
267
|
+
secondary_ax.yaxis.set_inverted(axis_setting(settings, 'y2_invert'))
|
|
268
|
+
|
|
269
|
+
y_min_lim, y_max_lim = secondary_ax.get_ylim()
|
|
270
|
+
if axis_setting(settings, 'y2_major_tick_mode') == 1:
|
|
271
|
+
interval = axis_setting(settings, 'y2_major_tick_interval')
|
|
272
|
+
if interval > 0 and not is_log:
|
|
273
|
+
secondary_ax.yaxis.set_major_locator(_safe_multiple_locator(interval, y_min_lim, y_max_lim))
|
|
274
|
+
if axis_setting(settings, 'y2_minor_ticks_visible'):
|
|
275
|
+
if is_log:
|
|
276
|
+
secondary_ax.yaxis.set_minor_locator(ticker.LogLocator(base=10.0, subs='auto'))
|
|
277
|
+
else:
|
|
278
|
+
interval = axis_setting(settings, 'y2_minor_tick_interval')
|
|
279
|
+
if interval > 0:
|
|
280
|
+
secondary_ax.yaxis.set_minor_locator(_safe_multiple_locator(interval, y_min_lim, y_max_lim))
|
|
281
|
+
elif not is_log:
|
|
282
|
+
secondary_ax.yaxis.set_minor_locator(ticker.NullLocator())
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def apply_secondary_y_axis(canvas, ax, secondary_ax, settings, style):
|
|
286
|
+
"""第2Y軸があれば右の枠線と目盛りを第2Y軸に任せ、無ければ主軸の右の枠線を出す。"""
|
|
287
|
+
if not secondary_ax:
|
|
288
|
+
ax.spines['right'].set_visible(True)
|
|
289
|
+
ax.spines['right'].set_linewidth(style.spine_width)
|
|
290
|
+
ax.spines['right'].set_color(style.spine_color)
|
|
291
|
+
return
|
|
292
|
+
_apply_secondary_y_limits_and_ticks(secondary_ax, settings)
|
|
293
|
+
y2_label = axis_setting(settings, 'y2_label')
|
|
294
|
+
secondary_ax.set_ylabel(y2_label, **font_kwargs_for_text(y2_label, style.label_font), color=style.label_color)
|
|
295
|
+
major_dir_y2 = axis_setting(settings, 'major_tick_direction_y2')
|
|
296
|
+
minor_dir_y2 = axis_setting(settings, 'minor_tick_direction_y2')
|
|
297
|
+
for label in secondary_ax.get_yticklabels():
|
|
298
|
+
label.set(**style.tick_font)
|
|
299
|
+
label.set_color(style.tick_color)
|
|
300
|
+
secondary_ax.tick_params(axis='y', which='major', width=style.tick_width, length=style.major_tick_length, color=style.spine_color, labelcolor=style.tick_color, direction=major_dir_y2)
|
|
301
|
+
secondary_ax.tick_params(axis='y', which='minor', width=style.tick_width * 0.75, length=style.minor_tick_length, color=style.spine_color, direction=minor_dir_y2)
|
|
302
|
+
secondary_ax.spines['right'].set_linewidth(style.spine_width)
|
|
303
|
+
secondary_ax.spines['right'].set_color(style.spine_color)
|
|
304
|
+
secondary_ax.spines['right'].set_visible(True)
|
|
305
|
+
secondary_ax.spines['left'].set_visible(False)
|
|
306
|
+
secondary_ax.spines['top'].set_visible(False)
|
|
307
|
+
secondary_ax.spines['bottom'].set_visible(False)
|
|
308
|
+
ax.spines['right'].set_visible(False)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def apply_unit_conversion_x_axis(canvas, ax, settings, style, is_date_x, is_category_x):
|
|
312
|
+
"""X の単位と表示したい単位が別々に選ばれていれば、上に単位を変換した第2X軸を付ける(数値の軸だけ)。"""
|
|
313
|
+
source_unit = axis_setting(settings, 'x_secondary_axis_source_unit')
|
|
314
|
+
target_unit = axis_setting(settings, 'x_secondary_axis_target_unit')
|
|
315
|
+
# 範囲の端が変換で inf/nan になる(波長 0nm など)と secondary_xaxis が例外を出し、描画全体が失敗する
|
|
316
|
+
if not (not is_date_x and not is_category_x
|
|
317
|
+
and source_unit != X_AXIS_UNIT_NONE and target_unit != X_AXIS_UNIT_NONE
|
|
318
|
+
and source_unit != target_unit
|
|
319
|
+
and np.all(np.isfinite(convert_x_axis_unit(np.array(ax.get_xlim()), source_unit, target_unit)))):
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
def _forward(x, _from=source_unit, _to=target_unit):
|
|
323
|
+
return convert_x_axis_unit(x, _from, _to)
|
|
324
|
+
|
|
325
|
+
def _inverse(x, _from=source_unit, _to=target_unit):
|
|
326
|
+
return convert_x_axis_unit(x, _to, _from)
|
|
327
|
+
|
|
328
|
+
secondary_x_ax = ax.secondary_xaxis('top', functions=(_forward, _inverse))
|
|
329
|
+
# 逆数の変換では値域が極端に広がり、既定の AutoLocator が千本を超える目盛りを作ろうとする
|
|
330
|
+
secondary_x_ax.xaxis.set_major_locator(ticker.MaxNLocator(nbins=8))
|
|
331
|
+
secondary_x_ax.set_xlabel(X_AXIS_UNIT_LABELS.get(target_unit, target_unit),
|
|
332
|
+
**style.label_font, color=style.label_color)
|
|
333
|
+
for label in secondary_x_ax.get_xticklabels():
|
|
334
|
+
label.set(**style.tick_font)
|
|
335
|
+
label.set_color(style.tick_color)
|
|
336
|
+
secondary_x_ax.tick_params(axis='x', which='major', width=style.tick_width, length=style.major_tick_length,
|
|
337
|
+
color=style.spine_color, labelcolor=style.tick_color,
|
|
338
|
+
direction=axis_setting(settings, 'major_tick_direction'))
|
|
339
|
+
secondary_x_ax.spines['top'].set_linewidth(style.spine_width)
|
|
340
|
+
secondary_x_ax.spines['top'].set_color(style.spine_color)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def apply_colorbar(canvas, ax, axis_index, settings, style):
|
|
344
|
+
"""
|
|
345
|
+
2Dマップ(または値で色分けした散布図)がこの軸にあればカラーバーを付ける。
|
|
346
|
+
location を渡すと向きは matplotlib が決めるので、orientation は渡さない(衝突しうる)。
|
|
347
|
+
"""
|
|
348
|
+
mappable = canvas._axis_2d_mappables.get(axis_index)
|
|
349
|
+
if mappable is None or not axis_setting(settings, 'colorbar_enabled'):
|
|
350
|
+
return
|
|
351
|
+
position = axis_setting(settings, 'colorbar_position')
|
|
352
|
+
if position not in ('right', 'left', 'top', 'bottom'):
|
|
353
|
+
position = 'right'
|
|
354
|
+
try:
|
|
355
|
+
fraction = float(axis_setting(settings, 'colorbar_width_fraction'))
|
|
356
|
+
except (TypeError, ValueError):
|
|
357
|
+
fraction = 0.05
|
|
358
|
+
if fraction <= 0:
|
|
359
|
+
fraction = 0.05
|
|
360
|
+
cbar = canvas.fig.colorbar(mappable, ax=ax, location=position, fraction=fraction, pad=0.04)
|
|
361
|
+
colorbar_label = axis_setting(settings, 'colorbar_label')
|
|
362
|
+
if colorbar_label:
|
|
363
|
+
cbar.set_label(colorbar_label, **font_kwargs_for_text(colorbar_label, style.label_font), color=style.label_color)
|
|
364
|
+
for tick_label in cbar.ax.get_yticklabels() + cbar.ax.get_xticklabels():
|
|
365
|
+
tick_label.set(**style.tick_font)
|
|
366
|
+
tick_label.set_color(style.tick_color)
|
|
367
|
+
cbar.outline.set_edgecolor(style.spine_color)
|
|
368
|
+
cbar.outline.set_linewidth(style.spine_width)
|