seolpyo-mplchart 0.1.3.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.
@@ -0,0 +1,550 @@
1
+ from matplotlib.collections import LineCollection
2
+ from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
3
+ import pandas as pd
4
+
5
+
6
+ from .cursor import CursorMixin, Chart as CM
7
+
8
+
9
+ class Mixin:
10
+ def on_click(self, e):
11
+ "This function works if mouse button click event active."
12
+ return
13
+ def on_release(self, e):
14
+ "This function works if mouse button release event active."
15
+ return
16
+ def draw_artist(self):
17
+ "This function works before canvas.blit()."
18
+ return
19
+
20
+
21
+ class NavgatorMixin(CursorMixin):
22
+ min_distance = 30
23
+ color_navigatorline = '#1e78ff'
24
+ color_navigator = 'k'
25
+
26
+ _x_click, _x_release = (0, 0)
27
+ is_click, is_move = (False, False)
28
+ _navcoordinate = (0, 0)
29
+
30
+ def _add_collection(self):
31
+ super()._add_collection()
32
+
33
+ # 슬라이더 네비게이터
34
+ self.navigator = LineCollection([], animated=True, edgecolors=[self.color_navigator, self.color_navigatorline], alpha=(0.2, 1.0))
35
+ self.ax_slider.add_artist(self.navigator)
36
+ return
37
+
38
+ def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True, calc_info=True):
39
+ super()._set_data(df, sort_df, calc_ma, change_lim, calc_info)
40
+
41
+ # 네비게이터 라인 선택 영역
42
+ xsub = self.xmax - self.xmin
43
+ self._navLineWidth = xsub * 0.008
44
+ if self._navLineWidth < 1: self._navLineWidth = 1
45
+ self._navLineWidth_half = self._navLineWidth / 2
46
+ return
47
+
48
+ def _connect_event(self):
49
+ super()._connect_event()
50
+ self.canvas.mpl_connect('axes_leave_event', lambda x: self._leave_axes(x))
51
+ self.canvas.mpl_connect('button_press_event', lambda x: self._on_click(x))
52
+ self.canvas.mpl_connect('button_release_event', lambda x: self._on_release(x))
53
+ return
54
+
55
+ def _leave_axes(self, e: MouseEvent):
56
+ if not self.is_click and e.inaxes is self.ax_slider:
57
+ self.canvas.set_cursor(cursors.POINTER)
58
+ return
59
+
60
+ def _on_click(self, e: MouseEvent):
61
+ if self.is_click or e.button != MouseButton.LEFT or e.inaxes is not self.ax_slider: return
62
+
63
+ self.is_click = True
64
+
65
+ x = e.xdata.__int__()
66
+ left, right = self._navcoordinate
67
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
68
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
69
+
70
+ gtl, ltr = (lmax < x, x < rmin)
71
+ if gtl and ltr:
72
+ self._x_click = x
73
+ self.is_move = True
74
+ self.canvas.set_cursor(cursors.MOVE)
75
+ else:
76
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
77
+ if not gtl and lmin <= x:
78
+ self._x_click = right
79
+ elif not ltr and x <= rmax:
80
+ self._x_click = left
81
+ else:
82
+ self._x_click = x
83
+
84
+ # 그리기 후 최초 클릭이면 좌표 수정
85
+ if left == right:
86
+ self._navcoordinate = (x, x)
87
+ return
88
+
89
+ def _on_release(self, e: MouseEvent):
90
+ if e.inaxes is not self.ax_slider: return
91
+ self.is_click, self.is_move = (False, False)
92
+
93
+ if self._navcoordinate[0] == self._navcoordinate[1]:
94
+ self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
95
+ return
96
+
97
+
98
+ class BackgroundMixin(NavgatorMixin):
99
+ def _on_draw(self, e):
100
+ self.background = None
101
+ self._restore_region()
102
+ return
103
+
104
+ def _restore_region(self, with_nav=True, empty=False, empty_with_nav=False):
105
+ if not self.background: self._create_background()
106
+
107
+ if empty: self.canvas.restore_region(self.background_empty)
108
+ elif empty_with_nav: self.canvas.restore_region(self.background_empty_with_nav)
109
+ elif with_nav: self.canvas.restore_region(self.background_with_nav)
110
+ else: self.canvas.renderer.restore_region(self.background)
111
+ return
112
+
113
+ def _copy_bbox(self):
114
+ renderer = self.canvas.renderer
115
+
116
+ self.background_empty = renderer.copy_from_bbox(self.fig.bbox)
117
+
118
+ self.ax_slider.xaxis.draw(renderer)
119
+ self.ax_slider.yaxis.draw(renderer)
120
+ self.slidercollection.draw(renderer)
121
+ self.background_empty_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
122
+
123
+ self._draw_artist()
124
+ self.background = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
125
+
126
+ self.navigator.draw(self.canvas.renderer)
127
+ self.background_with_nav = self.canvas.renderer.copy_from_bbox(self.fig.bbox)
128
+ return
129
+
130
+ def _draw_artist(self):
131
+ renderer = self.canvas.renderer
132
+
133
+ self.ax_price.xaxis.draw(renderer)
134
+ self.ax_price.yaxis.draw(renderer)
135
+
136
+ if self.candle_on_ma:
137
+ self.macollection.draw(renderer)
138
+ self.candlecollection.draw(renderer)
139
+ else:
140
+ self.candlecollection.draw(renderer)
141
+ self.macollection.draw(renderer)
142
+
143
+ self.ax_volume.xaxis.draw(renderer)
144
+ self.ax_volume.yaxis.draw(renderer)
145
+
146
+ self.volumecollection.draw(renderer)
147
+ return
148
+
149
+
150
+ class DrawMixin(BackgroundMixin):
151
+ def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True, calc_info=True):
152
+ super()._set_data(df, sort_df, calc_ma, change_lim, calc_info)
153
+
154
+ # 네비게이터 높이 설정
155
+ ysub = self._slider_ymax - self._slider_ymin
156
+ self._ymiddle = self._slider_ymax - ysub / 2
157
+ self.navigator.set_linewidth((ysub, 5))
158
+ return
159
+
160
+ def _on_release(self, e: MouseEvent):
161
+ super()._on_release(e)
162
+ self._set_navigator(*self._navcoordinate)
163
+
164
+ self._restore_region(empty=True)
165
+ self._creating_background = False
166
+ self._create_background()
167
+ self._restore_region()
168
+ self._blit()
169
+ return
170
+
171
+ def _on_move(self, e: MouseEvent):
172
+ self._restore_region((not self.is_click))
173
+
174
+ self._on_move_action(e)
175
+
176
+ if self.in_slider:
177
+ self._change_coordinate()
178
+ if self.is_click:
179
+ if self.is_move: self._set_navigator(*self._navcoordinate)
180
+ elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
181
+ self.navigator.draw(self.canvas.renderer)
182
+ self._slider_move_action(e)
183
+ elif self.is_click:
184
+ self.navigator.draw(self.canvas.renderer)
185
+ else:
186
+ if self.in_slider or self.in_price or self.in_volume:
187
+ self._slider_move_action(e)
188
+ if self.in_price or self.in_volume:
189
+ self._chart_move_action(e)
190
+
191
+ self._blit()
192
+ return
193
+
194
+ def _change_coordinate(self):
195
+ if self.intx is None: return
196
+ x = self.intx
197
+ left, right = self._navcoordinate
198
+
199
+ if not self.is_click:
200
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
201
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
202
+ ltel, gter = (x <= lmax, rmin <= x)
203
+ if ltel and lmin <= x:
204
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
205
+ elif gter and x <= rmax:
206
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
207
+ elif not ltel and not gter: self.canvas.set_cursor(cursors.MOVE)
208
+ else: self.canvas.set_cursor(cursors.POINTER)
209
+ else:
210
+ # 네비게이터 좌표 수정
211
+ intx = x.__int__()
212
+ if self.is_move:
213
+ xsub = self._x_click - intx
214
+ left, right = (left-xsub, right-xsub)
215
+ self._x_click = intx
216
+ else:
217
+ if intx == left: left = intx
218
+ elif intx == right: right = intx
219
+ else:
220
+ if self._x_click < intx: left, right = (self._x_click, intx)
221
+ else: left, right = (intx, self._x_click)
222
+
223
+ nsub = right - left
224
+ if right < 0 or self.df.index[-1] < left or nsub < self.min_distance: left, right = self._navcoordinate
225
+ self._navcoordinate = (left, right)
226
+ return
227
+
228
+ def _set_navigator(self, x1, x2):
229
+ xmin, xmax = (x1, x2) if x1 < x2 else (x2, x1)
230
+
231
+ left = ((self.xmin, self._ymiddle), (xmin, self._ymiddle))
232
+ right = ((xmax, self._ymiddle), (self.xmax, self._ymiddle))
233
+ leftline = ((xmin, self._slider_ymin), (xmin, self._slider_ymax))
234
+ rightline = ((xmax, self._slider_ymin), (xmax, self._slider_ymax))
235
+ self.navigator.set_segments((left, leftline, right, rightline))
236
+ return
237
+
238
+
239
+ class LimMixin(DrawMixin):
240
+ def _on_release(self, e: MouseEvent):
241
+ if e.inaxes is not self.ax_slider: return
242
+ self.is_click, self.is_move = (False, False)
243
+
244
+ if self._navcoordinate[0] == self._navcoordinate[1]:
245
+ self._navcoordinate = (self._navcoordinate[0], self._navcoordinate[1]+self.min_distance)
246
+ self._set_navigator(*self._navcoordinate)
247
+ self._lim()
248
+
249
+ self._restore_region(empty=True)
250
+ self._copy_bbox()
251
+ self._restore_region()
252
+ self._blit()
253
+ return
254
+
255
+ def _on_move(self, e):
256
+ self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
257
+
258
+ self._on_move_action(e)
259
+
260
+ if self.in_slider:
261
+ self._change_coordinate()
262
+ if self.is_click:
263
+ nsub = self._navcoordinate[1] - self._navcoordinate[0]
264
+ if self.min_distance <= nsub: self._lim()
265
+ if self.is_move: self._set_navigator(*self._navcoordinate)
266
+ elif self.intx is not None: self._set_navigator(self._x_click, self.intx)
267
+ self.navigator.draw(self.canvas.renderer)
268
+ self._draw_blit_artist()
269
+ self._slider_move_action(e)
270
+ elif self.is_click:
271
+ self.navigator.draw(self.canvas.renderer)
272
+ self._draw_blit_artist()
273
+ else:
274
+ if self.in_slider or self.in_price or self.in_volume:
275
+ self._slider_move_action(e)
276
+ if self.in_price or self.in_volume:
277
+ self._chart_move_action(e)
278
+
279
+ self._blit()
280
+ return
281
+
282
+ def _draw_blit_artist(self):
283
+ return self._draw_artist()
284
+
285
+ def _lim(self):
286
+ xmin, xmax = self._navcoordinate
287
+
288
+ xmax += 1
289
+ self.ax_price.set_xlim(xmin, xmax)
290
+ self.ax_volume.set_xlim(xmin, xmax)
291
+
292
+ indmin, indmax = (xmin, xmax)
293
+ if xmin < 0: indmin = 0
294
+ if indmax < 1: indmax = 1
295
+ if indmin == indmax: indmax += 1
296
+ ymin, ymax = (self.df[self.low][indmin:indmax].min(), self.df[self.high][indmin:indmax].max())
297
+ ysub = (ymax - ymin) / 15
298
+ pmin, pmax = (ymin-ysub, ymax+ysub)
299
+ self.ax_price.set_ylim(pmin, pmax)
300
+
301
+ ymax = self.df[self.volume][indmin:indmax].max()
302
+ # self._vol_ymax = ymax*1.2
303
+ volmax = ymax * 1.2
304
+ self.ax_volume.set_ylim(0, volmax)
305
+
306
+ self.set_text_coordante(xmin, xmax, pmin, pmax, volmax)
307
+ return
308
+
309
+
310
+ class SimpleMixin(LimMixin):
311
+ simpler = False
312
+ limit_volume = 1_500
313
+ default_left, default_right = (180, 10)
314
+ _draw_blit = False
315
+
316
+ def __init__(self, *args, **kwargs):
317
+ super().__init__(*args, **kwargs)
318
+
319
+ # 영역 이동시 주가 collection
320
+ self.blitcandle = LineCollection([], animated=True)
321
+ self.ax_price.add_collection(self.blitcandle)
322
+ self.priceline = LineCollection([], animated=True, edgecolors='k')
323
+ self.ax_price.add_artist(self.priceline)
324
+
325
+ # 영역 이동시 거래량 collection
326
+ self.blitvolume = LineCollection([], animated=True, edgecolors=self.colors_volume)
327
+ self.ax_volume.add_collection(self.blitvolume)
328
+ return
329
+
330
+ def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True, calc_info=True):
331
+ super()._set_data(df, sort_df, calc_ma, False, calc_info)
332
+
333
+ seg = self.df[['x', self.high, 'x', self.low]].values
334
+ seg = seg.reshape(seg.shape[0], 2, 2)
335
+ self.blitcandle.set_segments(seg)
336
+ self.blitcandle.set_edgecolor(self.df['edgecolor'])
337
+
338
+ pseg = self.df[['x', self.close]].values
339
+ self.priceline.set_verts(pseg.reshape(1, *pseg.shape))
340
+
341
+ l = self.df.__len__()
342
+ if l < self.limit_volume:
343
+ volseg = self.df.loc[:, ['x', 'zero', 'x', self.volume]].values
344
+ else:
345
+ v = self.df[['x', 'zero', 'x', self.volume]].sort_values([self.volume], axis=0, ascending=False)
346
+ volseg = v[:self.limit_volume].values
347
+
348
+ self.blitvolume.set_segments(volseg.reshape(volseg.shape[0], 2, 2))
349
+
350
+ if change_lim:
351
+ index = self.df.index[-1]
352
+ if index < self.default_left + self.default_right: self._navcoordinate = (int(self.xmin)-1, int(self.xmax)+1)
353
+ else: self._navcoordinate = (index-self.default_left, index+self.default_right)
354
+
355
+ self._set_navigator(*self._navcoordinate)
356
+ self._lim()
357
+ return
358
+
359
+ def _draw_blit_artist(self):
360
+ renderer = self.canvas.renderer
361
+
362
+ self.ax_price.xaxis.draw(renderer)
363
+ self.ax_price.yaxis.draw(renderer)
364
+
365
+ if self.simpler:
366
+ if self._draw_blit: self.priceline.draw(renderer)
367
+ else: self.blitcandle.draw(renderer)
368
+ elif self.candle_on_ma:
369
+ self.macollection.draw(renderer)
370
+ if self._draw_blit: self.blitcandle.draw(renderer)
371
+ else: self.candlecollection.draw(renderer)
372
+ else:
373
+ if self._draw_blit: self.blitcandle.draw(renderer)
374
+ else: self.candlecollection.draw(renderer)
375
+ self.macollection.draw(renderer)
376
+
377
+ self.ax_volume.xaxis.draw(renderer)
378
+ self.ax_volume.yaxis.draw(renderer)
379
+
380
+ self.blitvolume.draw(renderer)
381
+ return
382
+
383
+
384
+ class ClickMixin(SimpleMixin):
385
+ is_click_chart = False
386
+
387
+ def _on_click(self, e: MouseEvent):
388
+ if not self.is_click and e.button == MouseButton.LEFT:
389
+ if e.inaxes is self.ax_slider: pass
390
+ elif e.inaxes is self.ax_price or e.inaxes is self.ax_volume: return self._on_chart_click(e)
391
+ else: return
392
+ else: return
393
+
394
+ self.is_click = True
395
+
396
+ x = e.xdata.__int__()
397
+ left, right = self._navcoordinate
398
+ lmin, lmax = (left-self._navLineWidth, left+self._navLineWidth_half)
399
+ rmin, rmax = (right-self._navLineWidth_half, right+self._navLineWidth)
400
+
401
+ gtl, ltr = (lmax < x, x < rmin)
402
+ if gtl and ltr:
403
+ self._x_click = x
404
+ self.is_move = True
405
+ self.canvas.set_cursor(cursors.MOVE)
406
+ else:
407
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
408
+ if not gtl and lmin <= x:
409
+ self._x_click = right
410
+ elif not ltr and x <= rmax:
411
+ self._x_click = left
412
+ else:
413
+ self._x_click = x
414
+
415
+ # 그리기 후 최초 클릭이면 좌표 수정
416
+ if left == right:
417
+ self._navcoordinate = (x, x)
418
+ return
419
+
420
+ def _on_release(self, e: MouseEvent):
421
+ if not self.is_click: return
422
+ elif e.inaxes is self.ax_slider: return super()._on_release(e)
423
+ elif not self.in_price and not self.in_volume and not self.is_click_chart: return
424
+ # 차트 click release action
425
+ self.canvas.set_cursor(cursors.POINTER)
426
+ self.is_click, self.is_move = (False, False)
427
+ self.is_click_chart = False
428
+
429
+ self._restore_region(empty=True)
430
+ self._copy_bbox()
431
+ self._restore_region()
432
+ self._blit()
433
+ return
434
+
435
+ def _on_chart_click(self, e: MouseEvent):
436
+ self.is_click = True
437
+ self.is_click_chart = True
438
+ self._x_click = e.x.__int__()
439
+ self.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
440
+ return
441
+
442
+ def _change_coordinate(self):
443
+ if self.is_click_chart: self._change_coordinate_chart()
444
+ else: super()._change_coordinate()
445
+ return
446
+
447
+ def _change_coordinate_chart(self, e: MouseEvent):
448
+ x = e.x.__int__()
449
+ left, right = self._navcoordinate
450
+ nsub = right - left
451
+ xsub = x - self._x_click
452
+ xdiv = (xsub / (1200 / nsub)).__int__()
453
+ if xdiv:
454
+ left, right = (left-xdiv, right-xdiv)
455
+ if -1 < right and left < self.df.index[-1]:
456
+ self._navcoordinate = (left, right)
457
+ self._x_click = x
458
+ return
459
+
460
+ def _on_move(self, e):
461
+ self._restore_region(with_nav=(not self.is_click), empty_with_nav=self.is_click)
462
+
463
+ self._on_move_action(e)
464
+
465
+ if self.in_slider and not self.is_click_chart:
466
+ self._change_coordinate()
467
+ if self.is_click:
468
+ nsub = self._navcoordinate[1] - self._navcoordinate[0]
469
+ if self.is_move: self._set_navigator(*self._navcoordinate)
470
+ else:
471
+ self._draw_blit = 900 < nsub
472
+ if self.intx is not None: self._set_navigator(self._x_click, self.intx)
473
+
474
+ if self.min_distance <= nsub: self._lim()
475
+
476
+ self.navigator.draw(self.canvas.renderer)
477
+ self._draw_blit_artist()
478
+ self._slider_move_action(e)
479
+ elif self.is_click:
480
+ if self.is_click_chart and (self.in_price or self.in_volume):
481
+ if (self.vmin, self.vmax) != self._navcoordinate:
482
+ self._change_coordinate_chart(e)
483
+ self._lim()
484
+ self._set_navigator(*self._navcoordinate)
485
+ self.navigator.draw(self.canvas.renderer)
486
+ self._draw_blit_artist()
487
+ else:
488
+ if self.in_slider or self.in_price or self.in_volume:
489
+ self._slider_move_action(e)
490
+ if self.in_price or self.in_volume:
491
+ self._chart_move_action(e)
492
+
493
+ self._blit()
494
+ return
495
+
496
+
497
+ class SliderMixin(ClickMixin):
498
+ pass
499
+
500
+
501
+ class Chart(SliderMixin, CM, Mixin):
502
+ def _on_draw(self, e):
503
+ super()._on_draw(e)
504
+ return self.on_draw(e)
505
+
506
+ def _on_pick(self, e):
507
+ self.on_pick(e)
508
+ return super()._on_pick(e)
509
+
510
+ def _on_move(self, e):
511
+ super()._on_move(e)
512
+ return self.on_move(e)
513
+
514
+ def _draw_artist(self):
515
+ super()._draw_artist()
516
+ return self.draw_artist()
517
+ def _draw_blit_artist(self):
518
+ super()._draw_blit_artist()
519
+ return self.draw_artist()
520
+
521
+ def _on_click(self, e):
522
+ super()._on_click(e)
523
+ return self.on_click(e)
524
+ def _on_release(self, e):
525
+ super()._on_release(e)
526
+ return self.on_release(e)
527
+
528
+
529
+ if __name__ == '__main__':
530
+ import json
531
+ from time import time
532
+
533
+ import matplotlib.pyplot as plt
534
+ from pathlib import Path
535
+
536
+ file = Path(__file__).parent / 'data/samsung.txt'
537
+ # file = Path(__file__).parent / 'data/apple.txt'
538
+ with open(file, 'r', encoding='utf-8') as txt:
539
+ data = json.load(txt)
540
+ data = data
541
+ df = pd.DataFrame(data)
542
+
543
+ t = time()
544
+ # c = SimpleMixin()
545
+ c = SliderMixin()
546
+ c.set_data(df)
547
+ t2 = time() - t
548
+ print(f'{t2=}')
549
+ plt.show()
550
+
@@ -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, plus=False):
9
+ num.__round__(digit)
10
+ text = f'{num:+,.{digit}f}' if plus else 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))
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.2
2
+ Name: seolpyo-mplchart
3
+ Version: 0.1.3.1
4
+ Summary: Fast candlestick chart using Python. Includes navigator, slider, navigation, and text information display functions
5
+ Author-email: white-seolpyo <white-seolpyo@naver.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://white.seolpyo.com/
8
+ Project-URL: Documentation(English), https://white.seolpyo.com/entry/148/
9
+ Project-URL: Documentation(한글), https://white.seolpyo.com/entry/147/
10
+ Project-URL: repository, https://github.com/white-seolpyo/seolpyo-mplchart
11
+ Project-URL: Issues, https://github.com/white-seolpyo/seolpyo-mplchart/issues
12
+ Keywords: chart,차트,stock,주식,invest,투자,finance,파이낸스,candle,캔들,candlestick,캔들스틱,matplotlib,mplfinance,pyqtgraph,finplot,virtual currency,가상화폐,coin,코인,bitcoin,비트코인,ethereum,이더리움
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Framework :: Matplotlib
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: matplotlib>=3.7.0
26
+ Requires-Dist: pandas>=2.0.0
27
+
28
+ # Donation
29
+ Bitcoin: 1MKCHW8smDZv5DFMiVkA5G3DeXcMn871ZX
30
+
31
+ Ethereum: 0x1c5fb8a5e0b1153cd4116c91736bd16fabf83520
32
+
33
+
34
+ # Document
35
+ [English](https://white.seolpyo.com/entry/148/)
36
+
37
+ [한글](https://white.seolpyo.com/entry/147/)
38
+
39
+
40
+ # Sample
41
+ ![sample gif](https://raw.githubusercontent.com/white-seolpyo/seolpyo-mplchart/refs/heads/main/images/sample.gif)
42
+
43
+ <img alt="tkinter sample gif" src="https://github.com/white-seolpyo/seolpyo-mplchart/blob/main/images/with%20tkinter.gif?raw=true">
44
+
45
+ ![english sample](https://raw.githubusercontent.com/white-seolpyo/seolpyo-mplchart/refs/heads/main/images/change%20format.png)
46
+
47
+ ![korean sample](https://github.com/white-seolpyo/seolpyo-mplchart/blob/main/images/sample%20kor.png?raw=true)
48
+
49
+ <img alt="40,000 sample" src="https://github.com/white-seolpyo/seolpyo-mplchart/blob/main/images/40000.gif?raw=true">
@@ -0,0 +1,13 @@
1
+ seolpyo_mplchart/__init__.py,sha256=FiuciCaX9lU12gwuDBYUNw0yLD06p4o7o19kR5jGips,5248
2
+ seolpyo_mplchart/base.py,sha256=vQ4OOBm3nGwjJ4wjDLaD_3LGxYzlP6AWpI6SZrZiwnQ,3600
3
+ seolpyo_mplchart/cursor.py,sha256=rXGWf0p3oElnsVfEPObGVnD8dBMfvTgx2o6RermkMbE,18405
4
+ seolpyo_mplchart/draw.py,sha256=NJH1dnmfepafMlc7K2ccwZbv0FDS3ItSiirCq4gMlOI,13145
5
+ seolpyo_mplchart/slider.py,sha256=R29vyNAdJkLEXgpP4hxe9O0WoLgoOnPOUHuKFNpdcnw,19601
6
+ seolpyo_mplchart/test.py,sha256=cW2hoaVbRtoSXlpmA4i1BKHBjI3-FAqYq__kryxkrC8,1007
7
+ seolpyo_mplchart/utils.py,sha256=-8cq4-WwiqKQxtwu3NPxOVTDDvoWH28tu4OTWr4hPTg,1208
8
+ seolpyo_mplchart/data/apple.txt,sha256=0izAfweu1lLsC0IwVthdVlo9reG8KGbKGTSX5knI5Zc,1380864
9
+ seolpyo_mplchart/data/samsung.txt,sha256=UejaSkbzr4E5K3lkelCT0yJiWUPfmViBEaTyoXyphIs,2476424
10
+ seolpyo_mplchart-0.1.3.1.dist-info/METADATA,sha256=3fGWiUtSqRHxnuEEOLdwvPJAr8dJELx8RdGzU1wF9Sw,2347
11
+ seolpyo_mplchart-0.1.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
12
+ seolpyo_mplchart-0.1.3.1.dist-info/top_level.txt,sha256=KgqFn7rKWize7OjMaTCHxKm9ie6vqnyb5c8fN7y_tSo,17
13
+ seolpyo_mplchart-0.1.3.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+