seolpyo-mplchart 2.0.0.3__py3-none-any.whl → 2.1.0.4__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 (58) 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 +268 -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 +64 -0
  18. seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
  19. seolpyo_mplchart/_chart/cursor/h_data.py +62 -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/ax.py +2 -0
  32. seolpyo_mplchart/_config/candle.py +1 -0
  33. seolpyo_mplchart/_config/cursor.py +4 -0
  34. seolpyo_mplchart/_config/figure.py +4 -4
  35. seolpyo_mplchart/_config/ma.py +2 -0
  36. seolpyo_mplchart/_config/nums.py +67 -0
  37. seolpyo_mplchart/_config/slider/config.py +2 -2
  38. seolpyo_mplchart/_config/slider/figure.py +3 -4
  39. seolpyo_mplchart/_config/slider/nav.py +3 -2
  40. seolpyo_mplchart/_config/unit.py +1 -1
  41. seolpyo_mplchart/_config/volume.py +1 -0
  42. seolpyo_mplchart/_utils/__init__.py +10 -0
  43. seolpyo_mplchart/_utils/nums.py +1 -0
  44. seolpyo_mplchart/_utils/theme/__init__.py +15 -0
  45. seolpyo_mplchart/_utils/theme/dark.py +57 -0
  46. seolpyo_mplchart/_utils/theme/light.py +56 -0
  47. seolpyo_mplchart/_utils/utils.py +28 -0
  48. seolpyo_mplchart/_utils/xl/__init__.py +15 -0
  49. seolpyo_mplchart/_utils/xl/csv.py +46 -0
  50. seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
  51. seolpyo_mplchart/sample/apple.txt +6058 -0
  52. seolpyo_mplchart/sample/samsung.txt +5938 -0
  53. seolpyo_mplchart/test.py +5 -5
  54. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/METADATA +22 -14
  55. seolpyo_mplchart-2.1.0.4.dist-info/RECORD +90 -0
  56. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +0 -50
  57. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/WHEEL +0 -0
  58. {seolpyo_mplchart-2.0.0.3.dist-info → seolpyo_mplchart-2.1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,64 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.text import Text
3
+
4
+ from ..base.a_canvas import Figure
5
+
6
+
7
+ class Base:
8
+ figure: Figure
9
+ ax_price: Axes
10
+ ax_volume: Axes
11
+
12
+ key_volume: str
13
+
14
+ vxmin: int
15
+ vxmax: int
16
+ price_ymin: int
17
+ price_ymax: int
18
+ volume_ymax: int
19
+
20
+ artist_info_candle: Text
21
+ artist_info_volume: Text
22
+ artist_label_x: Text
23
+ artist_label_y: Text
24
+
25
+ axis: callable
26
+
27
+
28
+ class Mixin(Base):
29
+ def axis(self, xmin, *, xmax):
30
+ super().axis(xmin, xmax=xmax)
31
+ # print('cursor axis')
32
+
33
+ psub = (self.price_ymax - self.price_ymin)
34
+ self.min_height_box_candle = psub / 8
35
+
36
+ pydistance = psub / 20
37
+
38
+ self.min_height_box_volume = 10
39
+ if self.key_volume:
40
+ self.min_height_box_volume = self.volume_ymax / 4
41
+
42
+ vxsub = self.vxmax - self.vxmin
43
+ self.vmiddle = self.vxmax - int((vxsub) / 2)
44
+
45
+ vxdistance = vxsub / 50
46
+ self.v0, self.v1 = (self.vxmin + vxdistance, self.vxmax - vxdistance)
47
+
48
+ yvolume = self.volume_ymax * 0.85
49
+
50
+ # 정보 텍스트박스 y축 설정
51
+ self.artist_info_candle.set_y(self.price_ymax - pydistance)
52
+ self.artist_info_volume.set_y(yvolume)
53
+
54
+ return
55
+
56
+
57
+ class AxisMixin(Mixin):
58
+ v0: int
59
+ v1: int
60
+ vmiddle: int
61
+
62
+ min_height_box_candle: float
63
+ min_height_box_volume: float
64
+
@@ -0,0 +1,233 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.backend_bases import MouseEvent
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.text import Text
5
+ import pandas as pd
6
+
7
+ from ..base.a_canvas import Figure
8
+
9
+
10
+ class Base:
11
+ figure: Figure
12
+ ax_price: Axes
13
+ ax_volume: Axes
14
+
15
+ key_volume: str
16
+ df: pd.DataFrame
17
+
18
+ vxmin: int
19
+ vxmax: int
20
+ price_ymin: float
21
+ price_ymax: float
22
+ volume_ymax: float
23
+
24
+ collection_box_price: LineCollection
25
+ collection_box_volume: LineCollection
26
+ collection_price_crossline: LineCollection
27
+
28
+ index_list: list
29
+
30
+ min_height_box_candle: float
31
+ min_height_box_volume: float
32
+
33
+ connect_events: callable
34
+
35
+ artist_label_x: Text
36
+ artist_label_y: Text
37
+
38
+ in_chart = False
39
+ in_chart_price = False
40
+ in_chart_volume = False
41
+
42
+ in_candle = False
43
+ in_volume = False
44
+
45
+ _restore_region: callable
46
+
47
+ _set_crossline: callable
48
+ _draw_crossline: callable
49
+ _set_label_x: callable
50
+ _draw_label_x: callable
51
+
52
+ _set_label_y: callable
53
+ _draw_label_y: callable
54
+
55
+ _set_box_candle: callable
56
+ _draw_box_candle: callable
57
+ _set_box_volume: callable
58
+ _draw_box_volume: callable
59
+
60
+ _set_info_candle: callable
61
+ _draw_info_candle: callable
62
+ _set_info_volume: callable
63
+ _draw_info_volume: callable
64
+
65
+
66
+ class AxMixin(Base):
67
+ def _check_ax(self, e: MouseEvent):
68
+ ax = e.inaxes
69
+ # print(f'{ax=}')
70
+ self.in_chart = False
71
+ self.in_chart_price, self.in_chart_volume = (False, False)
72
+
73
+ if e.xdata is None or e.ydata is None:
74
+ return
75
+
76
+ if self.vxmin <= e.xdata and e.xdata <= self.vxmax:
77
+ if ax is self.ax_price and (
78
+ self.price_ymin <= e.ydata and e.ydata <= self.price_ymax
79
+ ):
80
+ self.in_chart = True
81
+ self.in_chart_price = True
82
+ elif ax is self.ax_volume and (
83
+ 0 <= e.ydata and e.ydata <= self.volume_ymax
84
+ ):
85
+ self.in_chart = True
86
+ self.in_chart_volume = True
87
+ return
88
+
89
+
90
+ class BoxMixin(Base):
91
+ def _draw_box_artist(self, e: MouseEvent):
92
+ xdata, ydata = (e.xdata, e.ydata)
93
+ ind = int(xdata)
94
+
95
+ self.in_candle, self.in_volume = (False, False)
96
+
97
+ if self.in_chart_price:
98
+ series = self.df.iloc[ind]
99
+ # print(f'{series=}')
100
+ # 박스 크기
101
+ high = series['box_candle_top']
102
+ low = series['box_candle_bottom']
103
+ height = series['box_candle_height']
104
+ # print(f'{(low, high)=}')
105
+ # print(f'{height=}')
106
+
107
+ # 박스 높이 보정
108
+ if height < self.min_height_box_candle:
109
+ sub = (self.min_height_box_candle - height) / 2
110
+ high, low = (high+sub, low-sub)
111
+
112
+ # 커서가 캔들 사이에 있는지 확인
113
+ if low <= ydata and ydata <= high:
114
+ self.in_candle = True
115
+
116
+ # 캔들 강조
117
+ x0, x1 = (ind-0.3, ind+1.3)
118
+ segment = [(
119
+ (x0, high),
120
+ (x1, high),
121
+ (x1, low),
122
+ (x0, low),
123
+ (x0, high)
124
+ )]
125
+ self._set_box_candle(segment)
126
+ self._draw_box_candle()
127
+
128
+ return 1
129
+ elif self.in_chart_volume and self.key_volume:
130
+ # 박스 크기
131
+ high = self.df.iloc[ind]['box_volume_top']
132
+ low = 0
133
+ if high < self.min_height_box_volume:
134
+ high = self.min_height_box_volume
135
+
136
+ if low <= ydata and ydata <= high:
137
+ # 거래량 강조
138
+ self.in_volume = True
139
+
140
+ x0, x1 = (ind-0.3, ind+1.3)
141
+ segment = [(
142
+ (x0, high),
143
+ (x1, high),
144
+ (x1, low),
145
+ (x0, low),
146
+ (x0, high)
147
+ )]
148
+ self._set_box_volume(segment)
149
+ self._draw_box_volume()
150
+
151
+ return 1
152
+ return
153
+
154
+
155
+ class InfoMixin(Base):
156
+ def _draw_info_artist(self, e: MouseEvent):
157
+ return
158
+
159
+
160
+ class Mixin(AxMixin, BoxMixin, InfoMixin):
161
+ _in_mouse_move = False
162
+
163
+ def need_restore(self):
164
+ if self.collection_price_crossline.get_segments():
165
+ self.collection_price_crossline.set_segments([])
166
+ return True
167
+ return
168
+
169
+ def _on_move(self, e: MouseEvent):
170
+ # print(f'{not self._in_mouse_move=}')
171
+ if not self._in_mouse_move:
172
+ self._in_mouse_move = True
173
+ # print(f'{(e.xdata, e.ydata)=}')
174
+ self.on_move(e)
175
+ self._in_mouse_move = False
176
+ return
177
+
178
+ def on_move(self, e: MouseEvent):
179
+ self._check_ax(e)
180
+ self._on_move_action(e)
181
+ return
182
+
183
+ def _set_and_draw_crossline(self, e: MouseEvent):
184
+ self._set_crossline(e)
185
+ self._draw_crossline()
186
+
187
+ if self._set_label_x(e):
188
+ # print('draw label x')
189
+ self._draw_label_x()
190
+ if self._draw_box_artist(e):
191
+ ind = int(e.xdata)
192
+ if self.in_chart_price:
193
+ self._set_info_candle(ind)
194
+ self._draw_info_candle()
195
+ else:
196
+ self._set_info_volume(ind)
197
+ self._draw_info_volume()
198
+ self._set_label_y(e, is_price_chart=self.in_chart_price)
199
+ self._draw_label_y()
200
+
201
+ return
202
+
203
+ def _on_move_action(self, e: MouseEvent):
204
+ if self.in_chart:
205
+ self._restore_region()
206
+
207
+ self._set_and_draw_crossline(e)
208
+
209
+ self.figure.canvas.blit()
210
+ self.figure.canvas.flush_events()
211
+ elif self.need_restore():
212
+ self._restore_region()
213
+ self.figure.canvas.blit()
214
+ self.figure.canvas.flush_events()
215
+ return
216
+
217
+
218
+ class EventMixin(Mixin):
219
+ in_chart = False
220
+ in_chart_price = False
221
+ in_chart_volume = False
222
+
223
+ in_candle = False
224
+ in_volume = False
225
+
226
+ _in_mouse_move = False
227
+
228
+ def connect_events(self):
229
+ super().connect_events()
230
+
231
+ self.figure.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
232
+ return
233
+
@@ -0,0 +1,62 @@
1
+ import pandas as pd
2
+
3
+ from ..._config import ConfigData
4
+
5
+
6
+ class Base:
7
+ CONFIG: ConfigData
8
+
9
+ key_volume: str
10
+ df: pd.DataFrame
11
+
12
+ _add_columns: callable
13
+ set_variables: callable
14
+
15
+
16
+ class Mixin(Base):
17
+ def _add_columns(self):
18
+ super()._add_columns()
19
+
20
+ self.df['compare'] = (self.df['close'] - self.df['pre_close']).fillna(0)
21
+ self.df['rate'] = (self.df['compare'] * 100 / self.df['pre_close']).__round__(2).fillna(0)
22
+ self.df['rate_open'] = ((self.df['open'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
23
+ self.df['rate_high'] = ((self.df['high'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
24
+ self.df['rate_low'] = ((self.df['low'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
25
+ if self.key_volume:
26
+ self.df['pre_volume'] = self.df['volume'].shift(1)
27
+ self.df['compare_volume'] = (self.df['volume'] - self.df['pre_volume']).fillna(0)
28
+ self.df['rate_volume'] = (self.df['compare_volume'] * 100 / self.df['pre_volume']).__round__(2).fillna(0)
29
+
30
+ self.df['space_box_candle'] = (self.df['high'] - self.df['low']) / 5
31
+ self.df['box_candle_top'] = self.df['high'] + self.df['space_box_candle']
32
+ self.df['box_candle_bottom'] = self.df['low'] - self.df['space_box_candle']
33
+ self.df['box_candle_height'] = self.df['box_candle_top'] - self.df['box_candle_bottom']
34
+
35
+ if self.key_volume:
36
+ self.df['box_volume_top'] = self.df['volume'] * 1.15
37
+
38
+ return
39
+
40
+ def set_variables(self):
41
+ super().set_variables()
42
+
43
+ self._set_length_text()
44
+ return
45
+
46
+ def _set_length_text(self):
47
+ func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit, word=self.CONFIG.UNIT.price))
48
+ self._length_text = self.df['high'].apply(func).max()
49
+
50
+ if self.key_volume:
51
+ func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit_volume, word=self.CONFIG.UNIT.volume))
52
+ lenth_volume = self.df['volume'].apply(func).max()
53
+ # print(f'{self._length_text=}')
54
+ # print(f'{lenth_volume=}')
55
+ if self._length_text < lenth_volume:
56
+ self._length_text = lenth_volume
57
+ return
58
+
59
+
60
+ class DataMixin(Mixin):
61
+ _length_text: int
62
+
@@ -0,0 +1,69 @@
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
+
15
+ from seolpyo_mplchart._utils.theme import set_theme
16
+ from seolpyo_mplchart._chart.cursor import Chart
17
+ from seolpyo_mplchart._config import DEFAULTCONFIG, DEFAULTCONFIG_EN
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[:300])
23
+
24
+
25
+ class C(Chart):
26
+ # limit_candle = 200
27
+ # limit_wick = 200
28
+ t = 'light'
29
+ # watermark = ''
30
+ def __init__(self):
31
+ super().__init__()
32
+ self.figure.canvas.mpl_connect('button_press_event', lambda x: self.theme(x))
33
+ self.CONFIG.FORMAT.candle += '\nCustom: {custom}'
34
+
35
+ def get_info_kwargs(self, is_price, **kwargs):
36
+ kwargs = super().get_info_kwargs(is_price, **kwargs)
37
+ kwargs['close'] = 'Cusotom close value'
38
+ kwargs['custom'] = 'this is custom add info kwargs'
39
+ return kwargs
40
+
41
+ def theme(self, e):
42
+ btn = getattr(e, 'button')
43
+ # print(f'{str(btn)=}')
44
+ if str(btn) == '3':
45
+ # print('refresh')
46
+ if self.t == 'light':
47
+ self.t = 'dark'
48
+ self.CONFIG = set_theme(DEFAULTCONFIG_EN, theme=self.t)
49
+ else:
50
+ self.t = 'light'
51
+ self.CONFIG = set_theme(DEFAULTCONFIG, theme=self.t)
52
+ # print(f'{self.t=}')
53
+ # self.CONFIG.MA.ma_list = []
54
+ self.refresh()
55
+ return
56
+
57
+
58
+ def run():
59
+ chart = C()
60
+ chart.set_data(df)
61
+ plt.show()
62
+ plt.close()
63
+ return
64
+
65
+
66
+ if __name__ == '__main__':
67
+ run()
68
+
69
+
@@ -0,0 +1,169 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+ from matplotlib.axes import Axes
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from ..._config import SLIDERCONFIG, SliderConfigData
9
+ from ..base import Chart as BaseChart
10
+ from ..cursor import CursorMixin
11
+
12
+ from .a_canvas import CanvasMixin, Figure
13
+ from .b_artist import ArtistMixin
14
+ from .c_draw import DrawMixin
15
+ from .d_segment import SegmentMixin
16
+ from .e_axis import AxisMixin
17
+ from .f_background import BackgroundMixin
18
+ from .g_event import EventMixin
19
+ from .h_data import DataMixin
20
+
21
+
22
+ class SliderMixin(
23
+ CanvasMixin,
24
+ ArtistMixin,
25
+ DrawMixin,
26
+ SegmentMixin,
27
+ AxisMixin,
28
+ BackgroundMixin,
29
+ EventMixin,
30
+ DataMixin,
31
+ ):
32
+ pass
33
+
34
+
35
+ class Chart(SliderMixin, CursorMixin, BaseChart):
36
+ candle_on_ma = True
37
+ slider_top = True
38
+ fraction = False
39
+
40
+ limit_candle = 400
41
+ limit_wick = 2_000
42
+ limit_volume = 200
43
+ limit_ma = 8_000
44
+
45
+ min_distance = 5
46
+
47
+ key_date = 'date'
48
+ key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
49
+ key_volume = 'volume'
50
+
51
+ index_list: list[int] = []
52
+
53
+ df: pd.DataFrame
54
+
55
+ CONFIG: SliderConfigData
56
+
57
+ figure: Figure
58
+ ax_legend: Axes
59
+ ax_price: Axes
60
+ ax_volume: Axes
61
+
62
+ ax_slider: Axes
63
+ ax_none: Axes
64
+ _ax_slider_top: Axes
65
+ _ax_slider_bottom: Axes
66
+
67
+ artist_watermark: Text
68
+ collection_candle: LineCollection
69
+ collection_volume: LineCollection
70
+ collection_ma: LineCollection
71
+
72
+ collection_slider: LineCollection
73
+ collection_nav: LineCollection
74
+ collection_slider_vline: LineCollection
75
+ artist_text_slider: Text
76
+
77
+ in_candle = False
78
+ in_volume = False
79
+
80
+ collection_price_crossline: LineCollection
81
+ collection_volume_crossline: LineCollection
82
+
83
+ artist_label_x: Text = None
84
+ artist_label_y: Text = None
85
+
86
+ collection_box_price: LineCollection
87
+ collection_box_volume: LineCollection
88
+
89
+ artist_info_candle: Text
90
+ artist_info_volume: Text
91
+
92
+ v0: int
93
+ v1: int
94
+ vmiddle: int
95
+
96
+ min_height_box_candle: float
97
+ min_height_box_volume: float
98
+
99
+ _length_text: int
100
+
101
+ _in_mouse_move = False
102
+
103
+ ###
104
+
105
+ segment_nav: np.ndarray
106
+ segment_volume: np.ndarray
107
+ segment_volume_wick: np.ndarray
108
+ facecolor_volume: np.ndarray
109
+ edgecolor_volume: np.ndarray
110
+
111
+ segment_candle: np.ndarray
112
+ segment_candle_wick: np.ndarray
113
+ segment_priceline: np.ndarray
114
+ facecolor_candle: np.ndarray
115
+ edgecolor_candle: np.ndarray
116
+
117
+ segment_ma: np.ndarray
118
+ edgecolor_ma: np.ndarray
119
+
120
+ price_ymin: int
121
+ price_ymax: int
122
+ volume_ymax: int
123
+
124
+ chart_price_ymax: float
125
+ chart_volume_ymax: float
126
+
127
+ vxmin: int
128
+ vxmax: int
129
+
130
+ _nav_width: float
131
+
132
+ slider_xmin: int
133
+ slider_xmax: int
134
+ slider_ymin: float
135
+ slider_ymax: float
136
+
137
+ in_chart = False
138
+ in_slider = False
139
+ in_chart_price = False
140
+ in_chart_volume = False
141
+
142
+ is_move_chart = False
143
+ is_click_slider = False
144
+ is_click_chart = False
145
+ x_click: float
146
+
147
+ click_nav_left = False
148
+ click_nav_right = False
149
+
150
+ navcoordinate: tuple[int, int]
151
+
152
+ ###
153
+
154
+ _visible_ma: set[int] = set()
155
+ _edgecolor_ma = []
156
+
157
+ _background = None
158
+ _creating_background = False
159
+
160
+ def __init__(self, config=SLIDERCONFIG):
161
+ self.CONFIG = config
162
+ super().__init__()
163
+ return
164
+
165
+ def refresh(self):
166
+ self._set_slider_artists()
167
+ super().refresh()
168
+ return
169
+