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,125 @@
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 ..base import Chart as BaseChart, Figure, ConfigData
9
+
10
+ from .b_artist import ArtistMixin
11
+ from .c_draw import DrawMixin
12
+ from .d_segment import SegmentMixin
13
+ from .e_axis import AxisMixin
14
+ from .g_event import EventMixin
15
+ from .h_data import DataMixin
16
+
17
+
18
+ class CursorMixin(
19
+ ArtistMixin,
20
+ DrawMixin,
21
+ SegmentMixin,
22
+ AxisMixin,
23
+ EventMixin,
24
+ DataMixin
25
+ ):
26
+ pass
27
+
28
+
29
+ class Chart(CursorMixin, BaseChart):
30
+ limit_candle = 400
31
+ limit_wick = 2_000
32
+ candle_on_ma = True
33
+ fraction = False
34
+
35
+ key_date = 'date'
36
+ key_open, key_high, key_low, key_close = ('open', 'high', 'low', 'close')
37
+ key_volume = 'volume'
38
+
39
+ index_list: list[int] = []
40
+
41
+ df: pd.DataFrame
42
+
43
+ CONFIG: ConfigData
44
+
45
+ figure: Figure
46
+ ax_legend: Axes
47
+ ax_price: Axes
48
+ ax_volume: Axes
49
+
50
+ artist_watermark: Text
51
+ collection_candle: LineCollection
52
+ collection_volume: LineCollection
53
+ collection_ma: LineCollection
54
+
55
+ in_chart = False
56
+ in_chart_price = False
57
+ in_chart_volume = False
58
+
59
+ in_candle = False
60
+ in_volume = False
61
+
62
+ collection_price_crossline: LineCollection
63
+ collection_volume_crossline: LineCollection
64
+
65
+ artist_label_x: Text = None
66
+ artist_label_y: Text = None
67
+
68
+ collection_box_price: LineCollection
69
+ collection_box_volume: LineCollection
70
+
71
+ artist_info_candle: Text
72
+ artist_info_volume: Text
73
+
74
+ ###
75
+
76
+ segment_volume: np.ndarray
77
+ segment_volume_wick: np.ndarray
78
+ facecolor_volume: np.ndarray
79
+ edgecolor_volume: np.ndarray
80
+
81
+ segment_candle: np.ndarray
82
+ segment_candle_wick: np.ndarray
83
+ segment_priceline: np.ndarray
84
+ facecolor_candle: np.ndarray
85
+ edgecolor_candle: np.ndarray
86
+
87
+ segment_ma: np.ndarray
88
+ edgecolor_ma: np.ndarray
89
+
90
+ price_ymin: int
91
+ price_ymax: int
92
+ volume_ymax: int
93
+
94
+ chart_price_ymax: float
95
+ chart_volume_ymax: float
96
+
97
+ vxmin: int
98
+ vxmax: int
99
+
100
+ v0: int
101
+ v1: int
102
+ vmiddle: int
103
+
104
+ min_height_box_candle: float
105
+ min_height_box_volume: float
106
+
107
+ ###
108
+
109
+ _visible_ma: set[int] = set()
110
+ _edgecolor_ma = []
111
+
112
+ _background = None
113
+ _background_background = None
114
+ _creating_background = False
115
+
116
+ _length_text: int
117
+
118
+ _in_mouse_move = False
119
+
120
+ def refresh(self):
121
+ super().refresh()
122
+
123
+ self._set_length_text()
124
+ return
125
+
@@ -0,0 +1,130 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+
5
+ from ..._config import ConfigData
6
+ from ..base.a_canvas import Figure
7
+
8
+
9
+ class Base:
10
+ CONFIG: ConfigData
11
+
12
+ figure: Figure
13
+ ax_price: Axes
14
+ ax_volume: Axes
15
+
16
+ add_artists: callable
17
+ set_artists: callable
18
+
19
+
20
+ class CrossLineMixin(Base):
21
+ def _add_crosslines(self):
22
+ kwargs = {'segments': [], 'animated': True}
23
+
24
+ self.collection_price_crossline = LineCollection(**kwargs)
25
+ self.ax_price.add_artist(self.collection_price_crossline)
26
+
27
+ self.collection_volume_crossline = LineCollection(**kwargs)
28
+ self.ax_volume.add_artist(self.collection_volume_crossline)
29
+ return
30
+
31
+ def _set_crosslines(self):
32
+ kwargs = self.CONFIG.CURSOR.CROSSLINE.__dict__
33
+ kwargs.update({'segments': [], 'animated': True})
34
+
35
+ self.collection_price_crossline.set(**kwargs)
36
+ self.collection_volume_crossline.set(**kwargs)
37
+ return
38
+
39
+
40
+ class LabelMixin(Base):
41
+ def _add_artist_labels(self):
42
+ kwargs = {'text': '', 'animated': True,}
43
+
44
+ self.artist_label_x = Text(**kwargs)
45
+ self.figure.add_artist(self.artist_label_x)
46
+
47
+ self.artist_label_y = Text(**kwargs)
48
+ self.figure.add_artist(self.artist_label_y)
49
+ return
50
+
51
+ def _set_artist_labels(self):
52
+ kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
53
+ kwargs.update({'text': ' ', 'animated': True, 'horizontalalignment': 'center', 'verticalalignment': 'center', 'clip_on':True})
54
+
55
+ self.artist_label_x.set(**kwargs)
56
+ self.artist_label_y.set(**kwargs)
57
+ return
58
+
59
+
60
+ class BoxMixin(Base):
61
+ def _add_box_collections(self):
62
+ kwargs = {'segments': [], 'animated': True,}
63
+
64
+ self.collection_box_price = LineCollection(**kwargs)
65
+ self.ax_price.add_artist(self.collection_box_price)
66
+ self.collection_box_volume = LineCollection(**kwargs)
67
+ self.ax_volume.add_artist(self.collection_box_volume)
68
+ return
69
+
70
+ def _set_box_collections(self):
71
+ kwargs = self.CONFIG.CURSOR.BOX.__dict__
72
+ kwargs.update({'segments': [], 'animated': True,})
73
+
74
+ self.collection_box_price.set(**kwargs)
75
+ self.collection_box_volume.set(**kwargs)
76
+ return
77
+
78
+
79
+ class InfoMixin(Base):
80
+ def _add_info_texts(self):
81
+ kwargs = {'text': '', 'animated': True, 'horizontalalignment': 'left', 'verticalalignment': 'top',}
82
+
83
+ self.artist_info_candle = Text(**kwargs)
84
+ self.ax_price.add_artist(self.artist_info_candle)
85
+ self.artist_info_volume = Text(**kwargs)
86
+ self.ax_volume.add_artist(self.artist_info_volume)
87
+ return
88
+
89
+ def _set_info_texts(self):
90
+ kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
91
+ kwargs.update({'text': '', 'animated': True, 'horizontalalignment': 'left', 'verticalalignment': 'top',})
92
+
93
+ self.artist_info_candle.set(**kwargs)
94
+ self.artist_info_volume.set(**kwargs)
95
+ return
96
+
97
+
98
+ class ArtistMixin(CrossLineMixin, LabelMixin, BoxMixin, InfoMixin):
99
+ collection_price_crossline: LineCollection
100
+ collection_volume_crossline: LineCollection
101
+
102
+ artist_label_x: Text = None
103
+ artist_label_y: Text = None
104
+
105
+ collection_box_price: LineCollection
106
+ collection_box_volume: LineCollection
107
+
108
+ artist_info_candle: Text
109
+ artist_info_volume: Text
110
+
111
+ def add_artists(self):
112
+ super().add_artists()
113
+
114
+ self._add_crosslines()
115
+ self._add_artist_labels()
116
+ self._add_box_collections()
117
+ self._add_info_texts()
118
+
119
+ self.set_artists()
120
+ return
121
+
122
+ def set_artists(self):
123
+ super().set_artists()
124
+
125
+ self._set_crosslines()
126
+ self._set_artist_labels()
127
+ self._set_box_collections()
128
+ self._set_info_texts()
129
+ return
130
+
@@ -0,0 +1,96 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+ import pandas as pd
5
+
6
+ from ..._config import ConfigData
7
+ from ..._chart.base.a_canvas import Figure
8
+
9
+
10
+ class Base:
11
+ CONFIG: ConfigData
12
+
13
+ df: pd.DataFrame
14
+
15
+ watermark: str
16
+
17
+ figure: Figure
18
+ ax_legend: Axes
19
+ ax_price: Axes
20
+ ax_volume: Axes
21
+ artist_watermark: Text
22
+ collection_candle: LineCollection
23
+ collection_volume: LineCollection
24
+ collection_ma: LineCollection
25
+
26
+ collection_price_crossline: LineCollection
27
+ collection_volume_crossline: LineCollection
28
+
29
+ artist_label_x: Text
30
+ artist_label_y: Text
31
+
32
+ collection_box_price: LineCollection
33
+ collection_box_volume: LineCollection
34
+
35
+ artist_info_candle: Text
36
+ artist_info_volume: Text
37
+
38
+ in_chart_price: bool
39
+ in_chart_volume: bool
40
+
41
+
42
+ class CrosslineMixin(Base):
43
+ def _draw_crossline(self):
44
+ renderer = self.figure.canvas.renderer
45
+ for artist in [
46
+ self.collection_price_crossline,
47
+ self.collection_volume_crossline,
48
+ ]:
49
+ artist.draw(renderer)
50
+ return
51
+
52
+
53
+ class LabelMixin(Base):
54
+ def _draw_label_x(self):
55
+ artist = self.artist_label_x
56
+ renderer = self.figure.canvas.renderer
57
+
58
+ artist.draw(renderer)
59
+ # print(f'{artist.get_position()=}')
60
+ return
61
+
62
+ def _draw_label_y(self):
63
+ artist = self.artist_label_y
64
+ renderer = self.figure.canvas.renderer
65
+
66
+ artist.draw(renderer)
67
+ return
68
+
69
+
70
+ class BoxMixin(Base):
71
+ def _draw_box_candle(self):
72
+ renderer = self.figure.canvas.renderer
73
+ self.collection_box_price.draw(renderer)
74
+ return
75
+
76
+ def _draw_box_volume(self):
77
+ renderer = self.figure.canvas.renderer
78
+ self.collection_box_volume.draw(renderer)
79
+ return
80
+
81
+
82
+ class InfoMixin(Base):
83
+ def _draw_info_candle(self):
84
+ renderer = self.figure.canvas.renderer
85
+ self.artist_info_candle.draw(renderer)
86
+ return
87
+
88
+ def _draw_info_volume(self):
89
+ renderer = self.figure.canvas.renderer
90
+ self.artist_info_volume.draw(renderer)
91
+ return
92
+
93
+
94
+ class DrawMixin(CrosslineMixin, LabelMixin, BoxMixin, InfoMixin):
95
+ pass
96
+
@@ -0,0 +1,359 @@
1
+ from fractions import Fraction
2
+
3
+ from matplotlib.axes import Axes
4
+ from matplotlib.collections import LineCollection
5
+ from matplotlib.text import Text
6
+ from matplotlib.backend_bases import MouseEvent
7
+ import pandas as pd
8
+
9
+ from ..._config import ConfigData
10
+ from ..._utils.nums import float_to_str
11
+ from ..base.a_canvas import Figure
12
+
13
+
14
+ class Base:
15
+ CONFIG: ConfigData
16
+
17
+ key_volume: str
18
+ _length_text: int
19
+ df: pd.DataFrame
20
+
21
+ watermark: str
22
+
23
+ v0: int
24
+ v1: int
25
+ vmiddle: int
26
+ vxmin: int
27
+ vxmax: int
28
+ price_ymin: int
29
+ price_ymax: int
30
+ volume_ymax: int
31
+
32
+ figure: Figure
33
+ ax_legend: Axes
34
+ ax_price: Axes
35
+ ax_volume: Axes
36
+ artist_watermark: Text
37
+ collection_candle: LineCollection
38
+ collection_volume: LineCollection
39
+ collection_ma: LineCollection
40
+
41
+ collection_price_crossline: LineCollection
42
+ collection_volume_crossline: LineCollection
43
+
44
+ artist_label_x: Text
45
+ artist_label_y: Text
46
+
47
+ collection_box_price: LineCollection
48
+ collection_box_volume: LineCollection
49
+
50
+ artist_info_candle: Text
51
+ artist_info_volume: Text
52
+
53
+ in_chart_price: bool
54
+ in_chart_volume: bool
55
+
56
+
57
+ class CrosslineMixin(Base):
58
+ def _set_crossline(self, e: MouseEvent,):
59
+ x, y = (e.xdata, e.ydata)
60
+
61
+ if self.in_chart_price:
62
+ seg = [
63
+ [
64
+ (x, self.price_ymin),
65
+ (x, self.price_ymax),
66
+ ],
67
+ [
68
+ (self.vxmin, y),
69
+ (self.vxmax, y),
70
+ ]
71
+ ]
72
+ self.collection_price_crossline.set_segments(seg)
73
+ seg = [
74
+ [
75
+ (x, 0),
76
+ (x, self.volume_ymax),
77
+ ]
78
+ ]
79
+ self.collection_volume_crossline.set_segments(seg)
80
+ elif self.in_chart_volume:
81
+ seg = [
82
+ [
83
+ (x, self.price_ymin),
84
+ (x, self.price_ymax),
85
+ ]
86
+ ]
87
+ self.collection_price_crossline.set_segments(seg)
88
+ seg = [
89
+ [
90
+ (x, 0),
91
+ (x, self.volume_ymax),
92
+ ],
93
+ [
94
+ (self.vxmin, y),
95
+ (self.vxmax, y)
96
+ ]
97
+ ]
98
+ self.collection_volume_crossline.set_segments(seg)
99
+
100
+ return
101
+
102
+
103
+ class LabelMixin(Base):
104
+ def _set_label_x(self, e: MouseEvent):
105
+ xdata, ydata = (e.xdata, e.xdata)
106
+ # print(f'{(x, xdata)=}')
107
+
108
+ if xdata < 0 or xdata is None:
109
+ return
110
+
111
+ try:
112
+ text = self.df.iloc[int(xdata)]['date']
113
+ except:
114
+ return
115
+ # print(f'{text=}')
116
+
117
+ display_coords = e.inaxes.transData.transform((xdata, ydata))
118
+ figure_coords = self.figure.transFigure.inverted()\
119
+ .transform(display_coords)
120
+ # print(f'{figure_coords=}')
121
+
122
+ artist = self.artist_label_x
123
+
124
+ artist.set_text(text)
125
+ artist.set_x(figure_coords[0])
126
+ self._set_label_x_position()
127
+ return 1
128
+
129
+ def _set_label_x_position(self):
130
+ artist = self.artist_label_x
131
+ renderer = self.figure.canvas.renderer
132
+
133
+ # Axes 하단 경계 좌표
134
+ boundary = self.ax_volume.get_position()\
135
+ .y0
136
+ # print(f'{y0=}')
137
+
138
+ if not artist.get_text():
139
+ artist.set_text(' ')
140
+
141
+ # Text bbox 너비
142
+ bbox = artist.get_bbox_patch()\
143
+ .get_window_extent(renderer)
144
+ bbox_size = bbox.height
145
+ # 밀어야 하는 값
146
+ fig_size = self.figure.bbox.height
147
+ offset = (bbox_size + 10) / fig_size
148
+ # print(f'{box_width_fig=}')
149
+
150
+ # x축 값(가격 또는 거래량)
151
+ # self.artist_label_y.set_x(x1)
152
+ y = boundary - (offset / 2)
153
+ # print(f'{(x1, x)=}')
154
+ artist.set_y(y)
155
+ return
156
+
157
+ def _set_label_y(self, e: MouseEvent, *, is_price_chart):
158
+ xdata, ydata = (e.xdata, e.ydata)
159
+ artist = self.artist_label_y
160
+
161
+ if is_price_chart:
162
+ text = float_to_str(ydata, digit=self.CONFIG.UNIT.digit) + self.CONFIG.UNIT.price
163
+ else:
164
+ text = float_to_str(ydata, digit=self.CONFIG.UNIT.digit_volume) + self.CONFIG.UNIT.volume
165
+
166
+ display_coords = e.inaxes.transData.transform((xdata, ydata))
167
+ figure_coords = self.figure.transFigure.inverted().transform(display_coords)
168
+
169
+ artist.set_text(text)
170
+ artist.set_y(figure_coords[1])
171
+ self._set_label_y_position()
172
+ return
173
+
174
+ def _set_label_y_position(self):
175
+ artist = self.artist_label_y
176
+ renderer = self.figure.canvas.renderer
177
+
178
+ # Axes 우측 경계 좌표
179
+ boundary = self.ax_volume.get_position()\
180
+ .x1
181
+ # print(f'{boundary=}')
182
+
183
+ if not artist.get_text():
184
+ artist.set_text(' ')
185
+
186
+ # Text bbox 너비
187
+ bbox = artist.get_bbox_patch()\
188
+ .get_window_extent(renderer)
189
+ bbox_size = bbox.width
190
+ # 밀어야 하는 값
191
+ fig_size = self.figure.bbox.width
192
+ offset = (bbox_size + 8) / fig_size
193
+ # print(f'{box_width_fig=}')
194
+
195
+ # x축 값(가격 또는 거래량)
196
+ # self.artist_label_y.set_x(x1)
197
+ x = boundary + (offset / 2)
198
+ # print(f'{(x1, x)=}')
199
+ artist.set_x(x)
200
+ return
201
+
202
+
203
+ class BoxMixin(Base):
204
+ def _set_box_candle(self, segment):
205
+ self.collection_box_price.set_segments(segment)
206
+ return
207
+
208
+ def _set_box_volume(self, segment):
209
+ self.collection_box_volume.set_segments(segment)
210
+ return
211
+
212
+
213
+ class InfoMixin(Base):
214
+ fraction = False
215
+
216
+ def _set_info_candle(self, ind):
217
+ text = self._get_info(ind, is_price=True)
218
+ self.artist_info_candle.set_text(text)
219
+
220
+ # 정보 텍스트를 중앙에 몰리게 설정할 수도 있지만,
221
+ # 그런 경우 차트를 가리므로 좌우 끝단에 위치하도록 설정
222
+ if self.vmiddle < ind:
223
+ self.artist_info_candle.set_x(self.v0)
224
+ else:
225
+ # self.artist_info_candle.set_x(self.vmax - self.x_distance)
226
+ # self.artist_info_candle.set_horizontalalignment('right')
227
+ # 텍스트박스 크기 가져오기
228
+ bbox = self.artist_info_candle.get_window_extent()\
229
+ .transformed(self.ax_price.transData.inverted())
230
+ width = bbox.x1 - bbox.x0
231
+ self.artist_info_candle.set_x(self.v1 - width)
232
+
233
+ self.artist_info_candle.draw(self.figure.canvas.renderer)
234
+ return
235
+
236
+ def _set_info_volume(self, ind):
237
+ text = self._get_info(ind, is_price=False)
238
+ self.artist_info_volume.set_text(text)
239
+
240
+ if self.vmiddle < ind:
241
+ self.artist_info_volume.set_x(self.v0)
242
+ else:
243
+ # self.artist_info_volume.set_x(self.vmax - self.x_distance)
244
+ # self.artist_info_volume.set_horizontalalignment('right')
245
+ # 텍스트박스 크기 가져오기
246
+ bbox = self.artist_info_volume.get_window_extent()\
247
+ .transformed(self.ax_price.transData.inverted())
248
+ width = bbox.x1 - bbox.x0
249
+ self.artist_info_volume.set_x(self.v1 - width)
250
+
251
+ self.artist_info_volume.draw(self.figure.canvas.renderer)
252
+ return
253
+
254
+ def get_info_kwargs(self, is_price: bool, **kwargs)-> dict:
255
+ """
256
+ get text info kwargs
257
+
258
+ Args:
259
+ is_price (bool): is price chart info or not
260
+
261
+ Returns:
262
+ dict[str, any]: text info kwargs
263
+ """
264
+ return kwargs
265
+
266
+ def _get_info(self, ind: int, is_price=True):
267
+ # print(f'{self._length_text=}')
268
+ series = self.df.iloc[ind]
269
+
270
+ dt = series['date']
271
+ if not self.key_volume:
272
+ v, vr = ('-', '-')
273
+ else:
274
+ v, vr = series.loc[['volume', 'rate_volume']]
275
+ # print(f'{self.CONFIG.UNIT.digit_volume=}')
276
+ v = float_to_str(v, digit=self.CONFIG.UNIT.digit_volume)
277
+ # if not v % 1:
278
+ # v = int(v)
279
+ vr = f'{vr:+06,.2f}'
280
+
281
+ if is_price:
282
+ o, h, l, c = (series['open'], series['high'], series['low'], series['close'])
283
+ rate, compare = (series['rate'], series['compare'])
284
+ r = f'{rate:+06,.2f}'
285
+ Or, hr, lr = (series['rate_open'], series['rate_high'], series['rate_low'])
286
+
287
+ if self.fraction:
288
+ data = {}
289
+ c = round(c, self.CONFIG.UNIT.digit)
290
+ for value, key in [
291
+ [c, 'close'],
292
+ [compare, 'compare'],
293
+ [o, 'open'],
294
+ [h, 'high'],
295
+ [l, 'low'],
296
+ ]:
297
+ div = divmod(value, 1)
298
+ if div[1]:
299
+ if div[0]:
300
+ data[key] = f'{float_to_str(div[0])} {Fraction((div[1]))}'
301
+ else:
302
+ data[key] = f'  {Fraction((div[1]))}'
303
+ else:
304
+ data[key] = float_to_str(div[0])
305
+ # print(f'{data=}')
306
+
307
+ kwargs = self.get_info_kwargs(
308
+ is_price=is_price,
309
+ dt=dt,
310
+ close=f'{data["close"]:>{self._length_text}}{self.CONFIG.UNIT.price}',
311
+ rate=f'{r:>{self._length_text}}%',
312
+ compare=f'{data["compare"]:>{self._length_text}}{self.CONFIG.UNIT.price}',
313
+ open=f'{data["open"]:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_open=f'{Or:+06,.2f}%',
314
+ high=f'{data["high"]:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_high=f'{hr:+06,.2f}%',
315
+ low=f'{data["low"]:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_low=f'{lr:+06,.2f}%',
316
+ volume=f'{v:>{self._length_text}}{self.CONFIG.UNIT.volume}', rate_volume=f'{vr}%',
317
+ )
318
+ text = self.CONFIG.FORMAT.candle.format(**kwargs)
319
+ else:
320
+ o, h, l, c = (
321
+ float_to_str(o, digit=self.CONFIG.UNIT.digit),
322
+ float_to_str(h, digit=self.CONFIG.UNIT.digit),
323
+ float_to_str(l, digit=self.CONFIG.UNIT.digit),
324
+ float_to_str(c, digit=self.CONFIG.UNIT.digit),
325
+ )
326
+ com = float_to_str(compare, digit=self.CONFIG.UNIT.digit, plus=True)
327
+
328
+ kwargs = self.get_info_kwargs(
329
+ is_price=is_price,
330
+ dt=dt,
331
+ close=f'{c:>{self._length_text}}{self.CONFIG.UNIT.price}',
332
+ rate=f'{r:>{self._length_text}}%',
333
+ compare=f'{com:>{self._length_text}}{self.CONFIG.UNIT.price}',
334
+ open=f'{o:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_open=f'{Or:+06,.2f}%',
335
+ high=f'{h:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_high=f'{hr:+06,.2f}%',
336
+ low=f'{l:>{self._length_text}}{self.CONFIG.UNIT.price}', rate_low=f'{lr:+06,.2f}%',
337
+ volume=f'{v:>{self._length_text}}{self.CONFIG.UNIT.volume}', rate_volume=f'{vr}%',
338
+ )
339
+ text = self.CONFIG.FORMAT.candle.format(**kwargs)
340
+ elif self.key_volume:
341
+ compare = self.df.loc[ind, 'compare_volume']
342
+ com = float_to_str(compare, digit=self.CONFIG.UNIT.digit_volume, plus=True)
343
+ kwargs = self.get_info_kwargs(
344
+ is_price=is_price,
345
+ dt=dt,
346
+ volume=f'{v:>{self._length_text}}{self.CONFIG.UNIT.volume}',
347
+ rate_volume=f'{vr:>{self._length_text}}%',
348
+ compare=f'{com:>{self._length_text}}{self.CONFIG.UNIT.volume}',
349
+ )
350
+ text = self.CONFIG.FORMAT.volume.format(**kwargs)
351
+ else:
352
+ text = ''
353
+
354
+ return text
355
+
356
+
357
+ class SegmentMixin(CrosslineMixin, LabelMixin, BoxMixin, InfoMixin):
358
+ fraction = False
359
+