seolpyo-mplchart 2.0.0.3__py3-none-any.whl → 2.1.0__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 (54) hide show
  1. seolpyo_mplchart/__init__.py +17 -133
  2. seolpyo_mplchart/_chart/__init__.py +39 -31
  3. seolpyo_mplchart/_chart/base/__init__.py +111 -0
  4. seolpyo_mplchart/_chart/base/a_canvas.py +250 -0
  5. seolpyo_mplchart/_chart/base/b_artist.py +143 -0
  6. seolpyo_mplchart/_chart/base/c_draw.py +100 -0
  7. seolpyo_mplchart/_chart/base/d_segment.py +262 -0
  8. seolpyo_mplchart/_chart/base/e_axis.py +267 -0
  9. seolpyo_mplchart/_chart/base/f_background.py +62 -0
  10. seolpyo_mplchart/_chart/base/g_event.py +66 -0
  11. seolpyo_mplchart/_chart/base/h_data.py +138 -0
  12. seolpyo_mplchart/_chart/base/test.py +58 -0
  13. seolpyo_mplchart/_chart/cursor/__init__.py +125 -0
  14. seolpyo_mplchart/_chart/cursor/b_artist.py +130 -0
  15. seolpyo_mplchart/_chart/cursor/c_draw.py +96 -0
  16. seolpyo_mplchart/_chart/cursor/d_segment.py +359 -0
  17. seolpyo_mplchart/_chart/cursor/e_axis.py +65 -0
  18. seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
  19. seolpyo_mplchart/_chart/cursor/h_data.py +61 -0
  20. seolpyo_mplchart/_chart/cursor/test.py +69 -0
  21. seolpyo_mplchart/_chart/slider/__init__.py +169 -0
  22. seolpyo_mplchart/_chart/slider/a_canvas.py +260 -0
  23. seolpyo_mplchart/_chart/slider/b_artist.py +91 -0
  24. seolpyo_mplchart/_chart/slider/c_draw.py +54 -0
  25. seolpyo_mplchart/_chart/slider/d_segment.py +166 -0
  26. seolpyo_mplchart/_chart/slider/e_axis.py +70 -0
  27. seolpyo_mplchart/_chart/slider/f_background.py +37 -0
  28. seolpyo_mplchart/_chart/slider/g_event.py +353 -0
  29. seolpyo_mplchart/_chart/slider/h_data.py +102 -0
  30. seolpyo_mplchart/_chart/slider/test.py +71 -0
  31. seolpyo_mplchart/_config/candle.py +1 -0
  32. seolpyo_mplchart/_config/figure.py +3 -4
  33. seolpyo_mplchart/_config/ma.py +2 -0
  34. seolpyo_mplchart/_config/slider/config.py +2 -2
  35. seolpyo_mplchart/_config/slider/figure.py +3 -4
  36. seolpyo_mplchart/_config/slider/nav.py +3 -2
  37. seolpyo_mplchart/_config/volume.py +1 -0
  38. seolpyo_mplchart/_utils/__init__.py +10 -0
  39. seolpyo_mplchart/_utils/nums.py +67 -0
  40. seolpyo_mplchart/_utils/theme/__init__.py +15 -0
  41. seolpyo_mplchart/_utils/theme/dark.py +57 -0
  42. seolpyo_mplchart/_utils/theme/light.py +56 -0
  43. seolpyo_mplchart/_utils/utils.py +28 -0
  44. seolpyo_mplchart/_utils/xl/__init__.py +15 -0
  45. seolpyo_mplchart/_utils/xl/csv.py +46 -0
  46. seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
  47. seolpyo_mplchart/sample/apple.txt +6058 -0
  48. seolpyo_mplchart/sample/samsung.txt +5938 -0
  49. seolpyo_mplchart/test.py +5 -5
  50. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/METADATA +21 -13
  51. seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
  52. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +0 -50
  53. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +0 -0
  54. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,353 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.backend_bases import MouseEvent, MouseButton, cursors
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.text import Text
5
+ import numpy as np
6
+
7
+ from ..cursor.g_event import EventMixin as Base
8
+
9
+
10
+ class BaseMixin(Base):
11
+ ax_slider: Axes
12
+
13
+ slider_xmin: int
14
+ slider_xmax: int
15
+ slider_ymin: float
16
+ slider_ymax: float
17
+
18
+ in_slider = False
19
+ is_click_slider = False
20
+ is_click_chart = False
21
+ is_move_chart = False
22
+
23
+ click_nav_left = False
24
+ click_nav_right = False
25
+
26
+ min_distance = 5
27
+
28
+ segment_nav: np.ndarray
29
+
30
+ _set_slider_vline: callable
31
+ _draw_slider_vline: callable
32
+ _set_slider_text: callable
33
+ _draw_slider_text: callable
34
+ draw_chart: callable
35
+ axis: callable
36
+ _set_cursor: callable
37
+ _move_chart: callable
38
+ _draw_nav: callable
39
+ _restore_region_background: callable
40
+
41
+ collection_slider_vline: LineCollection
42
+ artist_text_slider: Text
43
+ collection_nav: LineCollection
44
+
45
+ x_click: int
46
+ _nav_width: float
47
+ navcoordinate: tuple[int, int]
48
+ vxmin: int
49
+ vxmax: int
50
+
51
+ def get_nav_xlim(self):
52
+ seg = self.segment_nav
53
+ # print(f'{seg=}')
54
+ xmin = seg[-2][0][0]
55
+ xmax = seg[-1][0][0]
56
+
57
+ return (int(xmin), int(xmax))
58
+
59
+
60
+ class CursorMixin(BaseMixin):
61
+ def _set_cursor(self, e: MouseEvent):
62
+ # 마우스 커서 변경
63
+ if self.is_click_slider:
64
+ return
65
+ elif not self.in_slider:
66
+ self.figure.canvas.set_cursor(cursors.POINTER)
67
+ return
68
+
69
+ xmin, xmax = self.get_nav_xlim()
70
+ if xmin == xmax:
71
+ return
72
+
73
+ x = e.xdata
74
+
75
+ left0 = xmin - self._nav_width
76
+ left1 = xmin
77
+
78
+ if left0 <= x and x <= left1:
79
+ # 커서가 좌경계선 위에 위치
80
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
81
+ return
82
+
83
+ right0 = xmax
84
+ right1 = xmax + self._nav_width
85
+ if right0 <= x and x <= right1:
86
+ # 커서가 우경계선 위에 위치
87
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
88
+ return
89
+
90
+ if left1 < x and x < right0:
91
+ # 커서가 조회영역 위에 위치
92
+ self.figure.canvas.set_cursor(cursors.MOVE)
93
+ return
94
+
95
+ self.figure.canvas.set_cursor(cursors.POINTER)
96
+ return
97
+
98
+
99
+ class AxMixin(BaseMixin):
100
+ def _check_ax(self, e: MouseEvent):
101
+ ax = e.inaxes
102
+ # print(f'{ax=}')
103
+ self.in_chart = False
104
+ self.in_slider, self.in_chart_price, self.in_chart_volume = (False, False, False)
105
+
106
+ if e.xdata is None or e.ydata is None:
107
+ return
108
+
109
+ if ax is self.ax_slider:
110
+ if (
111
+ (self.slider_xmin <= e.xdata and e.xdata <= self.slider_xmax)
112
+ and (self.slider_ymin <= e.ydata and e.ydata <= self.slider_ymax)
113
+ ):
114
+ self.in_slider = True
115
+ else:
116
+ super()._check_ax(e)
117
+ return
118
+
119
+
120
+ class ClickMixin(BaseMixin):
121
+ is_click_slider = False
122
+ is_click_chart = False
123
+
124
+ click_nav_left = False
125
+ click_nav_right = False
126
+
127
+ def on_click(self, e: MouseEvent):
128
+ if e.xdata is not None and e.ydata is not None:
129
+ if self.in_chart and not self.is_click_chart:
130
+ if e.button == MouseButton.LEFT:
131
+ self._on_click_chart(e)
132
+ elif self.in_slider and not self.is_click_slider:
133
+ if e.button == MouseButton.LEFT:
134
+ self._on_click_slider(e)
135
+ return
136
+
137
+ def _on_click_chart(self, e: MouseEvent):
138
+ # 조회영역 이동 시작
139
+ self.is_click_chart = True
140
+ self.is_move_chart = True
141
+
142
+ x = int(e.xdata)
143
+ self.x_click = x
144
+
145
+ xmin, xmax = self.get_nav_xlim()
146
+ self.navcoordinate = (xmin, xmax)
147
+
148
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
149
+ return
150
+
151
+ def _on_click_slider(self, e: MouseEvent):
152
+ self.is_click_slider = True
153
+ self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
154
+
155
+ xmin, xmax = self.get_nav_xlim()
156
+ x = round(e.xdata)
157
+
158
+ xmin0 = xmin - self._nav_width
159
+ xmin1 = xmin
160
+ if xmin0 <= x and x <= xmin1:
161
+ # 좌경계선 이동 시작
162
+ self.navcoordinate = (xmin, xmax)
163
+ self.click_nav_left = True
164
+ self.x_click = xmin
165
+ return
166
+
167
+ xmax0 = xmax
168
+ if xmin1 < x and x < xmax0:
169
+ # 조회영역 이동 시작
170
+ self.navcoordinate = (xmin, xmax)
171
+ self.is_move_chart = True
172
+ self.x_click = x
173
+ return
174
+
175
+ if xmax0 <= x and x <= (xmax + self._nav_width):
176
+ # 우경계선 이동 시작
177
+ self.navcoordinate = (xmin, xmax)
178
+ self.click_nav_right = True
179
+ self.x_click = xmax
180
+ return
181
+
182
+ self.navcoordinate = (xmin, xmax)
183
+ self.x_click = x
184
+ return
185
+
186
+
187
+ class ReleaseMixin(BaseMixin):
188
+ def on_release(self, e: MouseEvent):
189
+ if e.button == MouseButton.LEFT:
190
+ if (
191
+ self.is_click_chart
192
+ and self.in_chart
193
+ ):
194
+ self._on_release_chart(e)
195
+ elif self.is_click_slider and self.in_slider:
196
+ self._on_release_slider(e)
197
+ return
198
+
199
+ def _on_release_chart(self, e):
200
+ self.x_click = None
201
+ self.is_click_chart = False
202
+ self.is_move_chart = False
203
+ self.figure.canvas.set_cursor(cursors.POINTER)
204
+
205
+ xmin, xmax = self.navcoordinate
206
+ self.axis(xmin, xmax=xmax)
207
+ self.figure.canvas.draw()
208
+ return
209
+
210
+ def _on_release_slider(self, e: MouseEvent):
211
+ if self.x_click:
212
+ xmin, xmax = self.get_nav_xlim()
213
+ xsub = xmax - xmin
214
+ min_distance = 5 if not self.min_distance or self.min_distance < 5 else self.min_distance
215
+ # print(f'{xsub=}')
216
+ # print(f'{min_distance=}')
217
+ if min_distance <= xsub:
218
+ self.navcoordinate = (xmin, xmax)
219
+ else:
220
+ xmin, xmax = self.navcoordinate
221
+
222
+ self.x_click = None
223
+ self.is_click_slider = False
224
+ self.is_move_chart = False
225
+ self.click_nav_left, self.click_nav_right = (False, False)
226
+ self.axis(xmin, xmax=xmax)
227
+
228
+ self.figure.canvas.draw()
229
+ return
230
+
231
+
232
+ class ChartMixin(BaseMixin):
233
+ def _move_chart(self, e: MouseEvent):
234
+ if self.is_click_chart and self.in_chart:
235
+ xdata = int(e.xdata)
236
+ # print(f'{(self.x_click, xdata)=}')
237
+ xsub = self.x_click - xdata
238
+ if xsub:
239
+ pre_xmin, pre_xmax = self.navcoordinate
240
+ xmin, xmax = (pre_xmin+xsub, pre_xmax+xsub)
241
+ # print(f'{(xmin, xmax)=}')
242
+ if 0 <= xmax and xmin <= self.index_list[-1] and xmin != xmax:
243
+ self.axis(xmin, xmax=xmax)
244
+ self.navcoordinate = (xmin, xmax)
245
+ elif self.is_click_slider and self.in_slider:
246
+ xdata = round(e.xdata)
247
+ pre_xmin, pre_xmax = self.navcoordinate
248
+ if self.click_nav_left:
249
+ if xdata < pre_xmax:
250
+ xmin, xmax = (xdata, pre_xmax)
251
+ else:
252
+ xmin, xmax = (pre_xmax, xdata)
253
+ elif self.click_nav_right:
254
+ if xdata < pre_xmin:
255
+ xmin, xmax = (xdata, pre_xmin)
256
+ else:
257
+ xmin, xmax = (pre_xmin, xdata)
258
+ else:
259
+ if self.is_move_chart:
260
+ xsub = self.x_click - xdata
261
+ xmax = -1
262
+ if xsub:
263
+ pre_xmin, pre_xmax = self.navcoordinate
264
+ xmin, xmax = (pre_xmin-xsub, pre_xmax-xsub)
265
+ else:
266
+ if xdata == self.x_click:
267
+ xmax = -1
268
+ elif xdata < self.x_click:
269
+ xmin, xmax = (xdata, self.x_click)
270
+ else:
271
+ xmin, xmax = (self.x_click, xdata)
272
+
273
+ if 0 <= xmax and xmin <= self.index_list[-1] and xmin != xmax:
274
+ self.axis(xmin, xmax=xmax)
275
+ return
276
+
277
+
278
+ class MoveMixin(BaseMixin):
279
+ def _set_and_draw_vline(self, e: MouseEvent):
280
+ self._set_slider_vline(e)
281
+ self._draw_slider_vline()
282
+ if self.in_slider and self._set_slider_text(e):
283
+ self._draw_slider_text()
284
+ return
285
+
286
+ def _set_and_draw_crossline(self, e: MouseEvent):
287
+ self._set_and_draw_vline(e)
288
+
289
+ super()._set_and_draw_crossline(e)
290
+ return
291
+
292
+ def _on_move_action(self, e: MouseEvent):
293
+ if e.xdata is not None:
294
+ if self.x_click is not None:
295
+ self._restore_region_background()
296
+
297
+ self._move_chart(e)
298
+ self.draw_chart()
299
+ self._draw_nav()
300
+ if self.in_slider:
301
+ self._set_and_draw_vline(e)
302
+
303
+ self.figure.canvas.blit()
304
+ self.figure.canvas.flush_events()
305
+ else:
306
+ self._set_cursor(e)
307
+
308
+ if self.in_slider:
309
+ self._restore_region()
310
+
311
+ self._set_and_draw_vline(e)
312
+
313
+ self.figure.canvas.blit()
314
+ self.figure.canvas.flush_events()
315
+ else:
316
+ super()._on_move_action(e)
317
+ else:
318
+ super()._on_move_action(e)
319
+ return
320
+
321
+ def need_restore(self):
322
+ if not super().need_restore():
323
+ if self.collection_slider_vline.get_segments():
324
+ self.collection_slider_vline.set_segments([])
325
+ return True
326
+ return
327
+
328
+
329
+ class EventMixin(AxMixin, CursorMixin, ClickMixin, ReleaseMixin, ChartMixin, MoveMixin,):
330
+ x_click = None
331
+ in_chart = False
332
+ in_slider = False
333
+ in_chart_price = False
334
+ in_chart_volume = False
335
+
336
+ is_move_chart = False
337
+ is_click_slider = False
338
+ is_click_chart = False
339
+ x_click: float
340
+
341
+ click_nav_left = False
342
+ click_nav_right = False
343
+
344
+ min_distance = 5
345
+
346
+ navcoordinate: tuple[int, int]
347
+
348
+ def connect_events(self):
349
+ super().connect_events()
350
+
351
+ self.figure.canvas.mpl_connect('button_press_event', lambda x: self.on_click(x))
352
+ self.figure.canvas.mpl_connect('button_release_event', lambda x: self.on_release(x))
353
+ return
@@ -0,0 +1,102 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.text import Text
3
+ import pandas as pd
4
+
5
+ from ..._config import ConfigData
6
+
7
+
8
+ class Base:
9
+ CONFIG: ConfigData
10
+ df: pd.DataFrame
11
+
12
+ key_date = 'date'
13
+ key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
14
+ key_volume = 'volume'
15
+
16
+ ax_slider: Axes
17
+
18
+ index_list: list[int] = []
19
+
20
+ artist_text_slider: Text
21
+
22
+ axis: callable
23
+ get_default_xlim: callable
24
+ set_variables: callable
25
+
26
+ _set_slider_collection: callable
27
+ vxmin: int
28
+ vxmax: int
29
+
30
+
31
+ class SliderMixin(Base):
32
+ min_distance = 5
33
+
34
+ def get_default_xlim(self):
35
+ xmax = self.index_list[-1] + 1
36
+ xmin = xmax - 120
37
+ if xmin < 0:
38
+ xmin = 0
39
+ return (xmin, xmax)
40
+
41
+ def set_variables(self):
42
+ super().set_variables()
43
+
44
+ self._set_slider()
45
+ return
46
+
47
+ def _set_slider(self):
48
+ # print('_set_slider')
49
+ self._set_slider_xtick()
50
+
51
+ xmax = self.index_list[-1]
52
+ # 슬라이더 xlim
53
+ xdistance = round(xmax / 30)
54
+ self.slider_xmin, self.slider_xmax = (-xdistance, xmax+xdistance)
55
+ self.ax_slider.set_xlim(self.slider_xmin, self.slider_xmax)
56
+
57
+ # 네비게이터 경계선 두께
58
+ self._nav_width = round((self.slider_xmax - self.slider_xmin) / 250, 2)
59
+
60
+ # 슬라이더 ylim
61
+ ymin, ymax = (self.df['low'].min(), self.df['high'].max())
62
+ ysub = ymax - ymin
63
+ ydistance = round(ysub / 5, self.CONFIG.UNIT.digit+2)
64
+ self.slider_ymin, self.slider_ymax = (ymin-ydistance, ymax+ydistance)
65
+ self.ax_slider.set_ylim(self.slider_ymin, self.slider_ymax)
66
+
67
+ self._set_slider_collection()
68
+ self._set_slider_text_position()
69
+
70
+ return
71
+
72
+ def _set_slider_text_position(self):
73
+ # 슬라이더 텍스트 y
74
+ self.artist_text_slider.set_y(self.df['high'].max())
75
+ return
76
+
77
+ def _set_slider_xtick(self):
78
+ indices = [0, self.index_list[-1]]
79
+ # print(f'{indices=}')
80
+
81
+ date_list = [self.df.iloc[idx]['date'] for idx in indices]
82
+ # print(f'{date_list=}')
83
+ # xtick 설정, major tick과 겹쳐서 무시되는 것 방지
84
+ self.ax_slider.set_xticks([idx+0.01 for idx in indices], labels=date_list, minor=True)
85
+
86
+ labels = self.ax_slider.get_xticklabels(minor=True)
87
+ # print(f'{labels=}')
88
+ for label, align in zip(labels, ['center', 'center']):
89
+ # 라벨 텍스트 정렬
90
+ label.set_horizontalalignment(align)
91
+ return
92
+
93
+
94
+ class DataMixin(SliderMixin):
95
+ min_distance = 5
96
+ _nav_width: float
97
+
98
+ slider_xmin: int
99
+ slider_xmax: int
100
+ slider_ymin: float
101
+ slider_ymax: float
102
+
@@ -0,0 +1,71 @@
1
+ import sys
2
+ from pathlib import Path
3
+ name_pkg = 'seolpyo_mplchart'
4
+ path_pkg = Path(__file__)
5
+ while path_pkg.name != name_pkg:
6
+ path_pkg = path_pkg.parent
7
+ sys.path = [path_pkg.parent.__str__()] + sys.path
8
+
9
+ import json
10
+
11
+ import pandas as pd
12
+ import matplotlib.pyplot as plt
13
+
14
+ from seolpyo_mplchart._utils.theme import set_theme
15
+ from seolpyo_mplchart._config import SLIDERCONFIG
16
+ from seolpyo_mplchart._chart.slider import Chart
17
+
18
+
19
+ path_file = path_pkg / 'sample' / 'samsung.txt'
20
+ with open(path_file, 'r', encoding='utf-8') as txt:
21
+ data = json.load(txt)
22
+ df = pd.DataFrame(data[:800])
23
+
24
+
25
+ class C(Chart):
26
+ limit_candle = 100
27
+ limit_wick = 300
28
+ limit_volume = 10
29
+ limit_ma = 200
30
+ t = 'light'
31
+ # watermark = ''
32
+ def __init__(self):
33
+ super().__init__()
34
+ # super().__init__(config=set_theme(SLIDERCONFIG, theme='dark'))
35
+ self.figure.canvas.mpl_connect('button_press_event', lambda x: self.theme(x))
36
+
37
+ def theme(self, e):
38
+ btn = getattr(e, 'button')
39
+ # print(f'{str(btn)=}')
40
+ if str(btn) == '3':
41
+ # print('refresh')
42
+ if self.t == 'light':
43
+ self.slider_top = True
44
+ self.t = 'dark'
45
+ # self.CONFIG.MA.linewidth = 1
46
+ self.get_candle_segment = lambda **x: Chart.get_candle_segment(self, **x)
47
+ self.CONFIG.CANDLE.linewidth = 0.8
48
+ else:
49
+ self.slider_top = False
50
+ self.t = 'light'
51
+ # self.CONFIG.MA.linewidth = 3
52
+ self.get_candle_segment = self.get_bar_segment
53
+ self.CONFIG.CANDLE.linewidth = 1.3
54
+ # print(f'{self.t=}')
55
+ self.CONFIG = set_theme(self.CONFIG, theme=self.t)
56
+ self.refresh()
57
+ return
58
+
59
+
60
+ def run():
61
+ chart = C()
62
+ chart.set_data(df)
63
+ plt.show()
64
+ plt.close()
65
+ return
66
+
67
+
68
+ if __name__ == '__main__':
69
+ run()
70
+
71
+
@@ -22,6 +22,7 @@ CANDLEEDGECOLOR = CandleEdgeColorData()
22
22
  class CandleData:
23
23
  def __init__(self):
24
24
  self.half_width = 0.24
25
+ self.linewidth = 0.8
25
26
  self.line_color: str|tuple[float, float, float, float] = 'k'
26
27
  self.FACECOLOR = CANDLEFACECOLOR
27
28
  self.EDGECOLOR = CANDLEEDGECOLOR
@@ -2,8 +2,7 @@
2
2
 
3
3
  class RatioData:
4
4
  def __init__(self):
5
- self.legend = 2
6
- self.price = 18
5
+ self.price = 5
7
6
  self.volume = 5
8
7
 
9
8
  RATIO = RatioData()
@@ -19,7 +18,7 @@ WATERMARK = WatermarkData()
19
18
  class AdjustData:
20
19
  def __init__(self):
21
20
  # 여백
22
- self.top = 0.95
21
+ self.top = 0.98
23
22
  self.bottom = 0.05
24
23
  self.left = 0.01
25
24
  self.right = 0.93
@@ -31,7 +30,7 @@ ADJUST = AdjustData()
31
30
 
32
31
  class FigureData:
33
32
  def __init__(self):
34
- self.facecolor: str|tuple[float, float, float, float] = 'w'
33
+ self.facecolor: str|tuple[float, float, float, float] = '#fafafa'
35
34
  self.figsize = (14, 7)
36
35
  self.RATIO = RATIO
37
36
  self.ADJUST = ADJUST
@@ -5,6 +5,8 @@ class MaData:
5
5
  def __init__(self):
6
6
  self.color_default: str|tuple[float, float, float, float] = 'k'
7
7
  self.format = '{}일선'
8
+ self.linewidth = 1
9
+ self.ncol = 10
8
10
  self.color_list: list[str|tuple[float, float, float, float]] = ['#8B00FF', '#008000', '#A0522D', '#008B8B', '#FF0080']
9
11
  self.ma_list = (5, 20, 60, 120, 240)
10
12
 
@@ -1,11 +1,11 @@
1
1
  from .. import config
2
2
  from .figure import FIGURE, SliderFigureData
3
- from .nav import NAVIGATOR
3
+ from .nav import NAV
4
4
 
5
5
 
6
6
  class SliderData:
7
7
  def __init__(self):
8
- self.NAVIGATOR = NAVIGATOR
8
+ self.NAV = NAV
9
9
 
10
10
  SLIDER = SliderData()
11
11
 
@@ -3,11 +3,10 @@ from .. import figure
3
3
 
4
4
  class RatioData:
5
5
  def __init__(self):
6
- self.legend = 2
7
- self.price = 18
8
- self.volume = 5
9
- self.slider = 3
6
+ self.price = 9
7
+ self.volume = 3
10
8
  self.none = 2
9
+ self.slider = 1.5
11
10
 
12
11
  RATIO = RatioData()
13
12
 
@@ -1,9 +1,10 @@
1
1
 
2
2
 
3
- class NavigatorData:
3
+ class Nav:
4
4
  def __init__(self):
5
5
  self.edgecolor = '#2962FF'
6
6
  self.facecolor = '#0000002E'
7
+ self.alpha = 0.18
7
8
 
8
- NAVIGATOR = NavigatorData()
9
+ NAV = Nav()
9
10
 
@@ -19,6 +19,7 @@ VOLUMEEDGECOLOR = VolumeEdgeColorData()
19
19
  class VolumeData:
20
20
  def __init__(self):
21
21
  self.half_width = 0.36
22
+ self.linewidth = 0.7
22
23
  self.FACECOLOR = VOLUMEFACECOLOR
23
24
  self.EDGECOLOR = VOLUMEEDGECOLOR
24
25
 
@@ -0,0 +1,10 @@
1
+ from .theme import set_theme
2
+ from .nums import (
3
+ convert_num,
4
+ float_to_str,
5
+ convert_unit, convert_unit_en,
6
+ data_unit_ko, data_unit_en
7
+ )
8
+ from .xl import xl_to_dataList
9
+ from .utils import switch_backend, show, close, list_to_DataFrame
10
+
@@ -0,0 +1,67 @@
1
+
2
+
3
+ def convert_num(num):
4
+ if isinstance(num, float) and num % 1:
5
+ return num
6
+ return int(num)
7
+
8
+
9
+ def float_to_str(num: float, *, digit=0, plus=False):
10
+ if 0 < digit:
11
+ num.__round__(digit)
12
+ text = f'{num:+,.{digit}f}' if plus else f'{num:,.{digit}f}'
13
+ else:
14
+ num = round(num, digit).__int__()
15
+ text = f'{num:+,}' if plus else f'{num:,}'
16
+ return text
17
+
18
+
19
+ data_unit_ko = {
20
+ '경': 10_000_000_000_000_000,
21
+ '조': 1_000_000_000_000,
22
+ '억': 100_000_000,
23
+ '만': 10_000,
24
+ }
25
+ def convert_unit(value, *, digit=0, word='원', unit_data: dict[str, int]=None):
26
+ if not unit_data:
27
+ unit_data = data_unit_ko
28
+ # print('ko')
29
+ # print(f'{value=:,}')
30
+ v = abs(value)
31
+ for unit, n in unit_data.items():
32
+ if n <= v:
33
+ # print(f'{n=:,}')
34
+ # print(f'{unit=}')
35
+ num = value / n
36
+ if word.startswith(' '):
37
+ return f'{float_to_str(num, digit=digit)}{unit}{word}'
38
+ return f'{float_to_str(num, digit=digit)}{unit} {word}'
39
+
40
+ if not value % 1:
41
+ value = int(value)
42
+ text = f'{float_to_str(value, digit=digit)}{word}'
43
+ # print(f'{text=}')
44
+ return text
45
+
46
+ data_unit_en = {
47
+ 'Qd': 1_000_000_000_000_000,
48
+ 'T': 1_000_000_000_000,
49
+ 'B': 1_000_000_000,
50
+ 'M': 1_000_000,
51
+ 'K': 1_000,
52
+ }
53
+ def convert_unit_en(value, *, digit=0, word='$', unit_data: dict[str, int]=None):
54
+ if not unit_data:
55
+ unit_data = data_unit_en
56
+ # print('en')
57
+ # print(f'{value=:,}')
58
+ return convert_unit(value, digit=digit, word=word, unit_data=unit_data)
59
+
60
+
61
+ if __name__ == '__main__':
62
+ a = 456.123
63
+ print(float_to_str(a))
64
+ print(float_to_str(a, 2))
65
+ print(float_to_str(a, 6))
66
+
67
+