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,176 @@
|
|
|
1
|
+
from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
|
|
2
|
+
|
|
3
|
+
from ._data import BaseMixin as Base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MouseMoveMixin(Base):
|
|
7
|
+
in_slider = False
|
|
8
|
+
is_click_slider = False
|
|
9
|
+
|
|
10
|
+
def _erase_crossline(self):
|
|
11
|
+
boolen = super()._erase_crossline()
|
|
12
|
+
if boolen:
|
|
13
|
+
return boolen
|
|
14
|
+
|
|
15
|
+
seg = self.collection_slider_vline.get_segments()
|
|
16
|
+
if seg:
|
|
17
|
+
self.collection_slider_vline.set_segments([])
|
|
18
|
+
return True
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def _on_move_action(self, e):
|
|
22
|
+
super()._on_move_action(e)
|
|
23
|
+
|
|
24
|
+
if self.in_slider:
|
|
25
|
+
self._restore_region(self.is_click_slider)
|
|
26
|
+
self._on_move_slider(e)
|
|
27
|
+
self.figure.canvas.blit()
|
|
28
|
+
elif self.in_price_chart or self.in_volume_chart:
|
|
29
|
+
self._restore_region()
|
|
30
|
+
self._draw_crossline(e)
|
|
31
|
+
self.figure.canvas.blit()
|
|
32
|
+
else:
|
|
33
|
+
if self._erase_crossline():
|
|
34
|
+
self._restore_region()
|
|
35
|
+
self.figure.canvas.blit()
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
def _on_move_action(self, e: MouseEvent):
|
|
39
|
+
self._check_ax(e)
|
|
40
|
+
|
|
41
|
+
self.intx = None
|
|
42
|
+
if self.in_slider or self.in_price_chart or self.in_volume_chart:
|
|
43
|
+
self._get_x(e)
|
|
44
|
+
|
|
45
|
+
self._set_cursor(e)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
def _set_cursor(self, e: MouseEvent):
|
|
49
|
+
# 마우스 커서 변경
|
|
50
|
+
if self.is_click_slider:
|
|
51
|
+
return
|
|
52
|
+
elif not self.in_slider:
|
|
53
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
navleft, navright = self.navcoordinate
|
|
57
|
+
if navleft == navright:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
x = e.xdata.__round__()
|
|
61
|
+
|
|
62
|
+
leftmin = navleft - self._navLineWidth
|
|
63
|
+
leftmax = navleft + self._navLineWidth_half
|
|
64
|
+
rightmin = navright - self._navLineWidth_half
|
|
65
|
+
rightmax = navright + self._navLineWidth
|
|
66
|
+
if x < leftmin:
|
|
67
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
68
|
+
elif x <= leftmax:
|
|
69
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
70
|
+
elif x < rightmin:
|
|
71
|
+
self.figure.canvas.set_cursor(cursors.MOVE)
|
|
72
|
+
elif x <= rightmax:
|
|
73
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
74
|
+
else:
|
|
75
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
def _check_ax(self, e: MouseEvent):
|
|
79
|
+
ax = e.inaxes
|
|
80
|
+
if not ax or e.xdata is None or e.ydata is None:
|
|
81
|
+
self.in_slider, self.in_price_chart, self.in_volume_chart = (False, False, False)
|
|
82
|
+
else:
|
|
83
|
+
if ax is self.ax_slider:
|
|
84
|
+
self.in_slider = True
|
|
85
|
+
self.in_price_chart = False
|
|
86
|
+
self.in_volume_chart = False
|
|
87
|
+
elif ax is self.ax_price:
|
|
88
|
+
self.in_slider = False
|
|
89
|
+
self.in_price_chart = True
|
|
90
|
+
self.in_volume_chart = False
|
|
91
|
+
elif ax is self.ax_volume:
|
|
92
|
+
self.in_slider = False
|
|
93
|
+
self.in_price_chart = False
|
|
94
|
+
self.in_volume_chart = True
|
|
95
|
+
else:
|
|
96
|
+
self.in_slider = False
|
|
97
|
+
self.in_price_chart = False
|
|
98
|
+
self.in_volume_chart = False
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
def _on_move_slider(self, e: MouseEvent):
|
|
102
|
+
x = e.xdata
|
|
103
|
+
if self.intx is not None:
|
|
104
|
+
renderer = self.figure.canvas.renderer
|
|
105
|
+
seg = [((x, self.slider_ymin), (x, self.slider_ymax))]
|
|
106
|
+
self.collection_slider_vline.set_segments(seg)
|
|
107
|
+
self.collection_slider_vline.draw(renderer)
|
|
108
|
+
|
|
109
|
+
if self.in_slider:
|
|
110
|
+
self.artist_text_slider.set_text(f'{self.df["date"][self.intx]}')
|
|
111
|
+
self.artist_text_slider.set_x(x)
|
|
112
|
+
self.artist_text_slider.draw(renderer)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
def _draw_crossline(self, e: MouseEvent):
|
|
116
|
+
x = e.xdata
|
|
117
|
+
self.collection_slider_vline.set_segments([((x-0.5, self.slider_ymin), (x-0.5, self.slider_ymax))])
|
|
118
|
+
self.collection_slider_vline.draw(self.figure.canvas.renderer)
|
|
119
|
+
return super()._draw_crossline(e)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ClickMixin(MouseMoveMixin):
|
|
123
|
+
x_click = None
|
|
124
|
+
is_move = False
|
|
125
|
+
click_navleft, click_navright = (False, False)
|
|
126
|
+
|
|
127
|
+
def _connect_events(self):
|
|
128
|
+
super()._connect_events()
|
|
129
|
+
|
|
130
|
+
self.figure.canvas.mpl_connect('button_press_event', lambda x: self.on_click(x))
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
def on_click(self, e: MouseEvent):
|
|
134
|
+
self._on_click(e)
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
def _on_click(self, e: MouseEvent):
|
|
138
|
+
if self.in_slider and not self.is_click_slider:
|
|
139
|
+
if e.button == MouseButton.LEFT:
|
|
140
|
+
self._on_click_slider(e)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
def _on_click_slider(self, e: MouseEvent):
|
|
144
|
+
self.background_with_nav_pre = self.background_with_nav
|
|
145
|
+
|
|
146
|
+
self.is_click_slider = True
|
|
147
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
148
|
+
|
|
149
|
+
navleft, navright = self.navcoordinate
|
|
150
|
+
x = e.xdata.__round__()
|
|
151
|
+
|
|
152
|
+
leftmax = navleft + self._navLineWidth_half
|
|
153
|
+
rightmin = navright - self._navLineWidth_half
|
|
154
|
+
|
|
155
|
+
grater_than_left = leftmax < x
|
|
156
|
+
less_then_right = x < rightmin
|
|
157
|
+
if grater_than_left and less_then_right:
|
|
158
|
+
self.is_move = True
|
|
159
|
+
self.x_click = x
|
|
160
|
+
else:
|
|
161
|
+
leftmin = navleft - self._navLineWidth
|
|
162
|
+
rightmax = navright + self._navLineWidth
|
|
163
|
+
if not grater_than_left and leftmin <= x:
|
|
164
|
+
self.click_navleft = True
|
|
165
|
+
self.x_click = navright
|
|
166
|
+
elif not less_then_right and x <= rightmax:
|
|
167
|
+
self.click_navright = True
|
|
168
|
+
self.x_click = navleft
|
|
169
|
+
else:
|
|
170
|
+
self.x_click = x
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class BaseMixin(ClickMixin):
|
|
175
|
+
pass
|
|
176
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
|
|
2
|
+
|
|
3
|
+
from ._mouse import BaseMixin as Base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SliderSelectMixin(Base):
|
|
7
|
+
limit_ma = 8_000
|
|
8
|
+
|
|
9
|
+
def _on_move_slider(self, e):
|
|
10
|
+
if self.is_click_slider:
|
|
11
|
+
self._set_navcoordinate(e)
|
|
12
|
+
return super()._on_move_slider(e)
|
|
13
|
+
|
|
14
|
+
def _set_navcoordinate(self, e: MouseEvent):
|
|
15
|
+
navmin, navmax = self.navcoordinate
|
|
16
|
+
|
|
17
|
+
x = e.xdata.__int__()
|
|
18
|
+
if self.is_move:
|
|
19
|
+
xsub = self.x_click - x
|
|
20
|
+
navmin, navmax = (navmin-xsub, navmax-xsub)
|
|
21
|
+
|
|
22
|
+
# 값 보정
|
|
23
|
+
if navmax < 0:
|
|
24
|
+
navmin, navmax = (navmin-navmax, 0)
|
|
25
|
+
if self.index_list[-1] < navmin:
|
|
26
|
+
navmin, navmax = (self.index_list[-1], self.index_list[-1] + (navmax-navmin))
|
|
27
|
+
|
|
28
|
+
self.navcoordinate = (navmin, navmax)
|
|
29
|
+
self.x_click = x
|
|
30
|
+
|
|
31
|
+
self.axis(navmin, xmax=navmax+1, simpler=True, draw_ma=(navmax-navmin < self.limit_ma))
|
|
32
|
+
|
|
33
|
+
self._axis_navigator(navmin, navmax)
|
|
34
|
+
self.collection_navigator.draw(self.figure.canvas.renderer)
|
|
35
|
+
|
|
36
|
+
self.draw_artists()
|
|
37
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
38
|
+
self._restore_region()
|
|
39
|
+
else:
|
|
40
|
+
navmin, navmax = (x, self.x_click) if x < self.x_click else (self.x_click, x)
|
|
41
|
+
|
|
42
|
+
# 슬라이더가 차트를 벗어나지 않도록 선택 영역 제한
|
|
43
|
+
if navmax < 0 or self.index_list[-1] < navmin:
|
|
44
|
+
seg = self.collection_navigator.get_segments()
|
|
45
|
+
navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
|
|
46
|
+
|
|
47
|
+
nsub = navmax - navmin
|
|
48
|
+
min_distance = 5 if not self.min_distance or self.min_distance < 5 else self.min_distance
|
|
49
|
+
if nsub < min_distance:
|
|
50
|
+
self._restore_region(False, False)
|
|
51
|
+
self._axis_navigator(navmin, navmax)
|
|
52
|
+
self.collection_navigator.draw(self.figure.canvas.renderer)
|
|
53
|
+
else:
|
|
54
|
+
self.axis(navmin, xmax=navmax+1, simpler=True, draw_ma=(nsub < self.limit_ma))
|
|
55
|
+
self._axis_navigator(navmin, navmax)
|
|
56
|
+
|
|
57
|
+
self.collection_navigator.draw(self.figure.canvas.renderer)
|
|
58
|
+
|
|
59
|
+
self.draw_artists()
|
|
60
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
61
|
+
self._restore_region(False, True)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ReleaseMixin(SliderSelectMixin):
|
|
66
|
+
def _connect_events(self):
|
|
67
|
+
super()._connect_events()
|
|
68
|
+
|
|
69
|
+
self.figure.canvas.mpl_connect('button_release_event', lambda x: self.on_release(x))
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
def on_release(self, e: MouseEvent):
|
|
73
|
+
self._on_release(e)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
def _on_release(self, e: MouseEvent):
|
|
77
|
+
if self.in_slider and self.is_click_slider:
|
|
78
|
+
if e.button == MouseButton.LEFT:
|
|
79
|
+
self._on_release_slider(e)
|
|
80
|
+
self.axis(self.vxmin, xmax=self.vxmax, simpler=False, draw_ma=True)
|
|
81
|
+
self.figure.canvas.draw()
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
def _on_release_slider(self, e: MouseEvent):
|
|
85
|
+
if not self.is_move:
|
|
86
|
+
seg = self.collection_navigator.get_segments()
|
|
87
|
+
navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
|
|
88
|
+
nsub = navmax - navmin
|
|
89
|
+
min_distance = 5 if not self.min_distance or self.min_distance < 5 else self.min_distance
|
|
90
|
+
if min_distance <= nsub:
|
|
91
|
+
self.navcoordinate = (navmin, navmax)
|
|
92
|
+
else:
|
|
93
|
+
self.background_with_nav = self.background_with_nav_pre
|
|
94
|
+
navmin, navmax = self.navcoordinate
|
|
95
|
+
self.axis(navmin, xmax=navmax+1, simpler=False, draw_ma=True)
|
|
96
|
+
self._restore_region(False, True)
|
|
97
|
+
self.figure.canvas.blit()
|
|
98
|
+
self._axis_navigator(*self.navcoordinate)
|
|
99
|
+
|
|
100
|
+
self.is_click_slider = False
|
|
101
|
+
self.is_move = False
|
|
102
|
+
self.click_navleft, self.click_navright = (False, False)
|
|
103
|
+
|
|
104
|
+
# self.figure.canvas.draw()
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ChartClickMixin(ReleaseMixin):
|
|
109
|
+
is_click_chart = False
|
|
110
|
+
|
|
111
|
+
def _on_click(self, e: MouseEvent):
|
|
112
|
+
if e.button == MouseButton.LEFT:
|
|
113
|
+
if (
|
|
114
|
+
not self.is_click_chart
|
|
115
|
+
and (self.in_price_chart or self.in_volume_chart)
|
|
116
|
+
):
|
|
117
|
+
self._on_click_chart(e)
|
|
118
|
+
elif not self.is_click_slider and self.in_slider:
|
|
119
|
+
self._on_click_slider(e)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
def _on_click_chart(self, e: MouseEvent):
|
|
123
|
+
self.is_click_chart = True
|
|
124
|
+
x = e.xdata.__int__()
|
|
125
|
+
self.x_click = x - self.navcoordinate[0]
|
|
126
|
+
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
def _on_release(self, e):
|
|
130
|
+
if e.button == MouseButton.LEFT:
|
|
131
|
+
if (
|
|
132
|
+
self.is_click_chart
|
|
133
|
+
and (self.in_price_chart or self.in_volume_chart)
|
|
134
|
+
):
|
|
135
|
+
self._on_release_chart(e)
|
|
136
|
+
self.axis(self.vxmin, xmax=self.vxmax, simpler=False, draw_ma=True)
|
|
137
|
+
self.figure.canvas.draw()
|
|
138
|
+
elif self.is_click_slider and self.in_slider:
|
|
139
|
+
self._on_release_slider(e)
|
|
140
|
+
self.axis(self.vxmin, xmax=self.vxmax, simpler=False, draw_ma=True)
|
|
141
|
+
self.figure.canvas.draw()
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
def _on_release_chart(self, e):
|
|
145
|
+
self.is_click_chart = False
|
|
146
|
+
self.figure.canvas.set_cursor(cursors.POINTER)
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
def _set_cursor(self, e):
|
|
150
|
+
if self.is_click_chart:
|
|
151
|
+
return
|
|
152
|
+
return super()._set_cursor(e)
|
|
153
|
+
|
|
154
|
+
def _on_move_action(self, e):
|
|
155
|
+
super()._on_move_action(e)
|
|
156
|
+
|
|
157
|
+
if self.in_slider:
|
|
158
|
+
self._restore_region(self.is_click_slider)
|
|
159
|
+
self._on_move_slider(e)
|
|
160
|
+
self.figure.canvas.blit()
|
|
161
|
+
elif self.in_price_chart or self.in_volume_chart:
|
|
162
|
+
self._restore_region(self.is_click_chart)
|
|
163
|
+
if not self.is_click_chart:
|
|
164
|
+
self._draw_crossline(e)
|
|
165
|
+
else:
|
|
166
|
+
self._move_chart(e)
|
|
167
|
+
self.figure.canvas.blit()
|
|
168
|
+
else:
|
|
169
|
+
if self._erase_crossline():
|
|
170
|
+
self._restore_region()
|
|
171
|
+
self.figure.canvas.blit()
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
def _move_chart(self, e: MouseEvent):
|
|
175
|
+
left, right = self.navcoordinate
|
|
176
|
+
x = e.xdata.__int__() - left
|
|
177
|
+
xsub = x - self.x_click
|
|
178
|
+
if not xsub:
|
|
179
|
+
self.collection_navigator.draw(self.figure.canvas.renderer)
|
|
180
|
+
self.draw_artists()
|
|
181
|
+
else:
|
|
182
|
+
left, right = (left-xsub, right-xsub)
|
|
183
|
+
if right < 0 or self.df.index[-1] < left:
|
|
184
|
+
self._restore_region()
|
|
185
|
+
else:
|
|
186
|
+
self.x_click = x
|
|
187
|
+
self.navcoordinate = (left, right)
|
|
188
|
+
self.axis(left, xmax=right+1, simpler=True, draw_ma=(right-left < self.limit_ma))
|
|
189
|
+
self._axis_navigator(left, right)
|
|
190
|
+
self.collection_navigator.draw(self.figure.canvas.renderer)
|
|
191
|
+
|
|
192
|
+
self.draw_artists()
|
|
193
|
+
self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
|
|
194
|
+
self._restore_region()
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class BaseMixin(ChartClickMixin):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class Chart(BaseMixin):
|
|
203
|
+
pass
|
|
204
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from matplotlib.axes import Axes
|
|
2
|
+
from matplotlib.collections import LineCollection
|
|
3
|
+
from matplotlib.text import Text
|
|
4
|
+
from matplotlib.axes import Axes
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ..._config import DEFAULTCONFIG, ConfigData
|
|
9
|
+
|
|
10
|
+
from .a_canvas import CanvasMixin, Figure
|
|
11
|
+
from .b_artist import ArtistMixin
|
|
12
|
+
from .c_draw import DrawMixin
|
|
13
|
+
from .d_segment import SegmentMixin
|
|
14
|
+
from .e_axis import AxisMixin
|
|
15
|
+
from .f_background import BackgroundMixin
|
|
16
|
+
from .g_event import EventMixin
|
|
17
|
+
from .h_data import DataMixin
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Chart(
|
|
21
|
+
CanvasMixin,
|
|
22
|
+
ArtistMixin,
|
|
23
|
+
DrawMixin,
|
|
24
|
+
SegmentMixin,
|
|
25
|
+
AxisMixin,
|
|
26
|
+
BackgroundMixin,
|
|
27
|
+
EventMixin,
|
|
28
|
+
DataMixin,
|
|
29
|
+
):
|
|
30
|
+
limit_candle = 400
|
|
31
|
+
limit_wick = 2_000
|
|
32
|
+
candle_on_ma = True
|
|
33
|
+
|
|
34
|
+
key_date = 'date'
|
|
35
|
+
key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
|
|
36
|
+
key_volume = 'volume'
|
|
37
|
+
|
|
38
|
+
index_list: list[int] = []
|
|
39
|
+
|
|
40
|
+
df: pd.DataFrame
|
|
41
|
+
|
|
42
|
+
CONFIG: ConfigData
|
|
43
|
+
|
|
44
|
+
figure: Figure
|
|
45
|
+
ax_legend: Axes
|
|
46
|
+
ax_price: Axes
|
|
47
|
+
ax_volume: Axes
|
|
48
|
+
|
|
49
|
+
artist_watermark: Text
|
|
50
|
+
collection_candle: LineCollection
|
|
51
|
+
collection_volume: LineCollection
|
|
52
|
+
collection_ma: LineCollection
|
|
53
|
+
|
|
54
|
+
###
|
|
55
|
+
|
|
56
|
+
segment_volume: np.ndarray
|
|
57
|
+
segment_volume_wick: np.ndarray
|
|
58
|
+
facecolor_volume: np.ndarray
|
|
59
|
+
edgecolor_volume: np.ndarray
|
|
60
|
+
|
|
61
|
+
segment_candle: np.ndarray
|
|
62
|
+
segment_candle_wick: np.ndarray
|
|
63
|
+
segment_priceline: np.ndarray
|
|
64
|
+
facecolor_candle: np.ndarray
|
|
65
|
+
edgecolor_candle: np.ndarray
|
|
66
|
+
|
|
67
|
+
segment_ma: np.ndarray
|
|
68
|
+
edgecolor_ma: np.ndarray
|
|
69
|
+
|
|
70
|
+
price_ymin: int
|
|
71
|
+
price_ymax: int
|
|
72
|
+
volume_ymax: int
|
|
73
|
+
|
|
74
|
+
chart_price_ymax: float
|
|
75
|
+
chart_volume_ymax: float
|
|
76
|
+
|
|
77
|
+
vxmin: int
|
|
78
|
+
vxmax: int
|
|
79
|
+
|
|
80
|
+
###
|
|
81
|
+
|
|
82
|
+
_visible_ma: set[int] = set()
|
|
83
|
+
|
|
84
|
+
_background = None
|
|
85
|
+
_background_background = None
|
|
86
|
+
_creating_background = False
|
|
87
|
+
|
|
88
|
+
def __init__(self, config=DEFAULTCONFIG):
|
|
89
|
+
self.CONFIG = config
|
|
90
|
+
super().__init__()
|
|
91
|
+
|
|
92
|
+
self.add_artists()
|
|
93
|
+
self.connect_events()
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
def refresh(self):
|
|
97
|
+
self.set_artists()
|
|
98
|
+
|
|
99
|
+
self.set_candle_segments()
|
|
100
|
+
self.set_color_segments()
|
|
101
|
+
ma_alpha = self.collection_ma.get_alpha()
|
|
102
|
+
# print(f'{ma_alpha=}')
|
|
103
|
+
if ma_alpha is not None:
|
|
104
|
+
self.collection_ma.set_alpha([1 for _ in ma_alpha])
|
|
105
|
+
|
|
106
|
+
self.axis(self.vxmin, xmax=self.vxmax-1)
|
|
107
|
+
|
|
108
|
+
self.set_canvas()
|
|
109
|
+
self.figure.canvas.draw()
|
|
110
|
+
return
|
|
111
|
+
|