seolpyo-mplchart 0.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.

@@ -0,0 +1,49 @@
1
+ """
2
+ This software includes Matplotlib, which is licensed under the BSD License.
3
+ Matplotlib Copyright (c) 2012- Matplotlib Development Team.
4
+ Full license can be found in the LICENSE file or at https://matplotlib.org/stable/users/license.html
5
+ """
6
+
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Literal
11
+
12
+ import matplotlib.pyplot as plt
13
+ import pandas as pd
14
+
15
+
16
+ from .slider import Chart
17
+
18
+
19
+ _name = {'samsung', 'apple'}
20
+ def sample(name: Literal['samsung', 'apple']='samsung'):
21
+ if name not in _name:
22
+ print('name should be either samsung or apple.')
23
+ return
24
+ file = Path(__file__).parent / f'data/{name}.txt'
25
+ with open(file, 'r', encoding='utf-8') as txt:
26
+ data = json.load(txt)
27
+ data = data
28
+ df = pd.DataFrame(data)
29
+
30
+ c = Chart()
31
+ if name == 'apple':
32
+ c.unit_price = '$'
33
+ c.unit_volume = ' vol'
34
+ c.digit_price = 3
35
+ c.label_ma = 'ma{}'
36
+ c.candleformat = '{}\n\nend: {}\nrate: {}\ncompare: {}\nopen: {}({})\nhigh: {}({})\nlow: {}({})\nvolume: {}({})'
37
+ c.volumeformat = '{}\n\nvolume: {}\nvolume rate: {}'
38
+ c.set_data(df)
39
+ show()
40
+
41
+ return
42
+
43
+
44
+ def show():
45
+ return plt.show()
46
+
47
+
48
+ if __name__ == '__main__':
49
+ sample('apple')
@@ -0,0 +1,113 @@
1
+ from time import time
2
+
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.style as mplstyle
5
+ from matplotlib.axes import Axes
6
+ from matplotlib.backends.backend_agg import FigureCanvasAgg
7
+
8
+
9
+ from .utils import convert_unit
10
+
11
+
12
+ try: plt.switch_backend('TkAgg')
13
+ except: pass
14
+
15
+ # 한글 깨짐 문제 방지
16
+ try: plt.rcParams['font.family'] ='Malgun Gothic'
17
+ except: pass
18
+
19
+ mplstyle.use('fast')
20
+
21
+
22
+ class Base:
23
+ canvas: FigureCanvasAgg
24
+ unit_price, unit_volume = ('원', '주')
25
+
26
+ figsize = (12, 6)
27
+ ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume = (3, 2, 18, 5)
28
+ adjust = dict(
29
+ top=0.95, bottom=0.05, left=0.01, right=0.93, # 여백
30
+ wspace=0, hspace=0 # 플롯간 간격
31
+ )
32
+ color_grid = '#d0d0d0'
33
+ color_background = '#fafafa'
34
+
35
+ slider_top = True
36
+ title = 'seolpyo mplchart'
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ # 기본 툴바 비활성화
40
+ plt.rcParams['toolbar'] = 'None'
41
+
42
+ self._set_plot()
43
+ return
44
+
45
+ def _set_plot(self):
46
+ if self.slider_top:
47
+ fig, ax = plt.subplots(
48
+ 4, # row 수
49
+ figsize=self.figsize, # 기본 크기
50
+ height_ratios=(self.ratio_ax_slider, self.ratio_ax_legend, self.ratio_ax_price, self.ratio_ax_volume) # row 크기 비율
51
+ )
52
+ ax: list[Axes]
53
+ ax_slider, ax_legend, ax_price, ax_volume = ax
54
+ else:
55
+ fig, ax = plt.subplots(
56
+ 5, # row 수
57
+ figsize=self.figsize, # 기본 크기
58
+ height_ratios=(self.ratio_ax_legend, self.ratio_ax_price, self.ratio_ax_volume, self.ratio_ax_legend, self.ratio_ax_slider) # row 크기 비율
59
+ )
60
+ ax: list[Axes]
61
+ ax_legend, ax_price, ax_volume, ax_none, ax_slider = ax
62
+ # 사용하지 않는 axes 숨기기
63
+ ax_none.axis('off')
64
+ ax_legend.axis('off')
65
+
66
+ ax_slider.xaxis.set_animated(True)
67
+ ax_slider.yaxis.set_animated(True)
68
+
69
+ ax_price.xaxis.set_animated(True)
70
+ ax_price.yaxis.set_animated(True)
71
+
72
+ ax_volume.xaxis.set_animated(True)
73
+ ax_volume.yaxis.set_animated(True)
74
+
75
+ fig.canvas.manager.set_window_title(f'{self.title}')
76
+
77
+ # 플롯간 간격 제거(Configure subplots)
78
+ fig.subplots_adjust(**self.adjust)
79
+
80
+ # y ticklabel foramt 설정
81
+ ax_slider.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price))
82
+ ax_price.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_price))
83
+ ax_volume.yaxis.set_major_formatter(lambda x, _: convert_unit(x, word=self.unit_volume))
84
+
85
+ # 공통 설정
86
+ for a in [ax_slider, ax_price, ax_volume]:
87
+ # y tick 우측으로 이동
88
+ a.tick_params(left=False, right=True, labelleft=False, labelright=True)
89
+ # 차트 영역 배경 색상
90
+ a.set_facecolor(self.color_background)
91
+ # grid(구분선, 격자) 그리기
92
+ a.grid(True, color=self.color_grid, linewidth=1)
93
+ # x tick 제거
94
+ a.set_xticklabels([])
95
+
96
+ self.fig, self.canvas = (fig, fig.canvas)
97
+ self.ax_slider, self.ax_legend, self.ax_price, self.ax_volume = (ax_slider, ax_legend, ax_price, ax_volume)
98
+
99
+ return self.set_plot()
100
+
101
+ def set_plot(self):
102
+ "This function works after set plot process is done."
103
+ return
104
+
105
+
106
+ class Chart(Base):
107
+ pass
108
+
109
+
110
+ if __name__ == '__main__':
111
+ Base()
112
+
113
+ plt.show()
@@ -0,0 +1,433 @@
1
+ from fractions import Fraction
2
+ from time import time
3
+
4
+ from matplotlib.backend_bases import MouseEvent
5
+ from matplotlib.collections import LineCollection
6
+ from matplotlib.text import Text
7
+ import pandas as pd
8
+
9
+
10
+ from .draw import DrawMixin, Chart as CM
11
+ from .utils import float_to_str
12
+
13
+
14
+ class Mixin:
15
+ def create_background(self):
16
+ "This function works befor canvas.copy_from_bbox()."
17
+ return
18
+ def on_draw(self, e):
19
+ "This function works if draw event active."
20
+ return
21
+
22
+
23
+ class CollectionMixin(DrawMixin):
24
+ lineKwargs = dict(edgecolor='k', linewidth=1, linestyle='-')
25
+ textboxKwargs = dict(boxstyle='round', facecolor='w')
26
+
27
+ def _add_collection(self):
28
+ super()._add_collection()
29
+ self.sliderline = LineCollection([], animated=True, **self.lineKwargs)
30
+ self.ax_slider.add_artist(self.sliderline)
31
+ self.slider_text = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='center')
32
+ self.ax_slider.add_artist(self.slider_text)
33
+
34
+ self.price_vline = LineCollection([], animated=True, **self.lineKwargs)
35
+ self.ax_price.add_artist(self.price_vline)
36
+ self.text_date_price = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='bottom', horizontalalignment='center')
37
+ self.ax_price.add_artist(self.text_date_price)
38
+ self.text_price = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='center', horizontalalignment='left')
39
+ self.ax_price.add_artist(self.text_price)
40
+
41
+ self.volumeh_vline = LineCollection([], animated=True, **self.lineKwargs)
42
+ self.ax_volume.add_artist(self.volumeh_vline)
43
+ self.text_date_volume = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='center')
44
+ self.ax_volume.add_artist(self.text_date_volume)
45
+ self.text_volume = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='center', horizontalalignment='left')
46
+ self.ax_volume.add_artist(self.text_volume)
47
+
48
+ self.price_hline = LineCollection([], animated=True, **self.lineKwargs)
49
+ self.ax_price.add_artist(self.price_hline)
50
+ self.price_box = LineCollection([], animated=True, linewidth=1.2, edgecolor='k')
51
+ self.ax_price.add_artist(self.price_box)
52
+ self.text_price_info = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='left')
53
+ self.ax_price.add_artist(self.text_price_info)
54
+
55
+ self.volume_hline = LineCollection([], animated=True, **self.lineKwargs)
56
+ self.ax_volume.add_artist(self.volume_hline)
57
+ self.volume_box = LineCollection([], animated=True, linewidth=1.2, edgecolor='k')
58
+ self.ax_volume.add_artist(self.volume_box)
59
+ self.text_volume_info = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='left')
60
+ self.ax_volume.add_artist(self.text_volume_info)
61
+
62
+ return
63
+
64
+
65
+ _set_key = {'rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume',}
66
+
67
+ class DataMixin(CollectionMixin):
68
+ def _generate_data(self, df: pd.DataFrame):
69
+ for i in ['date', 'Open', 'high', 'low', 'close', 'volume']:
70
+ v = getattr(self, i)
71
+ if v in _set_key: raise Exception(f'you can not set "self.{i}" value in {_set_key}.\nself.{i}={v!r}')
72
+
73
+ super()._generate_data(df)
74
+
75
+ df['rate'] = ((df[self.close] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
76
+ df['compare'] = (df[self.close] - df[self.close].shift(1)).fillna(0)
77
+ df['rate_open'] = ((df[self.Open] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
78
+ df['rate_high'] = ((df[self.high] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
79
+ df['rate_low'] = ((df[self.low] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
80
+ df['rate_volume'] = ((df[self.volume] - df[self.volume].shift(1)) / df[self.volume].shift(1) * 100).__round__(2).fillna(0)
81
+ return
82
+
83
+
84
+ class LineMixin(DataMixin):
85
+ in_slider, in_price, in_volume = (False, False, False)
86
+
87
+ intx, in_index = (None, False)
88
+ _in_candle, _in_volumebar = (False, False)
89
+
90
+ def _connect_event(self):
91
+ super()._connect_event()
92
+ self.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
93
+ return
94
+
95
+ def _blit(self):
96
+ self.canvas.blit()
97
+ return
98
+
99
+ def set_data(self, df):
100
+ super().set_data(df)
101
+
102
+ self.vmin, self.vmax = (self.xmin, self.xmax)
103
+ return
104
+
105
+ def _on_move(self, e):
106
+ self._restore_region()
107
+
108
+ self._on_move_action(e)
109
+
110
+ if self.in_slider or self.in_price or self.in_volume:
111
+ self._slider_move_action(e)
112
+ if self.in_price or self.in_volume:
113
+ self._chart_move_action(e)
114
+
115
+ self._blit()
116
+ return
117
+
118
+ def _on_move_action(self, e: MouseEvent):
119
+ if not e.inaxes:
120
+ self.intx, self.in_index = (None, False)
121
+ else:
122
+ self._check_ax(e)
123
+ x, y = (e.xdata, e.ydata)
124
+ self.intx = x.__int__()
125
+ if self.intx < 0: self.in_index = False
126
+ else:
127
+ try: self.df['x'][self.intx]
128
+ except: self.in_index = False
129
+ else: self.in_index = True
130
+ return
131
+
132
+ def _check_ax(self, e: MouseEvent):
133
+ ax = e.inaxes
134
+
135
+ self.in_slider = ax is self.ax_slider
136
+ self.in_price = False if self.in_slider else ax is self.ax_price
137
+ self.in_volume = False if (self.in_slider or self.in_price) else ax is self.ax_volume
138
+ return
139
+
140
+ def _slider_move_action(self, e: MouseEvent):
141
+ x = e.xdata
142
+
143
+ # 수직선
144
+ self.sliderline.set_segments([((x, self._slider_ymin), (x, self._slider_ymax))])
145
+ self.ax_slider.draw_artist(self.sliderline)
146
+ return
147
+
148
+ def _chart_move_action(self, e: MouseEvent):
149
+ x, y = (e.xdata, e.ydata)
150
+ if not y: return
151
+ roundy = y.__round__()
152
+
153
+ self.price_vline.set_segments([((x, self._price_ymin), (x, self._price_ymax))])
154
+ self.volumeh_vline.set_segments([((x, 0), (x, self._vol_ymax))])
155
+ self.ax_price.draw_artist(self.price_vline)
156
+ self.ax_volume.draw_artist(self.volumeh_vline)
157
+
158
+ if self.in_price: self._price_move_action(x, y, roundy)
159
+ else: self._volume_move_action(x, y, roundy)
160
+ return
161
+
162
+ def _price_move_action(self, _, y, roundy):
163
+ # 수평선
164
+ self.price_hline.set_segments([((self.vmin, y), (self.vmax, y))])
165
+ self.ax_price.draw_artist(self.price_hline)
166
+
167
+ # 가격
168
+ self.text_price.set_text(f'{roundy:,}{self.unit_price}')
169
+ self.text_price.set_y(y)
170
+ self.ax_price.draw_artist(self.text_price)
171
+
172
+ # 캔들 강조
173
+ if self.in_index:
174
+ intx = self.intx
175
+
176
+ high = self.df[self.high][intx] * 1.02
177
+ low = self.df[self.low][intx] * 0.98
178
+ if high < y or y < low: self._in_candle = False
179
+ else:
180
+ self._in_candle = True
181
+ x1, x2 = (intx-0.3, intx+1.4)
182
+ self.price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
183
+ self.ax_price.draw_artist(self.price_box)
184
+ return
185
+
186
+ def _volume_move_action(self, _, y, roundy):
187
+ # 수평선
188
+ self.volume_hline.set_segments([((self.vmin, y), (self.vmax, y))])
189
+ self.ax_volume.draw_artist(self.volume_hline)
190
+
191
+ # 거래량
192
+ self.text_volume.set_text(f'{roundy:,}{self.unit_volume}')
193
+ self.text_volume.set_y(y)
194
+ self.ax_volume.draw_artist(self.text_volume)
195
+
196
+ # 거래량 강조
197
+ if self.in_index:
198
+ intx = self.intx
199
+
200
+ high = self.df[self.volume][intx] * 1.1
201
+ low = 0
202
+ self._volumerange = (0, high)
203
+ if high < y or y < low: self._in_volumebar: False
204
+ else:
205
+ self._in_volumebar = True
206
+ x1, x2 = (intx-0.3, intx+1.4)
207
+ self.volume_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
208
+ self.ax_volume.draw_artist(self.volume_box)
209
+ return
210
+
211
+
212
+ class InfoMixin(LineMixin):
213
+ fraction = False
214
+ candleformat = '{}\n\n종가:  {}\n등락률: {}\n대비:  {}\n시가:  {}({})\n고가:  {}({})\n저가:  {}({})\n거래량: {}({})'
215
+ volumeformat = '{}\n\n거래량   : {}\n거래량증가율: {}'
216
+ digit_price, digit_volume = (0, 0)
217
+
218
+ def set_data(self, df):
219
+ super().set_data(df)
220
+
221
+ # 슬라이더 날짜 텍스트 y 위치
222
+ y = self._slider_ymax - (self._slider_ymax - self._slider_ymin) / 6
223
+ self.slider_text.set_y(y)
224
+
225
+ v = self.df[self.volume].max()
226
+ self._length_text = len(f'{v:,}')
227
+ self.set_text_coordante(self.xmin, self.xmax, self._price_ymin, self._price_ymax, self._vol_ymax)
228
+
229
+ return
230
+
231
+ def set_text_coordante(self, vmin, vmax, pmin, pmax, volmax):
232
+ # 주가, 거래량 텍스트 x 위치
233
+ x_distance = (vmax - vmin) / 30
234
+ self.v0, self.v1 = (vmin + x_distance, vmax - x_distance)
235
+ self.text_price.set_x(self.v0)
236
+ self.text_volume.set_x(self.v0)
237
+
238
+ self.vmin, self.vmax = (vmin, vmax)
239
+ self.vmiddle = vmax - int((vmax - vmin) / 2)
240
+
241
+ # 주가 날짜 텍스트 y 위치
242
+ y = (pmax - pmin) / 20 + pmin
243
+ self.text_date_price.set_y(y)
244
+ # 주가 정보 y 위치
245
+ y = pmax - (pmax - pmin) / 20
246
+ self.text_price_info.set_y(y)
247
+
248
+ # 거래량 날짜 텍스트 y 위치
249
+ y = volmax * 0.85
250
+ self.text_date_volume.set_y(y)
251
+ # 거래량 정보 y 위치
252
+ self.text_volume_info.set_y(y)
253
+
254
+ return
255
+
256
+ def _slider_move_action(self, e):
257
+ super()._slider_move_action(e)
258
+
259
+ intx = self.intx
260
+
261
+ if self.in_slider and self.in_index:
262
+ self.slider_text.set_text(f'{self.df[self.date][intx]}')
263
+ self.slider_text.set_x(e.xdata)
264
+ self.ax_slider.draw_artist(self.slider_text)
265
+ return
266
+
267
+ def _price_move_action(self, x, y, roundy):
268
+ super()._price_move_action(x, y, roundy)
269
+ if not self.in_index: return
270
+ intx = self.intx
271
+
272
+ # 텍스트
273
+ text = f'{self.df[self.date][intx]}'
274
+ self.text_date_volume.set_text(text)
275
+ self.text_date_volume.set_x(x)
276
+ self.ax_volume.draw_artist(self.text_date_volume)
277
+
278
+ # 캔들 강조
279
+ if self.in_price and self._in_candle:
280
+ # 캔들 정보
281
+ self.text_price_info.set_text(self._get_info(intx))
282
+ if x < self.vmiddle:
283
+ # 텍스트박스 크기 가져오기
284
+ bbox = self.text_price_info.get_window_extent().transformed(self.ax_price.transData.inverted())
285
+ width = bbox.x1 - bbox.x0
286
+ self.text_price_info.set_x(self.v1 - width)
287
+ else:
288
+ self.text_price_info.set_x(self.v0)
289
+ self.text_price_info.set_horizontalalignment('left')
290
+ self.ax_price.draw_artist(self.text_price_info)
291
+ return
292
+
293
+ def _volume_move_action(self, x, y, roundy):
294
+ super()._volume_move_action(x, y, roundy)
295
+ if not self.in_index: return
296
+ intx = self.intx
297
+
298
+ text = f'{self.df[self.date][intx]}'
299
+ self.text_date_price.set_text(text)
300
+ self.text_date_price.set_x(x)
301
+ self.ax_price.draw_artist(self.text_date_price)
302
+
303
+ # 거래량 강조
304
+ if self.in_volume and self._in_volumebar:
305
+ # 거래량 정보
306
+ if x < self.vmiddle:
307
+ bbox = self.text_volume_info.get_window_extent().transformed(self.ax_price.transData.inverted())
308
+ width = bbox.x1 - bbox.x0
309
+ self.text_volume_info.set_x(self.v1 - width)
310
+ else:
311
+ self.text_volume_info.set_x(self.v0)
312
+ self.text_volume_info.set_horizontalalignment('left')
313
+ self.text_volume_info.set_text(self._get_info(intx, False))
314
+ self.ax_volume.draw_artist(self.text_volume_info)
315
+ return
316
+
317
+ def _get_info(self, index, is_price=True):
318
+ dt = self.df[self.date][index]
319
+ v = self.df[self.volume][index]
320
+ v = float_to_str(v, self.digit_volume)
321
+ vr = self.df['rate_volume'][index]
322
+ if is_price:
323
+ o, h, l, c = (self.df[self.Open][index], self.df[self.high][index], self.df[self.low][index], self.df[self.close][index])
324
+ rate, compare = (self.df['rate'][index], self.df['compare'][index])
325
+ r = f'{rate:+06,.2f}'
326
+ Or, hr, lr = (self.df['rate_open'][index], self.df['rate_high'][index], self.df['rate_low'][index])
327
+
328
+ if self.fraction:
329
+ c = c.__round__(self.digit_price)
330
+ cd = divmod(c, 1)
331
+ if cd[1]: c = f'{float_to_str(cd[0])} {Fraction((cd[1]))}'
332
+ else: c = float_to_str(cd[0])
333
+ comd = divmod(compare, 1)
334
+ if comd[1]: com = f'{float_to_str(comd[0])} {Fraction(comd[1])}'
335
+ else: com = float_to_str(comd[0])
336
+ o = o.__round__(self.digit_price)
337
+ od = divmod(o, 1)
338
+ if od[1]: o = f'{float_to_str(od[0])} {Fraction(od[1])}'
339
+ else: o = float_to_str(od[0])
340
+ h = h.__round__(self.digit_price)
341
+ hd = divmod(h, 1)
342
+ if hd[1]: h = f'{float_to_str(hd[0])} {Fraction(hd[1])}'
343
+ else: h = float_to_str(hd[0])
344
+ l = l.__round__(self.digit_price)
345
+ ld = divmod(l, 1)
346
+ if ld[1]: l = f'{float_to_str(ld[0])} {Fraction(ld[1])}'
347
+ else: l = float_to_str(ld[0])
348
+
349
+ text = self.candleformat.format(
350
+ dt,
351
+ f'{c:>{self._length_text}}{self.unit_price}',
352
+ f'{r:>{self._length_text}}%',
353
+ f'{com:>{self._length_text}}{self.unit_price}',
354
+ f'{o:>{self._length_text}}{self.unit_price}', f'{Or:+06,.2f}%',
355
+ f'{h:>{self._length_text}}{self.unit_price}', f'{hr:+06,.2f}%',
356
+ f'{l:>{self._length_text}}{self.unit_price}', f'{lr:+06,.2f}%',
357
+ f'{v:>{self._length_text}}{self.unit_volume}', f'{vr:+06,.2f}%',
358
+ )
359
+ else:
360
+ 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))
361
+ com = float_to_str(compare, self.digit_price)
362
+
363
+ text = self.candleformat.format(
364
+ dt,
365
+ f'{c:>{self._length_text}}{self.unit_price}',
366
+ f'{r:>{self._length_text}}%',
367
+ f'{com:>{self._length_text}}{self.unit_price}',
368
+ f'{o:>{self._length_text}}{self.unit_price}', f'{Or:+06,.2f}%',
369
+ f'{h:>{self._length_text}}{self.unit_price}', f'{hr:+06,.2f}%',
370
+ f'{l:>{self._length_text}}{self.unit_price}', f'{lr:+06,.2f}%',
371
+ f'{v:>{self._length_text}}{self.unit_volume}', f'{vr:+06,.2f}%',
372
+ )
373
+ else:
374
+ vrate = f'{vr:+06,.2f}'
375
+ text = self.volumeformat.format(
376
+ dt,
377
+ f'{v:>{self._length_text}}{self.unit_volume}',
378
+ f'{vrate:>{self._length_text}}%',
379
+ )
380
+ return text
381
+
382
+
383
+ class CursorMixin(InfoMixin):
384
+ pass
385
+
386
+
387
+ class Chart(CursorMixin, CM, Mixin):
388
+ def _generate_data(self, df):
389
+ super()._generate_data(df)
390
+ return self.generate_data(df)
391
+
392
+ def _on_draw(self, e):
393
+ super()._on_draw(e)
394
+ return self.on_draw(e)
395
+
396
+ def _on_pick(self, e):
397
+ self.on_pick(e)
398
+ return super()._on_pick(e)
399
+
400
+ def _draw_artist(self):
401
+ super()._draw_artist()
402
+ return self.create_background()
403
+
404
+ def _blit(self):
405
+ super()._blit()
406
+ return self.on_blit()
407
+
408
+ def _on_move(self, e):
409
+ super()._on_move(e)
410
+ return self.on_move(e)
411
+
412
+
413
+ if __name__ == '__main__':
414
+ import json
415
+ from pathlib import Path
416
+ import matplotlib.pyplot as plt
417
+ file = Path(__file__).parent / 'data/samsung.txt'
418
+ file = Path(__file__).parent / 'data/apple.txt'
419
+ with open(file, 'r', encoding='utf-8') as txt:
420
+ data = json.load(txt)
421
+ data = data[:100]
422
+ df = pd.DataFrame(data)
423
+
424
+ t = time()
425
+ c = CursorMixin()
426
+ c.unit_price = '$'
427
+ # c.fraction = True
428
+ c.set_data(df=df)
429
+ t2 = time() - t
430
+ print(f'{t2=}')
431
+ plt.show()
432
+
433
+