seolpyo-mplchart 1.4.1__py3-none-any.whl → 2.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.
- seolpyo_mplchart/__init__.py +53 -333
- seolpyo_mplchart/_chart/__init__.py +145 -0
- seolpyo_mplchart/_chart/_base.py +217 -0
- seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
- seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
- seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
- seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
- seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
- seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
- seolpyo_mplchart/_chart/_draw/_data.py +314 -0
- seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
- seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
- seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
- seolpyo_mplchart/_chart/_slider/_base.py +268 -0
- seolpyo_mplchart/_chart/_slider/_data.py +105 -0
- seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
- seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
- seolpyo_mplchart/_chart/base/__init__.py +111 -0
- seolpyo_mplchart/_chart/base/a_canvas.py +250 -0
- seolpyo_mplchart/_chart/base/b_artist.py +143 -0
- seolpyo_mplchart/_chart/base/c_draw.py +100 -0
- seolpyo_mplchart/_chart/base/d_segment.py +262 -0
- seolpyo_mplchart/_chart/base/e_axis.py +267 -0
- seolpyo_mplchart/_chart/base/f_background.py +62 -0
- seolpyo_mplchart/_chart/base/g_event.py +66 -0
- seolpyo_mplchart/_chart/base/h_data.py +138 -0
- seolpyo_mplchart/_chart/base/test.py +58 -0
- seolpyo_mplchart/_chart/cursor/__init__.py +125 -0
- seolpyo_mplchart/_chart/cursor/b_artist.py +130 -0
- seolpyo_mplchart/_chart/cursor/c_draw.py +96 -0
- seolpyo_mplchart/_chart/cursor/d_segment.py +359 -0
- seolpyo_mplchart/_chart/cursor/e_axis.py +65 -0
- seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
- seolpyo_mplchart/_chart/cursor/h_data.py +61 -0
- seolpyo_mplchart/_chart/cursor/test.py +69 -0
- seolpyo_mplchart/_chart/slider/__init__.py +169 -0
- seolpyo_mplchart/_chart/slider/a_canvas.py +260 -0
- seolpyo_mplchart/_chart/slider/b_artist.py +91 -0
- seolpyo_mplchart/_chart/slider/c_draw.py +54 -0
- seolpyo_mplchart/_chart/slider/d_segment.py +166 -0
- seolpyo_mplchart/_chart/slider/e_axis.py +70 -0
- seolpyo_mplchart/_chart/slider/f_background.py +37 -0
- seolpyo_mplchart/_chart/slider/g_event.py +353 -0
- seolpyo_mplchart/_chart/slider/h_data.py +102 -0
- seolpyo_mplchart/_chart/slider/test.py +71 -0
- seolpyo_mplchart/_chart/test.py +121 -0
- seolpyo_mplchart/_config/__init__.py +3 -0
- seolpyo_mplchart/_config/ax.py +28 -0
- seolpyo_mplchart/_config/candle.py +31 -0
- seolpyo_mplchart/_config/config.py +21 -0
- seolpyo_mplchart/_config/cursor.py +49 -0
- seolpyo_mplchart/_config/figure.py +40 -0
- seolpyo_mplchart/_config/format.py +51 -0
- seolpyo_mplchart/_config/ma.py +17 -0
- seolpyo_mplchart/_config/slider/__init__.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +24 -0
- seolpyo_mplchart/_config/slider/figure.py +19 -0
- seolpyo_mplchart/_config/slider/nav.py +10 -0
- seolpyo_mplchart/_config/unit.py +19 -0
- seolpyo_mplchart/_config/utils.py +67 -0
- seolpyo_mplchart/_config/volume.py +27 -0
- seolpyo_mplchart/_cursor.py +27 -25
- seolpyo_mplchart/_draw.py +7 -18
- seolpyo_mplchart/_slider.py +26 -20
- seolpyo_mplchart/_utils/__init__.py +10 -0
- seolpyo_mplchart/_utils/nums.py +67 -0
- seolpyo_mplchart/_utils/theme/__init__.py +15 -0
- seolpyo_mplchart/_utils/theme/dark.py +57 -0
- seolpyo_mplchart/_utils/theme/light.py +56 -0
- seolpyo_mplchart/_utils/utils.py +28 -0
- seolpyo_mplchart/_utils/xl/__init__.py +15 -0
- seolpyo_mplchart/_utils/xl/csv.py +46 -0
- seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
- seolpyo_mplchart/sample/apple.txt +6058 -0
- seolpyo_mplchart/sample/samsung.txt +5938 -0
- seolpyo_mplchart/test.py +172 -56
- seolpyo_mplchart/xl_to_dict.py +47 -0
- seolpyo_mplchart-2.1.0.dist-info/METADATA +718 -0
- seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-1.4.1.dist-info/METADATA +0 -57
- seolpyo_mplchart-1.4.1.dist-info/RECORD +0 -17
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import matplotlib.style as mplstyle
|
|
3
|
+
from matplotlib.artist import Artist
|
|
4
|
+
from matplotlib.axes import Axes
|
|
5
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg, RendererAgg
|
|
6
|
+
from matplotlib.backend_bases import FigureManagerBase
|
|
7
|
+
from matplotlib.figure import Figure as Fig
|
|
8
|
+
from matplotlib.text import Text
|
|
9
|
+
|
|
10
|
+
try: plt.switch_backend('TkAgg')
|
|
11
|
+
except: pass
|
|
12
|
+
|
|
13
|
+
# 한글 깨짐 문제 방지
|
|
14
|
+
try: plt.rcParams['font.family'] ='Malgun Gothic'
|
|
15
|
+
except: pass
|
|
16
|
+
|
|
17
|
+
from ..._config import DEFAULTCONFIG, ConfigData
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
mplstyle.use('fast')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Canvas(FigureCanvasAgg):
|
|
24
|
+
manager: FigureManagerBase
|
|
25
|
+
renderer = RendererAgg
|
|
26
|
+
|
|
27
|
+
class Figure(Fig):
|
|
28
|
+
canvas: Canvas
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Base:
|
|
32
|
+
CONFIG: ConfigData
|
|
33
|
+
figure: Figure = None
|
|
34
|
+
|
|
35
|
+
def add_axes(self):
|
|
36
|
+
if not self.figure:
|
|
37
|
+
self.figure, *_ = plt.subplots(
|
|
38
|
+
3, # row 수
|
|
39
|
+
figsize=self.CONFIG.FIGURE.figsize, # 기본 크기
|
|
40
|
+
height_ratios=(
|
|
41
|
+
1,
|
|
42
|
+
self.CONFIG.FIGURE.RATIO.price,
|
|
43
|
+
self.CONFIG.FIGURE.RATIO.volume,
|
|
44
|
+
) # row 크기 비율
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.ax_legend, self.ax_price, self.ax_volume = self.figure.axes
|
|
48
|
+
self.ax_legend.set_label('legend ax')
|
|
49
|
+
self.ax_price.set_label('price ax')
|
|
50
|
+
self.ax_volume.set_label('volume ax')
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class FigureMixin(Base):
|
|
56
|
+
key_volume: str
|
|
57
|
+
ax_legend: Axes
|
|
58
|
+
|
|
59
|
+
def _set_figure_ratios(self):
|
|
60
|
+
gs = self.figure.axes[0].get_subplotspec().get_gridspec()
|
|
61
|
+
|
|
62
|
+
ratio_volume = self.CONFIG.FIGURE.RATIO.volume
|
|
63
|
+
if not self.key_volume:
|
|
64
|
+
ratio_volume = 0
|
|
65
|
+
|
|
66
|
+
legend = self.ax_legend.get_legend()
|
|
67
|
+
if not legend:
|
|
68
|
+
ratios = [
|
|
69
|
+
0,
|
|
70
|
+
self.CONFIG.FIGURE.RATIO.price, ratio_volume
|
|
71
|
+
]
|
|
72
|
+
else:
|
|
73
|
+
fig_heihgt = self.figure.get_figheight()
|
|
74
|
+
fig_px = fig_heihgt * (1-self.CONFIG.FIGURE.ADJUST.hspace*2) * self.figure.dpi
|
|
75
|
+
# print(f'{(fig_heihgt, fig_px)=}')
|
|
76
|
+
|
|
77
|
+
# Legend에 Axes 높이 맞추기
|
|
78
|
+
bbox = legend.get_window_extent().transformed(self.figure.transFigure.inverted())
|
|
79
|
+
ax_pos = self.ax_legend.get_position()
|
|
80
|
+
self.ax_legend.set_position([ax_pos.x0, ax_pos.y0, ax_pos.width, bbox.height])
|
|
81
|
+
|
|
82
|
+
legend_height = bbox.height
|
|
83
|
+
legend_px = legend.get_window_extent().height
|
|
84
|
+
# print(f'{(legend_height, legend_px)=}')
|
|
85
|
+
|
|
86
|
+
chart_px = fig_px - legend_height
|
|
87
|
+
chart_ratio = self.CONFIG.FIGURE.RATIO.price + ratio_volume
|
|
88
|
+
div_chart = chart_px / chart_ratio
|
|
89
|
+
price_px = div_chart * self.CONFIG.FIGURE.RATIO.price
|
|
90
|
+
volume_px = div_chart * ratio_volume
|
|
91
|
+
|
|
92
|
+
# 차트 비율 변경
|
|
93
|
+
ratios = [
|
|
94
|
+
legend_px * 1.2,
|
|
95
|
+
price_px, volume_px
|
|
96
|
+
]
|
|
97
|
+
# print(f'{ratios=}')
|
|
98
|
+
gs.set_height_ratios(ratios)
|
|
99
|
+
|
|
100
|
+
self.figure.tight_layout()
|
|
101
|
+
|
|
102
|
+
# 플롯간 간격 설정(Configure subplots)
|
|
103
|
+
self.figure.subplots_adjust(**self.CONFIG.FIGURE.ADJUST.__dict__)
|
|
104
|
+
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
def _set_figure(self):
|
|
108
|
+
self.figure.canvas.manager.set_window_title('Seolpyo MPLChart')
|
|
109
|
+
|
|
110
|
+
# print(f'{self.CONFIG.FIGURE.RATIO.volume=}')
|
|
111
|
+
# print(f'{gs.get_height_ratios()=}')
|
|
112
|
+
|
|
113
|
+
self._set_figure_ratios()
|
|
114
|
+
|
|
115
|
+
self.figure.set_facecolor(self.CONFIG.FIGURE.facecolor)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AxesMixin(FigureMixin):
|
|
120
|
+
def _set_axes(self):
|
|
121
|
+
# ax 요소 animated 처리
|
|
122
|
+
for ax in self.figure.axes:
|
|
123
|
+
ax.patch.set_animated(True)
|
|
124
|
+
|
|
125
|
+
# ax 경계선
|
|
126
|
+
for spine in ax.spines.values():
|
|
127
|
+
spine.set_animated(True)
|
|
128
|
+
|
|
129
|
+
for axis in (ax.xaxis, ax.yaxis):
|
|
130
|
+
axis.set_animated(True)
|
|
131
|
+
axis.label.set_animated(True)
|
|
132
|
+
for tick in axis.get_major_ticks() + axis.get_minor_ticks():
|
|
133
|
+
artists: list[Artist] = [
|
|
134
|
+
tick.tick1line, tick.tick2line,
|
|
135
|
+
tick.gridline,
|
|
136
|
+
tick.label1, tick.label2
|
|
137
|
+
]
|
|
138
|
+
for artist in artists:
|
|
139
|
+
if artist is not None:
|
|
140
|
+
artist.set_animated(True)
|
|
141
|
+
|
|
142
|
+
# y ticklabel foramt 설정
|
|
143
|
+
self.ax_price.yaxis.set_major_formatter(
|
|
144
|
+
lambda x, _: self.CONFIG.UNIT.func(
|
|
145
|
+
x,
|
|
146
|
+
word=self.CONFIG.UNIT.price,
|
|
147
|
+
digit=self.CONFIG.UNIT.digit+(0 if self.CONFIG.UNIT.digit % 1 else 2)
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
self.ax_volume.yaxis.set_major_formatter(
|
|
151
|
+
lambda x, _: self.CONFIG.UNIT.func(
|
|
152
|
+
x,
|
|
153
|
+
word=self.CONFIG.UNIT.volume,
|
|
154
|
+
digit=self.CONFIG.UNIT.digit_volume
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not self.key_volume:
|
|
159
|
+
# tick 그리지 않기
|
|
160
|
+
self.ax_volume.set_yticklabels([])
|
|
161
|
+
self.ax_volume.set_yticks([])
|
|
162
|
+
|
|
163
|
+
# 공통 설정
|
|
164
|
+
for ax in (self.ax_price, self.ax_volume):
|
|
165
|
+
ax.xaxis.set_animated(True)
|
|
166
|
+
ax.yaxis.set_animated(True)
|
|
167
|
+
|
|
168
|
+
# x tick 외부 눈금 표시하지 않기
|
|
169
|
+
ax.xaxis.set_ticks_position('none')
|
|
170
|
+
# x tick label 제거
|
|
171
|
+
ax.set_xticklabels([])
|
|
172
|
+
# y tick 위치를 우측으로 이동
|
|
173
|
+
ax.tick_params(left=False, right=True, labelleft=False, labelright=True)
|
|
174
|
+
|
|
175
|
+
# 차트 영역 배경 색상
|
|
176
|
+
ax.set_facecolor(self.CONFIG.AX.facecolor)
|
|
177
|
+
|
|
178
|
+
# Axes 외곽선 색 변경(틱 색과 일치)
|
|
179
|
+
for i in ['top', 'bottom', 'left', 'right']:
|
|
180
|
+
ax.spines[i].set_color(self.CONFIG.AX.TICK.edgecolor)
|
|
181
|
+
# 틱 색상
|
|
182
|
+
ax.tick_params('both', colors=self.CONFIG.AX.TICK.edgecolor)
|
|
183
|
+
# 틱 라벨 색상
|
|
184
|
+
ticklabels: list[Text] = ax.get_xticklabels() + ax.get_yticklabels()
|
|
185
|
+
for ticklabel in ticklabels:
|
|
186
|
+
ticklabel.set_color(self.CONFIG.AX.TICK.fontcolor)
|
|
187
|
+
|
|
188
|
+
# Axes grid(구분선, 격자) 그리기
|
|
189
|
+
# 어째서인지 grid의 zorder 값을 선언해도 1.6을 값으로 한다.
|
|
190
|
+
ax.grid(**self.CONFIG.AX.GRID.__dict__)
|
|
191
|
+
|
|
192
|
+
# 거래량 차트의 x tick 외부 눈금 표시하기
|
|
193
|
+
self.ax_volume.xaxis.set_ticks_position('bottom')
|
|
194
|
+
# major tick mark 길이를 0으로 만들어 튀어나오지 않게 하기
|
|
195
|
+
self.ax_volume.tick_params('x', which='major', length=0)
|
|
196
|
+
# minor tick mark 색상 변경
|
|
197
|
+
self.ax_volume.tick_params('x', which='minor', colors=self.CONFIG.AX.TICK.edgecolor)
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class LegendMixin(AxesMixin):
|
|
202
|
+
def _set_axes_legend(self):
|
|
203
|
+
# 이평선 라벨 axis 그리지 않기
|
|
204
|
+
self.ax_legend.set_axis_off()
|
|
205
|
+
self.ax_legend.xaxis.set_animated(True)
|
|
206
|
+
self.ax_legend.yaxis.set_animated(True)
|
|
207
|
+
self.ax_legend.set_animated(True)
|
|
208
|
+
|
|
209
|
+
# 이평선 라벨 Axes 배경색
|
|
210
|
+
legends = self.ax_legend.get_legend()
|
|
211
|
+
if legends:
|
|
212
|
+
legends.get_frame().set_facecolor(self.CONFIG.AX.facecolor)
|
|
213
|
+
|
|
214
|
+
# 이평선 라벨 Axes 테두리색
|
|
215
|
+
legends = self.ax_legend.get_legend()
|
|
216
|
+
if legends:
|
|
217
|
+
legends.get_frame().set_edgecolor(self.CONFIG.AX.TICK.edgecolor)
|
|
218
|
+
|
|
219
|
+
# 이평선 라벨 폰트 색상
|
|
220
|
+
fontcolor = self.CONFIG.AX.TICK.fontcolor
|
|
221
|
+
legends = self.ax_legend.get_legend()
|
|
222
|
+
if legends:
|
|
223
|
+
legend_labels: list[Text] = legends.texts
|
|
224
|
+
for i in legend_labels:
|
|
225
|
+
i.set_color(fontcolor)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class CanvasMixin(LegendMixin):
|
|
230
|
+
figure: Figure
|
|
231
|
+
ax_legend: Axes
|
|
232
|
+
ax_price: Axes
|
|
233
|
+
ax_volume: Axes
|
|
234
|
+
|
|
235
|
+
def __init__(self, config=DEFAULTCONFIG):
|
|
236
|
+
# 기본 툴바 비활성화
|
|
237
|
+
plt.rcParams['toolbar'] = 'None'
|
|
238
|
+
# plt.rcParams['figure.dpi'] = 600
|
|
239
|
+
|
|
240
|
+
self.CONFIG = config
|
|
241
|
+
self.add_axes()
|
|
242
|
+
self.set_canvas()
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
def set_canvas(self):
|
|
246
|
+
self._set_axes()
|
|
247
|
+
self._set_axes_legend()
|
|
248
|
+
self._set_figure()
|
|
249
|
+
return
|
|
250
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.lines import Line2D
|
|
4
|
+
from matplotlib.text import Text
|
|
5
|
+
|
|
6
|
+
from ..._config import ConfigData
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WatermarkMixin:
|
|
10
|
+
CONFIG: ConfigData
|
|
11
|
+
|
|
12
|
+
watermark = 'seolpyo mplchart'
|
|
13
|
+
|
|
14
|
+
ax_price: Axes
|
|
15
|
+
|
|
16
|
+
def _add_watermark(self):
|
|
17
|
+
self.artist_watermark = Text(
|
|
18
|
+
x=0.5, y=0.5, text='',
|
|
19
|
+
animated=True,
|
|
20
|
+
horizontalalignment='center', verticalalignment='center',
|
|
21
|
+
transform=self.ax_price.transAxes
|
|
22
|
+
)
|
|
23
|
+
self.ax_price.add_artist(self.artist_watermark)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
def _set_watermark(self):
|
|
27
|
+
self.artist_watermark.set(animated=True, horizontalalignment='center', verticalalignment='center',)
|
|
28
|
+
self.artist_watermark.set_text(self.watermark)
|
|
29
|
+
self.artist_watermark.set_fontsize(self.CONFIG.FIGURE.WATERMARK.fontsize)
|
|
30
|
+
self.artist_watermark.set_color(self.CONFIG.FIGURE.WATERMARK.color)
|
|
31
|
+
self.artist_watermark.set_alpha(self.CONFIG.FIGURE.WATERMARK.alpha)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CollectionMixin:
|
|
36
|
+
ax_price: Axes
|
|
37
|
+
ax_volume: Axes
|
|
38
|
+
ax_legend: Axes
|
|
39
|
+
|
|
40
|
+
def _add_collections(self):
|
|
41
|
+
self.collection_candle = LineCollection([], animated=True, linewidths=0.8)
|
|
42
|
+
self.ax_price.add_collection(self.collection_candle)
|
|
43
|
+
|
|
44
|
+
self.collection_ma = LineCollection([], animated=True, linewidths=1)
|
|
45
|
+
self.ax_price.add_collection(self.collection_ma)
|
|
46
|
+
|
|
47
|
+
self.collection_volume = LineCollection([], animated=True, linewidths=1)
|
|
48
|
+
self.ax_volume.add_collection(self.collection_volume)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def set_collection_candle(self, segment, facecolors, edgecolors):
|
|
52
|
+
self.collection_candle.set_segments(segment)
|
|
53
|
+
self.collection_candle.set_facecolor(facecolors)
|
|
54
|
+
self.collection_candle.set_edgecolor(edgecolors)
|
|
55
|
+
self.collection_candle.set_transform(self.ax_price.transData)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
def set_collection_volume(self, segment, facecolors, edgecolors):
|
|
59
|
+
self.collection_volume.set_segments(segment)
|
|
60
|
+
self.collection_volume.set_facecolor(facecolors)
|
|
61
|
+
self.collection_volume.set_edgecolor(edgecolors)
|
|
62
|
+
self.collection_volume.set_transform(self.ax_volume.transData)
|
|
63
|
+
self.collection_volume.set_antialiased(False)
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class MaMixin:
|
|
68
|
+
CONFIG: ConfigData
|
|
69
|
+
_visible_ma = set()
|
|
70
|
+
|
|
71
|
+
ax_legend: Axes
|
|
72
|
+
ax_price: Axes
|
|
73
|
+
collection_ma: LineCollection
|
|
74
|
+
_set_figure_ratios: callable
|
|
75
|
+
|
|
76
|
+
def set_collection_ma(self, segment, edgecolors):
|
|
77
|
+
self.collection_ma.set_segments(segment)
|
|
78
|
+
self.collection_ma.set_facecolor([])
|
|
79
|
+
self.collection_ma.set_edgecolor(edgecolors)
|
|
80
|
+
# print(self.collection_ma.get_linewidth())
|
|
81
|
+
self.collection_ma.set_linewidth(self.CONFIG.MA.linewidth)
|
|
82
|
+
self.collection_ma.set_transform(self.ax_price.transData)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
def _set_legends(self):
|
|
86
|
+
legends = self.ax_legend.get_legend()
|
|
87
|
+
if legends:
|
|
88
|
+
legends.remove()
|
|
89
|
+
|
|
90
|
+
self._visible_ma.clear()
|
|
91
|
+
|
|
92
|
+
label_list, handle_list, edgecolor_list = ([], [], [])
|
|
93
|
+
# Legend Ax에 표시하는 선 segment
|
|
94
|
+
arr = [0, 1]
|
|
95
|
+
for n, ma in enumerate(self.CONFIG.MA.ma_list):
|
|
96
|
+
self._visible_ma.add(ma)
|
|
97
|
+
label_list.append(self.CONFIG.MA.format.format(ma))
|
|
98
|
+
try:
|
|
99
|
+
color = self.CONFIG.MA.color_list[n]
|
|
100
|
+
except:
|
|
101
|
+
color = self.CONFIG.MA.color_default
|
|
102
|
+
edgecolor_list.append(color)
|
|
103
|
+
handle_list.append(Line2D(arr, ydata=arr, color=color, linewidth=5, label=ma))
|
|
104
|
+
|
|
105
|
+
self.set_collection_ma([], edgecolors=edgecolor_list)
|
|
106
|
+
|
|
107
|
+
# 가격이동평균선 legend 생성
|
|
108
|
+
if handle_list:
|
|
109
|
+
legends = self.ax_legend.legend(
|
|
110
|
+
handle_list, label_list, loc='lower left', ncol=self.CONFIG.MA.ncol,
|
|
111
|
+
borderpad=0.55,
|
|
112
|
+
facecolor=self.CONFIG.AX.facecolor, edgecolor=self.CONFIG.AX.TICK.edgecolor,
|
|
113
|
+
labelcolor=self.CONFIG.AX.TICK.fontcolor,
|
|
114
|
+
)
|
|
115
|
+
for handle in legends.legend_handles:
|
|
116
|
+
# legend 클릭시 pick event가 발생할 수 있도록 설정
|
|
117
|
+
handle.set_picker(5)
|
|
118
|
+
|
|
119
|
+
# legend ax 크기 조정
|
|
120
|
+
self._set_figure_ratios()
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ArtistMixin(WatermarkMixin, CollectionMixin, MaMixin):
|
|
125
|
+
artist_watermark: Text
|
|
126
|
+
collection_candle: LineCollection
|
|
127
|
+
collection_volume: LineCollection
|
|
128
|
+
collection_ma: LineCollection
|
|
129
|
+
|
|
130
|
+
_visible_ma: set[int] = set()
|
|
131
|
+
|
|
132
|
+
def add_artists(self):
|
|
133
|
+
self._add_watermark()
|
|
134
|
+
self._set_watermark()
|
|
135
|
+
self._add_collections()
|
|
136
|
+
self._set_legends()
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
def set_artists(self):
|
|
140
|
+
self._set_watermark()
|
|
141
|
+
self._set_legends()
|
|
142
|
+
return
|
|
143
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.text import Text
|
|
4
|
+
|
|
5
|
+
from .a_canvas import Figure
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Base:
|
|
9
|
+
candle_on_ma = True
|
|
10
|
+
|
|
11
|
+
watermark: str
|
|
12
|
+
|
|
13
|
+
figure: Figure
|
|
14
|
+
ax_legend: Axes
|
|
15
|
+
ax_price: Axes
|
|
16
|
+
ax_volume: Axes
|
|
17
|
+
|
|
18
|
+
artist_watermark: Text
|
|
19
|
+
collection_candle: LineCollection
|
|
20
|
+
collection_volume: LineCollection
|
|
21
|
+
collection_ma: LineCollection
|
|
22
|
+
|
|
23
|
+
def draw_chart(self):
|
|
24
|
+
self._draw_ax_price()
|
|
25
|
+
self._draw_ax_volume()
|
|
26
|
+
self._draw_chart_price()
|
|
27
|
+
self._draw_chart_volume()
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
def _draw_chart_volume(self):
|
|
31
|
+
renderer = self.figure.canvas.renderer
|
|
32
|
+
|
|
33
|
+
self.collection_volume.draw(renderer)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
def _draw_chart_price(self):
|
|
37
|
+
renderer = self.figure.canvas.renderer
|
|
38
|
+
# print(f'{renderer=}')
|
|
39
|
+
|
|
40
|
+
# print(self.collection_candle.get_segments())
|
|
41
|
+
if self.candle_on_ma:
|
|
42
|
+
self.collection_ma.draw(renderer)
|
|
43
|
+
self.collection_candle.draw(renderer)
|
|
44
|
+
else:
|
|
45
|
+
self.collection_candle.draw(renderer)
|
|
46
|
+
self.collection_ma.draw(renderer)
|
|
47
|
+
|
|
48
|
+
if self.watermark:
|
|
49
|
+
if self.watermark != self.artist_watermark.get_text():
|
|
50
|
+
self.artist_watermark.set_text(self.watermark)
|
|
51
|
+
self.artist_watermark.draw(renderer)
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
def draw_artists(self):
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
def draw_background(self):
|
|
58
|
+
self._draw_ax(self.ax_price)
|
|
59
|
+
self._draw_ax(self.ax_volume)
|
|
60
|
+
|
|
61
|
+
legend = self.ax_legend.get_legend()
|
|
62
|
+
if legend:
|
|
63
|
+
legend.draw(self.figure.canvas.renderer)
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
def _draw_ax(self, ax: Axes):
|
|
67
|
+
renderer = self.figure.canvas.renderer
|
|
68
|
+
|
|
69
|
+
# ax 배경
|
|
70
|
+
ax.patch.draw(renderer)
|
|
71
|
+
|
|
72
|
+
# ax 외곽선
|
|
73
|
+
for spine in ax.spines.values():
|
|
74
|
+
spine.draw(renderer)
|
|
75
|
+
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
def _draw_ax_volume(self):
|
|
79
|
+
renderer = self.figure.canvas.renderer
|
|
80
|
+
|
|
81
|
+
# grid, tick, ticklabel
|
|
82
|
+
ax = self.ax_volume
|
|
83
|
+
for axis in (ax.xaxis, ax.yaxis):
|
|
84
|
+
axis.draw(renderer)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
def _draw_ax_price(self):
|
|
88
|
+
renderer = self.figure.canvas.renderer
|
|
89
|
+
# print(f'{renderer=}')
|
|
90
|
+
|
|
91
|
+
# grid, tick, ticklabel
|
|
92
|
+
ax = self.ax_price
|
|
93
|
+
for axis in (ax.xaxis, ax.yaxis):
|
|
94
|
+
axis.draw(renderer)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DrawMixin(Base):
|
|
99
|
+
candle_on_ma = True
|
|
100
|
+
|