seolpyo-mplchart 0.1.3.1__py3-none-any.whl → 2.0.0.3__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 +164 -99
- seolpyo_mplchart/_base.py +117 -0
- seolpyo_mplchart/_chart/__init__.py +137 -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/test.py +121 -0
- seolpyo_mplchart/_config/__init__.py +3 -0
- seolpyo_mplchart/_config/ax.py +28 -0
- seolpyo_mplchart/_config/candle.py +30 -0
- seolpyo_mplchart/_config/config.py +21 -0
- seolpyo_mplchart/_config/cursor.py +49 -0
- seolpyo_mplchart/_config/figure.py +41 -0
- seolpyo_mplchart/_config/format.py +51 -0
- seolpyo_mplchart/_config/ma.py +15 -0
- seolpyo_mplchart/_config/slider/__init__.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +24 -0
- seolpyo_mplchart/_config/slider/figure.py +20 -0
- seolpyo_mplchart/_config/slider/nav.py +9 -0
- seolpyo_mplchart/_config/unit.py +19 -0
- seolpyo_mplchart/_config/utils.py +67 -0
- seolpyo_mplchart/_config/volume.py +26 -0
- seolpyo_mplchart/_cursor.py +559 -0
- seolpyo_mplchart/_draw.py +634 -0
- seolpyo_mplchart/_slider.py +634 -0
- seolpyo_mplchart/base.py +70 -67
- seolpyo_mplchart/cursor.py +308 -271
- seolpyo_mplchart/draw.py +449 -237
- seolpyo_mplchart/slider.py +451 -396
- seolpyo_mplchart/test.py +173 -24
- seolpyo_mplchart/utils.py +15 -4
- seolpyo_mplchart/xl_to_dict.py +47 -0
- seolpyo_mplchart-2.0.0.3.dist-info/METADATA +710 -0
- seolpyo_mplchart-2.0.0.3.dist-info/RECORD +50 -0
- {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-0.1.3.1.dist-info/METADATA +0 -49
- seolpyo_mplchart-0.1.3.1.dist-info/RECORD +0 -13
- {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/top_level.txt +0 -0
seolpyo_mplchart/slider.py
CHANGED
|
@@ -1,504 +1,573 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
from matplotlib.axes import Axes
|
|
1
3
|
from matplotlib.collections import LineCollection
|
|
4
|
+
from matplotlib.text import Text
|
|
2
5
|
from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
|
|
3
6
|
import pandas as pd
|
|
4
7
|
|
|
8
|
+
from .base import convert_unit
|
|
9
|
+
from .cursor import BaseMixin as BM, Mixin as M
|
|
5
10
|
|
|
6
|
-
from .cursor import CursorMixin, Chart as CM
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
class Mixin:
|
|
12
|
+
class Mixin(M):
|
|
10
13
|
def on_click(self, e):
|
|
11
14
|
"This function works if mouse button click event active."
|
|
12
15
|
return
|
|
13
16
|
def on_release(self, e):
|
|
14
17
|
"This function works if mouse button release event active."
|
|
15
18
|
return
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlotMixin(BM):
|
|
22
|
+
slider_top = True
|
|
23
|
+
ratio_ax_slider = 3
|
|
24
|
+
ratio_ax_none = 2
|
|
25
|
+
|
|
26
|
+
def _get_plot(self):
|
|
27
|
+
if self.slider_top:
|
|
28
|
+
self.figure, axes = plt.subplots(
|
|
29
|
+
4, # row 수
|
|
30
|
+
figsize=self.figsize, # 기본 크기
|
|
31
|
+
height_ratios=(self.ratio_ax_slider, self.ratio_ax_legend, self.ratio_ax_price, self.ratio_ax_volume) # row 크기 비율
|
|
32
|
+
)
|
|
33
|
+
axes: list[Axes]
|
|
34
|
+
self.ax_slider, self.ax_legend, self.ax_price, self.ax_volume = axes
|
|
35
|
+
else:
|
|
36
|
+
self.figure, axes = plt.subplots(
|
|
37
|
+
5, # row 수
|
|
38
|
+
figsize=self.figsize, # 기본 크기
|
|
39
|
+
height_ratios=(self.ratio_ax_legend, self.ratio_ax_price, self.ratio_ax_volume, self.ratio_ax_none, self.ratio_ax_slider) # row 크기 비율
|
|
40
|
+
)
|
|
41
|
+
axes: list[Axes]
|
|
42
|
+
self.ax_legend, self.ax_price, self.ax_volume, ax_none, self.ax_slider = axes
|
|
43
|
+
|
|
44
|
+
ax_none.set_axis_off()
|
|
45
|
+
ax_none.xaxis.set_animated(True)
|
|
46
|
+
ax_none.yaxis.set_animated(True)
|
|
47
|
+
|
|
48
|
+
self.ax_slider.set_label('slider ax')
|
|
49
|
+
self.ax_legend.set_label('legend ax')
|
|
50
|
+
self.ax_price.set_label('price ax')
|
|
51
|
+
self.ax_volume.set_label('volume ax')
|
|
52
|
+
|
|
53
|
+
self.figure.canvas.manager.set_window_title(f'{self.title}')
|
|
54
|
+
self.figure.set_facecolor(self.color_background)
|
|
55
|
+
|
|
56
|
+
# 플롯간 간격 제거(Configure subplots)
|
|
57
|
+
self.figure.subplots_adjust(**self.adjust)
|
|
58
|
+
|
|
59
|
+
self.ax_legend.set_axis_off()
|
|
60
|
+
|
|
61
|
+
# y ticklabel foramt 설정
|
|
62
|
+
self.ax_slider.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price, digit=2))
|
|
63
|
+
self.ax_price.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price, digit=2))
|
|
64
|
+
self.ax_volume.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_volume, digit=2))
|
|
65
|
+
|
|
66
|
+
gridKwargs = {'visible': True, 'linewidth': 0.5, 'color': '#d0d0d0', 'linestyle': '-', 'dashes': (1, 0)}
|
|
67
|
+
gridKwargs.update(self.gridKwargs)
|
|
68
|
+
# 공통 설정
|
|
69
|
+
for ax in (self.ax_slider, self.ax_price, self.ax_volume):
|
|
70
|
+
ax.xaxis.set_animated(True)
|
|
71
|
+
ax.yaxis.set_animated(True)
|
|
72
|
+
|
|
73
|
+
# x tick 외부 눈금 표시하지 않기
|
|
74
|
+
ax.xaxis.set_ticks_position('none')
|
|
75
|
+
# x tick label 제거
|
|
76
|
+
ax.set_xticklabels([])
|
|
77
|
+
# y tick 우측으로 이동
|
|
78
|
+
ax.tick_params(left=False, right=True, labelleft=False, labelright=True, colors=self.color_tick_label)
|
|
79
|
+
# Axes 외곽선 색 변경
|
|
80
|
+
for i in ['top', 'bottom', 'left', 'right']: ax.spines[i].set_color(self.color_tick)
|
|
81
|
+
|
|
82
|
+
# 차트 영역 배경 색상
|
|
83
|
+
ax.set_facecolor(self.color_background)
|
|
84
|
+
|
|
85
|
+
# grid(구분선, 격자) 그리기
|
|
86
|
+
# 어째서인지 grid의 zorder 값을 선언해도 1.6을 값으로 한다.
|
|
87
|
+
ax.grid(**gridKwargs)
|
|
18
88
|
return
|
|
19
89
|
|
|
20
90
|
|
|
21
|
-
class
|
|
91
|
+
class CollectionMixin(PlotMixin):
|
|
22
92
|
min_distance = 30
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_x_click, _x_release = (0, 0)
|
|
27
|
-
is_click, is_move = (False, False)
|
|
28
|
-
_navcoordinate = (0, 0)
|
|
93
|
+
color_navigator_line = '#1e78ff'
|
|
94
|
+
color_navigator_cover = 'k'
|
|
29
95
|
|
|
30
96
|
def _add_collection(self):
|
|
31
97
|
super()._add_collection()
|
|
32
98
|
|
|
99
|
+
self.collection_slider = LineCollection([], animated=True)
|
|
100
|
+
self.ax_slider.add_artist(self.collection_slider)
|
|
101
|
+
|
|
33
102
|
# 슬라이더 네비게이터
|
|
34
|
-
self.navigator = LineCollection([], animated=True, edgecolors=[self.
|
|
103
|
+
self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator_cover, self.color_navigator_line], alpha=(0.3, 1.0))
|
|
35
104
|
self.ax_slider.add_artist(self.navigator)
|
|
36
|
-
return
|
|
37
105
|
|
|
38
|
-
|
|
39
|
-
|
|
106
|
+
lineKwargs = {'edgecolor': 'k', 'linewidth': 1, 'linestyle': '-'}
|
|
107
|
+
lineKwargs.update(self.lineKwargs)
|
|
108
|
+
lineKwargs.update({'segments': [], 'animated': True})
|
|
40
109
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
self._navLineWidth = xsub * 0.008
|
|
44
|
-
if self._navLineWidth < 1: self._navLineWidth = 1
|
|
45
|
-
self._navLineWidth_half = self._navLineWidth / 2
|
|
46
|
-
return
|
|
110
|
+
self.slider_vline = LineCollection(**lineKwargs)
|
|
111
|
+
self.ax_slider.add_artist(self.slider_vline)
|
|
47
112
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
113
|
+
textboxKwargs = {'boxstyle': 'round', 'facecolor': 'w'}
|
|
114
|
+
textboxKwargs.update(self.textboxKwargs)
|
|
115
|
+
textKwargs = self.textKwargs
|
|
116
|
+
textKwargs.update({'animated': True, 'bbox': textboxKwargs, 'horizontalalignment': '', 'verticalalignment': ''})
|
|
117
|
+
(textKwargs.pop('horizontalalignment'), textKwargs.pop('verticalalignment'))
|
|
54
118
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
self.canvas.set_cursor(cursors.POINTER)
|
|
119
|
+
self.text_slider = Text(**textKwargs, horizontalalignment='center', verticalalignment='top')
|
|
120
|
+
self.ax_slider.add_artist(self.text_slider)
|
|
58
121
|
return
|
|
59
122
|
|
|
60
|
-
def
|
|
61
|
-
|
|
123
|
+
def _get_segments(self):
|
|
124
|
+
super()._get_segments()
|
|
62
125
|
|
|
63
|
-
|
|
126
|
+
keys = []
|
|
127
|
+
for i in reversed(self.list_ma):
|
|
128
|
+
keys.append('_x')
|
|
129
|
+
keys.append(f'ma{i}')
|
|
64
130
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
131
|
+
segment_slider = self.df[keys + ['_x', self.close] ].values
|
|
132
|
+
segment_slider = segment_slider.reshape(segment_slider.shape[0], len(self.list_ma)+1, 2).swapaxes(0, 1)
|
|
133
|
+
self.collection_slider.set_segments(segment_slider)
|
|
134
|
+
return
|
|
69
135
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self._x_click = x
|
|
73
|
-
self.is_move = True
|
|
74
|
-
self.canvas.set_cursor(cursors.MOVE)
|
|
75
|
-
else:
|
|
76
|
-
self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
77
|
-
if not gtl and lmin <= x:
|
|
78
|
-
self._x_click = right
|
|
79
|
-
elif not ltr and x <= rmax:
|
|
80
|
-
self._x_click = left
|
|
81
|
-
else:
|
|
82
|
-
self._x_click = x
|
|
136
|
+
def _get_color_segment(self):
|
|
137
|
+
super()._get_color_segment()
|
|
83
138
|
|
|
84
|
-
|
|
85
|
-
if left == right:
|
|
86
|
-
self._navcoordinate = (x, x)
|
|
139
|
+
self.collection_slider.set_edgecolor(self.edgecolor_ma + [self.color_priceline])
|
|
87
140
|
return
|
|
88
141
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
self.is_click, self.is_move = (False, False)
|
|
142
|
+
def change_background_color(self, color):
|
|
143
|
+
super().change_background_color(color)
|
|
92
144
|
|
|
93
|
-
|
|
94
|
-
|
|
145
|
+
self.ax_slider.set_facecolor(color)
|
|
146
|
+
self.text_slider.set_backgroundcolor(color)
|
|
95
147
|
return
|
|
96
148
|
|
|
149
|
+
def change_tick_color(self, color):
|
|
150
|
+
super().change_tick_color(color)
|
|
97
151
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self.background = None
|
|
101
|
-
self._restore_region()
|
|
152
|
+
for i in ['top', 'bottom', 'left', 'right']: self.ax_slider.spines[i].set_color(self.color_tick)
|
|
153
|
+
self.ax_slider.tick_params(colors=color)
|
|
102
154
|
return
|
|
103
155
|
|
|
104
|
-
def
|
|
105
|
-
|
|
156
|
+
def change_text_color(self, color):
|
|
157
|
+
super().change_text_color(color)
|
|
106
158
|
|
|
107
|
-
|
|
108
|
-
elif empty_with_nav: self.canvas.restore_region(self.background_empty_with_nav)
|
|
109
|
-
elif with_nav: self.canvas.restore_region(self.background_with_nav)
|
|
110
|
-
else: self.canvas.renderer.restore_region(self.background)
|
|
159
|
+
self.text_slider.set_color(color)
|
|
111
160
|
return
|
|
112
161
|
|
|
113
|
-
def
|
|
114
|
-
|
|
162
|
+
def change_line_color(self, color):
|
|
163
|
+
super().change_line_color(color)
|
|
115
164
|
|
|
116
|
-
self.
|
|
165
|
+
self.text_slider.get_bbox_patch().set_edgecolor(color)
|
|
166
|
+
return
|
|
117
167
|
|
|
118
|
-
self.ax_slider.xaxis.draw(renderer)
|
|
119
|
-
self.ax_slider.yaxis.draw(renderer)
|
|
120
|
-
self.slidercollection.draw(renderer)
|
|
121
|
-
self.background_empty_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
|
|
122
168
|
|
|
123
|
-
|
|
124
|
-
|
|
169
|
+
class NavigatorMixin(CollectionMixin):
|
|
170
|
+
def _set_slider_lim(self):
|
|
171
|
+
xmax = self.list_index[-1]
|
|
172
|
+
# 슬라이더 xlim
|
|
173
|
+
xdistance = xmax / 30
|
|
174
|
+
self.slider_xmin, self.slider_xmax = (-xdistance, xmax + xdistance)
|
|
175
|
+
self.ax_slider.set_xlim(self.slider_xmin, self.slider_xmax)
|
|
125
176
|
|
|
126
|
-
|
|
127
|
-
|
|
177
|
+
# 슬라이더 ylim
|
|
178
|
+
ymin, ymax = (self.df[self.low].min(), self.df[self.high].max())
|
|
179
|
+
ysub = ymax - ymin
|
|
180
|
+
self.sldier_ymiddle = ymin + (ysub / 2)
|
|
181
|
+
ydistance = ysub / 5
|
|
182
|
+
self.slider_ymin, self.slider_ymax = (ymin-ydistance, ymax+ydistance)
|
|
183
|
+
self.ax_slider.set_ylim(self.slider_ymin, self.slider_ymax)
|
|
184
|
+
|
|
185
|
+
# 슬라이더 텍스트 y
|
|
186
|
+
self.text_slider.set_y(ymax)
|
|
187
|
+
|
|
188
|
+
self.navigator.set_linewidth([ysub, 5])
|
|
189
|
+
|
|
190
|
+
# 네비게이터 라인 선택 범위
|
|
191
|
+
xsub = self.slider_xmax - self.slider_xmin
|
|
192
|
+
self._navLineWidth = xsub * 8 / 1_000
|
|
193
|
+
if self._navLineWidth < 1: self._navLineWidth = 1
|
|
194
|
+
self._navLineWidth_half = self._navLineWidth / 2
|
|
128
195
|
return
|
|
129
196
|
|
|
130
|
-
def
|
|
131
|
-
|
|
197
|
+
def _set_navigator(self, navmin, navmax):
|
|
198
|
+
navseg = [
|
|
199
|
+
(
|
|
200
|
+
(self.slider_xmin, self.sldier_ymiddle),
|
|
201
|
+
(navmin, self.sldier_ymiddle)
|
|
202
|
+
),
|
|
203
|
+
(
|
|
204
|
+
(navmin, self.slider_ymin),
|
|
205
|
+
(navmin, self.slider_ymax)
|
|
206
|
+
),
|
|
207
|
+
(
|
|
208
|
+
(navmax, self.sldier_ymiddle),
|
|
209
|
+
(self.slider_xmax, self.sldier_ymiddle)
|
|
210
|
+
),
|
|
211
|
+
(
|
|
212
|
+
(navmax, self.slider_ymin),
|
|
213
|
+
(navmax, self.slider_ymax)
|
|
214
|
+
),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
self.navigator.set_segments(navseg)
|
|
218
|
+
return
|
|
132
219
|
|
|
133
|
-
self.ax_price.xaxis.draw(renderer)
|
|
134
|
-
self.ax_price.yaxis.draw(renderer)
|
|
135
220
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
221
|
+
class DataMixin(NavigatorMixin):
|
|
222
|
+
navcoordinate = (0, 0)
|
|
223
|
+
|
|
224
|
+
def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, calc_info=True, change_lim=True, *args, **kwargs):
|
|
225
|
+
self._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
|
|
226
|
+
self._get_segments()
|
|
142
227
|
|
|
143
|
-
self.
|
|
144
|
-
self.
|
|
228
|
+
vmin, vmax = self.navcoordinate
|
|
229
|
+
if change_lim or (vmax-vmin) < self.min_distance:
|
|
230
|
+
vmin, vmax = self.get_default_lim()
|
|
231
|
+
self.navcoordinate = (vmin, vmax)
|
|
145
232
|
|
|
146
|
-
self.
|
|
233
|
+
self._set_lim(vmin, vmax)
|
|
234
|
+
self._set_slider_lim()
|
|
235
|
+
self._set_navigator(vmin, vmax)
|
|
236
|
+
|
|
237
|
+
self._length_text = self.df[(self.volume if self.volume else self.high)].apply(lambda x: len(str(x))).max()
|
|
147
238
|
return
|
|
148
239
|
|
|
240
|
+
def get_default_lim(self):
|
|
241
|
+
xmax = self.list_index[-1]
|
|
242
|
+
return (xmax-120, xmax)
|
|
243
|
+
|
|
149
244
|
|
|
150
|
-
class
|
|
151
|
-
def
|
|
152
|
-
|
|
245
|
+
class BackgroundMixin(DataMixin):
|
|
246
|
+
def _copy_bbox(self):
|
|
247
|
+
renderer = self.figure.canvas.renderer
|
|
153
248
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
self.
|
|
157
|
-
self.
|
|
249
|
+
self.ax_slider.xaxis.draw(renderer)
|
|
250
|
+
self.ax_slider.yaxis.draw(renderer)
|
|
251
|
+
self.collection_slider.draw(renderer)
|
|
252
|
+
self.background_emtpy = renderer.copy_from_bbox(self.figure.bbox)
|
|
253
|
+
|
|
254
|
+
self._draw_artist()
|
|
255
|
+
self.background = renderer.copy_from_bbox(self.figure.bbox)
|
|
256
|
+
|
|
257
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
258
|
+
self.background_with_nav = renderer.copy_from_bbox(self.figure.bbox)
|
|
158
259
|
return
|
|
159
260
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
self._set_navigator(*self._navcoordinate)
|
|
261
|
+
def _restore_region(self, is_empty=False, with_nav=True):
|
|
262
|
+
if not self.background: self._create_background()
|
|
163
263
|
|
|
164
|
-
self.
|
|
165
|
-
self.
|
|
166
|
-
self.
|
|
167
|
-
self._restore_region()
|
|
168
|
-
self._blit()
|
|
264
|
+
if is_empty: self.figure.canvas.renderer.restore_region(self.background_emtpy)
|
|
265
|
+
elif with_nav: self.figure.canvas.renderer.restore_region(self.background_with_nav)
|
|
266
|
+
else: self.figure.canvas.renderer.restore_region(self.background)
|
|
169
267
|
return
|
|
170
268
|
|
|
171
|
-
def _on_move(self, e: MouseEvent):
|
|
172
|
-
self._restore_region((not self.is_click))
|
|
173
269
|
|
|
270
|
+
class MouseMoveMixin(BackgroundMixin):
|
|
271
|
+
in_slider = False
|
|
272
|
+
is_click_slider = False
|
|
273
|
+
|
|
274
|
+
def _on_move(self, e):
|
|
174
275
|
self._on_move_action(e)
|
|
175
276
|
|
|
277
|
+
self._restore_region((self.is_click_slider and self.in_slider))
|
|
278
|
+
|
|
176
279
|
if self.in_slider:
|
|
177
|
-
self.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
self._slider_move_action(e)
|
|
183
|
-
elif self.is_click:
|
|
184
|
-
self.navigator.draw(self.canvas.renderer)
|
|
185
|
-
else:
|
|
186
|
-
if self.in_slider or self.in_price or self.in_volume:
|
|
187
|
-
self._slider_move_action(e)
|
|
188
|
-
if self.in_price or self.in_volume:
|
|
189
|
-
self._chart_move_action(e)
|
|
280
|
+
self._on_move_slider(e)
|
|
281
|
+
elif self.in_price_chart:
|
|
282
|
+
self._on_move_price_chart(e)
|
|
283
|
+
elif self.in_volume_chart:
|
|
284
|
+
self._on_move_volume_chart(e)
|
|
190
285
|
|
|
191
286
|
self._blit()
|
|
192
287
|
return
|
|
193
288
|
|
|
194
|
-
def
|
|
195
|
-
|
|
196
|
-
x = self.intx
|
|
197
|
-
left, right = self._navcoordinate
|
|
198
|
-
|
|
199
|
-
if not self.is_click:
|
|
200
|
-
lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
|
|
201
|
-
rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
|
|
202
|
-
ltel, gter = (x <= lmax, rmin <= x)
|
|
203
|
-
if ltel and lmin <= x:
|
|
204
|
-
self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
205
|
-
elif gter and x <= rmax:
|
|
206
|
-
self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
207
|
-
elif not ltel and not gter: self.canvas.set_cursor(cursors.MOVE)
|
|
208
|
-
else: self.canvas.set_cursor(cursors.POINTER)
|
|
209
|
-
else:
|
|
210
|
-
# 네비게이터 좌표 수정
|
|
211
|
-
intx = x.__int__()
|
|
212
|
-
if self.is_move:
|
|
213
|
-
xsub = self._x_click - intx
|
|
214
|
-
left, right = (left-xsub, right-xsub)
|
|
215
|
-
self._x_click = intx
|
|
216
|
-
else:
|
|
217
|
-
if intx == left: left = intx
|
|
218
|
-
elif intx == right: right = intx
|
|
219
|
-
else:
|
|
220
|
-
if self._x_click < intx: left, right = (self._x_click, intx)
|
|
221
|
-
else: left, right = (intx, self._x_click)
|
|
289
|
+
def _on_move_action(self, e: MouseEvent):
|
|
290
|
+
self._check_ax(e)
|
|
222
291
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
self._navcoordinate = (left, right)
|
|
226
|
-
return
|
|
292
|
+
self.intx = None
|
|
293
|
+
if self.in_slider or self.in_price_chart or self.in_volume_chart: self._get_x(e)
|
|
227
294
|
|
|
228
|
-
|
|
229
|
-
|
|
295
|
+
self._change_cursor(e)
|
|
296
|
+
return
|
|
230
297
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
298
|
+
def _change_cursor(self, e: MouseEvent):
|
|
299
|
+
# 마우스 커서 변경
|
|
300
|
+
if self.is_click_slider: return
|
|
301
|
+
elif not self.in_slider:
|
|
302
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
navleft, navright = self.navcoordinate
|
|
306
|
+
if navleft == navright: return
|
|
307
|
+
|
|
308
|
+
x = e.xdata
|
|
309
|
+
leftmin, leftmax = (navleft-self._navLineWidth, navleft+self._navLineWidth_half)
|
|
310
|
+
rightmin, rightmax = (navright-self._navLineWidth_half, navright+self._navLineWidth)
|
|
311
|
+
if x < leftmin: self.figure.canvas.set_cursor(cursors.POINTER)
|
|
312
|
+
elif x < leftmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
313
|
+
elif x < rightmin: self.figure.canvas.set_cursor(cursors.MOVE)
|
|
314
|
+
elif x < rightmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
315
|
+
else: self.figure.canvas.set_cursor(cursors.POINTER)
|
|
236
316
|
return
|
|
237
317
|
|
|
318
|
+
def _check_ax(self, e: MouseEvent):
|
|
319
|
+
ax = e.inaxes
|
|
320
|
+
if not ax or e.xdata is None or e.ydata is None:
|
|
321
|
+
self.in_slider, self.in_price_chart, self.in_volume_chart = (False, False, False)
|
|
322
|
+
else:
|
|
323
|
+
self.in_slider = ax is self.ax_slider
|
|
324
|
+
self.in_price_chart = False if self.in_slider else ax is self.ax_price
|
|
325
|
+
self.in_volume_chart = False if (self.in_slider or self.in_price_chart) else ax is self.ax_volume
|
|
326
|
+
return
|
|
238
327
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if e.inaxes is not self.ax_slider: return
|
|
242
|
-
self.is_click, self.is_move = (False, False)
|
|
328
|
+
def _on_move_slider(self, e: MouseEvent):
|
|
329
|
+
x = e.xdata
|
|
243
330
|
|
|
244
|
-
if self.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
331
|
+
if self.intx is not None:
|
|
332
|
+
renderer = self.figure.canvas.renderer
|
|
333
|
+
self.slider_vline.set_segments([((x, self.slider_ymin), (x, self.slider_ymax))])
|
|
334
|
+
self.slider_vline.draw(renderer)
|
|
248
335
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
336
|
+
if self.in_slider:
|
|
337
|
+
self.text_slider.set_text(f'{self.df[self.date][self.intx]}')
|
|
338
|
+
self.text_slider.set_x(x)
|
|
339
|
+
self.text_slider.draw(renderer)
|
|
253
340
|
return
|
|
254
341
|
|
|
255
|
-
def _on_move(self, e):
|
|
256
|
-
self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
|
|
257
342
|
|
|
258
|
-
|
|
343
|
+
class ClickMixin(MouseMoveMixin):
|
|
344
|
+
x_click = None
|
|
345
|
+
is_move = False
|
|
346
|
+
click_navleft, click_navright = (False, False)
|
|
259
347
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if self.is_click:
|
|
263
|
-
nsub = self._navcoordinate[1] - self._navcoordinate[0]
|
|
264
|
-
if self.min_distance <= nsub: self._lim()
|
|
265
|
-
if self.is_move: self._set_navigator(*self._navcoordinate)
|
|
266
|
-
elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
|
|
267
|
-
self.navigator.draw(self.canvas.renderer)
|
|
268
|
-
self._draw_blit_artist()
|
|
269
|
-
self._slider_move_action(e)
|
|
270
|
-
elif self.is_click:
|
|
271
|
-
self.navigator.draw(self.canvas.renderer)
|
|
272
|
-
self._draw_blit_artist()
|
|
273
|
-
else:
|
|
274
|
-
if self.in_slider or self.in_price or self.in_volume:
|
|
275
|
-
self._slider_move_action(e)
|
|
276
|
-
if self.in_price or self.in_volume:
|
|
277
|
-
self._chart_move_action(e)
|
|
348
|
+
def _connect_event(self):
|
|
349
|
+
super()._connect_event()
|
|
278
350
|
|
|
279
|
-
self.
|
|
351
|
+
self.figure.canvas.mpl_connect('button_press_event', lambda x: self._on_click(x))
|
|
280
352
|
return
|
|
281
353
|
|
|
282
|
-
def
|
|
283
|
-
|
|
354
|
+
def _on_click(self, e: MouseEvent):
|
|
355
|
+
if self.in_slider: self._on_click_slider(e)
|
|
356
|
+
return
|
|
284
357
|
|
|
285
|
-
def
|
|
286
|
-
|
|
358
|
+
def _on_click_slider(self, e: MouseEvent):
|
|
359
|
+
if self.is_click_slider or e.button != MouseButton.LEFT: return
|
|
287
360
|
|
|
288
|
-
|
|
289
|
-
self.ax_price.set_xlim(xmin, xmax)
|
|
290
|
-
self.ax_volume.set_xlim(xmin, xmax)
|
|
361
|
+
self.background_with_nav_pre = self.background_with_nav
|
|
291
362
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if indmax < 1: indmax = 1
|
|
295
|
-
if indmin == indmax: indmax += 1
|
|
296
|
-
ymin, ymax = (self.df[self.low][indmin:indmax].min(), self.df[self.high][indmin:indmax].max())
|
|
297
|
-
ysub = (ymax - ymin) / 15
|
|
298
|
-
pmin, pmax = (ymin-ysub, ymax+ysub)
|
|
299
|
-
self.ax_price.set_ylim(pmin, pmax)
|
|
300
|
-
|
|
301
|
-
ymax = self.df[self.volume][indmin:indmax].max()
|
|
302
|
-
# self._vol_ymax = ymax*1.2
|
|
303
|
-
volmax = ymax * 1.2
|
|
304
|
-
self.ax_volume.set_ylim(0, volmax)
|
|
363
|
+
self.is_click_slider = True
|
|
364
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
305
365
|
|
|
306
|
-
|
|
366
|
+
x = e.xdata.__int__()
|
|
367
|
+
navmin, navmax = self.navcoordinate
|
|
368
|
+
|
|
369
|
+
leftmin, leftmax = (navmin-self._navLineWidth, navmin+self._navLineWidth_half)
|
|
370
|
+
rightmin, rightmax = (navmax-self._navLineWidth_half, navmax+self._navLineWidth)
|
|
371
|
+
|
|
372
|
+
grater_than_left, less_then_right = (leftmax < x, x < rightmin)
|
|
373
|
+
if grater_than_left and less_then_right:
|
|
374
|
+
self.is_move = True
|
|
375
|
+
self.x_click = x
|
|
376
|
+
else:
|
|
377
|
+
if not grater_than_left and leftmin <= x:
|
|
378
|
+
self.click_navleft = True
|
|
379
|
+
self.x_click = navmax
|
|
380
|
+
elif not less_then_right and x <= rightmax:
|
|
381
|
+
self.click_navright = True
|
|
382
|
+
self.x_click = navmin
|
|
383
|
+
else:
|
|
384
|
+
self.x_click = x
|
|
307
385
|
return
|
|
308
386
|
|
|
309
387
|
|
|
310
|
-
class
|
|
311
|
-
|
|
312
|
-
limit_volume = 1_500
|
|
313
|
-
default_left, default_right = (180, 10)
|
|
314
|
-
_draw_blit = False
|
|
388
|
+
class SliderSelectMixin(ClickMixin):
|
|
389
|
+
limit_ma = 8_000
|
|
315
390
|
|
|
316
|
-
def
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
# 영역 이동시 주가 collection
|
|
320
|
-
self.blitcandle = LineCollection([], animated=True)
|
|
321
|
-
self.ax_price.add_collection(self.blitcandle)
|
|
322
|
-
self.priceline = LineCollection([], animated=True, edgecolors='k')
|
|
323
|
-
self.ax_price.add_artist(self.priceline)
|
|
391
|
+
def _on_move_slider(self, e):
|
|
392
|
+
if self.is_click_slider: self._set_navcoordinate(e)
|
|
393
|
+
return super()._on_move_slider(e)
|
|
324
394
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
self.
|
|
328
|
-
return
|
|
395
|
+
def _set_navcoordinate(self, e: MouseEvent):
|
|
396
|
+
x = e.xdata.__int__()
|
|
397
|
+
navmin, navmax = self.navcoordinate
|
|
329
398
|
|
|
330
|
-
|
|
331
|
-
|
|
399
|
+
if self.is_move:
|
|
400
|
+
xsub = self.x_click - x
|
|
401
|
+
navmin, navmax = (navmin-xsub, navmax-xsub)
|
|
332
402
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
self.blitcandle.set_edgecolor(self.df['edgecolor'])
|
|
403
|
+
# 값 보정
|
|
404
|
+
if navmax < 0: navmin, navmax = (navmin-navmax, 0)
|
|
405
|
+
if self.list_index[-1] < navmin: navmin, navmax = (self.list_index[-1], self.list_index[-1] + (navmax-navmin))
|
|
337
406
|
|
|
338
|
-
|
|
339
|
-
|
|
407
|
+
self.navcoordinate = (navmin, navmax)
|
|
408
|
+
self.x_click = x
|
|
340
409
|
|
|
341
|
-
|
|
342
|
-
if l < self.limit_volume:
|
|
343
|
-
volseg = self.df.loc[:, ['x', 'zero', 'x', self.volume]].values
|
|
344
|
-
else:
|
|
345
|
-
v = self.df[['x', 'zero', 'x', self.volume]].sort_values([self.volume], axis=0, ascending=False)
|
|
346
|
-
volseg = v[:self.limit_volume].values
|
|
410
|
+
self._set_lim(navmin, navmax, simpler=True, set_ma=(navmax-navmin < self.limit_ma))
|
|
347
411
|
|
|
348
|
-
|
|
412
|
+
self._set_navigator(navmin, navmax)
|
|
413
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
349
414
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
415
|
+
self._draw_artist()
|
|
416
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
417
|
+
self._restore_region(False, True)
|
|
418
|
+
else:
|
|
419
|
+
navmin, navmax = (x, self.x_click) if x < self.x_click else (self.x_click, x)
|
|
420
|
+
|
|
421
|
+
# 슬라이더가 차트를 벗어나지 않도록 선택 영역 제한
|
|
422
|
+
if navmax < 0 or self.list_index[-1] < navmin:
|
|
423
|
+
seg = self.navigator.get_segments()
|
|
424
|
+
navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
|
|
425
|
+
|
|
426
|
+
nsub = navmax - navmin
|
|
427
|
+
if nsub < self.min_distance:
|
|
428
|
+
self._restore_region(False, False)
|
|
429
|
+
self._set_navigator(navmin, navmax)
|
|
430
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
431
|
+
else:
|
|
432
|
+
self._set_lim(navmin, navmax, simpler=True, set_ma=(nsub < self.limit_ma))
|
|
433
|
+
self._set_navigator(navmin, navmax)
|
|
354
434
|
|
|
355
|
-
|
|
356
|
-
self._lim()
|
|
357
|
-
return
|
|
435
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
358
436
|
|
|
359
|
-
|
|
360
|
-
|
|
437
|
+
self._draw_artist()
|
|
438
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
439
|
+
self._restore_region(False, True)
|
|
440
|
+
return
|
|
361
441
|
|
|
362
|
-
self.ax_price.xaxis.draw(renderer)
|
|
363
|
-
self.ax_price.yaxis.draw(renderer)
|
|
364
442
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
elif self.candle_on_ma:
|
|
369
|
-
self.macollection.draw(renderer)
|
|
370
|
-
if self._draw_blit: self.blitcandle.draw(renderer)
|
|
371
|
-
else: self.candlecollection.draw(renderer)
|
|
372
|
-
else:
|
|
373
|
-
if self._draw_blit: self.blitcandle.draw(renderer)
|
|
374
|
-
else: self.candlecollection.draw(renderer)
|
|
375
|
-
self.macollection.draw(renderer)
|
|
443
|
+
class ReleaseMixin(SliderSelectMixin):
|
|
444
|
+
def _connect_event(self):
|
|
445
|
+
super()._connect_event()
|
|
376
446
|
|
|
377
|
-
self.
|
|
378
|
-
|
|
447
|
+
self.figure.canvas.mpl_connect('button_release_event', lambda x: self._on_release(x))
|
|
448
|
+
return
|
|
379
449
|
|
|
380
|
-
|
|
450
|
+
def _on_release(self, e: MouseEvent):
|
|
451
|
+
if self.in_slider and self.is_click_slider: self._on_release_slider(e)
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
def _on_release_slider(self, e: MouseEvent):
|
|
455
|
+
if not self.is_move:
|
|
456
|
+
seg = self.navigator.get_segments()
|
|
457
|
+
navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
|
|
458
|
+
nsub = navmax - navmin
|
|
459
|
+
if self.min_distance <= nsub: self.navcoordinate = (navmin, navmax)
|
|
460
|
+
else:
|
|
461
|
+
self.background_with_nav = self.background_with_nav_pre
|
|
462
|
+
navmin, navmax = self.navcoordinate
|
|
463
|
+
self._set_lim(navmin, navmax, simpler=True, set_ma=(nsub < self.limit_ma))
|
|
464
|
+
self._restore_region(False, True)
|
|
465
|
+
self._blit()
|
|
466
|
+
self._set_navigator(*self.navcoordinate)
|
|
467
|
+
|
|
468
|
+
self.is_click_slider = False
|
|
469
|
+
self.is_move = False
|
|
470
|
+
self.click_navleft, self.click_navright = (False, False)
|
|
381
471
|
return
|
|
382
472
|
|
|
383
473
|
|
|
384
|
-
class
|
|
474
|
+
class ChartClickMixin(ReleaseMixin):
|
|
385
475
|
is_click_chart = False
|
|
386
476
|
|
|
387
477
|
def _on_click(self, e: MouseEvent):
|
|
388
|
-
if
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
else: return
|
|
392
|
-
else: return
|
|
478
|
+
if self.in_price_chart or self.in_volume_chart: self._on_click_chart(e)
|
|
479
|
+
elif self.in_slider: self._on_click_slider(e)
|
|
480
|
+
return
|
|
393
481
|
|
|
394
|
-
|
|
482
|
+
def _on_click_chart(self, e: MouseEvent):
|
|
483
|
+
if self.is_click_chart: return
|
|
395
484
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
gtl, ltr = (lmax < x, x < rmin)
|
|
402
|
-
if gtl and ltr:
|
|
403
|
-
self._x_click = x
|
|
404
|
-
self.is_move = True
|
|
405
|
-
self.canvas.set_cursor(cursors.MOVE)
|
|
406
|
-
else:
|
|
407
|
-
self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
408
|
-
if not gtl and lmin <= x:
|
|
409
|
-
self._x_click = right
|
|
410
|
-
elif not ltr and x <= rmax:
|
|
411
|
-
self._x_click = left
|
|
412
|
-
else:
|
|
413
|
-
self._x_click = x
|
|
485
|
+
self.is_click_chart = True
|
|
486
|
+
self._x_click = e.x.__round__(2)
|
|
487
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
488
|
+
return
|
|
414
489
|
|
|
415
|
-
|
|
416
|
-
if
|
|
417
|
-
|
|
490
|
+
def _on_release(self, e):
|
|
491
|
+
if self.is_click_chart and (self.in_price_chart or self.in_volume_chart): self._on_release_chart(e)
|
|
492
|
+
elif self.is_click_slider and self.in_slider: self._on_release_slider(e)
|
|
418
493
|
return
|
|
419
494
|
|
|
420
|
-
def
|
|
421
|
-
if not self.is_click: return
|
|
422
|
-
elif e.inaxes is self.ax_slider: return super()._on_release(e)
|
|
423
|
-
elif not self.in_price and not self.in_volume and not self.is_click_chart: return
|
|
424
|
-
# 차트 click release action
|
|
425
|
-
self.canvas.set_cursor(cursors.POINTER)
|
|
426
|
-
self.is_click, self.is_move = (False, False)
|
|
495
|
+
def _on_release_chart(self, e):
|
|
427
496
|
self.is_click_chart = False
|
|
497
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
def _change_cursor(self, e):
|
|
501
|
+
if self.is_click_chart: return
|
|
502
|
+
return super()._change_cursor(e)
|
|
503
|
+
|
|
504
|
+
def _on_move(self, e):
|
|
505
|
+
self._on_move_action(e)
|
|
506
|
+
|
|
507
|
+
need_slider_action = self.is_click_slider and self.in_slider
|
|
508
|
+
need_chart_action = False if need_slider_action else self.is_click_chart and (self.in_price_chart or self.in_volume_chart)
|
|
509
|
+
self._restore_region((need_slider_action or need_chart_action))
|
|
510
|
+
|
|
511
|
+
if self.in_slider:
|
|
512
|
+
self._on_move_slider(e)
|
|
513
|
+
elif self.in_price_chart:
|
|
514
|
+
self._on_move_price_chart(e)
|
|
515
|
+
elif self.in_volume_chart:
|
|
516
|
+
self._on_move_volume_chart(e)
|
|
428
517
|
|
|
429
|
-
self._restore_region(empty=True)
|
|
430
|
-
self._copy_bbox()
|
|
431
|
-
self._restore_region()
|
|
432
518
|
self._blit()
|
|
433
519
|
return
|
|
434
520
|
|
|
435
|
-
def
|
|
436
|
-
self.
|
|
437
|
-
|
|
438
|
-
self._x_click = e.x.__int__()
|
|
439
|
-
self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
440
|
-
return
|
|
521
|
+
def _on_move_price_chart(self, e):
|
|
522
|
+
if self.is_click_chart: self._move_chart(e)
|
|
523
|
+
return super()._on_move_price_chart(e)
|
|
441
524
|
|
|
442
|
-
def
|
|
443
|
-
if self.is_click_chart: self.
|
|
444
|
-
|
|
445
|
-
return
|
|
525
|
+
def _on_move_volume_chart(self, e):
|
|
526
|
+
if self.is_click_chart: self._move_chart(e)
|
|
527
|
+
return super()._on_move_volume_chart(e)
|
|
446
528
|
|
|
447
|
-
def
|
|
448
|
-
x = e.x.
|
|
449
|
-
left, right = self.
|
|
529
|
+
def _move_chart(self, e: MouseEvent):
|
|
530
|
+
x = e.x.__round__(2)
|
|
531
|
+
left, right = self.navcoordinate
|
|
450
532
|
nsub = right - left
|
|
451
533
|
xsub = x - self._x_click
|
|
452
534
|
xdiv = (xsub / (1200 / nsub)).__int__()
|
|
453
|
-
if xdiv:
|
|
535
|
+
if not xdiv:
|
|
536
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
537
|
+
self._draw_artist()
|
|
538
|
+
else:
|
|
454
539
|
left, right = (left-xdiv, right-xdiv)
|
|
455
|
-
if
|
|
456
|
-
|
|
540
|
+
if right < 0 or self.df.index[-1] < left: self._restore_region(False, True)
|
|
541
|
+
else:
|
|
542
|
+
self.navcoordinate = (left, right)
|
|
543
|
+
self._set_lim(left, right, simpler=True, set_ma=((right-left) < self.limit_ma))
|
|
544
|
+
self._set_navigator(left, right)
|
|
545
|
+
self.navigator.draw(self.figure.canvas.renderer)
|
|
546
|
+
|
|
547
|
+
self._draw_artist()
|
|
548
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
549
|
+
self._restore_region(False, True)
|
|
457
550
|
self._x_click = x
|
|
458
551
|
return
|
|
459
552
|
|
|
460
|
-
def _on_move(self, e):
|
|
461
|
-
self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
|
|
462
553
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if self.in_slider and not self.is_click_chart:
|
|
466
|
-
self._change_coordinate()
|
|
467
|
-
if self.is_click:
|
|
468
|
-
nsub = self._navcoordinate[1] - self._navcoordinate[0]
|
|
469
|
-
if self.is_move: self._set_navigator(*self._navcoordinate)
|
|
470
|
-
else:
|
|
471
|
-
self._draw_blit = 900 < nsub
|
|
472
|
-
if self.intx is not None: self._set_navigator(self._x_click, self.intx)
|
|
473
|
-
|
|
474
|
-
if self.min_distance <= nsub: self._lim()
|
|
475
|
-
|
|
476
|
-
self.navigator.draw(self.canvas.renderer)
|
|
477
|
-
self._draw_blit_artist()
|
|
478
|
-
self._slider_move_action(e)
|
|
479
|
-
elif self.is_click:
|
|
480
|
-
if self.is_click_chart and (self.in_price or self.in_volume):
|
|
481
|
-
if (self.vmin, self.vmax) != self._navcoordinate:
|
|
482
|
-
self._change_coordinate_chart(e)
|
|
483
|
-
self._lim()
|
|
484
|
-
self._set_navigator(*self._navcoordinate)
|
|
485
|
-
self.navigator.draw(self.canvas.renderer)
|
|
486
|
-
self._draw_blit_artist()
|
|
487
|
-
else:
|
|
488
|
-
if self.in_slider or self.in_price or self.in_volume:
|
|
489
|
-
self._slider_move_action(e)
|
|
490
|
-
if self.in_price or self.in_volume:
|
|
491
|
-
self._chart_move_action(e)
|
|
554
|
+
class BaseMixin(ChartClickMixin):
|
|
555
|
+
pass
|
|
492
556
|
|
|
493
|
-
self._blit()
|
|
494
|
-
return
|
|
495
557
|
|
|
558
|
+
class Chart(BaseMixin, Mixin):
|
|
559
|
+
def _add_collection(self):
|
|
560
|
+
super()._add_collection()
|
|
561
|
+
return self.add_artist()
|
|
496
562
|
|
|
497
|
-
|
|
498
|
-
|
|
563
|
+
def _draw_artist(self):
|
|
564
|
+
super()._draw_artist()
|
|
565
|
+
return self.draw_artist()
|
|
499
566
|
|
|
567
|
+
def _get_segments(self):
|
|
568
|
+
self.generate_data()
|
|
569
|
+
return super()._get_segments()
|
|
500
570
|
|
|
501
|
-
class Chart(SliderMixin, CM, Mixin):
|
|
502
571
|
def _on_draw(self, e):
|
|
503
572
|
super()._on_draw(e)
|
|
504
573
|
return self.on_draw(e)
|
|
@@ -507,44 +576,30 @@ class Chart(SliderMixin, CM, Mixin):
|
|
|
507
576
|
self.on_pick(e)
|
|
508
577
|
return super()._on_pick(e)
|
|
509
578
|
|
|
579
|
+
def _set_candle_segments(self, index_start, index_end):
|
|
580
|
+
super()._set_candle_segments(index_start, index_end)
|
|
581
|
+
self.set_segment(index_start, index_end)
|
|
582
|
+
return
|
|
583
|
+
|
|
584
|
+
def _set_wick_segments(self, index_start, index_end, simpler=False):
|
|
585
|
+
super()._set_wick_segments(index_start, index_end, simpler)
|
|
586
|
+
self.set_segment(index_start, index_end, simpler)
|
|
587
|
+
return
|
|
588
|
+
|
|
589
|
+
def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
|
|
590
|
+
super()._set_line_segments(index_start, index_end, simpler, set_ma)
|
|
591
|
+
self.set_segment(index_start, index_end, simpler, set_ma)
|
|
592
|
+
return
|
|
593
|
+
|
|
510
594
|
def _on_move(self, e):
|
|
511
595
|
super()._on_move(e)
|
|
512
596
|
return self.on_move(e)
|
|
513
597
|
|
|
514
|
-
def _draw_artist(self):
|
|
515
|
-
super()._draw_artist()
|
|
516
|
-
return self.draw_artist()
|
|
517
|
-
def _draw_blit_artist(self):
|
|
518
|
-
super()._draw_blit_artist()
|
|
519
|
-
return self.draw_artist()
|
|
520
|
-
|
|
521
598
|
def _on_click(self, e):
|
|
522
599
|
super()._on_click(e)
|
|
523
600
|
return self.on_click(e)
|
|
524
|
-
def _on_release(self, e):
|
|
525
|
-
super()._on_release(e)
|
|
526
|
-
return self.on_release(e)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if __name__ == '__main__':
|
|
530
|
-
import json
|
|
531
|
-
from time import time
|
|
532
601
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
file = Path(__file__).parent / 'data/samsung.txt'
|
|
537
|
-
# file = Path(__file__).parent / 'data/apple.txt'
|
|
538
|
-
with open(file, 'r', encoding='utf-8') as txt:
|
|
539
|
-
data = json.load(txt)
|
|
540
|
-
data = data
|
|
541
|
-
df = pd.DataFrame(data)
|
|
542
|
-
|
|
543
|
-
t = time()
|
|
544
|
-
# c = SimpleMixin()
|
|
545
|
-
c = SliderMixin()
|
|
546
|
-
c.set_data(df)
|
|
547
|
-
t2 = time() - t
|
|
548
|
-
print(f'{t2=}')
|
|
549
|
-
plt.show()
|
|
602
|
+
def on_release(self, e):
|
|
603
|
+
super().on_release(e)
|
|
604
|
+
return self.on_release(e)
|
|
550
605
|
|