seolpyo-mplchart 1.1.0__py3-none-any.whl → 1.2.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.

@@ -0,0 +1,608 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.axes import Axes
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.text import Text
5
+ from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
6
+ import pandas as pd
7
+
8
+ from ._base import convert_unit
9
+ from ._cursor import BaseMixin as BM, Mixin as M
10
+
11
+
12
+ class Mixin(M):
13
+ def on_click(self, e):
14
+ "This function works if mouse button click event active."
15
+ return
16
+ def on_release(self, e):
17
+ "This function works if mouse button release event active."
18
+ return
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)
88
+ return
89
+
90
+
91
+ class CollectionMixin(PlotMixin):
92
+ min_distance = 30
93
+ color_navigator_line = '#1e78ff'
94
+ color_navigator_cover = 'k'
95
+
96
+ def _add_collection(self):
97
+ super()._add_collection()
98
+
99
+ self.collection_slider = LineCollection([], animated=True)
100
+ self.ax_slider.add_artist(self.collection_slider)
101
+
102
+ # 슬라이더 네비게이터
103
+ self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator_cover, self.color_navigator_line], alpha=(0.3, 1.0))
104
+ self.ax_slider.add_artist(self.navigator)
105
+
106
+ lineKwargs = {'edgecolor': 'k', 'linewidth': 1, 'linestyle': '-'}
107
+ lineKwargs.update(self.lineKwargs)
108
+ lineKwargs.update({'segments': [], 'animated': True})
109
+
110
+ self.slider_vline = LineCollection(**lineKwargs)
111
+ self.ax_slider.add_artist(self.slider_vline)
112
+
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'))
118
+
119
+ self.text_slider = Text(**textKwargs, horizontalalignment='center', verticalalignment='top')
120
+ self.ax_slider.add_artist(self.text_slider)
121
+ return
122
+
123
+ def _get_segments(self):
124
+ super()._get_segments()
125
+
126
+ keys = []
127
+ for i in reversed(self.list_ma):
128
+ keys.append('_x')
129
+ keys.append(f'ma{i}')
130
+
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
135
+
136
+ def _get_color_segment(self):
137
+ super()._get_color_segment()
138
+
139
+ self.collection_slider.set_edgecolor(self.edgecolor_ma + [self.color_priceline])
140
+ return
141
+
142
+ def change_background_color(self, color):
143
+ super().change_background_color(color)
144
+
145
+ self.ax_slider.set_facecolor(color)
146
+ self.text_slider.set_backgroundcolor(color)
147
+ return
148
+
149
+ def change_tick_color(self, color):
150
+ super().change_tick_color(color)
151
+
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)
154
+ return
155
+
156
+ def change_text_color(self, color):
157
+ super().change_text_color(color)
158
+
159
+ self.text_slider.set_color(color)
160
+ return
161
+
162
+ def change_line_color(self, color):
163
+ super().change_line_color(color)
164
+
165
+ self.text_slider.get_bbox_patch().set_edgecolor(color)
166
+ self.slider_vline.set_edgecolor(color)
167
+ return
168
+
169
+
170
+ class NavigatorMixin(CollectionMixin):
171
+ def _set_slider_lim(self):
172
+ xmax = self.list_index[-1]
173
+ # 슬라이더 xlim
174
+ xdistance = xmax / 30
175
+ self.slider_xmin, self.slider_xmax = (-xdistance, xmax + xdistance)
176
+ self.ax_slider.set_xlim(self.slider_xmin, self.slider_xmax)
177
+
178
+ # 슬라이더 ylim
179
+ ymin, ymax = (self.df[self.low].min(), self.df[self.high].max())
180
+ ysub = ymax - ymin
181
+ self.sldier_ymiddle = ymin + (ysub / 2)
182
+ ydistance = ysub / 5
183
+ self.slider_ymin, self.slider_ymax = (ymin-ydistance, ymax+ydistance)
184
+ self.ax_slider.set_ylim(self.slider_ymin, self.slider_ymax)
185
+
186
+ # 슬라이더 텍스트 y
187
+ self.text_slider.set_y(ymax)
188
+
189
+ self.navigator.set_linewidth([ysub, 5])
190
+
191
+ # 네비게이터 라인 선택 범위
192
+ xsub = self.slider_xmax - self.slider_xmin
193
+ self._navLineWidth = xsub * 8 / 1_000
194
+ if self._navLineWidth < 1: self._navLineWidth = 1
195
+ self._navLineWidth_half = self._navLineWidth / 2
196
+ return
197
+
198
+ def _set_navigator(self, navmin, navmax):
199
+ navseg = [
200
+ (
201
+ (self.slider_xmin, self.sldier_ymiddle),
202
+ (navmin, self.sldier_ymiddle)
203
+ ),
204
+ (
205
+ (navmin, self.slider_ymin),
206
+ (navmin, self.slider_ymax)
207
+ ),
208
+ (
209
+ (navmax, self.sldier_ymiddle),
210
+ (self.slider_xmax, self.sldier_ymiddle)
211
+ ),
212
+ (
213
+ (navmax, self.slider_ymin),
214
+ (navmax, self.slider_ymax)
215
+ ),
216
+ ]
217
+
218
+ self.navigator.set_segments(navseg)
219
+ return
220
+
221
+
222
+ class DataMixin(NavigatorMixin):
223
+ navcoordinate = (0, 0)
224
+
225
+ 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):
226
+ self._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
227
+ self._get_segments()
228
+
229
+ vmin, vmax = self.navcoordinate
230
+ if change_lim or (vmax-vmin) < self.min_distance:
231
+ vmin, vmax = self.get_default_lim()
232
+ self.navcoordinate = (vmin, vmax)
233
+
234
+ self._set_lim(vmin, vmax)
235
+ self._set_slider_lim()
236
+ self._set_navigator(vmin, vmax)
237
+
238
+ self._length_text = self.df[(self.volume if self.volume else self.high)].apply(lambda x: len(str(x))).max()
239
+ return
240
+
241
+ def get_default_lim(self):
242
+ xmax = self.list_index[-1]
243
+ xmin = xmax - 120
244
+ if xmin < 0: xmin = 0
245
+ return (xmin, xmax)
246
+
247
+
248
+ class BackgroundMixin(DataMixin):
249
+ def _copy_bbox(self):
250
+ renderer = self.figure.canvas.renderer
251
+
252
+ self.ax_slider.xaxis.draw(renderer)
253
+ self.ax_slider.yaxis.draw(renderer)
254
+ self.collection_slider.draw(renderer)
255
+ self.background_emtpy = renderer.copy_from_bbox(self.figure.bbox)
256
+
257
+ self._draw_artist()
258
+ self.background = renderer.copy_from_bbox(self.figure.bbox)
259
+
260
+ self.navigator.draw(self.figure.canvas.renderer)
261
+ self.background_with_nav = renderer.copy_from_bbox(self.figure.bbox)
262
+ return
263
+
264
+ def _restore_region(self, is_empty=False, with_nav=True):
265
+ if not self.background: self._create_background()
266
+
267
+ if is_empty: self.figure.canvas.renderer.restore_region(self.background_emtpy)
268
+ elif with_nav: self.figure.canvas.renderer.restore_region(self.background_with_nav)
269
+ else: self.figure.canvas.renderer.restore_region(self.background)
270
+ return
271
+
272
+
273
+ class MouseMoveMixin(BackgroundMixin):
274
+ in_slider = False
275
+ is_click_slider = False
276
+
277
+ def _on_move(self, e):
278
+ self._on_move_action(e)
279
+
280
+ self._restore_region((self.is_click_slider and self.in_slider))
281
+
282
+ if self.in_slider:
283
+ self._on_move_slider(e)
284
+ elif self.in_price_chart:
285
+ self._on_move_price_chart(e)
286
+ elif self.in_volume_chart:
287
+ self._on_move_volume_chart(e)
288
+
289
+ self._blit()
290
+ return
291
+
292
+ def _on_move_action(self, e: MouseEvent):
293
+ self._check_ax(e)
294
+
295
+ self.intx = None
296
+ if self.in_slider or self.in_price_chart or self.in_volume_chart: self._get_x(e)
297
+
298
+ self._change_cursor(e)
299
+ return
300
+
301
+ def _change_cursor(self, e: MouseEvent):
302
+ # 마우스 커서 변경
303
+ if self.is_click_slider: return
304
+ elif not self.in_slider:
305
+ self.figure.canvas.set_cursor(cursors.POINTER)
306
+ return
307
+
308
+ navleft, navright = self.navcoordinate
309
+ if navleft == navright: return
310
+
311
+ x = e.xdata
312
+ leftmin, leftmax = (navleft-self._navLineWidth, navleft+self._navLineWidth_half)
313
+ rightmin, rightmax = (navright-self._navLineWidth_half, navright+self._navLineWidth)
314
+ if x < leftmin: self.figure.canvas.set_cursor(cursors.POINTER)
315
+ elif x < leftmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
316
+ elif x < rightmin: self.figure.canvas.set_cursor(cursors.MOVE)
317
+ elif x < rightmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
318
+ else: self.figure.canvas.set_cursor(cursors.POINTER)
319
+ return
320
+
321
+ def _check_ax(self, e: MouseEvent):
322
+ ax = e.inaxes
323
+ if not ax or e.xdata is None or e.ydata is None:
324
+ self.in_slider, self.in_price_chart, self.in_volume_chart = (False, False, False)
325
+ else:
326
+ self.in_slider = ax is self.ax_slider
327
+ self.in_price_chart = False if self.in_slider else ax is self.ax_price
328
+ self.in_volume_chart = False if (self.in_slider or self.in_price_chart) else ax is self.ax_volume
329
+ return
330
+
331
+ def _on_move_slider(self, e: MouseEvent):
332
+ x = e.xdata
333
+
334
+ if self.intx is not None:
335
+ renderer = self.figure.canvas.renderer
336
+ self.slider_vline.set_segments([((x, self.slider_ymin), (x, self.slider_ymax))])
337
+ self.slider_vline.draw(renderer)
338
+
339
+ if self.in_slider:
340
+ self.text_slider.set_text(f'{self.df[self.date][self.intx]}')
341
+ self.text_slider.set_x(x)
342
+ self.text_slider.draw(renderer)
343
+ return
344
+
345
+
346
+ class ClickMixin(MouseMoveMixin):
347
+ x_click = None
348
+ is_move = False
349
+ click_navleft, click_navright = (False, False)
350
+
351
+ def _connect_event(self):
352
+ super()._connect_event()
353
+
354
+ self.figure.canvas.mpl_connect('button_press_event', lambda x: self._on_click(x))
355
+ return
356
+
357
+ def _on_click(self, e: MouseEvent):
358
+ if self.in_slider: self._on_click_slider(e)
359
+ return
360
+
361
+ def _on_click_slider(self, e: MouseEvent):
362
+ if self.is_click_slider or e.button != MouseButton.LEFT: return
363
+
364
+ self.background_with_nav_pre = self.background_with_nav
365
+
366
+ self.is_click_slider = True
367
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
368
+
369
+ x = e.xdata.__int__()
370
+ navmin, navmax = self.navcoordinate
371
+
372
+ leftmin, leftmax = (navmin-self._navLineWidth, navmin+self._navLineWidth_half)
373
+ rightmin, rightmax = (navmax-self._navLineWidth_half, navmax+self._navLineWidth)
374
+
375
+ grater_than_left, less_then_right = (leftmax < x, x < rightmin)
376
+ if grater_than_left and less_then_right:
377
+ self.is_move = True
378
+ self.x_click = x
379
+ else:
380
+ if not grater_than_left and leftmin <= x:
381
+ self.click_navleft = True
382
+ self.x_click = navmax
383
+ elif not less_then_right and x <= rightmax:
384
+ self.click_navright = True
385
+ self.x_click = navmin
386
+ else:
387
+ self.x_click = x
388
+ return
389
+
390
+
391
+ class SliderSelectMixin(ClickMixin):
392
+ limit_ma = 8_000
393
+
394
+ def _on_move_slider(self, e):
395
+ if self.is_click_slider: self._set_navcoordinate(e)
396
+ return super()._on_move_slider(e)
397
+
398
+ def _set_navcoordinate(self, e: MouseEvent):
399
+ x = e.xdata.__int__()
400
+ navmin, navmax = self.navcoordinate
401
+
402
+ if self.is_move:
403
+ xsub = self.x_click - x
404
+ navmin, navmax = (navmin-xsub, navmax-xsub)
405
+
406
+ # 값 보정
407
+ if navmax < 0: navmin, navmax = (navmin-navmax, 0)
408
+ if self.list_index[-1] < navmin: navmin, navmax = (self.list_index[-1], self.list_index[-1] + (navmax-navmin))
409
+
410
+ self.navcoordinate = (navmin, navmax)
411
+ self.x_click = x
412
+
413
+ self._set_lim(navmin, navmax, simpler=True, set_ma=(navmax-navmin < self.limit_ma))
414
+
415
+ self._set_navigator(navmin, navmax)
416
+ self.navigator.draw(self.figure.canvas.renderer)
417
+
418
+ self._draw_artist()
419
+ self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
420
+ self._restore_region(False, True)
421
+ else:
422
+ navmin, navmax = (x, self.x_click) if x < self.x_click else (self.x_click, x)
423
+
424
+ # 슬라이더가 차트를 벗어나지 않도록 선택 영역 제한
425
+ if navmax < 0 or self.list_index[-1] < navmin:
426
+ seg = self.navigator.get_segments()
427
+ navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
428
+
429
+ nsub = navmax - navmin
430
+ if nsub < self.min_distance:
431
+ self._restore_region(False, False)
432
+ self._set_navigator(navmin, navmax)
433
+ self.navigator.draw(self.figure.canvas.renderer)
434
+ else:
435
+ self._set_lim(navmin, navmax, simpler=True, set_ma=(nsub < self.limit_ma))
436
+ self._set_navigator(navmin, navmax)
437
+
438
+ self.navigator.draw(self.figure.canvas.renderer)
439
+
440
+ self._draw_artist()
441
+ self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
442
+ self._restore_region(False, True)
443
+ return
444
+
445
+
446
+ class ReleaseMixin(SliderSelectMixin):
447
+ def _connect_event(self):
448
+ super()._connect_event()
449
+
450
+ self.figure.canvas.mpl_connect('button_release_event', lambda x: self._on_release(x))
451
+ return
452
+
453
+ def _on_release(self, e: MouseEvent):
454
+ if self.in_slider and self.is_click_slider: self._on_release_slider(e)
455
+ return
456
+
457
+ def _on_release_slider(self, e: MouseEvent):
458
+ if not self.is_move:
459
+ seg = self.navigator.get_segments()
460
+ navmin, navmax = (int(seg[1][0][0]), int(seg[3][0][0]))
461
+ nsub = navmax - navmin
462
+ if self.min_distance <= nsub: self.navcoordinate = (navmin, navmax)
463
+ else:
464
+ self.background_with_nav = self.background_with_nav_pre
465
+ navmin, navmax = self.navcoordinate
466
+ self._set_lim(navmin, navmax, simpler=True, set_ma=(nsub < self.limit_ma))
467
+ self._restore_region(False, True)
468
+ self._blit()
469
+ self._set_navigator(*self.navcoordinate)
470
+
471
+ self.is_click_slider = False
472
+ self.is_move = False
473
+ self.click_navleft, self.click_navright = (False, False)
474
+ return
475
+
476
+
477
+ class ChartClickMixin(ReleaseMixin):
478
+ is_click_chart = False
479
+
480
+ def _on_click(self, e: MouseEvent):
481
+ if self.in_price_chart or self.in_volume_chart: self._on_click_chart(e)
482
+ elif self.in_slider: self._on_click_slider(e)
483
+ return
484
+
485
+ def _on_click_chart(self, e: MouseEvent):
486
+ if self.is_click_chart: return
487
+
488
+ self.is_click_chart = True
489
+ self._x_click = e.x.__round__(2)
490
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
491
+ return
492
+
493
+ def _on_release(self, e):
494
+ if self.is_click_chart and (self.in_price_chart or self.in_volume_chart): self._on_release_chart(e)
495
+ elif self.is_click_slider and self.in_slider: self._on_release_slider(e)
496
+ return
497
+
498
+ def _on_release_chart(self, e):
499
+ self.is_click_chart = False
500
+ self.figure.canvas.set_cursor(cursors.POINTER)
501
+ return
502
+
503
+ def _change_cursor(self, e):
504
+ if self.is_click_chart: return
505
+ return super()._change_cursor(e)
506
+
507
+ def _on_move(self, e):
508
+ self._on_move_action(e)
509
+
510
+ need_slider_action = self.is_click_slider and self.in_slider
511
+ need_chart_action = False if need_slider_action else self.is_click_chart and (self.in_price_chart or self.in_volume_chart)
512
+ self._restore_region((need_slider_action or need_chart_action))
513
+
514
+ if self.in_slider:
515
+ self._on_move_slider(e)
516
+ elif self.in_price_chart:
517
+ self._on_move_price_chart(e)
518
+ elif self.in_volume_chart:
519
+ self._on_move_volume_chart(e)
520
+
521
+ self._blit()
522
+ return
523
+
524
+ def _on_move_price_chart(self, e):
525
+ if self.is_click_chart: self._move_chart(e)
526
+ return super()._on_move_price_chart(e)
527
+
528
+ def _on_move_volume_chart(self, e):
529
+ if self.is_click_chart: self._move_chart(e)
530
+ return super()._on_move_volume_chart(e)
531
+
532
+ def _move_chart(self, e: MouseEvent):
533
+ x = e.x.__round__(2)
534
+ left, right = self.navcoordinate
535
+ nsub = right - left
536
+ xsub = x - self._x_click
537
+ xdiv = (xsub / (1200 / nsub)).__int__()
538
+ if not xdiv:
539
+ self.navigator.draw(self.figure.canvas.renderer)
540
+ self._draw_artist()
541
+ else:
542
+ left, right = (left-xdiv, right-xdiv)
543
+ if right < 0 or self.df.index[-1] < left: self._restore_region(False, True)
544
+ else:
545
+ self.navcoordinate = (left, right)
546
+ self._set_lim(left, right, simpler=True, set_ma=((right-left) < self.limit_ma))
547
+ self._set_navigator(left, right)
548
+ self.navigator.draw(self.figure.canvas.renderer)
549
+
550
+ self._draw_artist()
551
+ self.background_with_nav = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
552
+ self._restore_region(False, True)
553
+ self._x_click = x
554
+ return
555
+
556
+
557
+ class BaseMixin(ChartClickMixin):
558
+ pass
559
+
560
+
561
+ class Chart(BaseMixin, Mixin):
562
+ def _add_collection(self):
563
+ super()._add_collection()
564
+ return self.add_artist()
565
+
566
+ def _draw_artist(self):
567
+ super()._draw_artist()
568
+ return self.draw_artist()
569
+
570
+ def _get_segments(self):
571
+ self.generate_data()
572
+ return super()._get_segments()
573
+
574
+ def _on_draw(self, e):
575
+ super()._on_draw(e)
576
+ return self.on_draw(e)
577
+
578
+ def _on_pick(self, e):
579
+ self.on_pick(e)
580
+ return super()._on_pick(e)
581
+
582
+ def _set_candle_segments(self, index_start, index_end):
583
+ super()._set_candle_segments(index_start, index_end)
584
+ self.set_segment(index_start, index_end)
585
+ return
586
+
587
+ def _set_wick_segments(self, index_start, index_end, simpler=False):
588
+ super()._set_wick_segments(index_start, index_end, simpler)
589
+ self.set_segment(index_start, index_end, simpler)
590
+ return
591
+
592
+ def _set_line_segments(self, index_start, index_end, simpler=False, set_ma=True):
593
+ super()._set_line_segments(index_start, index_end, simpler, set_ma)
594
+ self.set_segment(index_start, index_end, simpler, set_ma)
595
+ return
596
+
597
+ def _on_move(self, e):
598
+ super()._on_move(e)
599
+ return self.on_move(e)
600
+
601
+ def _on_click(self, e):
602
+ super()._on_click(e)
603
+ return self.on_click(e)
604
+
605
+ def on_release(self, e):
606
+ super().on_release(e)
607
+ return self.on_release(e)
608
+
@@ -129,7 +129,9 @@ class DataMixin(CollectionMixin):
129
129
  self.df['rate_open'] = ((self.df[self.Open] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
130
130
  self.df['rate_high'] = ((self.df[self.high] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
131
131
  self.df['rate_low'] = ((self.df[self.low] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
132
- if self.volume: self.df['rate_volume'] = ((self.df[self.volume] - self.df[self.volume].shift(1)) / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
132
+ if self.volume:
133
+ self.df['compare_volume'] = (self.df[self.volume] - self.df[self.volume].shift(1)).fillna(0)
134
+ self.df['rate_volume'] = (self.df['compare_volume'] / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
133
135
 
134
136
  self.df['_boxheight'] = (self.df[self.high] - self.df[self.low]) / 5
135
137
  self.df['_boxmin'] = self.df[self.low] - self.df['_boxheight']
@@ -305,9 +307,9 @@ class LineMixin(EventMixin):
305
307
 
306
308
 
307
309
  format_candleinfo_ko = '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
308
- format_volumeinfo_ko = '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
310
+ format_volumeinfo_ko = '{dt}\n\n거래량:    {volume}\n거래량증가율: {rate_volume}\n대비:     {compare}'
309
311
  format_candleinfo_en = '{dt}\n\nclose: {close}\nrate: {rate}\ncompare: {compare}\nopen: {open}({rate_open})\nhigh: {high}({rate_high})\nlow: {low}({rate_low})\nvolume: {volume}({rate_volume})'
310
- format_volumeinfo_en = '{dt}\n\nvolume: {volume}\nvolume rate: {rate_volume}'
312
+ format_volumeinfo_en = '{dt}\n\nvolume: {volume}\nvolume rate: {rate_volume}\ncompare: {compare}'
311
313
 
312
314
  class InfoMixin(LineMixin):
313
315
  fraction = False
@@ -425,10 +427,13 @@ class InfoMixin(LineMixin):
425
427
  volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=vr,
426
428
  )
427
429
  elif self.volume:
430
+ compare = self.df['compare_volume'][index]
431
+ com = float_to_str(compare, self.digit_volume, plus=True)
428
432
  text = self.format_volumeinfo.format(
429
433
  dt=dt,
430
434
  volume=f'{v:>{self._length_text}}{self.unit_volume}',
431
435
  rate_volume=f'{vr:>{self._length_text}}%',
436
+ compare=f'{com:>{self._length_text}}{self.unit_volume}',
432
437
  )
433
438
  else: text = ''
434
439
  return text
@@ -441,7 +446,7 @@ class BaseMixin(InfoMixin):
441
446
  class Chart(BaseMixin, Mixin):
442
447
  def _add_collection(self):
443
448
  super()._add_collection()
444
- return self.add_collection()
449
+ return self.add_artist()
445
450
 
446
451
  def _draw_artist(self):
447
452
  super()._draw_artist()
seolpyo_mplchart/draw.py CHANGED
@@ -10,7 +10,7 @@ from .base import Base
10
10
 
11
11
 
12
12
  class Mixin:
13
- def add_collection(self):
13
+ def add_artist(self):
14
14
  "This method work when ```__init__()``` run."
15
15
  return
16
16
 
@@ -548,7 +548,7 @@ class BaseMixin(BackgroundMixin):
548
548
  class Chart(BaseMixin, Mixin):
549
549
  def _add_collection(self):
550
550
  super()._add_collection()
551
- return self.add_collection()
551
+ return self.add_artist()
552
552
 
553
553
  def _draw_artist(self):
554
554
  super()._draw_artist()
@@ -558,7 +558,7 @@ class BaseMixin(ChartClickMixin):
558
558
  class Chart(BaseMixin, Mixin):
559
559
  def _add_collection(self):
560
560
  super()._add_collection()
561
- return self.add_collection()
561
+ return self.add_artist()
562
562
 
563
563
  def _draw_artist(self):
564
564
  super()._draw_artist()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: seolpyo-mplchart
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Fast candlestick chart using Python. Includes navigator, slider, navigation, and text information display functions
5
5
  Author-email: white-seolpyo <white-seolpyo@naver.com>
6
6
  License: MIT License