seolpyo-mplchart 1.3.1.2__py3-none-any.whl → 1.4.0.1__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.
Potentially problematic release.
This version of seolpyo-mplchart might be problematic. Click here for more details.
- seolpyo_mplchart/__init__.py +8 -8
- seolpyo_mplchart/_cursor.py +221 -182
- seolpyo_mplchart/_draw.py +211 -235
- seolpyo_mplchart/_slider.py +146 -138
- {seolpyo_mplchart-1.3.1.2.dist-info → seolpyo_mplchart-1.4.0.1.dist-info}/METADATA +1 -1
- {seolpyo_mplchart-1.3.1.2.dist-info → seolpyo_mplchart-1.4.0.1.dist-info}/RECORD +8 -8
- {seolpyo_mplchart-1.3.1.2.dist-info → seolpyo_mplchart-1.4.0.1.dist-info}/WHEEL +1 -1
- {seolpyo_mplchart-1.3.1.2.dist-info → seolpyo_mplchart-1.4.0.1.dist-info}/top_level.txt +0 -0
seolpyo_mplchart/__init__.py
CHANGED
|
@@ -297,19 +297,19 @@ def set_theme(chart: SliderChart|CursorChart|OnlyChart, theme: Literal['light',
|
|
|
297
297
|
chart.color_volume_up, chart.color_volume_down = ('#32CD32', '#FF4500')
|
|
298
298
|
chart.color_volume_flat = 'w'
|
|
299
299
|
|
|
300
|
-
chart.list_macolor = ('#
|
|
300
|
+
chart.list_macolor = ('#1E90FF', '#FFA500', '#FF1493', '#FFFF00', '#00CED1')
|
|
301
301
|
|
|
302
302
|
chart.lineKwargs = {'edgecolor': 'w'}
|
|
303
303
|
chart.color_box = 'w'
|
|
304
304
|
chart.textboxKwargs = {'facecolor': 'k', 'edgecolor': 'w'}
|
|
305
305
|
chart.textKwargs = {'color': 'w'}
|
|
306
|
-
chart.color_navigator_cover, chart.color_navigator_line = ('
|
|
306
|
+
chart.color_navigator_cover, chart.color_navigator_line = ('k', '#FF2400')
|
|
307
307
|
|
|
308
308
|
if initialized:
|
|
309
309
|
chart.change_background_color('k')
|
|
310
310
|
chart.change_tick_color('w')
|
|
311
311
|
chart.change_line_color('w')
|
|
312
|
-
if hasattr(chart, 'navigator'): chart.
|
|
312
|
+
if hasattr(chart, 'navigator'): chart.collection_navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
|
|
313
313
|
|
|
314
314
|
if hasattr(chart, 'df'):
|
|
315
315
|
chart.set_data(chart.df, sort_df=False, calc_ma=False, set_candlecolor=True, set_volumecolor=True, calc_info=False, change_lim=False)
|
|
@@ -324,22 +324,22 @@ def set_theme(chart: SliderChart|CursorChart|OnlyChart, theme: Literal['light',
|
|
|
324
324
|
chart.color_flat = 'k'
|
|
325
325
|
chart.color_up_down, chart.color_down_up = ('w', 'w')
|
|
326
326
|
|
|
327
|
-
chart.color_volume_up, chart.color_volume_down = ('#
|
|
328
|
-
chart.color_volume_flat = '#
|
|
327
|
+
chart.color_volume_up, chart.color_volume_down = ('#FF6666', '#5CA8F4')
|
|
328
|
+
chart.color_volume_flat = '#808080'
|
|
329
329
|
|
|
330
|
-
chart.list_macolor = ('#
|
|
330
|
+
chart.list_macolor = ('#006400', '#8B008B', '#FFA500', '#0000FF', '#FF0000')
|
|
331
331
|
|
|
332
332
|
chart.lineKwargs = {'edgecolor': 'k'}
|
|
333
333
|
chart.color_box = 'k'
|
|
334
334
|
chart.textboxKwargs = {'facecolor': 'w', 'edgecolor': 'k'}
|
|
335
335
|
chart.textKwargs = {'color': 'k'}
|
|
336
|
-
chart.color_navigator_cover, chart.color_navigator_line = ('k', '#
|
|
336
|
+
chart.color_navigator_cover, chart.color_navigator_line = ('k', '#1E78FF')
|
|
337
337
|
|
|
338
338
|
if initialized:
|
|
339
339
|
chart.change_background_color('#fafafa')
|
|
340
340
|
chart.change_tick_color('k')
|
|
341
341
|
chart.change_line_color('k')
|
|
342
|
-
if hasattr(chart, 'navigator'): chart.
|
|
342
|
+
if hasattr(chart, 'navigator'): chart.collection_navigator.set_edgecolor([chart.color_navigator_cover, chart.color_navigator_line])
|
|
343
343
|
|
|
344
344
|
if hasattr(chart, 'df'):
|
|
345
345
|
chart.set_data(chart.df, sort_df=False, calc_ma=False, set_candlecolor=True, set_volumecolor=True, calc_info=False, change_lim=False)
|
seolpyo_mplchart/_cursor.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from fractions import Fraction
|
|
2
2
|
|
|
3
|
-
import matplotlib.pyplot as plt
|
|
4
3
|
from matplotlib.backend_bases import MouseEvent
|
|
5
4
|
from matplotlib.collections import LineCollection
|
|
6
5
|
from matplotlib.text import Text
|
|
@@ -34,72 +33,72 @@ class CollectionMixin(BM):
|
|
|
34
33
|
textKwargs.update({'animated': True, 'bbox': textboxKwargs, 'horizontalalignment': '', 'verticalalignment': ''})
|
|
35
34
|
(textKwargs.pop('horizontalalignment'), textKwargs.pop('verticalalignment'))
|
|
36
35
|
|
|
37
|
-
self.
|
|
38
|
-
self.ax_price.add_artist(self.
|
|
39
|
-
self.
|
|
40
|
-
self.ax_price.add_artist(self.
|
|
41
|
-
self.
|
|
42
|
-
self.ax_price.add_artist(self.
|
|
43
|
-
|
|
44
|
-
self.
|
|
45
|
-
self.ax_volume.add_artist(self.
|
|
46
|
-
self.
|
|
47
|
-
self.ax_volume.add_artist(self.
|
|
48
|
-
self.
|
|
49
|
-
self.ax_volume.add_artist(self.
|
|
50
|
-
|
|
51
|
-
self.
|
|
52
|
-
self.ax_price.add_artist(self.
|
|
53
|
-
self.
|
|
54
|
-
self.ax_price.add_artist(self.
|
|
55
|
-
|
|
56
|
-
self.
|
|
57
|
-
self.ax_volume.add_artist(self.
|
|
58
|
-
self.
|
|
59
|
-
self.ax_volume.add_artist(self.
|
|
36
|
+
self.collection_price_crossline = LineCollection(**lineKwargs)
|
|
37
|
+
self.ax_price.add_artist(self.collection_price_crossline)
|
|
38
|
+
self.artist_text_date_price = Text(**textKwargs, horizontalalignment='center', verticalalignment='bottom')
|
|
39
|
+
self.ax_price.add_artist(self.artist_text_date_price)
|
|
40
|
+
self.artist_text_price = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
|
|
41
|
+
self.ax_price.add_artist(self.artist_text_price)
|
|
42
|
+
|
|
43
|
+
self.collection_volume_crossline = LineCollection(**lineKwargs)
|
|
44
|
+
self.ax_volume.add_artist(self.collection_volume_crossline)
|
|
45
|
+
self.artist_text_date_volume = Text(**textKwargs, horizontalalignment='center', verticalalignment='top')
|
|
46
|
+
self.ax_volume.add_artist(self.artist_text_date_volume)
|
|
47
|
+
self.artist_text_volume = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
|
|
48
|
+
self.ax_volume.add_artist(self.artist_text_volume)
|
|
49
|
+
|
|
50
|
+
self.collection_price_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
|
|
51
|
+
self.ax_price.add_artist(self.collection_price_box)
|
|
52
|
+
self.artist_text_price_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
|
|
53
|
+
self.ax_price.add_artist(self.artist_text_price_info)
|
|
54
|
+
|
|
55
|
+
self.collection_volume_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
|
|
56
|
+
self.ax_volume.add_artist(self.collection_volume_box)
|
|
57
|
+
self.artist_text_volume_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
|
|
58
|
+
self.ax_volume.add_artist(self.artist_text_volume_info)
|
|
60
59
|
return
|
|
61
60
|
|
|
62
61
|
def change_background_color(self, color):
|
|
63
62
|
super().change_background_color(color)
|
|
64
63
|
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
64
|
+
self.artist_text_price.set_backgroundcolor(color)
|
|
65
|
+
self.artist_text_volume.set_backgroundcolor(color)
|
|
67
66
|
|
|
68
|
-
self.
|
|
69
|
-
self.
|
|
67
|
+
self.artist_text_date_price.set_backgroundcolor(color)
|
|
68
|
+
self.artist_text_date_volume.set_backgroundcolor(color)
|
|
70
69
|
|
|
71
|
-
self.
|
|
72
|
-
self.
|
|
70
|
+
self.artist_text_price_info.set_backgroundcolor(color)
|
|
71
|
+
self.artist_text_volume_info.set_backgroundcolor(color)
|
|
73
72
|
return
|
|
74
73
|
|
|
75
74
|
def change_text_color(self, color):
|
|
76
75
|
super().change_text_color(color)
|
|
77
76
|
|
|
78
|
-
self.
|
|
79
|
-
self.
|
|
77
|
+
self.artist_text_price.set_color(color)
|
|
78
|
+
self.artist_text_volume.set_color(color)
|
|
80
79
|
|
|
81
|
-
self.
|
|
82
|
-
self.
|
|
80
|
+
self.artist_text_date_price.set_color(color)
|
|
81
|
+
self.artist_text_date_volume.set_color(color)
|
|
83
82
|
|
|
84
|
-
self.
|
|
85
|
-
self.
|
|
83
|
+
self.artist_text_price_info.set_color(color)
|
|
84
|
+
self.artist_text_volume_info.set_color(color)
|
|
86
85
|
return
|
|
87
86
|
|
|
88
87
|
def change_line_color(self, color):
|
|
89
|
-
self.
|
|
90
|
-
self.
|
|
88
|
+
self.collection_price_crossline.set_edgecolor(color)
|
|
89
|
+
self.collection_volume_crossline.set_edgecolor(color)
|
|
91
90
|
|
|
92
|
-
self.
|
|
93
|
-
self.
|
|
91
|
+
self.collection_price_box.set_edgecolor(color)
|
|
92
|
+
self.collection_volume_box.set_edgecolor(color)
|
|
94
93
|
|
|
95
|
-
self.
|
|
96
|
-
self.
|
|
94
|
+
self.artist_text_price.get_bbox_patch().set_edgecolor(color)
|
|
95
|
+
self.artist_text_volume.get_bbox_patch().set_edgecolor(color)
|
|
97
96
|
|
|
98
|
-
self.
|
|
99
|
-
self.
|
|
97
|
+
self.artist_text_date_price.get_bbox_patch().set_edgecolor(color)
|
|
98
|
+
self.artist_text_date_volume.get_bbox_patch().set_edgecolor(color)
|
|
100
99
|
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
100
|
+
self.artist_text_price_info.get_bbox_patch().set_edgecolor(color)
|
|
101
|
+
self.artist_text_volume_info.get_bbox_patch().set_edgecolor(color)
|
|
103
102
|
return
|
|
104
103
|
|
|
105
104
|
|
|
@@ -121,6 +120,10 @@ class DataMixin(CollectionMixin):
|
|
|
121
120
|
if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
|
|
122
121
|
return
|
|
123
122
|
|
|
123
|
+
def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, calc_info=True, *args, **kwargs):
|
|
124
|
+
super().set_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
|
|
125
|
+
return
|
|
126
|
+
|
|
124
127
|
def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *_, **__):
|
|
125
128
|
super()._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__)
|
|
126
129
|
|
|
@@ -144,19 +147,20 @@ class DataMixin(CollectionMixin):
|
|
|
144
147
|
self.df['space_box_candle'] = (self.df[self.high] - self.df[self.low]) / 5
|
|
145
148
|
self.df['bottom_box_candle'] = self.df[self.low] - self.df['space_box_candle']
|
|
146
149
|
self.df['top_box_candle'] = self.df[self.high] + self.df['space_box_candle']
|
|
147
|
-
|
|
150
|
+
self.df['height_box_candle'] = self.df['top_box_candle'] - self.df['bottom_box_candle']
|
|
151
|
+
if self.volume: self.df['max_box_volume'] = self.df[self.volume] * 1.15
|
|
148
152
|
return
|
|
149
153
|
|
|
150
154
|
def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
|
|
151
155
|
super()._set_lim(xmin, xmax, simpler, set_ma)
|
|
152
156
|
|
|
153
157
|
psub = (self.price_ymax - self.price_ymin)
|
|
154
|
-
self.
|
|
158
|
+
self.min_height_box_candle = psub / 8
|
|
155
159
|
|
|
156
160
|
pydistance = psub / 20
|
|
157
|
-
self.
|
|
161
|
+
self.artist_text_date_price.set_y(self.price_ymin + pydistance)
|
|
158
162
|
|
|
159
|
-
self.
|
|
163
|
+
self.min_height_box_volume = self.volume_ymax / 4
|
|
160
164
|
|
|
161
165
|
vxsub = self.vxmax - self.vxmin
|
|
162
166
|
self.vmiddle = self.vxmax - int((vxsub) / 2)
|
|
@@ -167,11 +171,11 @@ class DataMixin(CollectionMixin):
|
|
|
167
171
|
self.veighth = self.vxmin + int((vxsub) / 8)
|
|
168
172
|
|
|
169
173
|
yvolume = self.volume_ymax * 0.85
|
|
170
|
-
self.
|
|
174
|
+
self.artist_text_date_volume.set_y(yvolume)
|
|
171
175
|
|
|
172
176
|
# 정보 텍스트박스
|
|
173
|
-
self.
|
|
174
|
-
self.
|
|
177
|
+
self.artist_text_price_info.set_y(self.price_ymax - pydistance)
|
|
178
|
+
self.artist_text_volume_info.set_y(yvolume)
|
|
175
179
|
return
|
|
176
180
|
|
|
177
181
|
|
|
@@ -200,9 +204,15 @@ class EventMixin(DataMixin):
|
|
|
200
204
|
if not ax or e.xdata is None or e.ydata is None:
|
|
201
205
|
self.in_price_chart, self.in_volume_chart = (False, False)
|
|
202
206
|
else:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
if ax is self.ax_price:
|
|
208
|
+
self.in_price_chart = True
|
|
209
|
+
self.in_volume_chart = False
|
|
210
|
+
elif ax is self.ax_volume:
|
|
211
|
+
self.in_price_chart = False
|
|
212
|
+
self.in_volume_chart = True
|
|
213
|
+
else:
|
|
214
|
+
self.in_price_chart = False
|
|
215
|
+
self.in_volume_chart = False
|
|
206
216
|
return
|
|
207
217
|
|
|
208
218
|
def _get_x(self, e: MouseEvent):
|
|
@@ -214,112 +224,159 @@ class EventMixin(DataMixin):
|
|
|
214
224
|
return
|
|
215
225
|
|
|
216
226
|
|
|
217
|
-
class
|
|
227
|
+
class CrossLineMixin(EventMixin):
|
|
218
228
|
digit_price, digit_volume = (0, 0)
|
|
219
229
|
in_candle, in_volumebar = (False, False)
|
|
220
230
|
|
|
221
231
|
def _on_move(self, e):
|
|
222
232
|
super()._on_move(e)
|
|
223
233
|
|
|
224
|
-
self.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
if self.in_price_chart or self.in_volume_chart:
|
|
235
|
+
self._restore_region()
|
|
236
|
+
self._draw_crossline(e, self.in_price_chart)
|
|
237
|
+
self.figure.canvas.blit()
|
|
238
|
+
else:
|
|
239
|
+
if self._erase_crossline():
|
|
240
|
+
self._restore_region()
|
|
241
|
+
self.figure.canvas.blit()
|
|
230
242
|
return
|
|
231
243
|
|
|
232
|
-
def
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
renderer = self.figure.canvas.renderer
|
|
244
|
+
def _erase_crossline(self):
|
|
245
|
+
seg = self.collection_price_crossline.get_segments()
|
|
246
|
+
if seg:
|
|
247
|
+
self.collection_price_crossline.set_segments([])
|
|
248
|
+
return True
|
|
249
|
+
return False
|
|
240
250
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
self.text_price.set_x(self.v0 if self.veighth < x else self.vsixth)
|
|
244
|
-
self.text_price.set_y(y)
|
|
245
|
-
self.text_price.draw(renderer)
|
|
251
|
+
def _draw_crossline(self, e: MouseEvent, in_price_chart):
|
|
252
|
+
x, y = (e.xdata, e.ydata)
|
|
246
253
|
|
|
247
|
-
|
|
248
|
-
|
|
254
|
+
if in_price_chart:
|
|
255
|
+
self.collection_price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax)), ((self.vxmin, y), (self.vxmax, y))])
|
|
256
|
+
self.collection_volume_crossline.set_segments([((x, 0), (x, self.volume_ymax))])
|
|
249
257
|
else:
|
|
250
|
-
|
|
251
|
-
self.
|
|
252
|
-
self.text_date_volume.set_x(x)
|
|
253
|
-
self.text_date_volume.draw(renderer)
|
|
254
|
-
|
|
255
|
-
# 캔들 강조
|
|
256
|
-
low = self.df['bottom_box_candle'][index]
|
|
257
|
-
high = self.df['top_box_candle'][index]
|
|
258
|
-
sub = high - low
|
|
259
|
-
if sub < self.min_candleboxheight:
|
|
260
|
-
sub = (self.min_candleboxheight - sub) / 2
|
|
261
|
-
low -= sub
|
|
262
|
-
high += sub
|
|
263
|
-
|
|
264
|
-
if high < y or y < low: self.in_candle = False
|
|
265
|
-
else:
|
|
266
|
-
self.in_candle = True
|
|
267
|
-
x1, x2 = (index-0.3, index+1.4)
|
|
268
|
-
self.price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
|
|
269
|
-
self.price_box.draw(renderer)
|
|
270
|
-
return
|
|
258
|
+
self.collection_price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax))])
|
|
259
|
+
self.collection_volume_crossline.set_segments([((x, 0), (x, self.volume_ymax)), ((self.vxmin, y), (self.vxmax, y))])
|
|
271
260
|
|
|
272
|
-
def _draw_crossline(self):
|
|
273
261
|
renderer = self.figure.canvas.renderer
|
|
274
|
-
self.
|
|
275
|
-
self.
|
|
262
|
+
self.collection_price_crossline.draw(renderer)
|
|
263
|
+
self.collection_volume_crossline.draw(renderer)
|
|
264
|
+
|
|
265
|
+
self._draw_text_artist(e, in_price_chart)
|
|
276
266
|
return
|
|
277
267
|
|
|
278
|
-
def
|
|
268
|
+
def _draw_text_artist(self, e: MouseEvent, in_price_chart):
|
|
279
269
|
x, y = (e.xdata, e.ydata)
|
|
280
270
|
|
|
281
|
-
self.price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax))])
|
|
282
|
-
self.volume_crossline.set_segments([((x, 0), (x, self.volume_ymax)), ((self.vxmin, y), (self.vxmax, y))])
|
|
283
|
-
self._draw_crossline()
|
|
284
|
-
|
|
285
|
-
if not self.volume: return
|
|
286
|
-
|
|
287
271
|
renderer = self.figure.canvas.renderer
|
|
272
|
+
if in_price_chart:
|
|
273
|
+
# 가격
|
|
274
|
+
self.artist_text_price.set_text(f'{float_to_str(y, self.digit_price)}{self.unit_price}')
|
|
275
|
+
self.artist_text_price.set_x(self.v0 if self.veighth < x else self.vsixth)
|
|
276
|
+
self.artist_text_price.set_y(y)
|
|
277
|
+
self.artist_text_price.draw(renderer)
|
|
278
|
+
|
|
279
|
+
if self.intx is not None:
|
|
280
|
+
# 기준시간 표시
|
|
281
|
+
self.artist_text_date_volume.set_text(f'{self.df[self.date][self.intx]}')
|
|
282
|
+
self.artist_text_date_volume.set_x(x)
|
|
283
|
+
self.artist_text_date_volume.draw(renderer)
|
|
284
|
+
else:
|
|
285
|
+
# 거래량
|
|
286
|
+
self.artist_text_volume.set_text(f'{float_to_str(y, self.digit_volume)}{self.unit_volume}')
|
|
287
|
+
self.artist_text_volume.set_x(self.v0 if self.veighth < x else self.vsixth)
|
|
288
|
+
self.artist_text_volume.set_y(y)
|
|
289
|
+
self.artist_text_volume.draw(renderer)
|
|
290
|
+
|
|
291
|
+
if self.intx is not None:
|
|
292
|
+
# 기준시간 표시
|
|
293
|
+
self.artist_text_date_price.set_text(f'{self.df[self.date][self.intx]}')
|
|
294
|
+
self.artist_text_date_price.set_x(x)
|
|
295
|
+
self.artist_text_date_price.draw(renderer)
|
|
296
|
+
return
|
|
288
297
|
|
|
289
|
-
# 거래량
|
|
290
|
-
self.text_volume.set_text(f'{float_to_str(y, self.digit_volume)}{self.unit_volume}')
|
|
291
|
-
self.text_volume.set_x(self.v0 if self.veighth < x else self.vsixth)
|
|
292
|
-
self.text_volume.set_y(y)
|
|
293
|
-
self.text_volume.draw(renderer)
|
|
294
298
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
self.text_date_price.set_x(x)
|
|
301
|
-
self.text_date_price.draw(renderer)
|
|
299
|
+
class BoxMixin(CrossLineMixin):
|
|
300
|
+
def _draw_crossline(self, e, in_price_chart):
|
|
301
|
+
super()._draw_crossline(e, in_price_chart)
|
|
302
|
+
self._draw_box_artist(e, in_price_chart)
|
|
303
|
+
return
|
|
302
304
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
low = 0
|
|
306
|
-
if high < self.min_volumeboxheight: high = self.min_volumeboxheight
|
|
305
|
+
def _draw_box_artist(self, e: MouseEvent, in_price_chart):
|
|
306
|
+
y = e.ydata
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
renderer = self.figure.canvas.renderer
|
|
309
|
+
if self.intx is not None:
|
|
310
|
+
if in_price_chart:
|
|
311
|
+
# 박스 크기
|
|
312
|
+
high = self.df['top_box_candle'][self.intx]
|
|
313
|
+
low = self.df['bottom_box_candle'][self.intx]
|
|
314
|
+
height = self.df['height_box_candle'][self.intx]
|
|
315
|
+
if height < self.min_height_box_candle:
|
|
316
|
+
sub = (self.min_height_box_candle - height) / 2
|
|
317
|
+
high, low = (high+sub, low-sub)
|
|
318
|
+
|
|
319
|
+
# 커서가 캔들 사이에 있는지 확인
|
|
320
|
+
if high < y or y < low: self.in_candle = False
|
|
321
|
+
else:
|
|
322
|
+
# 캔들 강조
|
|
323
|
+
self.in_candle = True
|
|
324
|
+
x1, x2 = (self.intx-0.3, self.intx+1.4)
|
|
325
|
+
self.collection_price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
|
|
326
|
+
self.collection_price_box.draw(renderer)
|
|
309
327
|
else:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
self.
|
|
328
|
+
# 거래량 강조
|
|
329
|
+
high = self.df['max_box_volume'][self.intx]
|
|
330
|
+
low = 0
|
|
331
|
+
if high < self.min_height_box_volume: high = self.min_height_box_volume
|
|
332
|
+
|
|
333
|
+
if high < y or y < low: self.in_volumebar = False
|
|
334
|
+
else:
|
|
335
|
+
self.in_volumebar = True
|
|
336
|
+
x1, x2 = (self.intx-0.3, self.intx+1.4)
|
|
337
|
+
self.collection_volume_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
|
|
338
|
+
self.collection_volume_box.draw(renderer)
|
|
314
339
|
return
|
|
315
340
|
|
|
316
341
|
|
|
317
|
-
format_candleinfo_ko =
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
342
|
+
format_candleinfo_ko = """\
|
|
343
|
+
{dt}
|
|
344
|
+
|
|
345
|
+
종가: {close}
|
|
346
|
+
등락률: {rate}
|
|
347
|
+
대비: {compare}
|
|
348
|
+
시가: {open}({rate_open})
|
|
349
|
+
고가: {high}({rate_high})
|
|
350
|
+
저가: {low}({rate_low})
|
|
351
|
+
거래량: {volume}({rate_volume})\
|
|
352
|
+
"""
|
|
353
|
+
format_volumeinfo_ko = """\
|
|
354
|
+
{dt}
|
|
355
|
+
|
|
356
|
+
거래량: {volume}
|
|
357
|
+
거래량증가율: {rate_volume}
|
|
358
|
+
대비: {compare}\
|
|
359
|
+
"""
|
|
360
|
+
format_candleinfo_en = """\
|
|
361
|
+
{dt}
|
|
362
|
+
|
|
363
|
+
close: {close}
|
|
364
|
+
rate: {rate}
|
|
365
|
+
compare: {compare}
|
|
366
|
+
open: {open}({rate_open})
|
|
367
|
+
high: {high}({rate_high})
|
|
368
|
+
low: {low}({rate_low})
|
|
369
|
+
volume: {volume}({rate_volume})\
|
|
370
|
+
"""
|
|
371
|
+
format_volumeinfo_en = """\
|
|
372
|
+
{dt}
|
|
373
|
+
|
|
374
|
+
volume: {volume}
|
|
375
|
+
volume rate: {rate_volume}
|
|
376
|
+
compare: {compare}\
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
class InfoMixin(BoxMixin):
|
|
323
380
|
fraction = False
|
|
324
381
|
format_candleinfo = format_candleinfo_ko
|
|
325
382
|
format_volumeinfo = format_volumeinfo_ko
|
|
@@ -330,46 +387,47 @@ class InfoMixin(LineMixin):
|
|
|
330
387
|
self._length_text = self.df[(self.volume if self.volume else self.high)].apply(lambda x: len(f'{x:,}')).max()
|
|
331
388
|
return
|
|
332
389
|
|
|
333
|
-
def
|
|
334
|
-
super().
|
|
390
|
+
def _draw_box_artist(self, e, in_price_chart):
|
|
391
|
+
super()._draw_box_artist(e, in_price_chart)
|
|
335
392
|
|
|
336
|
-
|
|
337
|
-
|
|
393
|
+
if self.intx is not None:
|
|
394
|
+
if self.in_candle: self._draw_candle_info_artist(e)
|
|
395
|
+
elif self.in_volumebar: self._draw_volume_info_artist(e)
|
|
396
|
+
return
|
|
338
397
|
|
|
398
|
+
def _draw_candle_info_artist(self, e: MouseEvent):
|
|
339
399
|
# 캔들 정보
|
|
340
|
-
self.
|
|
400
|
+
self.artist_text_price_info.set_text(self._get_info(self.intx))
|
|
341
401
|
|
|
342
|
-
|
|
402
|
+
# 정보 텍스트를 중앙에 몰리게 설정할 수도 있지만,
|
|
403
|
+
# 그런 경우 차트를 가리므로 좌우 끝단에 위치하도록 설정
|
|
404
|
+
if self.vmiddle < e.xdata:
|
|
405
|
+
self.artist_text_price_info.set_x(self.v0)
|
|
343
406
|
else:
|
|
344
|
-
# self.
|
|
345
|
-
# self.
|
|
407
|
+
# self.artist_text_price_info.set_x(self.vmax - self.x_distance)
|
|
408
|
+
# self.artist_text_price_info.set_horizontalalignment('right')
|
|
346
409
|
# 텍스트박스 크기 가져오기
|
|
347
|
-
bbox = self.
|
|
410
|
+
bbox = self.artist_text_price_info.get_window_extent().transformed(self.ax_price.transData.inverted())
|
|
348
411
|
width = bbox.x1 - bbox.x0
|
|
349
|
-
self.
|
|
412
|
+
self.artist_text_price_info.set_x(self.v1 - width)
|
|
350
413
|
|
|
351
|
-
self.
|
|
414
|
+
self.artist_text_price_info.draw(self.figure.canvas.renderer)
|
|
352
415
|
return
|
|
353
416
|
|
|
354
|
-
def
|
|
355
|
-
super()._on_move_volume_chart(e)
|
|
356
|
-
|
|
357
|
-
# 거래량 강조 확인
|
|
358
|
-
if not self.in_volumebar: return
|
|
359
|
-
|
|
417
|
+
def _draw_volume_info_artist(self, e: MouseEvent):
|
|
360
418
|
# 거래량 정보
|
|
361
|
-
self.
|
|
419
|
+
self.artist_text_volume_info.set_text(self._get_info(self.intx, is_price=False))
|
|
362
420
|
|
|
363
|
-
if self.vmiddle < e.xdata: self.
|
|
421
|
+
if self.vmiddle < e.xdata: self.artist_text_volume_info.set_x(self.v0)
|
|
364
422
|
else:
|
|
365
|
-
# self.
|
|
366
|
-
# self.
|
|
423
|
+
# self.artist_text_volume_info.set_x(self.vmax - self.x_distance)
|
|
424
|
+
# self.artist_text_volume_info.set_horizontalalignment('right')
|
|
367
425
|
# 텍스트박스 크기 가져오기
|
|
368
|
-
bbox = self.
|
|
426
|
+
bbox = self.artist_text_volume_info.get_window_extent().transformed(self.ax_price.transData.inverted())
|
|
369
427
|
width = bbox.x1 - bbox.x0
|
|
370
|
-
self.
|
|
428
|
+
self.artist_text_volume_info.set_x(self.v1 - width)
|
|
371
429
|
|
|
372
|
-
self.
|
|
430
|
+
self.artist_text_volume_info.draw(self.figure.canvas.renderer)
|
|
373
431
|
return
|
|
374
432
|
|
|
375
433
|
def _get_info(self, index, is_price=True):
|
|
@@ -452,17 +510,13 @@ class BaseMixin(InfoMixin):
|
|
|
452
510
|
|
|
453
511
|
|
|
454
512
|
class Chart(BaseMixin, Mixin):
|
|
455
|
-
def _add_collection(self):
|
|
456
|
-
super()._add_collection()
|
|
457
|
-
return self.add_artist()
|
|
458
|
-
|
|
459
513
|
def _draw_artist(self):
|
|
460
514
|
super()._draw_artist()
|
|
461
515
|
return self.draw_artist()
|
|
462
516
|
|
|
463
|
-
def
|
|
464
|
-
|
|
465
|
-
return
|
|
517
|
+
def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
|
|
518
|
+
super()._set_lim(xmin, xmax, simpler, set_ma)
|
|
519
|
+
return self.on_change_xlim(xmin, xmax, simpler, set_ma)
|
|
466
520
|
|
|
467
521
|
def _on_draw(self, e):
|
|
468
522
|
super()._on_draw(e)
|
|
@@ -472,21 +526,6 @@ class Chart(BaseMixin, Mixin):
|
|
|
472
526
|
self.on_pick(e)
|
|
473
527
|
return super()._on_pick(e)
|
|
474
528
|
|
|
475
|
-
def _set_candle_segments(self, index_start, index_end):
|
|
476
|
-
super()._set_candle_segments(index_start, index_end)
|
|
477
|
-
self.set_segment(index_start, index_end)
|
|
478
|
-
return
|
|
479
|
-
|
|
480
|
-
def _set_wick_segments(self, index_start, index_end, simpler=False):
|
|
481
|
-
super()._set_wick_segments(index_start, index_end, simpler)
|
|
482
|
-
self.set_segment(index_start, index_end, simpler)
|
|
483
|
-
return
|
|
484
|
-
|
|
485
|
-
def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
|
|
486
|
-
super()._set_line_segments(index_start, index_end, simpler, set_ma)
|
|
487
|
-
self.set_segment(index_start, index_end, simpler, set_ma)
|
|
488
|
-
return
|
|
489
|
-
|
|
490
529
|
def _on_move(self, e):
|
|
491
530
|
super()._on_move(e)
|
|
492
531
|
return self.on_move(e)
|