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