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,601 @@
1
+ from time import time
2
+
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
5
+ import pandas as pd
6
+
7
+
8
+ from .cursor import CursorMixin, Chart as CM
9
+
10
+
11
+ def get_wickline(x: pd.Series):
12
+ v = x.values
13
+ return ((v[0], v[1]), (v[0], v[2]))
14
+ def get_volumeline(x: pd.Series):
15
+ v = x.values
16
+ return ((v[0], 0), (v[0], v[1]))
17
+
18
+
19
+ class Mixin(CursorMixin):
20
+ def on_click(self, e):
21
+ "This function works if mouse button click event active."
22
+ return
23
+ def on_release(self, e):
24
+ "This function works if mouse button release event active."
25
+ return
26
+
27
+ class NavgatorMixin(Mixin):
28
+ min_distance = 30
29
+ color_navigatorline = '#1e78ff'
30
+ color_navigator = 'k'
31
+
32
+ _x_click, _x_release = (0, 0)
33
+ is_click, is_move = (False, False)
34
+ _navcoordinate = (0, 0)
35
+
36
+ def _add_collection(self):
37
+ super()._add_collection()
38
+
39
+ # 슬라이더 네비게이터
40
+ self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator, self.color_navigatorline], alpha=(0.2, 1.0))
41
+ self.ax_slider.add_artist(self.navigator)
42
+ return
43
+
44
+ def set_data(self, df):
45
+ super().set_data(df)
46
+
47
+ # 네비게이터 라인 선택 영역
48
+ xsub = self.xmax - self.xmin
49
+ self._navLineWidth = xsub * 0.008
50
+ if self._navLineWidth < 1: self._navLineWidth = 1
51
+ self._navLineWidth_half = self._navLineWidth / 2
52
+ return
53
+
54
+ def _connect_event(self):
55
+ super()._connect_event()
56
+ self.canvas.mpl_connect('axes_leave_event', lambda x: self._leave_axes(x))
57
+ self.canvas.mpl_connect('button_press_event', lambda x: self._on_click(x))
58
+ self.canvas.mpl_connect('button_release_event', lambda x: self._on_release(x))
59
+ return
60
+
61
+ def _leave_axes(self, e: MouseEvent):
62
+ if not self.is_click and e.inaxes is self.ax_slider:
63
+ self.canvas.set_cursor(cursors.POINTER)
64
+ return
65
+
66
+ def _on_click(self, e: MouseEvent):
67
+ if self.is_click or e.button != MouseButton.LEFT or e.inaxes is not self.ax_slider: return
68
+
69
+ self.is_click = True
70
+
71
+ x = e.xdata.__int__()
72
+ left, right = self._navcoordinate
73
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
74
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
75
+
76
+ gtl, ltr = (lmax < x, x < rmin)
77
+ if gtl and ltr:
78
+ self._x_click = x
79
+ self.is_move = True
80
+ self.canvas.set_cursor(cursors.MOVE)
81
+ else:
82
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
83
+ if not gtl and lmin <= x:
84
+ self._x_click = right
85
+ elif not ltr and x <= rmax:
86
+ self._x_click = left
87
+ else:
88
+ self._x_click = x
89
+
90
+ # 그리기 후 최초 클릭이면 좌표 수정
91
+ if left == right:
92
+ self._navcoordinate = (x, x)
93
+ return
94
+
95
+ def _on_release(self, e: MouseEvent):
96
+ if e.inaxes is not self.ax_slider: return
97
+ self.is_click, self.is_move = (False, False)
98
+
99
+ if self._navcoordinate[0] == self._navcoordinate[1]:
100
+ self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
101
+
102
+ self.background = None
103
+ self._draw()
104
+ return
105
+
106
+
107
+ class BackgroundMixin(NavgatorMixin):
108
+ def _on_draw(self, e):
109
+ self.background = None
110
+ self._restore_region()
111
+ return
112
+
113
+ def _restore_region(self, with_nav=True):
114
+ if not self.background: self._create_background()
115
+
116
+ if with_nav: self.canvas.restore_region(self.background_with_nav)
117
+ else: self.canvas.renderer.restore_region(self.background)
118
+ return
119
+
120
+ def _copy_bbox(self):
121
+ self.ax_slider.xaxis.draw(self.canvas.renderer)
122
+ self.ax_slider.yaxis.draw(self.canvas.renderer)
123
+ self.slidercollection.draw(self.canvas.renderer)
124
+
125
+ super()._copy_bbox()
126
+
127
+ self.navigator.draw(self.canvas.renderer)
128
+ self.background_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
129
+ return
130
+
131
+ def _draw_artist(self):
132
+ renderer = self.canvas.renderer
133
+
134
+ self.ax_price.xaxis.draw(renderer)
135
+ self.ax_price.yaxis.draw(renderer)
136
+
137
+ if self.candle_on_ma:
138
+ self.macollection.draw(renderer)
139
+ self.candlecollection.draw(renderer)
140
+ else:
141
+ self.candlecollection.draw(renderer)
142
+ self.macollection.draw(renderer)
143
+
144
+ self.ax_volume.xaxis.draw(renderer)
145
+ self.ax_volume.yaxis.draw(renderer)
146
+
147
+ self.volumecollection.draw(renderer)
148
+ return
149
+
150
+
151
+ class DrawMixin(BackgroundMixin):
152
+ def set_data(self, df):
153
+ super().set_data(df)
154
+
155
+ # 네비게이터 높이 설정
156
+ if 0 < self._slider_ymin: ysub = self._slider_ymax
157
+ else: ysub = self._slider_ymax - self._slider_ymin
158
+ self._ymiddle = ysub / 2
159
+ self.navigator.set_linewidth((ysub, 5))
160
+ return
161
+
162
+ def _on_release(self, e: MouseEvent):
163
+ if e.inaxes is not self.ax_slider: return
164
+ self.is_click, self.is_move = (False, False)
165
+
166
+ if self._navcoordinate[0] == self._navcoordinate[1]:
167
+ self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
168
+ self._set_navigator(*self._navcoordinate)
169
+
170
+ self.background = None
171
+ self._draw()
172
+ return
173
+
174
+ def _on_move(self, e: MouseEvent):
175
+ self._restore_region((not self.is_click))
176
+
177
+ self._on_move_action(e)
178
+
179
+ if self.in_slider:
180
+ self._change_coordinate()
181
+ if self.is_click:
182
+ if self.is_move: self._set_navigator(*self._navcoordinate)
183
+ elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
184
+ self.navigator.draw(self.canvas.renderer)
185
+ self._slider_move_action(e)
186
+ elif self.is_click:
187
+ self.navigator.draw(self.canvas.renderer)
188
+ else:
189
+ if self.in_slider or self.in_price or self.in_volume:
190
+ self._slider_move_action(e)
191
+ if self.in_price or self.in_volume:
192
+ self._chart_move_action(e)
193
+
194
+ self._blit()
195
+ return
196
+
197
+ def _change_coordinate(self):
198
+ if self.intx is None: return
199
+ x = self.intx
200
+ left, right = self._navcoordinate
201
+
202
+ if not self.is_click:
203
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
204
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
205
+ ltel, gter = (x <= lmax, rmin <= x)
206
+ if ltel and lmin <= x:
207
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
208
+ elif gter and x <= rmax:
209
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
210
+ elif not ltel and not gter: self.canvas.set_cursor(cursors.MOVE)
211
+ else: self.canvas.set_cursor(cursors.POINTER)
212
+ else:
213
+ # 네비게이터 좌표 수정
214
+ intx = x.__int__()
215
+ if self.is_move:
216
+ xsub = self._x_click - intx
217
+ left, right = (left-xsub, right-xsub)
218
+ self._x_click = intx
219
+ else:
220
+ if intx == left: left = intx
221
+ elif intx == right: right = intx
222
+ else:
223
+ if self._x_click < intx: left, right = (self._x_click, intx)
224
+ else: left, right = (intx, self._x_click)
225
+
226
+ nsub = right - left
227
+ if right < 0 or self.df.index[-1] < left or nsub < self.min_distance: left, right = self._navcoordinate
228
+ self._navcoordinate = (left, right)
229
+ return
230
+
231
+ def _set_navigator(self, x1, x2):
232
+ xmin, xmax = (x1, x2) if x1 < x2 else (x2, x1)
233
+
234
+ left = ((self.xmin, self._ymiddle), (xmin, self._ymiddle))
235
+ right = ((xmax, self._ymiddle), (self.xmax, self._ymiddle))
236
+ leftline = ((xmin, self._slider_ymin), (xmin, self._slider_ymax))
237
+ rightline = ((xmax, self._slider_ymin), (xmax, self._slider_ymax))
238
+ self.navigator.set_segments((left, leftline, right, rightline))
239
+ return
240
+
241
+
242
+ class LimMixin(DrawMixin):
243
+ def _restore_region(self, with_nav=True, empty=False):
244
+ if not self.background: self._create_background()
245
+
246
+ if empty: self.canvas.restore_region(self.background_empty)
247
+ elif with_nav: self.canvas.restore_region(self.background_with_nav)
248
+ else: self.canvas.renderer.restore_region(self.background)
249
+ return
250
+
251
+ def _copy_bbox(self):
252
+ self.ax_slider.xaxis.draw(self.canvas.renderer)
253
+ self.ax_slider.yaxis.draw(self.canvas.renderer)
254
+ self.slidercollection.draw(self.canvas.renderer)
255
+ self.background_empty = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
256
+
257
+ self._draw_artist()
258
+ self.background = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
259
+
260
+ self.navigator.draw(self.canvas.renderer)
261
+ self.background_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
262
+ return
263
+
264
+ def _on_release(self, e: MouseEvent):
265
+ if e.inaxes is not self.ax_slider: return
266
+ self.is_click, self.is_move = (False, False)
267
+
268
+ if self._navcoordinate[0] == self._navcoordinate[1]:
269
+ self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
270
+ self._set_navigator(*self._navcoordinate)
271
+ self._lim()
272
+
273
+ self.background = None
274
+ self._draw()
275
+ return
276
+
277
+ def _on_move(self, e):
278
+ self._restore_region(with_nav=(not self.is_click), empty=self.is_click)
279
+
280
+ self._on_move_action(e)
281
+
282
+ if self.in_slider:
283
+ self._change_coordinate()
284
+ if self.is_click:
285
+ nsub = self._navcoordinate[1] - self._navcoordinate[0]
286
+ if self.min_distance <= nsub: self._lim()
287
+ if self.is_move: self._set_navigator(*self._navcoordinate)
288
+ elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
289
+ self.navigator.draw(self.canvas.renderer)
290
+ self._draw_blit_artist()
291
+ self._slider_move_action(e)
292
+ elif self.is_click:
293
+ self.navigator.draw(self.canvas.renderer)
294
+ self._draw_blit_artist()
295
+ else:
296
+ if self.in_slider or self.in_price or self.in_volume:
297
+ self._slider_move_action(e)
298
+ if self.in_price or self.in_volume:
299
+ self._chart_move_action(e)
300
+
301
+ self._blit()
302
+ return
303
+
304
+ def _draw_blit_artist(self):
305
+ return self._draw_artist()
306
+
307
+ def _lim(self):
308
+ xmin, xmax = self._navcoordinate
309
+
310
+ xmax += 1
311
+ self.ax_price.set_xlim(xmin, xmax)
312
+ self.ax_volume.set_xlim(xmin, xmax)
313
+
314
+ indmin, indmax = (xmin, xmax)
315
+ if xmin < 0: indmin = 0
316
+ if indmax < 1: indmax = 1
317
+ if indmin == indmax: indmax += 1
318
+ ymin, ymax = (self.df[self.low][indmin:indmax].min(), self.df[self.high][indmin:indmax].max())
319
+ ysub = (ymax - ymin) / 15
320
+ pmin, pmax = (ymin-ysub, ymax+ysub)
321
+ self.ax_price.set_ylim(pmin, pmax)
322
+
323
+ ymax = self.df[self.volume][indmin:indmax].max()
324
+ # self._vol_ymax = ymax*1.2
325
+ volmax = ymax * 1.2
326
+ self.ax_volume.set_ylim(0, volmax)
327
+
328
+ self.set_text_coordante(xmin, xmax, pmin, pmax, volmax)
329
+ return
330
+
331
+
332
+ class SimpleMixin(LimMixin):
333
+ simpler = False
334
+ limit_volume = 2_000
335
+
336
+ def __init__(self, *args, **kwargs):
337
+ super().__init__(*args, **kwargs)
338
+
339
+ # 영역 이동시 주가 collection
340
+ self.blitcandle = LineCollection([], animated=True)
341
+ self.ax_price.add_collection(self.blitcandle)
342
+ self.priceline = LineCollection([], animated=True, edgecolors='k')
343
+ self.ax_price.add_artist(self.priceline)
344
+
345
+ # 영역 이동시 거래량 collection
346
+ self.blitvolume = LineCollection([], animated=True, edgecolors=self.colors_volume)
347
+ self.ax_volume.add_collection(self.blitvolume)
348
+ return
349
+
350
+ def set_data(self, df):
351
+ super().set_data(df)
352
+
353
+ seg = self.df[['x', self.high, self.low]].agg(get_wickline, axis=1)
354
+ self.blitcandle.set_segments(seg)
355
+ self.blitcandle.set_edgecolor(self.df['edgecolor'])
356
+ self.priceline.set_verts([self.df[['x', self.close]].apply(tuple, axis=1).tolist()])
357
+
358
+ volmax = self.df[self.volume].max()
359
+ l = self.df.__len__()
360
+ if l < self.limit_volume:
361
+ volseg = self.df.loc[:, ['x', self.volume]].agg(get_volumeline, axis=1)
362
+ else:
363
+ n, step = (1, 1 / self.limit_volume)
364
+ for _ in range(self.limit_volume):
365
+ n -= step
366
+ volmin = volmax * n
367
+ length = self.df.loc[volmin < self.df[self.volume]].__len__()
368
+ if self.limit_volume < length: break
369
+
370
+ volseg = self.df.loc[volmin < self.df[self.volume], ['x', self.volume]].agg(get_volumeline, axis=1)
371
+ self.blitvolume.set_segments(volseg)
372
+
373
+ index = self.df.index[-1]
374
+ if index < 120: self._navcoordinate = (int(self.xmin)-1, int(self.xmax)+1)
375
+ else: self._navcoordinate = (index-80, index+10)
376
+ self._set_navigator(*self._navcoordinate)
377
+ self._lim()
378
+ return self._draw()
379
+
380
+ def _draw_blit_artist(self):
381
+ renderer = self.canvas.renderer
382
+
383
+ self.ax_price.xaxis.draw(renderer)
384
+ self.ax_price.yaxis.draw(renderer)
385
+
386
+ if self.candle_on_ma:
387
+ self.macollection.draw(renderer)
388
+ if self.simpler: self.blitcandle.draw(renderer)
389
+ else: self.candlecollection.draw(renderer)
390
+ else:
391
+ if self.simpler: self.blitcandle.draw(renderer)
392
+ else: self.candlecollection.draw(renderer)
393
+ self.macollection.draw(renderer)
394
+
395
+ self.ax_volume.xaxis.draw(renderer)
396
+ self.ax_volume.yaxis.draw(renderer)
397
+
398
+ self.blitvolume.draw(renderer)
399
+ return
400
+
401
+
402
+ class ClickMixin(SimpleMixin):
403
+ is_click_chart = False
404
+
405
+ def _on_click(self, e: MouseEvent):
406
+ if not self.is_click and e.button == MouseButton.LEFT:
407
+ if e.inaxes is self.ax_slider: pass
408
+ elif e.inaxes is self.ax_price or e.inaxes is self.ax_volume: return self._on_chart_click(e)
409
+ else: return
410
+ else: return
411
+
412
+ self.is_click = True
413
+
414
+ x = e.xdata.__int__()
415
+ left, right = self._navcoordinate
416
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
417
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
418
+
419
+ gtl, ltr = (lmax < x, x < rmin)
420
+ if gtl and ltr:
421
+ self._x_click = x
422
+ self.is_move = True
423
+ self.canvas.set_cursor(cursors.MOVE)
424
+ else:
425
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
426
+ if not gtl and lmin <= x:
427
+ self._x_click = right
428
+ elif not ltr and x <= rmax:
429
+ self._x_click = left
430
+ else:
431
+ self._x_click = x
432
+
433
+ # 그리기 후 최초 클릭이면 좌표 수정
434
+ if left == right:
435
+ self._navcoordinate = (x, x)
436
+ return
437
+
438
+ def _on_release(self, e: MouseEvent):
439
+ if not self.is_click: return
440
+ elif e.inaxes is self.ax_slider: return super()._on_release(e)
441
+ elif not self.in_price and not self.in_volume and not self.is_click_chart: return
442
+ self.canvas.set_cursor(cursors.POINTER)
443
+ self.is_click, self.is_move = (False, False)
444
+ self.is_click_chart = False
445
+
446
+ self._draw()
447
+ return self._restore_region()
448
+
449
+ def _on_chart_click(self, e: MouseEvent):
450
+ self.is_click = True
451
+ self.is_click_chart = True
452
+ self._x_click = e.x.__int__()
453
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
454
+ return
455
+
456
+ def _change_coordinate(self):
457
+ if self.is_click_chart: self._change_coordinate_chart()
458
+ else: super()._change_coordinate()
459
+ return
460
+
461
+ def _change_coordinate_chart(self, e: MouseEvent):
462
+ x = e.x.__int__()
463
+ left, right = self._navcoordinate
464
+ nsub = right - left
465
+ xsub = x - self._x_click
466
+ xdiv = (xsub / (1200 / nsub)).__int__()
467
+ if xdiv:
468
+ left, right = (left-xdiv, right-xdiv)
469
+ if -1 < right and left < self.df.index[-1]:
470
+ self._navcoordinate = (left, right)
471
+ self._x_click = x
472
+ return
473
+
474
+ def _on_move(self, e):
475
+ self._restore_region(with_nav=(not self.is_click), empty=self.is_click)
476
+
477
+ self._on_move_action(e)
478
+
479
+ if self.in_slider and not self.is_click_chart:
480
+ self._change_coordinate()
481
+ if self.is_click:
482
+ nsub = self._navcoordinate[1] - self._navcoordinate[0]
483
+ if self.min_distance <= nsub: self._lim()
484
+ if self.is_move: self._set_navigator(*self._navcoordinate)
485
+ elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
486
+ self.navigator.draw(self.canvas.renderer)
487
+ self._draw_blit_artist()
488
+ self._slider_move_action(e)
489
+ elif self.is_click:
490
+ if self.is_click_chart and (self.in_price or self.in_volume):
491
+ if (self.vmin, self.vmax) != self._navcoordinate:
492
+ self._change_coordinate_chart(e)
493
+ self._lim()
494
+ self._set_navigator(*self._navcoordinate)
495
+ self.navigator.draw(self.canvas.renderer)
496
+ self._draw_blit_artist()
497
+ else:
498
+ if self.in_slider or self.in_price or self.in_volume:
499
+ self._slider_move_action(e)
500
+ if self.in_price or self.in_volume:
501
+ self._chart_move_action(e)
502
+
503
+ self._blit()
504
+ return
505
+
506
+
507
+ class SliderMixin(ClickMixin):
508
+ pass
509
+
510
+
511
+ class Chart(SliderMixin, CM, Mixin):
512
+ r"""
513
+ You can see the guidance document:
514
+ Korean: https://white.seolpyo.com/entry/147/
515
+ English: https://white.seolpyo.com/entry/148/
516
+
517
+ Variables:
518
+ unit_price, unit_volume: unit for price and volume. default ('원', '주').
519
+
520
+ figsize: figure size if you use plt.show(). default (12, 6).
521
+ ratio_ax_slider, ratio_ax_legend, ratio_ax_price, ratio_ax_volume: Axes ratio. default (3, 2, 18, 5).
522
+ adjust: figure adjust. default dict(top=0.95, bottom=0.05, left=0.01, right=0.93, wspace=0, hspace=0).
523
+ slider_top: ax_slider is located at the top or bottom. default True.
524
+ color_background: color of background. default '#fafafa'.
525
+ color_grid: color of grid. default '#d0d0d0'.
526
+
527
+ df: stock data.
528
+ date: date column key. default 'date'
529
+ Open, high, low, close: price column key. default ('open', 'high', 'low', 'close')
530
+ volume: volume column key. default 'volume'
531
+
532
+ label_ma: moving average legend label format. default '{}일선'
533
+ list_ma: Decide how many days to draw the moving average line. default (5, 20, 60, 120, 240)
534
+ list_macolor: Color the moving average line. If the number of colors is greater than the moving average line, black is applied. default ('darkred', 'fuchsia', 'olive', 'orange', 'navy', 'darkmagenta', 'limegreen', 'darkcyan',)
535
+
536
+ candle_on_ma: Decide whether to draw candles on the moving average line. default True
537
+ color_sliderline: Color of closing price line in ax_slider. default 'k'
538
+ color_navigatorline: Color of left and right dividing lines in selected area. default '#1e78ff'
539
+ color_navigator: Color of unselected area. default 'k'
540
+
541
+ color_up: The color of the candle. When the closing price is greater than the opening price. default '#fe3032'
542
+ color_down: The color of the candle. When the opening price is greater than the opening price. default '#0095ff'
543
+ color_flat: The color of the candle. WWhen the closing price is the same as the opening price. default 'k'
544
+ color_up_down: The color of the candle. If the closing price is greater than the opening price, but is lower than the previous day's closing price. default 'w'
545
+ color_down_up: The color of the candle. If the opening price is greater than the closing price, but is higher than the closing price of the previous day. default 'w'
546
+ colors_volume: The color of the volume bar. default '#1f77b4'
547
+
548
+ lineKwargs: Options applied to horizontal and vertical lines drawn along the mouse position. default dict(edgecolor='k', linewidth=1, linestyle='-')
549
+ textboxKwargs: Options that apply to the information text box. dufault dict(boxstyle='round', facecolor='w')
550
+
551
+ fraction: Decide whether to express information as a fraction. default False
552
+ candleformat: Candle information text format. default '{}\n\n종가:  {}\n등락률: {}\n대비:  {}\n시가:  {}({})\n고가:  {}({})\n저가:  {}({})\n거래량: {}({})'
553
+ volumeformat: Volume information text format. default '{}\n\n거래량   : {}\n거래량증가율: {}'
554
+ digit_price, digit_volume: Number of decimal places expressed in informational text. default (0, 0)
555
+
556
+ min_distance: Minimum number of candles that can be selected with the slider. default 30
557
+ simpler: Decide whether to display candles simply when moving the chart. default False
558
+ limit_volume: Maximum number of volume bars drawn when moving the chart. default 2_000
559
+ """
560
+ def _generate_data(self, df):
561
+ super()._generate_data(df)
562
+ return self.generate_data(self.df)
563
+
564
+ def _on_draw(self, e):
565
+ super()._on_draw(e)
566
+ return self.on_draw(e)
567
+
568
+ def _on_pick(self, e):
569
+ self.on_pick(e)
570
+ return super()._on_pick(e)
571
+
572
+ def _draw_artist(self):
573
+ super()._draw_artist()
574
+ return self.create_background()
575
+
576
+ def _blit(self):
577
+ super()._blit()
578
+ return self.on_blit()
579
+
580
+ def _on_move(self, e):
581
+ super()._on_move(e)
582
+ return self.on_move(e)
583
+
584
+
585
+ if __name__ == '__main__':
586
+ import json
587
+ from pathlib import Path
588
+ import matplotlib.pyplot as plt
589
+ file = Path(__file__).parent / 'data/samsung.txt'
590
+ # file = Path(__file__).parent / 'data/apple.txt'
591
+ with open(file, 'r', encoding='utf-8') as txt:
592
+ data = json.load(txt)
593
+ data = data
594
+ df = pd.DataFrame(data)
595
+
596
+ t = time()
597
+ c = SliderMixin()
598
+ c.set_data(df)
599
+ t2 = time() - t
600
+ print(f'{t2=}')
601
+ plt.show()
@@ -0,0 +1,38 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Literal
4
+
5
+ import matplotlib.pyplot as plt
6
+ import pandas as pd
7
+
8
+
9
+ from seolpyo_mplchart import Chart
10
+
11
+
12
+ _name = {'samsung', 'apple'}
13
+ def sample(name: Literal['samsung', 'apple']='samsung'):
14
+ if name not in _name:
15
+ print('name should be either samsung or apple.')
16
+ return
17
+ file = Path(__file__).parent / f'data/{name}.txt'
18
+ with open(file, 'r', encoding='utf-8') as txt:
19
+ data = json.load(txt)
20
+ data = data
21
+ df = pd.DataFrame(data)
22
+
23
+ c = Chart()
24
+ if name == 'apple':
25
+ c.unit_price = '$'
26
+ c.unit_volume = ' vol'
27
+ c.digit_price = 3
28
+ c.label_ma = 'ma{}'
29
+ c.candleformat = '{}\n\nclose: {}\nrate: {}\ncompare: {}\nopen: {}({})\nhigh: {}({})\nlow: {}({})\nvolume: {}({})'
30
+ c.volumeformat = '{}\n\nvolume: {}\nvolume rate: {}'
31
+ c.set_data(df)
32
+ plt.show()
33
+
34
+ return
35
+
36
+
37
+ if __name__ == '__main__':
38
+ sample('apple')
@@ -0,0 +1,45 @@
1
+ from re import search
2
+
3
+ def convert_num(num):
4
+ if isinstance(num, float) and num % 1: return num
5
+ return int(num)
6
+
7
+
8
+ def float_to_str(num: float, digit=0):
9
+ num.__round__(digit)
10
+ text = f'{num:,.{digit}f}'
11
+ return text
12
+
13
+
14
+ dict_unit = {
15
+ '경': 10_000_000_000_000_000,
16
+ '조': 1_000_000_000_000,
17
+ '억': 100_000_000,
18
+ '만': 10_000,
19
+ '천': 1_000,
20
+ }
21
+ dict_unit_en = {
22
+ 'Qd': 1_000_000_000_000_000,
23
+ 'T': 1_000_000_000_000,
24
+ 'B': 1_000_000_000,
25
+ 'M': 1_000_000,
26
+ 'K': 1_000,
27
+ }
28
+
29
+ def convert_unit(value, digit=0, word='원'):
30
+ v = abs(value)
31
+ du = dict_unit if search('[가-힣]', word) else dict_unit_en
32
+ for unit, n in du.items():
33
+ if n <= v:
34
+ num = value / n
35
+ return f'{float_to_str(num, digit)}{unit} {word}'
36
+ if not value % 1: value = int(value)
37
+ text = f'{float_to_str(value, digit)}{word}'
38
+ return text
39
+
40
+
41
+ if __name__ == '__main__':
42
+ a = 456.123
43
+ print(float_to_str(a))
44
+ print(float_to_str(a, 2))
45
+ print(float_to_str(a, 6))