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