seolpyo-mplchart 0.1.3.1__py3-none-any.whl → 2.0.0.3__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.
Files changed (50) hide show
  1. seolpyo_mplchart/__init__.py +164 -99
  2. seolpyo_mplchart/_base.py +117 -0
  3. seolpyo_mplchart/_chart/__init__.py +137 -0
  4. seolpyo_mplchart/_chart/_base.py +217 -0
  5. seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
  6. seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
  7. seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
  8. seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
  9. seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
  10. seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
  11. seolpyo_mplchart/_chart/_draw/_data.py +314 -0
  12. seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
  13. seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
  14. seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
  15. seolpyo_mplchart/_chart/_slider/_base.py +268 -0
  16. seolpyo_mplchart/_chart/_slider/_data.py +105 -0
  17. seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
  18. seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
  19. seolpyo_mplchart/_chart/test.py +121 -0
  20. seolpyo_mplchart/_config/__init__.py +3 -0
  21. seolpyo_mplchart/_config/ax.py +28 -0
  22. seolpyo_mplchart/_config/candle.py +30 -0
  23. seolpyo_mplchart/_config/config.py +21 -0
  24. seolpyo_mplchart/_config/cursor.py +49 -0
  25. seolpyo_mplchart/_config/figure.py +41 -0
  26. seolpyo_mplchart/_config/format.py +51 -0
  27. seolpyo_mplchart/_config/ma.py +15 -0
  28. seolpyo_mplchart/_config/slider/__init__.py +2 -0
  29. seolpyo_mplchart/_config/slider/config.py +24 -0
  30. seolpyo_mplchart/_config/slider/figure.py +20 -0
  31. seolpyo_mplchart/_config/slider/nav.py +9 -0
  32. seolpyo_mplchart/_config/unit.py +19 -0
  33. seolpyo_mplchart/_config/utils.py +67 -0
  34. seolpyo_mplchart/_config/volume.py +26 -0
  35. seolpyo_mplchart/_cursor.py +559 -0
  36. seolpyo_mplchart/_draw.py +634 -0
  37. seolpyo_mplchart/_slider.py +634 -0
  38. seolpyo_mplchart/base.py +70 -67
  39. seolpyo_mplchart/cursor.py +308 -271
  40. seolpyo_mplchart/draw.py +449 -237
  41. seolpyo_mplchart/slider.py +451 -396
  42. seolpyo_mplchart/test.py +173 -24
  43. seolpyo_mplchart/utils.py +15 -4
  44. seolpyo_mplchart/xl_to_dict.py +47 -0
  45. seolpyo_mplchart-2.0.0.3.dist-info/METADATA +710 -0
  46. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +50 -0
  47. {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/WHEEL +1 -1
  48. seolpyo_mplchart-0.1.3.1.dist-info/METADATA +0 -49
  49. seolpyo_mplchart-0.1.3.1.dist-info/RECORD +0 -13
  50. {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,559 @@
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
+ from ._draw import BaseMixin as BM, Mixin as M
9
+ from .utils import float_to_str
10
+
11
+
12
+ class Mixin(M):
13
+ def on_move(self, e):
14
+ "If mouse move event active, This method work."
15
+ return
16
+
17
+
18
+ class CollectionMixin(BM):
19
+ lineKwargs = {}
20
+ textboxKwargs = {}
21
+ textKwargs = {}
22
+ color_box = 'k'
23
+
24
+ def _add_collection(self):
25
+ super()._add_collection()
26
+
27
+ lineKwargs = {'edgecolor': 'k', 'linewidth': 1, 'linestyle': '-'}
28
+ lineKwargs.update(self.lineKwargs)
29
+ lineKwargs.update({'segments': [], 'animated': True})
30
+ textboxKwargs = {'boxstyle': 'round', 'facecolor': 'w'}
31
+ textboxKwargs.update(self.textboxKwargs)
32
+ textKwargs = self.textKwargs
33
+ textKwargs.update({'animated': True, 'bbox': textboxKwargs, 'horizontalalignment': '', 'verticalalignment': ''})
34
+ (textKwargs.pop('horizontalalignment'), textKwargs.pop('verticalalignment'))
35
+
36
+ self.collection_price_crossline = LineCollection(**lineKwargs)
37
+ self.ax_price.add_artist(self.collection_price_crossline)
38
+ self.artist_text_date_price = Text(**textKwargs, horizontalalignment='center', verticalalignment='bottom')
39
+ self.ax_price.add_artist(self.artist_text_date_price)
40
+ self.artist_text_price = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
41
+ self.ax_price.add_artist(self.artist_text_price)
42
+
43
+ self.collection_volume_crossline = LineCollection(**lineKwargs)
44
+ self.ax_volume.add_artist(self.collection_volume_crossline)
45
+ self.artist_text_date_volume = Text(**textKwargs, horizontalalignment='center', verticalalignment='top')
46
+ self.ax_volume.add_artist(self.artist_text_date_volume)
47
+ self.artist_text_volume = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
48
+ self.ax_volume.add_artist(self.artist_text_volume)
49
+
50
+ self.collection_price_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
51
+ self.ax_price.add_artist(self.collection_price_box)
52
+ self.artist_text_price_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
53
+ self.ax_price.add_artist(self.artist_text_price_info)
54
+
55
+ self.collection_volume_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
56
+ self.ax_volume.add_artist(self.collection_volume_box)
57
+ self.artist_text_volume_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
58
+ self.ax_volume.add_artist(self.artist_text_volume_info)
59
+ return
60
+
61
+ def change_background_color(self, color):
62
+ super().change_background_color(color)
63
+
64
+ self.artist_text_price.set_backgroundcolor(color)
65
+ self.artist_text_volume.set_backgroundcolor(color)
66
+
67
+ self.artist_text_date_price.set_backgroundcolor(color)
68
+ self.artist_text_date_volume.set_backgroundcolor(color)
69
+
70
+ self.artist_text_price_info.set_backgroundcolor(color)
71
+ self.artist_text_volume_info.set_backgroundcolor(color)
72
+ return
73
+
74
+ def change_text_color(self, color):
75
+ super().change_text_color(color)
76
+
77
+ self.artist_text_price.set_color(color)
78
+ self.artist_text_volume.set_color(color)
79
+
80
+ self.artist_text_date_price.set_color(color)
81
+ self.artist_text_date_volume.set_color(color)
82
+
83
+ self.artist_text_price_info.set_color(color)
84
+ self.artist_text_volume_info.set_color(color)
85
+ return
86
+
87
+ def change_line_color(self, color):
88
+ self.collection_price_crossline.set_edgecolor(color)
89
+ self.collection_volume_crossline.set_edgecolor(color)
90
+
91
+ self.collection_price_box.set_edgecolor(color)
92
+ self.collection_volume_box.set_edgecolor(color)
93
+
94
+ self.artist_text_price.get_bbox_patch().set_edgecolor(color)
95
+ self.artist_text_volume.get_bbox_patch().set_edgecolor(color)
96
+
97
+ self.artist_text_date_price.get_bbox_patch().set_edgecolor(color)
98
+ self.artist_text_date_volume.get_bbox_patch().set_edgecolor(color)
99
+
100
+ self.artist_text_price_info.get_bbox_patch().set_edgecolor(color)
101
+ self.artist_text_volume_info.get_bbox_patch().set_edgecolor(color)
102
+ return
103
+
104
+
105
+ _set_key = {
106
+ 'compare', 'rate',
107
+ 'rate_open', 'rate_high', 'rate_low',
108
+ 'volume_pre', 'compare_volume', 'rate_volume',
109
+ 'space_box_candle',
110
+ 'bottom_box_candle', 'top_box_candle',
111
+ 'max_box_volume',
112
+ }
113
+
114
+ class DataMixin(CollectionMixin):
115
+ def _validate_column_key(self, df):
116
+ super()._validate_column_key(df)
117
+
118
+ for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
119
+ v = getattr(self, i)
120
+ if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
121
+ return
122
+
123
+ def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, calc_info=True, *args, **kwargs):
124
+ super().set_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
125
+ return
126
+
127
+ def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *_, **__):
128
+ super()._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__)
129
+
130
+ if not calc_info:
131
+ keys = set(df.keys())
132
+ list_key = ['compare', 'rate', 'rate_open', 'rate_high', 'rate_low',]
133
+ if self.volume: list_key += ['compare_volume', 'rate_volume',]
134
+ for i in list_key:
135
+ if i not in keys:
136
+ raise Exception(f'"{i}" column not in DataFrame.\nadd column or set calc_info=True.')
137
+ else:
138
+ self.df['compare'] = (self.df[self.close] - self.df['close_pre']).fillna(0)
139
+ self.df['rate'] = (self.df['compare'] * 100 / self.df['close_pre']).__round__(2).fillna(0)
140
+ self.df['rate_open'] = ((self.df[self.Open] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
141
+ self.df['rate_high'] = ((self.df[self.high] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
142
+ self.df['rate_low'] = ((self.df[self.low] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
143
+ if self.volume:
144
+ self.df['volume_pre'] = self.df[self.volume].shift(1)
145
+ self.df['compare_volume'] = (self.df[self.volume] - self.df['volume_pre']).fillna(0)
146
+ self.df['rate_volume'] = (self.df['compare_volume'] * 100 / self.df['volume_pre']).__round__(2).fillna(0)
147
+
148
+ self.df['space_box_candle'] = (self.df[self.high] - self.df[self.low]) / 5
149
+ self.df['bottom_box_candle'] = self.df[self.low] - self.df['space_box_candle']
150
+ self.df['top_box_candle'] = self.df[self.high] + self.df['space_box_candle']
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
153
+ return
154
+
155
+ def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
156
+ super()._set_lim(xmin, xmax, simpler, set_ma)
157
+
158
+ psub = (self.price_ymax - self.price_ymin)
159
+ self.min_height_box_candle = psub / 8
160
+
161
+ pydistance = psub / 20
162
+ self.artist_text_date_price.set_y(self.price_ymin + pydistance)
163
+
164
+ self.min_height_box_volume = self.volume_ymax / 4
165
+
166
+ vxsub = self.vxmax - self.vxmin
167
+ self.vmiddle = self.vxmax - int((vxsub) / 2)
168
+
169
+ vxdistance = vxsub / 50
170
+ self.v0, self.v1 = (self.vxmin + vxdistance, self.vxmax - vxdistance)
171
+ self.vsixth = self.vxmin + int((vxsub) / 6)
172
+ self.veighth = self.vxmin + int((vxsub) / 8)
173
+
174
+ yvolume = self.volume_ymax * 0.85
175
+ self.artist_text_date_volume.set_y(yvolume)
176
+
177
+ # 정보 텍스트박스
178
+ self.artist_text_price_info.set_y(self.price_ymax - pydistance)
179
+ self.artist_text_volume_info.set_y(yvolume)
180
+ return
181
+
182
+
183
+ class EventMixin(DataMixin):
184
+ in_price_chart, in_volume_chart = (False, False)
185
+ intx = None
186
+
187
+ def _connect_event(self):
188
+ super()._connect_event()
189
+ self.figure.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
190
+ return
191
+
192
+ def _on_move(self, e):
193
+ self._on_move_action(e)
194
+ return
195
+
196
+ def _on_move_action(self, e: MouseEvent):
197
+ self._check_ax(e)
198
+
199
+ self.intx = None
200
+ if self.in_price_chart or self.in_volume_chart: self._get_x(e)
201
+ return
202
+
203
+ def _check_ax(self, e: MouseEvent):
204
+ ax = e.inaxes
205
+ if not ax or e.xdata is None or e.ydata is None:
206
+ self.in_price_chart, self.in_volume_chart = (False, False)
207
+ else:
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
217
+ return
218
+
219
+ def _get_x(self, e: MouseEvent):
220
+ self.intx = e.xdata.__int__()
221
+ if self.intx < 0: self.intx = None
222
+ else:
223
+ try: self.list_index[self.intx]
224
+ except: self.intx = None
225
+ return
226
+
227
+
228
+ class CrossLineMixin(EventMixin):
229
+ digit_price, digit_volume = (0, 0)
230
+ in_candle, in_volumebar = (False, False)
231
+
232
+ def _on_move(self, e):
233
+ super()._on_move(e)
234
+
235
+ if self.in_price_chart or self.in_volume_chart:
236
+ self._restore_region()
237
+ self._draw_crossline(e)
238
+ self.figure.canvas.blit()
239
+ else:
240
+ if self._erase_crossline():
241
+ self._restore_region()
242
+ self.figure.canvas.blit()
243
+ return
244
+
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
251
+
252
+ def _draw_crossline(self, e: MouseEvent):
253
+ x, y = (e.xdata, e.ydata)
254
+
255
+ if self.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))])
258
+ else:
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))])
261
+
262
+ renderer = self.figure.canvas.renderer
263
+ self.collection_price_crossline.draw(renderer)
264
+ self.collection_volume_crossline.draw(renderer)
265
+
266
+ self._draw_text_artist(e)
267
+ return
268
+
269
+ def _draw_text_artist(self, e: MouseEvent):
270
+ x, y = (e.xdata, e.ydata)
271
+
272
+ renderer = self.figure.canvas.renderer
273
+ if self.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 is not None:
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 is not None:
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
298
+
299
+
300
+ class BoxMixin(CrossLineMixin):
301
+ def _draw_crossline(self, e):
302
+ super()._draw_crossline(e)
303
+ self._draw_box_artist(e)
304
+ return
305
+
306
+ def _draw_box_artist(self, e: MouseEvent):
307
+ y = e.ydata
308
+
309
+ renderer = self.figure.canvas.renderer
310
+
311
+ self.in_candle = False
312
+ self.in_volumebar = False
313
+ if self.intx is not None:
314
+ if self.in_price_chart:
315
+ # 박스 크기
316
+ high = self.df['top_box_candle'][self.intx]
317
+ low = self.df['bottom_box_candle'][self.intx]
318
+ height = self.df['height_box_candle'][self.intx]
319
+ if height < self.min_height_box_candle:
320
+ sub = (self.min_height_box_candle - height) / 2
321
+ high, low = (high+sub, low-sub)
322
+
323
+ # 커서가 캔들 사이에 있는지 확인
324
+ if low <= y and y <= high:
325
+ # 캔들 강조
326
+ self.in_candle = True
327
+ x1, x2 = (self.intx-0.3, self.intx+1.3)
328
+ self.collection_price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
329
+ self.collection_price_box.draw(renderer)
330
+ elif self.in_volume_chart and self.volume:
331
+ # 거래량 강조
332
+ high = self.df['max_box_volume'][self.intx]
333
+ low = 0
334
+ if high < self.min_height_box_volume: high = self.min_height_box_volume
335
+
336
+ if low <= y and y <= high:
337
+ self.in_volumebar = True
338
+ x1, x2 = (self.intx-0.3, self.intx+1.3)
339
+ self.collection_volume_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
340
+ self.collection_volume_box.draw(renderer)
341
+ return
342
+
343
+
344
+ format_candleinfo_ko = """\
345
+ {dt}
346
+
347
+ 종가:  {close}
348
+ 등락률: {rate}
349
+ 대비:  {compare}
350
+ 시가:  {open}({rate_open})
351
+ 고가:  {high}({rate_high})
352
+ 저가:  {low}({rate_low})
353
+ 거래량: {volume}({rate_volume})\
354
+ """
355
+ format_volumeinfo_ko = """\
356
+ {dt}
357
+
358
+ 거래량:    {volume}
359
+ 거래량증가율: {rate_volume}
360
+ 대비:     {compare}\
361
+ """
362
+ format_candleinfo_en = """\
363
+ {dt}
364
+
365
+ close: {close}
366
+ rate: {rate}
367
+ compare: {compare}
368
+ open: {open}({rate_open})
369
+ high: {high}({rate_high})
370
+ low: {low}({rate_low})
371
+ volume: {volume}({rate_volume})\
372
+ """
373
+ format_volumeinfo_en = """\
374
+ {dt}
375
+
376
+ volume: {volume}
377
+ volume rate: {rate_volume}
378
+ compare: {compare}\
379
+ """
380
+
381
+ class InfoMixin(BoxMixin):
382
+ fraction = False
383
+ format_candleinfo = format_candleinfo_ko
384
+ format_volumeinfo = format_volumeinfo_ko
385
+
386
+ def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, calc_info=True, *args, **kwargs):
387
+ super().set_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
388
+
389
+ self._set_length_text()
390
+ return
391
+
392
+ def _set_length_text(self):
393
+ self._length_text = self.df[(self.volume if self.volume else self.high)].apply(lambda x: len(f'{x:,}')).max()
394
+ lenth_high = self.df[self.high].apply(lambda x: len(f'{x:,}')).max()
395
+ lenth_volume = 0 if not self.volume else self.df[self.volume].apply(lambda x: len(f'{x:,}')).max()
396
+ self._length_text = lenth_high if lenth_volume < lenth_high else lenth_volume
397
+ return
398
+
399
+ def _draw_box_artist(self, e):
400
+ super()._draw_box_artist(e)
401
+
402
+ if self.intx is not None:
403
+ if self.in_candle: self._draw_candle_info_artist(e)
404
+ elif self.volume and self.in_volumebar: self._draw_volume_info_artist(e)
405
+ return
406
+
407
+ def _draw_candle_info_artist(self, e: MouseEvent):
408
+ # 캔들 정보
409
+ self.artist_text_price_info.set_text(self._get_info(self.intx))
410
+
411
+ # 정보 텍스트를 중앙에 몰리게 설정할 수도 있지만,
412
+ # 그런 경우 차트를 가리므로 좌우 끝단에 위치하도록 설정
413
+ if self.vmiddle < e.xdata:
414
+ self.artist_text_price_info.set_x(self.v0)
415
+ else:
416
+ # self.artist_text_price_info.set_x(self.vmax - self.x_distance)
417
+ # self.artist_text_price_info.set_horizontalalignment('right')
418
+ # 텍스트박스 크기 가져오기
419
+ bbox = self.artist_text_price_info.get_window_extent().transformed(self.ax_price.transData.inverted())
420
+ width = bbox.x1 - bbox.x0
421
+ self.artist_text_price_info.set_x(self.v1 - width)
422
+
423
+ self.artist_text_price_info.draw(self.figure.canvas.renderer)
424
+ return
425
+
426
+ def _draw_volume_info_artist(self, e: MouseEvent):
427
+ # 거래량 정보
428
+ self.artist_text_volume_info.set_text(self._get_info(self.intx, is_price=False))
429
+
430
+ if self.vmiddle < e.xdata: self.artist_text_volume_info.set_x(self.v0)
431
+ else:
432
+ # self.artist_text_volume_info.set_x(self.vmax - self.x_distance)
433
+ # self.artist_text_volume_info.set_horizontalalignment('right')
434
+ # 텍스트박스 크기 가져오기
435
+ bbox = self.artist_text_volume_info.get_window_extent().transformed(self.ax_price.transData.inverted())
436
+ width = bbox.x1 - bbox.x0
437
+ self.artist_text_volume_info.set_x(self.v1 - width)
438
+
439
+ self.artist_text_volume_info.draw(self.figure.canvas.renderer)
440
+ return
441
+
442
+ def get_info_kwargs(self, is_price: bool, **kwargs)-> dict:
443
+ """
444
+ get text info kwargs
445
+
446
+ Args:
447
+ is_price (bool): is price chart info or not
448
+
449
+ Returns:
450
+ dict[str, any]: text info kwargs
451
+ """
452
+ return kwargs
453
+
454
+ def _get_info(self, index, is_price=True):
455
+ dt = self.df[self.date][index]
456
+ if not self.volume: v, vr = ('-', '-')
457
+ else:
458
+ v = self.df[self.volume][index]
459
+ v = float_to_str(v, self.digit_volume)
460
+ # if not v % 1: v = int(v)
461
+ vr = self.df['rate_volume'][index]
462
+ vr = f'{vr:+06,.2f}'
463
+
464
+ if is_price:
465
+ o, h, l, c = (self.df[self.Open][index], self.df[self.high][index], self.df[self.low][index], self.df[self.close][index])
466
+ rate, compare = (self.df['rate'][index], self.df['compare'][index])
467
+ r = f'{rate:+06,.2f}'
468
+ Or, hr, lr = (self.df['rate_open'][index], self.df['rate_high'][index], self.df['rate_low'][index])
469
+
470
+ if self.fraction:
471
+ c = c.__round__(self.digit_price)
472
+ cd = divmod(c, 1)
473
+ if cd[1]: c = f'{float_to_str(cd[0])} {Fraction((cd[1]))}'
474
+ else: c = float_to_str(cd[0])
475
+ comd = divmod(compare, 1)
476
+ if comd[1]: com = f'{float_to_str(comd[0], plus=True)} {Fraction(comd[1])}'
477
+ else: com = float_to_str(comd[0], plus=True)
478
+ o = o.__round__(self.digit_price)
479
+ od = divmod(o, 1)
480
+ if od[1]: o = f'{float_to_str(od[0])} {Fraction(od[1])}'
481
+ else: o = float_to_str(od[0])
482
+ h = h.__round__(self.digit_price)
483
+ hd = divmod(h, 1)
484
+ if hd[1]: h = f'{float_to_str(hd[0])} {Fraction(hd[1])}'
485
+ else: h = float_to_str(hd[0])
486
+ l = l.__round__(self.digit_price)
487
+ ld = divmod(l, 1)
488
+ if ld[1]: l = f'{float_to_str(ld[0])} {Fraction(ld[1])}'
489
+ else: l = float_to_str(ld[0])
490
+
491
+ kwargs = self.get_info_kwargs(
492
+ is_price=is_price,
493
+ dt=dt,
494
+ close=f'{c:>{self._length_text}}{self.unit_price}',
495
+ rate=f'{r:>{self._length_text}}%',
496
+ compare=f'{com:>{self._length_text}}{self.unit_price}',
497
+ open=f'{o:>{self._length_text}}{self.unit_price}', rate_open=f'{Or:+06,.2f}%',
498
+ high=f'{h:>{self._length_text}}{self.unit_price}', rate_high=f'{hr:+06,.2f}%',
499
+ low=f'{l:>{self._length_text}}{self.unit_price}', rate_low=f'{lr:+06,.2f}%',
500
+ volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=f'{vr}%',
501
+ )
502
+ text = self.format_candleinfo.format(**kwargs)
503
+ else:
504
+ 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))
505
+ com = float_to_str(compare, self.digit_price, plus=True)
506
+
507
+ kwargs = self.get_info_kwargs(
508
+ is_price=is_price,
509
+ dt=dt,
510
+ close=f'{c:>{self._length_text}}{self.unit_price}',
511
+ rate=f'{r:>{self._length_text}}%',
512
+ compare=f'{com:>{self._length_text}}{self.unit_price}',
513
+ open=f'{o:>{self._length_text}}{self.unit_price}', rate_open=f'{Or:+06,.2f}%',
514
+ high=f'{h:>{self._length_text}}{self.unit_price}', rate_high=f'{hr:+06,.2f}%',
515
+ low=f'{l:>{self._length_text}}{self.unit_price}', rate_low=f'{lr:+06,.2f}%',
516
+ volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=f'{vr}%',
517
+ )
518
+ text = self.format_candleinfo.format(**kwargs)
519
+ elif self.volume:
520
+ compare = self.df['compare_volume'][index]
521
+ com = float_to_str(compare, self.digit_volume, plus=True)
522
+ kwargs = self.get_info_kwargs(
523
+ is_price=is_price,
524
+ dt=dt,
525
+ volume=f'{v:>{self._length_text}}{self.unit_volume}',
526
+ rate_volume=f'{vr:>{self._length_text}}%',
527
+ compare=f'{com:>{self._length_text}}{self.unit_volume}',
528
+ )
529
+ text = self.format_volumeinfo.format(**kwargs)
530
+ else: text = ''
531
+
532
+ return text
533
+
534
+
535
+ class BaseMixin(InfoMixin):
536
+ pass
537
+
538
+
539
+ class Chart(BaseMixin, Mixin):
540
+ def _draw_artist(self):
541
+ super()._draw_artist()
542
+ return self.draw_artist()
543
+
544
+ def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
545
+ super()._set_lim(xmin, xmax, simpler, set_ma)
546
+ return self.on_change_xlim(xmin, xmax, simpler, set_ma)
547
+
548
+ def _on_draw(self, e):
549
+ super()._on_draw(e)
550
+ return self.on_draw(e)
551
+
552
+ def _on_pick(self, e):
553
+ self.on_pick(e)
554
+ return super()._on_pick(e)
555
+
556
+ def _on_move(self, e):
557
+ super()._on_move(e)
558
+ return self.on_move(e)
559
+