ncepst-pyexceltools 0.1.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.
- ModifyChart.py +959 -0
- ScatterChart.py +704 -0
- create_sheet_index.py +95 -0
- ncepst_pyexceltools-0.1.0.dist-info/METADATA +345 -0
- ncepst_pyexceltools-0.1.0.dist-info/RECORD +9 -0
- ncepst_pyexceltools-0.1.0.dist-info/WHEEL +5 -0
- ncepst_pyexceltools-0.1.0.dist-info/licenses/LICENSE +21 -0
- ncepst_pyexceltools-0.1.0.dist-info/top_level.txt +4 -0
- pyexceltools/__init__.py +2 -0
ModifyChart.py
ADDED
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
# ModifyChart.py
|
|
2
|
+
# Copyright (c) 2025 ncepst
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
既存のExcelグラフ(散布図)の書式設定を行うモジュールです
|
|
6
|
+
|
|
7
|
+
使用法:
|
|
8
|
+
from ModifyChart import ModifyChart, RGB
|
|
9
|
+
# chart に対して標準プリセット "std" の書式設定を適用、系列数=2 を指定
|
|
10
|
+
ModifyChart(chart, preset="std", NS=2)
|
|
11
|
+
"""
|
|
12
|
+
from xlwings.constants import AxisType
|
|
13
|
+
from win32com.client import constants
|
|
14
|
+
# 型ヒントには Python 3.10 以降の記法を使用しています
|
|
15
|
+
|
|
16
|
+
# RGB値をExcel用の整数に変換するヘルパー関数
|
|
17
|
+
def RGB(r, g, b):
|
|
18
|
+
return r | (g << 8) | (b << 16)
|
|
19
|
+
|
|
20
|
+
# cm → pt 換算関数 (1 point = 1/72 inch, 1 inch = 2.54 cm)
|
|
21
|
+
def cm_to_pt(cm):
|
|
22
|
+
return cm * 72 / 2.54
|
|
23
|
+
|
|
24
|
+
def emphasize_line(axis,value=0,weight=1):
|
|
25
|
+
axis.CrossesAt = value
|
|
26
|
+
line = axis.Format.Line
|
|
27
|
+
line.Weight = weight
|
|
28
|
+
line.ForeColor.RGB = RGB(0,0,0)
|
|
29
|
+
|
|
30
|
+
PRESET = {
|
|
31
|
+
# "Aptos Narrow 本文"は Excel 2021以降のみ
|
|
32
|
+
"excel2021": {
|
|
33
|
+
"title_font_size": 14,
|
|
34
|
+
"title_font_color": RGB(89,89,89),
|
|
35
|
+
"title_font_bold": False,
|
|
36
|
+
"title_font_name": "Aptos Narrow 本文",
|
|
37
|
+
"axis_title_font_size": 10,
|
|
38
|
+
"axis_title_font_name": "Aptos Narrow 本文",
|
|
39
|
+
"axis_title_font_color": RGB(89,89,89),
|
|
40
|
+
"axis_title_font_bold": False,
|
|
41
|
+
"axis_tick_font_color": RGB(89, 89, 89),
|
|
42
|
+
"axis_tick_font_size": 9,
|
|
43
|
+
"axis_tick_font_name": "Aptos Narrow 本文",
|
|
44
|
+
"axis_tick_font_bold": False,
|
|
45
|
+
"axis_line": True,
|
|
46
|
+
"axis_line_color": RGB(191, 191, 191),
|
|
47
|
+
"axis_line_weight": 0.75,
|
|
48
|
+
"x_major_grid": True,
|
|
49
|
+
"y_major_grid": True,
|
|
50
|
+
"x_minor_grid": None,
|
|
51
|
+
"y_minor_grid": None,
|
|
52
|
+
"major_grid_color": RGB(217, 217, 217),
|
|
53
|
+
"major_grid_weight": 0.75,
|
|
54
|
+
# TickMark: None, Inside, Outside, Cross
|
|
55
|
+
"x_major_tickmark": constants.xlTickMarkNone, # 目盛の内向き/外向きなし
|
|
56
|
+
"y_major_tickmark": constants.xlTickMarkNone,
|
|
57
|
+
"x_minor_tickmark": None,
|
|
58
|
+
"y_minor_tickmark": None,
|
|
59
|
+
# 外枠の有無は引数で指定してください
|
|
60
|
+
"frame_color": RGB(217,217,217),
|
|
61
|
+
"frame_weight": 0.75,
|
|
62
|
+
"plot_area_frame": None,
|
|
63
|
+
"plot_area_frame_color": RGB(0,0,0),
|
|
64
|
+
"plot_area_frame_weight": 1.0,
|
|
65
|
+
"style": "line+marker",
|
|
66
|
+
"smooth": True,
|
|
67
|
+
"alpha": None,
|
|
68
|
+
"line_weight": 1.5,
|
|
69
|
+
"marker": "C",
|
|
70
|
+
"markersize": 5,
|
|
71
|
+
"y2_major_grid": False, # 副軸グリッド表示なし
|
|
72
|
+
"y2_minor_grid": False,
|
|
73
|
+
"y2_major_tickmark": constants.xlTickMarkNone,
|
|
74
|
+
"y2_minor_tickmark": None,
|
|
75
|
+
},
|
|
76
|
+
"std": {
|
|
77
|
+
"title_font_color": RGB(0,0,0),
|
|
78
|
+
"axis_title_font_color": RGB(0,0,0),
|
|
79
|
+
"axis_tick_font_color": RGB(0,0,0),
|
|
80
|
+
},
|
|
81
|
+
"no_grid":{
|
|
82
|
+
"x_major_grid": False,
|
|
83
|
+
"y_major_grid": False,
|
|
84
|
+
"plot_area_frame": True,
|
|
85
|
+
"axis_line": True,
|
|
86
|
+
"axis_line_color": RGB(0, 0, 0),
|
|
87
|
+
"x_major_tickmark": constants.xlTickMarkInside,
|
|
88
|
+
"y_major_tickmark": constants.xlTickMarkInside,
|
|
89
|
+
},
|
|
90
|
+
"grid":{
|
|
91
|
+
"title_font_name": "Arial",
|
|
92
|
+
"axis_title_font_name": "Arial",
|
|
93
|
+
"axis_tick_font_name": "Arial",
|
|
94
|
+
"title_font_bold": True,
|
|
95
|
+
"axis_title_font_bold": True,
|
|
96
|
+
"axis_tick_font_bold": True,
|
|
97
|
+
"major_grid_color": RGB(0, 0, 0),
|
|
98
|
+
"axis_line_color": RGB(0, 0, 0),
|
|
99
|
+
"x_major_tickmark": constants.xlTickMarkOutside,
|
|
100
|
+
"y_major_tickmark": constants.xlTickMarkOutside,
|
|
101
|
+
"frame_weight": 1,
|
|
102
|
+
"major_grid_weight": 1,
|
|
103
|
+
"axis_line_weight": 1,
|
|
104
|
+
"axis_title_font_size": 12,
|
|
105
|
+
"axis_tick_font_size": 12,
|
|
106
|
+
"title_font_size": 14,
|
|
107
|
+
},
|
|
108
|
+
"no_change":{
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
# std を excel2021 ベースで上書き
|
|
112
|
+
PRESET["std"] = {**PRESET["excel2021"], **PRESET["std"]}
|
|
113
|
+
# std ベースで上書き
|
|
114
|
+
PRESET["no_grid"] = {**PRESET["std"], **PRESET["no_grid"]}
|
|
115
|
+
PRESET["grid"] = {**PRESET["std"], **PRESET["grid"]}
|
|
116
|
+
|
|
117
|
+
marker_map = {
|
|
118
|
+
"C":constants.xlMarkerStyleCircle,
|
|
119
|
+
"S":constants.xlMarkerStyleSquare,
|
|
120
|
+
"D":constants.xlMarkerStyleDiamond,
|
|
121
|
+
"T":constants.xlMarkerStyleTriangle,
|
|
122
|
+
"N":constants.xlMarkerStyleNone
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# False:無効化, None: 変更なし もしくは デフォルト値
|
|
126
|
+
# 優先順位 series cfg > 引数 > preset
|
|
127
|
+
def ModifyChart(chart, # ExcelのChartオブジェクト
|
|
128
|
+
ws = None, # None (系列ごとにRange指定する場合のみWorksheetオブジェクトを指定)
|
|
129
|
+
preset = "std", # プリセットスタイル名
|
|
130
|
+
width_cm = None, # グラフ幅(cm), Noneで変更なし
|
|
131
|
+
height_cm = None, # グラフ高さ(cm), Noneで変更なし
|
|
132
|
+
name = None, # グラフ名変更, Noneで変更なし
|
|
133
|
+
title: str|bool|None = None, # タイトル文字列, Falseで無効化, Noneで変更なし
|
|
134
|
+
title_font_color = None, # タイトルフォント色(RGB)
|
|
135
|
+
title_font_size = None, # タイトルフォントサイズ
|
|
136
|
+
title_space = +0, # タイトルとグラフの間隔(pt)
|
|
137
|
+
NS = 1, # データ系列数
|
|
138
|
+
series_list: list[dict]|None = None, # 各系列の設定
|
|
139
|
+
style = None, # 線+マーカーのスタイル
|
|
140
|
+
smooth = None, # Trueで曲線、Falseで折れ線
|
|
141
|
+
marker = None, # マーカー種類: "C":●, "S":■, "D":◆, "T":▲, "N":なし
|
|
142
|
+
alpha = None, # 線の透明度(0~1)
|
|
143
|
+
x_title: str|bool|None = None,# X軸タイトル文字列。Falseで無効化、Noneで変更なし
|
|
144
|
+
x_title_space = +0, # プロットエリアを下側に広げる場合はマイナス
|
|
145
|
+
x_min: float|str|None = None, # X軸最小値, "auto"で自動調整, Noneで変更なし
|
|
146
|
+
x_max: float|str|None = None, # X軸最大値, "auto"で自動調整, Noneで変更なし
|
|
147
|
+
x_major: float|None = None, # X軸主目盛間隔, Noneで変更なし, グリッド線表示設定はPRESET
|
|
148
|
+
x_minor: float|None = None, # X軸副目盛間隔, Noneで変更なし, グリッド線表示設定はPRESET
|
|
149
|
+
x_cross = None, # Y軸との交差位置, Noneで変更なし
|
|
150
|
+
x_format = None, # X軸表示形式 ("0.00", "0.0E+00", "0%" など), Noneで変更なし
|
|
151
|
+
x_log: bool|None = None, # Trueで対数表示, Noneで変更なし
|
|
152
|
+
y_title: str|bool|None = None,# Y軸タイトル, Noneで変更なし
|
|
153
|
+
y_title_space = +0,
|
|
154
|
+
y_min: float|str|None = None,
|
|
155
|
+
y_max: float|str|None = None,
|
|
156
|
+
y_major: float|None = None,
|
|
157
|
+
y_minor: float|None = None,
|
|
158
|
+
y_cross = None, # X軸との交差位置, Noneで変更なし
|
|
159
|
+
y_format = None,
|
|
160
|
+
y_log: bool|None = None,
|
|
161
|
+
y2_title: str|bool|None = None, # Y軸タイトル文字列, Falseで無効化, Noneで変更なし
|
|
162
|
+
y2_min: float|str|None = None,
|
|
163
|
+
y2_max: float|str|None = None,
|
|
164
|
+
y2_major: float|None = None,
|
|
165
|
+
y2_minor: float|None = None,
|
|
166
|
+
y2_format = None,
|
|
167
|
+
y2_log: bool|None = None,
|
|
168
|
+
frame_color: bool|int|None = None, # グラフ枠色 (False:枠なし, 0:黒枠, Noneでpreset)
|
|
169
|
+
legend: bool|str|None = None, # 凡例表示(False:非表示, Noneで変更なし, "right"で右側に表示)
|
|
170
|
+
legend_font_size = None, # 凡例のフォントサイズ, Noneで変更なし
|
|
171
|
+
legend_width_inc = 0, # 凡例ボックスの幅増減(pt)
|
|
172
|
+
legend_height_inc = 0, # 凡例ボックスの高さ増減(pt)
|
|
173
|
+
legend_right_space = 0, # 凡例の右端 = プロットエリアの右端を基準とした凡例位置制御
|
|
174
|
+
legent_top_space = 0, # 凡例の上端 = プロットエリアの上端を基準とした凡例位置制御
|
|
175
|
+
transparent_bg: bool|None = None, # 背景を透明化する場合はTrue
|
|
176
|
+
chart_type = None, # "bar"で棒グラフに変更
|
|
177
|
+
x_bold_line: float|None = None, # x_bold_line=0でx=0が太線
|
|
178
|
+
y_bold_line: float|None = None, # y_bold_line=0でy=0が太線
|
|
179
|
+
plot_area_space: str = "relative", # プロットエリア調整時の基準位置を"absolute"/"relative"で指定
|
|
180
|
+
width_inc = 0, # プロットエリアの幅増減(pt)
|
|
181
|
+
height_inc = 0, # プロットエリアの高さ増減(pt)
|
|
182
|
+
right_space = 0, # plot_area_space="abs"のときの右のスペース指定
|
|
183
|
+
):
|
|
184
|
+
|
|
185
|
+
p = PRESET.get(preset, PRESET["std"]) or {}
|
|
186
|
+
|
|
187
|
+
# 注意) series_listの系列数をデータ範囲以下にしないと例外発生となる
|
|
188
|
+
# list / dict はミュータブルのため、デフォルト引数は None
|
|
189
|
+
if series_list is None:
|
|
190
|
+
series_list = [{"color": "blue"}]
|
|
191
|
+
|
|
192
|
+
COLOR_NAME_TO_RGB = {
|
|
193
|
+
"blue": RGB(68,114,196),
|
|
194
|
+
"orange":RGB(237,125,49),
|
|
195
|
+
"green":RGB(112,173,71),
|
|
196
|
+
"yellow":RGB(255,192,0),
|
|
197
|
+
"purple":RGB(112,48,160),
|
|
198
|
+
"brown":RGB(192,0,0),
|
|
199
|
+
"navy":RGB(0,32,96),
|
|
200
|
+
"gray":RGB(165,165,165),
|
|
201
|
+
"teal":RGB(0,128,128),
|
|
202
|
+
"cyan": RGB(0,255,255),
|
|
203
|
+
"magenta": RGB(255,0,255),
|
|
204
|
+
}
|
|
205
|
+
for cfg in series_list:
|
|
206
|
+
c = cfg.get("color")
|
|
207
|
+
if isinstance(c, str):
|
|
208
|
+
cfg["color"] = COLOR_NAME_TO_RGB.get(c.lower(), None)
|
|
209
|
+
|
|
210
|
+
# グラフ全体のサイズ変更
|
|
211
|
+
if width_cm not in (None, "", 0):
|
|
212
|
+
chart.width = cm_to_pt(width_cm)
|
|
213
|
+
if height_cm not in (None, "", 0):
|
|
214
|
+
chart.height = cm_to_pt(height_cm)
|
|
215
|
+
|
|
216
|
+
# グラフのチャート名 (エクセル画面左上の表示で確認できる)
|
|
217
|
+
if name not in (None, ""):
|
|
218
|
+
chart.name = name
|
|
219
|
+
|
|
220
|
+
# Chart本体 → api[1]
|
|
221
|
+
ch = chart.api[1]
|
|
222
|
+
|
|
223
|
+
# グラフタイトル
|
|
224
|
+
if title is False:
|
|
225
|
+
ch.HasTitle = True
|
|
226
|
+
ch.ChartTitle.Text = ""
|
|
227
|
+
elif title is True:
|
|
228
|
+
ch.HasTitle = True
|
|
229
|
+
ch.ChartTitle.Text = "グラフタイトル"
|
|
230
|
+
elif title not in (None, ""):
|
|
231
|
+
ch.HasTitle = True
|
|
232
|
+
ch.ChartTitle.Text = title
|
|
233
|
+
|
|
234
|
+
# 横軸のオプション
|
|
235
|
+
x_axis = ch.Axes(AxisType.xlCategory)
|
|
236
|
+
if x_min == "auto":
|
|
237
|
+
x_axis.MinimumScaleIsAuto = True
|
|
238
|
+
elif x_min not in (None, ""):
|
|
239
|
+
x_axis.MinimumScale = x_min # 最小値
|
|
240
|
+
if x_max == "auto":
|
|
241
|
+
x_axis.MaximumScaleIsAuto = True
|
|
242
|
+
elif x_max not in (None, ""):
|
|
243
|
+
x_axis.MaximumScale = x_max # 最大値
|
|
244
|
+
if p.get("x_major_grid") is not None:
|
|
245
|
+
x_axis.HasMajorGridlines = p.get("x_major_grid")
|
|
246
|
+
if p.get("x_major_tickmark") is not None:
|
|
247
|
+
x_axis.MajorTickMark = p.get("x_major_tickmark")
|
|
248
|
+
if p.get("x_minor_grid") is not None:
|
|
249
|
+
x_axis.HasMinorGridlines = p.get("x_minor_grid")
|
|
250
|
+
if p.get("x_minor_tickmark") is not None:
|
|
251
|
+
x_axis.MinorTickMark = p.get("x_minor_tickmark")
|
|
252
|
+
if x_major not in (None, ""):
|
|
253
|
+
x_axis.MajorUnit = x_major # 目盛間隔
|
|
254
|
+
if x_minor not in (None, ""):
|
|
255
|
+
x_axis.MinorUnit = x_minor
|
|
256
|
+
if x_cross not in (None, ""): # 交差位置(縦軸との交点)
|
|
257
|
+
x_axis.CrossesAt = x_cross
|
|
258
|
+
if x_format not in (None, ""):
|
|
259
|
+
x_axis.TickLabels.NumberFormatLocal = x_format
|
|
260
|
+
if x_log not in (None, ""):
|
|
261
|
+
x_axis.ScaleType = 1 if x_log == True else 0
|
|
262
|
+
|
|
263
|
+
# 横軸のタイトル
|
|
264
|
+
if x_title is False:
|
|
265
|
+
x_axis.HasTitle = False
|
|
266
|
+
elif x_title is True:
|
|
267
|
+
x_axis.HasTitle = True
|
|
268
|
+
x_axis.AxisTitle.Text = "x軸タイトル"
|
|
269
|
+
elif x_title not in (None, ""):
|
|
270
|
+
x_axis.HasTitle = True
|
|
271
|
+
x_axis.AxisTitle.Text = x_title
|
|
272
|
+
|
|
273
|
+
# 縦軸のオプション
|
|
274
|
+
y_axis = ch.Axes(AxisType.xlValue)
|
|
275
|
+
if y_min == "auto":
|
|
276
|
+
y_axis.MinimumScaleIsAuto = True
|
|
277
|
+
elif y_min not in (None, ""):
|
|
278
|
+
y_axis.MinimumScale = y_min
|
|
279
|
+
if y_max == "auto":
|
|
280
|
+
y_axis.MaximumScaleIsAuto = True
|
|
281
|
+
elif y_max not in (None, ""):
|
|
282
|
+
y_axis.MaximumScale = y_max
|
|
283
|
+
if p.get("y_major_grid") is not None:
|
|
284
|
+
y_axis.HasMajorGridlines = p.get("y_major_grid")
|
|
285
|
+
if p.get("y_major_tickmark") is not None:
|
|
286
|
+
y_axis.MajorTickMark = p.get("y_major_tickmark")
|
|
287
|
+
if p.get("y_minor_grid") is not None:
|
|
288
|
+
y_axis.HasMinorGridlines = p.get("y_minor_grid")
|
|
289
|
+
if p.get("y_minor_tickmark") is not None:
|
|
290
|
+
y_axis.MinorTickMark = p.get("y_minor_tickmark")
|
|
291
|
+
if y_major not in (None, ""):
|
|
292
|
+
y_axis.MajorUnit = y_major # 目盛間隔
|
|
293
|
+
if y_minor not in (None, ""):
|
|
294
|
+
y_axis.MinorUnit = y_minor
|
|
295
|
+
if y_cross not in (None, ""): # 交差位置(縦軸との交点)
|
|
296
|
+
y_axis.CrossesAt = y_cross
|
|
297
|
+
if y_format not in (None, ""):
|
|
298
|
+
y_axis.TickLabels.NumberFormatLocal = y_format
|
|
299
|
+
if y_log not in (None, ""):
|
|
300
|
+
y_axis.ScaleType = 1 if y_log == True else 0
|
|
301
|
+
|
|
302
|
+
# 縦軸のタイトル
|
|
303
|
+
if y_title is False:
|
|
304
|
+
y_axis.HasTitle = False
|
|
305
|
+
elif y_title is True:
|
|
306
|
+
y_axis.HasTitle = True
|
|
307
|
+
y_axis.AxisTitle.Text = "y軸タイトル"
|
|
308
|
+
elif y_title not in (None, ""):
|
|
309
|
+
y_axis.HasTitle = True
|
|
310
|
+
y_axis.AxisTitle.Text = y_title
|
|
311
|
+
|
|
312
|
+
if chart_type not in (None,""):
|
|
313
|
+
if chart_type == "bar":
|
|
314
|
+
chart_type = "column_clustered"
|
|
315
|
+
chart.chart_type = chart_type
|
|
316
|
+
|
|
317
|
+
# 系列の設定 -----------------------------------------------------------------------------
|
|
318
|
+
use_secondary = False
|
|
319
|
+
NS = max(len(series_list), NS)
|
|
320
|
+
for i in range(1, NS + 1):
|
|
321
|
+
if i <= len(series_list):
|
|
322
|
+
cfg = series_list[i - 1]
|
|
323
|
+
else:
|
|
324
|
+
cfg = {}
|
|
325
|
+
try:
|
|
326
|
+
series = ch.SeriesCollection(i)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
print(f"系列{i}を取得できません: {e}")
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
# 系列名
|
|
332
|
+
if cfg.get("name") not in (None, ""):
|
|
333
|
+
series.Name = cfg["name"]
|
|
334
|
+
|
|
335
|
+
# XValues / Values
|
|
336
|
+
if cfg.get("XValues") not in (None, ""):
|
|
337
|
+
try:
|
|
338
|
+
wsx = cfg.get("sheet",ws)
|
|
339
|
+
if isinstance(wsx, str):
|
|
340
|
+
wsx = ws.parent.sheets[wsx]
|
|
341
|
+
series.XValues = wsx.range(cfg["XValues"]).api
|
|
342
|
+
except Exception:
|
|
343
|
+
print(f"系列{i}: wsの設定または範囲指定に問題")
|
|
344
|
+
if cfg.get("Values") not in (None, ""):
|
|
345
|
+
try:
|
|
346
|
+
wsy = cfg.get("sheet",ws)
|
|
347
|
+
if isinstance(wsy, str):
|
|
348
|
+
wsy = ws.parent.sheets[wsy]
|
|
349
|
+
series.Values = wsy.range(cfg["Values"]).api
|
|
350
|
+
except Exception:
|
|
351
|
+
print(f"系列{i}: wsの設定または範囲指定に問題")
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
if cfg.get("chart_type") == "bar":
|
|
355
|
+
series.ChartType = constants.xlColumnClustered
|
|
356
|
+
|
|
357
|
+
# 色
|
|
358
|
+
color = cfg.get("color")
|
|
359
|
+
if color not in (None, ""):
|
|
360
|
+
try:
|
|
361
|
+
series.Format.Line.ForeColor.RGB = color # 線の色
|
|
362
|
+
series.MarkerForegroundColor = color # マーカー枠線の色
|
|
363
|
+
series.MarkerBackgroundColor = color # マーカー内部の色
|
|
364
|
+
except:
|
|
365
|
+
# hasattr(series.Format, "Fill")
|
|
366
|
+
series.Format.Fill.ForeColor.RGB = color
|
|
367
|
+
|
|
368
|
+
# スムーズ
|
|
369
|
+
if cfg.get("smooth") not in (None, ""):
|
|
370
|
+
smooth_i = cfg["smooth"]
|
|
371
|
+
elif smooth not in (None, ""):
|
|
372
|
+
smooth_i = smooth
|
|
373
|
+
else:
|
|
374
|
+
smooth_i = p.get("smooth",True)
|
|
375
|
+
if smooth_i not in (None, ""):
|
|
376
|
+
try:
|
|
377
|
+
series.Smooth = bool(smooth_i)
|
|
378
|
+
except:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
# スタイル(線とマーカー)
|
|
382
|
+
if cfg.get("style") not in (None, ""):
|
|
383
|
+
style_i = cfg["style"]
|
|
384
|
+
elif style not in (None, ""):
|
|
385
|
+
style_i = style
|
|
386
|
+
else:
|
|
387
|
+
style_i = p.get("style") or None
|
|
388
|
+
|
|
389
|
+
if style_i is None:
|
|
390
|
+
pass
|
|
391
|
+
elif "marker" in style_i:
|
|
392
|
+
if cfg.get("marker") not in (None, ""):
|
|
393
|
+
marker_i = cfg["marker"]
|
|
394
|
+
elif marker not in (None, ""):
|
|
395
|
+
marker_i = marker
|
|
396
|
+
elif p.get("marker") not in (None, ""):
|
|
397
|
+
marker_i = p["marker"]
|
|
398
|
+
else:
|
|
399
|
+
marker_i = None
|
|
400
|
+
series.MarkerStyle = marker_map.get(marker_i, constants.xlMarkerStyleCircle) # マーカー:〇
|
|
401
|
+
series.MarkerSize = cfg.get("size",p.get("markersize")) # マーカーサイズ
|
|
402
|
+
else:
|
|
403
|
+
series.MarkerStyle = constants.xlMarkerStyleNone
|
|
404
|
+
|
|
405
|
+
if style_i is None:
|
|
406
|
+
pass
|
|
407
|
+
elif "line" in style_i:
|
|
408
|
+
series.Format.Line.Visible = True
|
|
409
|
+
series.Format.Line.Weight = cfg.get("weight", p.get("line_weight")) # 線の太さ(pt)
|
|
410
|
+
elif isinstance(style_i, str) and ("dash" in style_i or "--" in style_i):
|
|
411
|
+
series.Format.Line.Visible = True
|
|
412
|
+
series.Format.Line.DashStyle = 4
|
|
413
|
+
elif isinstance(style_i, str) and ("chain" in style_i or "-." in style_i):
|
|
414
|
+
series.Format.Line.Visible = True
|
|
415
|
+
series.Format.Line.DashStyle = 5
|
|
416
|
+
else:
|
|
417
|
+
series.Format.Line.Visible = False
|
|
418
|
+
|
|
419
|
+
# 線の透明度の設定(0~1), デフォルト値は0
|
|
420
|
+
if cfg.get("alpha") not in (None, ""):
|
|
421
|
+
alpha_i = cfg["alpha"]
|
|
422
|
+
elif alpha not in (None, ""):
|
|
423
|
+
alpha_i = alpha
|
|
424
|
+
elif p.get("alpha") not in (None, ""):
|
|
425
|
+
alpha_i = p["alpha"]
|
|
426
|
+
else:
|
|
427
|
+
alpha_i = None
|
|
428
|
+
if alpha_i not in (None, ""):
|
|
429
|
+
series.Format.Line.Transparency = float(alpha_i)
|
|
430
|
+
|
|
431
|
+
axis_name = cfg.get("axis", "primary")
|
|
432
|
+
if axis_name == "secondary" or axis_name == "y2":
|
|
433
|
+
series.AxisGroup = constants.xlSecondary
|
|
434
|
+
use_secondary = True
|
|
435
|
+
else:
|
|
436
|
+
series.AxisGroup = constants.xlPrimary
|
|
437
|
+
|
|
438
|
+
tl = cfg.get("trendline")
|
|
439
|
+
if tl not in (None, ""):
|
|
440
|
+
try:
|
|
441
|
+
series.Trendlines().Delete()
|
|
442
|
+
except Exception:
|
|
443
|
+
pass
|
|
444
|
+
trend = None
|
|
445
|
+
if tl == 0:
|
|
446
|
+
pass
|
|
447
|
+
elif isinstance(tl, int):
|
|
448
|
+
if tl == 1:
|
|
449
|
+
trend = series.Trendlines().Add(Type=constants.xlLinear)
|
|
450
|
+
elif 2 <= tl <= 6:
|
|
451
|
+
trend = series.Trendlines().Add(Type=constants.xlPolynomial)
|
|
452
|
+
trend.Order = tl
|
|
453
|
+
elif isinstance(tl, str):
|
|
454
|
+
tl_map = {
|
|
455
|
+
"exp": constants.xlExponential, # 指数
|
|
456
|
+
"log": constants.xlLogarithmic, # 対数
|
|
457
|
+
"pow": constants.xlPower, # 累乗
|
|
458
|
+
"mov": constants.xlMovingAvg, # 移動平均
|
|
459
|
+
}
|
|
460
|
+
ttype = tl_map.get(tl.lower())
|
|
461
|
+
if ttype is not None:
|
|
462
|
+
trend = series.Trendlines().Add(Type=ttype)
|
|
463
|
+
if trend is not None:
|
|
464
|
+
if cfg.get("trendline_name") is not None:
|
|
465
|
+
trend.Name = cfg.get("trendline_name", "Regression line")
|
|
466
|
+
trend.Format.Line.ForeColor.RGB = cfg.get("trendline_color",cfg.get("color",RGB(0,0,0)))
|
|
467
|
+
trend.Format.Line.Weight = cfg.get("trendline_weight", 1.5)
|
|
468
|
+
Dashstyle = cfg.get("trendline_style", "solid").lower()
|
|
469
|
+
if Dashstyle in ("dash", "--"):
|
|
470
|
+
trend.Format.Line.DashStyle = 4
|
|
471
|
+
elif Dashstyle in ("chain", "-."):
|
|
472
|
+
trend.Format.Line.DashStyle = 5
|
|
473
|
+
else: # solid / default
|
|
474
|
+
# trend.Format.Line.DashStyle = constants.xlSolid
|
|
475
|
+
pass
|
|
476
|
+
t_option = cfg.get("trendline_option", "")
|
|
477
|
+
if "eq" in t_option:
|
|
478
|
+
trend.DisplayEquation = True # 近似式を表示
|
|
479
|
+
# trend.DataLabel.Left
|
|
480
|
+
# trend.DataLabel.Top
|
|
481
|
+
if "r2" in t_option:
|
|
482
|
+
trend.DisplayRSquared = True # 決定係数(R²)を表示
|
|
483
|
+
|
|
484
|
+
if cfg.get("legend") is False:
|
|
485
|
+
series.HasLegendKey = False
|
|
486
|
+
|
|
487
|
+
if cfg.get("data_label", False):
|
|
488
|
+
series.HasDataLabels = True
|
|
489
|
+
|
|
490
|
+
except Exception as e:
|
|
491
|
+
print(f"系列{i}の設定でエラー発生:{e}")
|
|
492
|
+
|
|
493
|
+
# フォーマットの設定 -------------------------------------------------
|
|
494
|
+
try:
|
|
495
|
+
# タイトルの設定
|
|
496
|
+
if ch.HasTitle:
|
|
497
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Bold = p.get("title_font_bold")
|
|
498
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Name = p.get("title_font_name")
|
|
499
|
+
if title_font_color not in (None, ""):
|
|
500
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = title_font_color
|
|
501
|
+
else:
|
|
502
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = p.get("title_font_color")
|
|
503
|
+
if title_font_size not in (None, ""):
|
|
504
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Size = title_font_size
|
|
505
|
+
else:
|
|
506
|
+
ch.ChartTitle.Format.TextFrame2.TextRange.Font.Size = p.get("title_font_size")
|
|
507
|
+
except Exception as e:
|
|
508
|
+
print("タイトルの設定でエラー:",e)
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
# グリッド線の設定
|
|
512
|
+
axes = [x_axis, y_axis]
|
|
513
|
+
for ax in axes:
|
|
514
|
+
if ax.HasMajorGridlines:
|
|
515
|
+
ax.MajorGridlines.Format.Line.ForeColor.RGB = p.get("major_grid_color")
|
|
516
|
+
ax.MajorGridlines.Format.Line.Weight = p.get("major_grid_weight")
|
|
517
|
+
if p.get("axis_line") is None:
|
|
518
|
+
pass
|
|
519
|
+
elif p.get("axis_line") is True:
|
|
520
|
+
ax.Format.Line.Visible = True
|
|
521
|
+
ax.Format.Line.ForeColor.RGB = p.get("axis_line_color")
|
|
522
|
+
ax.Format.Line.Weight = p.get("axis_line_weight")
|
|
523
|
+
elif p.get("axis_line") is False:
|
|
524
|
+
ax.Format.Line.Visible = False
|
|
525
|
+
except Exception as e:
|
|
526
|
+
print("グリッド線の設定でエラー:",e)
|
|
527
|
+
|
|
528
|
+
try:
|
|
529
|
+
# 副軸の設定
|
|
530
|
+
if use_secondary:
|
|
531
|
+
y2 = ch.Axes(AxisType.xlValue, constants.xlSecondary)
|
|
532
|
+
axes.append(y2)
|
|
533
|
+
if y2_min == "auto":
|
|
534
|
+
y2.MinimumScaleIsAuto = True
|
|
535
|
+
elif y2_min not in (None, ""):
|
|
536
|
+
y2.MinimumScale = y2_min
|
|
537
|
+
if y2_max == "auto":
|
|
538
|
+
y2.MaximumScaleIsAuto = True
|
|
539
|
+
elif y2_max not in (None, ""):
|
|
540
|
+
y2.MaximumScale = y2_max
|
|
541
|
+
if p.get("y2_major_grid") is not None:
|
|
542
|
+
y2.HasMajorGridlines = p.get("y2_major_grid")
|
|
543
|
+
if p.get("y2_major_tickmark") is not None:
|
|
544
|
+
y2.MajorTickMark = p.get("y2_major_tickmark")
|
|
545
|
+
if p.get("y2_minor_grid") is not None:
|
|
546
|
+
y2.HasMinorGridlines = p.get("y2_minor_grid")
|
|
547
|
+
if p.get("y2_minor_tickmark") is not None:
|
|
548
|
+
y2.MinorTickMark = p.get("y2_minor_tickmark")
|
|
549
|
+
if y2_major not in (None, ""):
|
|
550
|
+
y2.MajorUnit = y2_major
|
|
551
|
+
if y2_minor not in (None, ""):
|
|
552
|
+
y2.MinorUnit = y2_minor
|
|
553
|
+
if y2_format not in (None, ""):
|
|
554
|
+
y2.TickLabels.NumberFormatLocal = y2_format
|
|
555
|
+
if y2_log not in (None, ""):
|
|
556
|
+
y2.ScaleType = 1 if y2_log == True else 0
|
|
557
|
+
if y2_title is False:
|
|
558
|
+
y2.HasTitle = False
|
|
559
|
+
elif y2_title is True:
|
|
560
|
+
y2.HasTitle = True
|
|
561
|
+
y2.AxisTitle.Text = "副軸タイトル"
|
|
562
|
+
elif y2_title not in (None, ""):
|
|
563
|
+
y2.HasTitle = True
|
|
564
|
+
y2.AxisTitle.Text = y2_title
|
|
565
|
+
except Exception as e:
|
|
566
|
+
print("副軸の設定でエラー:",e)
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
# 軸のフォント設定
|
|
570
|
+
for ax in axes:
|
|
571
|
+
tl_font = ax.TickLabels.Font
|
|
572
|
+
tl_font.Color = p.get("axis_tick_font_color")
|
|
573
|
+
tl_font.Size = p.get("axis_tick_font_size")
|
|
574
|
+
tl_font.Name = p.get("axis_tick_font_name")
|
|
575
|
+
tl_font.Bold = p.get("axis_tick_font_bold")
|
|
576
|
+
if ax.HasTitle:
|
|
577
|
+
ax_font = ax.AxisTitle.Format.TextFrame2.TextRange.Font
|
|
578
|
+
ax_font.Fill.ForeColor.RGB = p.get("axis_title_font_color")
|
|
579
|
+
ax_font.Bold = p.get("axis_title_font_bold")
|
|
580
|
+
ax_font.Size = p.get("axis_title_font_size")
|
|
581
|
+
ax_font.Name = p.get("axis_title_font_name")
|
|
582
|
+
except Exception as e:
|
|
583
|
+
print("軸のフォント設定でエラー:",e)
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
# chart_area/plot_area のオブジェクト取得
|
|
587
|
+
chart_area = ch.ChartArea
|
|
588
|
+
plot_area = ch.PlotArea
|
|
589
|
+
|
|
590
|
+
# 外枠の設定
|
|
591
|
+
if frame_color is False: # False:枠なし、0:黒枠
|
|
592
|
+
chart_area.Border.LineStyle = 0 # 枠なし
|
|
593
|
+
elif frame_color not in (None, True, ""):
|
|
594
|
+
chart_area.Format.Line.ForeColor.RGB = frame_color
|
|
595
|
+
chart_area.Format.Line.Weight = p.get("frame_weight")
|
|
596
|
+
else:
|
|
597
|
+
chart_area.Format.Line.ForeColor.RGB = p.get("frame_color") # 薄いグレー
|
|
598
|
+
chart_area.Format.Line.Weight = p.get("frame_weight") # 枠線の太さ(pt)
|
|
599
|
+
|
|
600
|
+
# プロットエリアの枠設定
|
|
601
|
+
if p.get("plot_area_frame") is not None:
|
|
602
|
+
plot_area.Format.Line.Visible = p.get("plot_area_frame")
|
|
603
|
+
if p.get("plot_area_frame") is True:
|
|
604
|
+
plot_area.Format.Line.ForeColor.RGB = p.get("plot_area_frame_color")
|
|
605
|
+
plot_area.Format.Line.Weight = p.get("plot_area_frame_weight")
|
|
606
|
+
|
|
607
|
+
# 背景の透明化設定
|
|
608
|
+
if transparent_bg is True:
|
|
609
|
+
chart_area.Format.Fill.Visible = False
|
|
610
|
+
plot_area.Format.Fill.Visible = False
|
|
611
|
+
elif transparent_bg is False:
|
|
612
|
+
chart_area.Format.Fill.Visible = True
|
|
613
|
+
plot_area.Format.Fill.Visible = True
|
|
614
|
+
chart_area.Format.Fill.ForeColor.RGB = RGB(255,255,255)
|
|
615
|
+
plot_area.Format.Fill.ForeColor.RGB = RGB(255,255,255)
|
|
616
|
+
|
|
617
|
+
# ラインの強調
|
|
618
|
+
if x_bold_line is not None:
|
|
619
|
+
emphasize_line(x_axis,x_bold_line)
|
|
620
|
+
if y_bold_line is not None:
|
|
621
|
+
emphasize_line(y_axis,y_bold_line)
|
|
622
|
+
except Exception as e:
|
|
623
|
+
print("チャートエリア、プロットエリアの設定でエラー:",e)
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
# 凡例を一度無効にする(例外あり)
|
|
627
|
+
if isinstance(legend, str) and "right" in legend:
|
|
628
|
+
ch.HasLegend = True
|
|
629
|
+
if legend_font_size not in (None, ""):
|
|
630
|
+
ch.Legend.Format.TextFrame2.TextRange.Font.Size = legend_font_size
|
|
631
|
+
elif legend not in (None, ""):
|
|
632
|
+
ch.HasLegend = False
|
|
633
|
+
|
|
634
|
+
# プロットエリアの調整
|
|
635
|
+
debug = False
|
|
636
|
+
if plot_area_space in ("absolute","abs"):
|
|
637
|
+
|
|
638
|
+
def fit_inside(chart, L=0, T=0, R=0, B=0, it=6, tol=0.8):
|
|
639
|
+
ch = chart.api[1]
|
|
640
|
+
co, ca, pl = ch.Parent, ch.ChartArea, ch.PlotArea
|
|
641
|
+
pl.Position = constants.xlChartElementPositionCustom
|
|
642
|
+
|
|
643
|
+
tgtL = float(co.Left) + float(L)
|
|
644
|
+
tgtT = float(co.Top) + float(T)
|
|
645
|
+
tgtR = float(co.Left) + float(co.Width) - float(R)
|
|
646
|
+
tgtB = float(co.Top) + float(co.Height) - float(B)
|
|
647
|
+
|
|
648
|
+
for _ in range(it):
|
|
649
|
+
# 現在の Inside(シート基準)を読む
|
|
650
|
+
curL = float(ca.Left) + float(pl.InsideLeft)
|
|
651
|
+
curT = float(ca.Top) + float(pl.InsideTop)
|
|
652
|
+
curR = curL + float(pl.InsideWidth)
|
|
653
|
+
curB = curT + float(pl.InsideHeight)
|
|
654
|
+
|
|
655
|
+
# --- 左(右端固定で合わせる)---
|
|
656
|
+
dL = tgtL - curL
|
|
657
|
+
if abs(dL) > tol:
|
|
658
|
+
# 右端(外形の Right)を維持するように、Left を動かし Width を逆側に補正
|
|
659
|
+
right_outer = float(pl.Left) + float(pl.Width)
|
|
660
|
+
new_left = float(pl.Left) + dL
|
|
661
|
+
new_width = max(right_outer - new_left, 10.0)
|
|
662
|
+
pl.Left, pl.Width = new_left, new_width
|
|
663
|
+
|
|
664
|
+
# Inside 再読込(安定させるため)
|
|
665
|
+
curL = float(ca.Left) + float(pl.InsideLeft)
|
|
666
|
+
|
|
667
|
+
# --- 右(Left 固定で合わせる)---
|
|
668
|
+
curR = float(ca.Left) + float(pl.InsideLeft) + float(pl.InsideWidth)
|
|
669
|
+
dR = tgtR - curR
|
|
670
|
+
if abs(dR) > tol:
|
|
671
|
+
# Left 外形は固定、Width だけ増減
|
|
672
|
+
pl.Width = max(float(pl.Width) + dR, 10.0)
|
|
673
|
+
|
|
674
|
+
# --- 上(下端固定で合わせる)---
|
|
675
|
+
curT = float(ca.Top) + float(pl.InsideTop)
|
|
676
|
+
dT = tgtT - curT
|
|
677
|
+
if abs(dT) > tol:
|
|
678
|
+
bottom_outer = float(pl.Top) + float(pl.Height)
|
|
679
|
+
new_top = float(pl.Top) + dT
|
|
680
|
+
new_height = max(bottom_outer - new_top, 10.0)
|
|
681
|
+
pl.Top, pl.Height = new_top, new_height
|
|
682
|
+
|
|
683
|
+
# --- 下(Top 固定で合わせる)---
|
|
684
|
+
curB = float(ca.Top) + float(pl.InsideTop) + float(pl.InsideHeight)
|
|
685
|
+
dB = tgtB - curB
|
|
686
|
+
if abs(dB) > tol:
|
|
687
|
+
pl.Height = max(float(pl.Height) + dB, 10.0)
|
|
688
|
+
|
|
689
|
+
if debug:
|
|
690
|
+
print(
|
|
691
|
+
f"PlotArea\n"
|
|
692
|
+
f"Outer (sheet): "
|
|
693
|
+
f"Left={pl.Left:.1f}, Top={pl.Top:.1f}, "
|
|
694
|
+
f"Width={pl.Width:.1f}, Height={pl.Height:.1f} \n"
|
|
695
|
+
f"Inside (chart): "
|
|
696
|
+
f"Left={pl.InsideLeft:.1f}, Top={pl.InsideTop:.1f}, "
|
|
697
|
+
f"Width={pl.InsideWidth:.1f}, Height={pl.InsideHeight:.1f}"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
fit_inside(chart, L=y_title_space+42, T=title_space+35, R=right_space+20, B=x_title_space+40)
|
|
701
|
+
|
|
702
|
+
else: # relative / dafault
|
|
703
|
+
left = plot_area.InsideLeft + y_title_space
|
|
704
|
+
top = plot_area.InsideTop + title_space
|
|
705
|
+
width = plot_area.InsideWidth - y_title_space + width_inc
|
|
706
|
+
height = plot_area.InsideHeight - title_space - x_title_space + height_inc
|
|
707
|
+
|
|
708
|
+
if debug:
|
|
709
|
+
print(
|
|
710
|
+
f"PlotArea\n"
|
|
711
|
+
f"Inside (chart): "
|
|
712
|
+
f"Left={left:.1f}, Top={top:.1f}, "
|
|
713
|
+
f"Width={width:.1f}, Height={height:.1f}"
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
plot_area.InsideLeft = left
|
|
717
|
+
plot_area.InsideTop = top
|
|
718
|
+
plot_area.InsideWidth = width
|
|
719
|
+
plot_area.InsideHeight = height
|
|
720
|
+
|
|
721
|
+
# 凡例設定
|
|
722
|
+
if legend in (None, ""):
|
|
723
|
+
pass
|
|
724
|
+
elif legend is False:
|
|
725
|
+
ch.HasLegend = False
|
|
726
|
+
else:
|
|
727
|
+
ch.HasLegend = True
|
|
728
|
+
if legend_font_size not in (None, ""):
|
|
729
|
+
ch.Legend.Format.TextFrame2.TextRange.Font.Size = legend_font_size
|
|
730
|
+
if legend_width_inc!=0:
|
|
731
|
+
ch.Legend.Width += legend_width_inc
|
|
732
|
+
if legend_height_inc!=0:
|
|
733
|
+
ch.Legend.Height += legend_height_inc
|
|
734
|
+
if legend is True:
|
|
735
|
+
pass
|
|
736
|
+
else:
|
|
737
|
+
if "T" in legend: # 大文字のTを含む場合
|
|
738
|
+
ch.Legend.Top = plot_area.InsideTop + legent_top_space
|
|
739
|
+
elif "B" in legend: # 大文字のBを含む場合
|
|
740
|
+
ch.Legend.Top = plot_area.InsideTop + plot_area.InsideHeight - ch.Legend.Height - legent_top_space
|
|
741
|
+
if "R" in legend: # 大文字のRを含む場合
|
|
742
|
+
ch.Legend.Left = plot_area.InsideLeft + plot_area.InsideWidth - ch.Legend.Width - legend_right_space
|
|
743
|
+
elif "L" in legend: # 大文字のLを含む場合
|
|
744
|
+
ch.Legend.Left = plot_area.InsideLeft + legend_right_space
|
|
745
|
+
elif "C" in legend: # 大文字のCを含む場合
|
|
746
|
+
ch.Legend.Left = plot_area.InsideLeft + (plot_area.InsideWidth - ch.Legend.Width)/2
|
|
747
|
+
if "bw" in legend: # background white
|
|
748
|
+
ch.Legend.Format.Fill.ForeColor.RGB = RGB(255, 255, 255)
|
|
749
|
+
ch.Legend.Format.Fill.Visible = True
|
|
750
|
+
if "fb" in legend: # frame black
|
|
751
|
+
ch.Legend.Format.Line.ForeColor.RGB = RGB(0, 0, 0)
|
|
752
|
+
ch.Legend.Format.Line.Weight = 0.75
|
|
753
|
+
ch.Legend.Format.Line.Visible = True
|
|
754
|
+
if "tb" in legend: # text black
|
|
755
|
+
ch.Legend.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = RGB(0, 0, 0)
|
|
756
|
+
except Exception as e:
|
|
757
|
+
print("凡例 もしくは プロットエリアの調整でエラー:",e)
|
|
758
|
+
|
|
759
|
+
return chart
|
|
760
|
+
|
|
761
|
+
# プロットエリア内に帯や注釈用のボックスを配置する
|
|
762
|
+
def add_shape(chart, x_start=None, x_end=None, y_start=None, y_end=None,
|
|
763
|
+
left=None, width=None, top=None, height=None, delta_width=0, delta_height=0,
|
|
764
|
+
color=RGB(0,0,255), alpha=0.8, frame_color=None, white_box=None,
|
|
765
|
+
text=None, font_color=RGB(0,0,0), font_name="Calibri", font_size=10,
|
|
766
|
+
font_bold=True, alignment=None, auto_size=None,
|
|
767
|
+
right=None, bottom=None):
|
|
768
|
+
"""
|
|
769
|
+
優先順位 帯の指定(x_start, x_end) > 位置の指定(left, right) の両者指定 > left or right と width
|
|
770
|
+
(y_start, y_end) > 位置の指定(top, bottom) の両者指定 > top or bottom と height
|
|
771
|
+
1) start, x_end, y_start, y_endをすべてNoneとすることで位置 (数値pt または "数値%") で指定可能です。
|
|
772
|
+
2) left, right, top, bottomには 負の数値、"負の数値%" を指定できます。width,heightは0より大きい値を指定してください。
|
|
773
|
+
3) left + right > width もしくは top + bottom > height では負のサイズにより、エラー発生します。
|
|
774
|
+
4) delta_width, delta_heightで Excel描画都合の微調整ができます。
|
|
775
|
+
5) white_box = True では、不透明の白背景、黒枠のテキストボックスとなります。それ以外の条件はNoneにしてください。
|
|
776
|
+
6) white_boxのNone/True に関わらず、text = "1行目\n2行目" (改行\n) でテキスト文字を指定できます。
|
|
777
|
+
7) テキストありで、auto_size=True の場合、left\topを基準に右下方向へ自動拡張されます。
|
|
778
|
+
8) alignment ではテキスト配置を 左揃え:"left"、中央揃え:"center"、右揃え:"right"
|
|
779
|
+
と 上揃え:"top", 上下中央揃え:"middle", 下揃え:"bottom を組み合わせて指定できます。
|
|
780
|
+
"""
|
|
781
|
+
ch = chart.api[1]
|
|
782
|
+
plot_area = ch.PlotArea
|
|
783
|
+
x_axis = ch.Axes(constants.xlCategory)
|
|
784
|
+
x_min, x_max = x_axis.MinimumScale, x_axis.MaximumScale
|
|
785
|
+
y_axis = ch.Axes(constants.xlValue)
|
|
786
|
+
y_min, y_max = y_axis.MinimumScale, y_axis.MaximumScale
|
|
787
|
+
|
|
788
|
+
# (x_start=None, x_end=None) or (y_start=None, y_end=None) で使用する関数
|
|
789
|
+
def parse_size(value, total_size):
|
|
790
|
+
"""
|
|
791
|
+
value:
|
|
792
|
+
- 数値 → px
|
|
793
|
+
- "xx%" → total_size * xx/100
|
|
794
|
+
- None → 呼び出し側で解釈
|
|
795
|
+
"""
|
|
796
|
+
if isinstance(value, str) and value.endswith("%"):
|
|
797
|
+
try:
|
|
798
|
+
pct = float(value.strip("%")) / 100
|
|
799
|
+
return total_size * pct
|
|
800
|
+
except ValueError:
|
|
801
|
+
raise ValueError(f"Invalid percentage value: {value}")
|
|
802
|
+
else:
|
|
803
|
+
return value
|
|
804
|
+
|
|
805
|
+
def resolve_1d(start, end, axis_min, axis_max, left, right, width, origin, total):
|
|
806
|
+
if start is not None and end is None: end = axis_max
|
|
807
|
+
if start is None and end is not None: start = axis_min
|
|
808
|
+
if start is not None and end is not None:
|
|
809
|
+
left = origin + (start - axis_min) / (axis_max - axis_min) * total
|
|
810
|
+
width = (end - start) / (axis_max - axis_min) * total
|
|
811
|
+
elif left is None and right is None:
|
|
812
|
+
left = origin
|
|
813
|
+
width = parse_size(width, total)
|
|
814
|
+
if width is None:
|
|
815
|
+
width = total
|
|
816
|
+
elif left is not None and right is not None:
|
|
817
|
+
left_val = parse_size(left, total)
|
|
818
|
+
right_val = parse_size(right, total)
|
|
819
|
+
left = origin + left_val
|
|
820
|
+
width = total - left_val - right_val
|
|
821
|
+
elif left is not None and right is None:
|
|
822
|
+
left_val = parse_size(left, total)
|
|
823
|
+
left = origin + left_val
|
|
824
|
+
width = parse_size(width, total)
|
|
825
|
+
if width is None:
|
|
826
|
+
width = total - left_val
|
|
827
|
+
elif left is None and right is not None:
|
|
828
|
+
right_val = parse_size(right, total)
|
|
829
|
+
width = parse_size(width, total)
|
|
830
|
+
if width is None:
|
|
831
|
+
width = total - right_val
|
|
832
|
+
left = origin + total - width - right_val
|
|
833
|
+
return left, width
|
|
834
|
+
|
|
835
|
+
# X軸座標変換
|
|
836
|
+
origin = plot_area.InsideLeft
|
|
837
|
+
total = plot_area.InsideWidth
|
|
838
|
+
left, width = resolve_1d(x_start, x_end, x_min, x_max, left, right, width, origin, total)
|
|
839
|
+
# Excel描画の都合の微調整
|
|
840
|
+
width += delta_width
|
|
841
|
+
# Y軸座標変換
|
|
842
|
+
origin = plot_area.InsideTop
|
|
843
|
+
total = plot_area.InsideHeight
|
|
844
|
+
top, height = resolve_1d(y_start, y_end, y_min, y_max, top, bottom, height, origin, total)
|
|
845
|
+
# Excel描画の都合の微調整
|
|
846
|
+
height += delta_height
|
|
847
|
+
|
|
848
|
+
if width <= 0 or height <= 0:
|
|
849
|
+
raise ValueError(f"Invalid shape size: width={width}, height={height}")
|
|
850
|
+
|
|
851
|
+
if white_box:
|
|
852
|
+
shape = ch.Shapes.AddTextbox(1, left, top, width, height)
|
|
853
|
+
color = RGB(255,255,255)
|
|
854
|
+
alpha = 0
|
|
855
|
+
frame_color = RGB(0,0,0)
|
|
856
|
+
else:
|
|
857
|
+
# 1 = msoShapeRectangle
|
|
858
|
+
shape = ch.Shapes.AddShape(1, left, top, width, height)
|
|
859
|
+
|
|
860
|
+
shape.Fill.ForeColor.RGB = color
|
|
861
|
+
shape.Fill.Transparency = alpha
|
|
862
|
+
if frame_color is None:
|
|
863
|
+
shape.Line.Visible = False
|
|
864
|
+
else:
|
|
865
|
+
shape.Line.Visible = True
|
|
866
|
+
shape.Line.ForeColor.RGB = frame_color
|
|
867
|
+
shape.Line.Weight = 0.75
|
|
868
|
+
if text is not None:
|
|
869
|
+
shape.TextFrame.Characters().Text = text
|
|
870
|
+
if auto_size:
|
|
871
|
+
shape.TextFrame.AutoSize = True
|
|
872
|
+
shape.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = font_color
|
|
873
|
+
shape.TextFrame2.TextRange.Font.Name = font_name
|
|
874
|
+
shape.TextFrame2.TextRange.Font.Size = font_size
|
|
875
|
+
shape.TextFrame2.TextRange.Font.Bold = font_bold
|
|
876
|
+
if alignment is not None:
|
|
877
|
+
alignment = alignment.strip().lower()
|
|
878
|
+
if "left" in alignment:
|
|
879
|
+
shape.TextFrame.HorizontalAlignment = constants.xlHAlignLeft
|
|
880
|
+
elif "center" in alignment:
|
|
881
|
+
shape.TextFrame.HorizontalAlignment = constants.xlHAlignCenter
|
|
882
|
+
elif "right" in alignment:
|
|
883
|
+
shape.TextFrame.HorizontalAlignment = constants.xlHAlignRight
|
|
884
|
+
if "top" in alignment:
|
|
885
|
+
shape.TextFrame.VerticalAlignment = constants.xlVAlignTop
|
|
886
|
+
elif "middle" in alignment:
|
|
887
|
+
shape.TextFrame.VerticalAlignment = constants.xlVAlignCenter
|
|
888
|
+
elif "bottom" in alignment:
|
|
889
|
+
shape.TextFrame.VerticalAlignment = constants.xlVAlignBottom
|
|
890
|
+
return shape
|
|
891
|
+
|
|
892
|
+
def add_line(chart, x=None, y=None, color=RGB(0, 0, 0), weight=1.5, dash=True):
|
|
893
|
+
"""
|
|
894
|
+
プロットエリア内に基準線を描画する
|
|
895
|
+
|
|
896
|
+
x : 縦線(x軸の値)
|
|
897
|
+
y : 横線(y軸の値)
|
|
898
|
+
"""
|
|
899
|
+
|
|
900
|
+
if (x is None) == (y is None):
|
|
901
|
+
raise ValueError("x または y のどちらか一方だけ指定してください")
|
|
902
|
+
|
|
903
|
+
ch = chart.api[1]
|
|
904
|
+
plot = ch.PlotArea
|
|
905
|
+
|
|
906
|
+
x_axis = ch.Axes(constants.xlCategory)
|
|
907
|
+
y_axis = ch.Axes(constants.xlValue)
|
|
908
|
+
|
|
909
|
+
# --- 縦線 ---
|
|
910
|
+
if x is not None:
|
|
911
|
+
x_pos = plot.InsideLeft + (x - x_axis.MinimumScale)/(x_axis.MaximumScale - x_axis.MinimumScale)*plot.InsideWidth
|
|
912
|
+
x1 = x2 = x_pos
|
|
913
|
+
y1 = plot.InsideTop
|
|
914
|
+
y2 = plot.InsideTop + plot.InsideHeight
|
|
915
|
+
|
|
916
|
+
# --- 横線 ---
|
|
917
|
+
else:
|
|
918
|
+
y_pos = plot.InsideTop + (1 - (y - y_axis.MinimumScale)/(y_axis.MaximumScale - y_axis.MinimumScale))*plot.InsideHeight
|
|
919
|
+
y1 = y2 = y_pos
|
|
920
|
+
x1 = plot.InsideLeft
|
|
921
|
+
x2 = plot.InsideLeft + plot.InsideWidth
|
|
922
|
+
|
|
923
|
+
line = ch.Shapes.AddLine(x1, y1, x2, y2)
|
|
924
|
+
line.Line.ForeColor.RGB = color
|
|
925
|
+
line.Line.Weight = weight
|
|
926
|
+
if dash:
|
|
927
|
+
line.Line.DashStyle = 4
|
|
928
|
+
return line
|
|
929
|
+
|
|
930
|
+
def template(chart,user,name="グラフ 1",
|
|
931
|
+
width_cm=None, height_cm=None, title=None, x_title=None, y_title=None):
|
|
932
|
+
"""
|
|
933
|
+
グラフテンプレート機能を使う
|
|
934
|
+
user: ユーザー名
|
|
935
|
+
name: テンプレート名
|
|
936
|
+
"""
|
|
937
|
+
if width_cm is not None:
|
|
938
|
+
chart.width = cm_to_pt(width_cm)
|
|
939
|
+
if height_cm is not None:
|
|
940
|
+
chart.height = cm_to_pt(height_cm)
|
|
941
|
+
|
|
942
|
+
ch = chart.api[1]
|
|
943
|
+
ch.ApplyChartTemplate(rf"C:\Users\{user}\AppData\Roaming\Microsoft\Templates\Charts\{name}.crtx")
|
|
944
|
+
|
|
945
|
+
if title is not None:
|
|
946
|
+
ch.HasTitle = True
|
|
947
|
+
ch.ChartTitle.Text = title
|
|
948
|
+
|
|
949
|
+
if x_title is not None:
|
|
950
|
+
x_axis = ch.Axes(AxisType.xlCategory)
|
|
951
|
+
x_axis.HasTitle = True
|
|
952
|
+
x_axis.AxisTitle.Text = x_title
|
|
953
|
+
|
|
954
|
+
if y_title is not None:
|
|
955
|
+
y_axis = ch.Axes(AxisType.xlValue)
|
|
956
|
+
y_axis.HasTitle = True
|
|
957
|
+
|
|
958
|
+
y_axis.AxisTitle.Text = y_title
|
|
959
|
+
|