seolpyo-mplchart 0.1.3.4__py3-none-any.whl → 1.0.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.

Potentially problematic release.


This version of seolpyo-mplchart might be problematic. Click here for more details.

@@ -1,342 +1,335 @@
1
1
  from fractions import Fraction
2
2
 
3
+ import matplotlib.pyplot as plt
3
4
  from matplotlib.backend_bases import MouseEvent
4
5
  from matplotlib.collections import LineCollection
5
6
  from matplotlib.text import Text
6
7
  import pandas as pd
7
8
 
8
-
9
- from .draw import DrawMixin, Chart as CM
9
+ from .draw import BaseMixin as BM, Mixin as M
10
10
  from .utils import float_to_str
11
11
 
12
12
 
13
- class Mixin:
14
- def on_draw(self, e):
15
- "This function works if draw event active."
16
- return
13
+ class Mixin(M):
17
14
  def on_move(self, e):
18
- "This function works if mouse move event active."
15
+ "If mouse move event active, This method work."
19
16
  return
20
17
 
21
18
 
22
- class CollectionMixin(DrawMixin):
23
- lineKwargs = dict(edgecolor='k', linewidth=1, linestyle='-')
24
- textboxKwargs = dict(boxstyle='round', facecolor='w')
19
+ class CollectionMixin(BM):
20
+ lineKwargs = {}
21
+ textboxKwargs = {}
22
+ textKwargs = {}
23
+ color_box = 'k'
25
24
 
26
25
  def _add_collection(self):
27
26
  super()._add_collection()
28
- self.sliderline = LineCollection([], animated=True, **self.lineKwargs)
29
- self.ax_slider.add_artist(self.sliderline)
30
- self.slider_text = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='center')
31
- self.ax_slider.add_artist(self.slider_text)
32
-
33
- self.price_vline = LineCollection([], animated=True, **self.lineKwargs)
34
- self.ax_price.add_artist(self.price_vline)
35
- self.text_date_price = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='bottom', horizontalalignment='center')
27
+
28
+ lineKwargs = {'edgecolor': 'k', 'linewidth': 1, 'linestyle': '-'}
29
+ lineKwargs.update(self.lineKwargs)
30
+ lineKwargs.update({'segments': [], 'animated': True})
31
+ textboxKwargs = {'boxstyle': 'round', 'facecolor': 'w'}
32
+ textboxKwargs.update(self.textboxKwargs)
33
+ textKwargs = self.textKwargs
34
+ textKwargs.update({'animated': True, 'bbox': textboxKwargs, 'horizontalalignment': '', 'verticalalignment': ''})
35
+ (textKwargs.pop('horizontalalignment'), textKwargs.pop('verticalalignment'))
36
+
37
+ self.price_crossline = LineCollection(**lineKwargs)
38
+ self.ax_price.add_artist(self.price_crossline)
39
+ self.text_date_price = Text(**textKwargs, horizontalalignment='center', verticalalignment='bottom')
36
40
  self.ax_price.add_artist(self.text_date_price)
37
- self.text_price = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='center', horizontalalignment='left')
41
+ self.text_price = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
38
42
  self.ax_price.add_artist(self.text_price)
39
43
 
40
- self.volumeh_vline = LineCollection([], animated=True, **self.lineKwargs)
41
- self.ax_volume.add_artist(self.volumeh_vline)
42
- self.text_date_volume = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='center')
44
+ self.volume_crossline = LineCollection(**lineKwargs)
45
+ self.ax_volume.add_artist(self.volume_crossline)
46
+ self.text_date_volume = Text(**textKwargs, horizontalalignment='center', verticalalignment='top')
43
47
  self.ax_volume.add_artist(self.text_date_volume)
44
- self.text_volume = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='center', horizontalalignment='left')
48
+ self.text_volume = Text(**textKwargs, horizontalalignment='left', verticalalignment='center')
45
49
  self.ax_volume.add_artist(self.text_volume)
46
50
 
47
- self.price_hline = LineCollection([], animated=True, **self.lineKwargs)
48
- self.ax_price.add_artist(self.price_hline)
49
- self.price_box = LineCollection([], animated=True, linewidth=1.1, edgecolor='k')
51
+ self.price_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
50
52
  self.ax_price.add_artist(self.price_box)
51
- self.text_price_info = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='left')
53
+ self.text_price_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
52
54
  self.ax_price.add_artist(self.text_price_info)
53
55
 
54
- self.volume_hline = LineCollection([], animated=True, **self.lineKwargs)
55
- self.ax_volume.add_artist(self.volume_hline)
56
- self.volume_box = LineCollection([], animated=True, linewidth=1.1, edgecolor='k')
56
+ self.volume_box = LineCollection([], animated=True, linewidth=1.2, edgecolor=self.color_box)
57
57
  self.ax_volume.add_artist(self.volume_box)
58
- self.text_volume_info = Text(animated=True, bbox=self.textboxKwargs, verticalalignment='top', horizontalalignment='left')
58
+ self.text_volume_info = Text(**textKwargs, horizontalalignment='left', verticalalignment='top')
59
59
  self.ax_volume.add_artist(self.text_volume_info)
60
-
61
60
  return
62
61
 
63
62
 
64
- _set_key = {'rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume',}
63
+ _set_key = {'rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume', '_boxheight', '_boxmin', '_boxmax', '_volumeboxmax',}
65
64
 
66
65
  class DataMixin(CollectionMixin):
67
- def _generate_data(self, df, sort_df=True, calc_ma=True, calc_info=True):
68
- for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
66
+ def _validate_column_key(self):
67
+ super()._validate_column_key()
68
+ for i in ['date', 'Open', 'high', 'low', 'close', 'volume']:
69
69
  v = getattr(self, i)
70
- if v in _set_key:
71
- raise Exception(f'you can not set "self.{i}" value in {_set_key}.\nself.{i}={v!r}')
70
+ if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
71
+ return
72
72
 
73
- super()._generate_data(df, sort_df, calc_ma)
74
- df = self.df
73
+ def _generate_data(self, df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *_, **__):
74
+ super()._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__)
75
75
 
76
76
  if not calc_info:
77
77
  keys = set(df.keys())
78
- for i in ('rate', 'compare', 'rate_open', 'rate_high', 'rate_low', 'rate_volume'):
78
+ list_key = ['rate', 'compare', 'rate_open', 'rate_high', 'rate_low',]
79
+ if self.volume: list_key.append('rate_volume')
80
+ for i in list_key:
79
81
  if i not in keys:
80
82
  raise Exception(f'"{i}" column not in DataFrame.\nadd column or set calc_info=True.')
81
83
  else:
82
- df['rate'] = ((df[self.close] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
83
- df['compare'] = (df[self.close] - df[self.close].shift(1)).fillna(0)
84
- df['rate_open'] = ((df[self.Open] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
85
- df['rate_high'] = ((df[self.high] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
86
- df['rate_low'] = ((df[self.low] - df[self.close].shift(1)) / df[self.close] * 100).__round__(2).fillna(0)
87
- df['rate_volume'] = ((df[self.volume] - df[self.volume].shift(1)) / df[self.volume].shift(1) * 100).__round__(2).fillna(0)
88
-
89
- self.df = df
84
+ self.df['compare'] = (self.df[self.close] - self.df['_pre']).fillna(0)
85
+ self.df['rate'] = (self.df['compare'] / self.df[self.close] * 100).__round__(2).fillna(0)
86
+ self.df['rate_open'] = ((self.df[self.Open] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
87
+ self.df['rate_high'] = ((self.df[self.high] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
88
+ self.df['rate_low'] = ((self.df[self.low] - self.df['_pre']) / self.df[self.close] * 100).__round__(2).fillna(0)
89
+ if self.volume: self.df['rate_volume'] = ((self.df[self.volume] - self.df[self.volume].shift(1)) / self.df[self.volume].shift(1) * 100).__round__(2).fillna(0)
90
+
91
+ self.df['_boxheight'] = (self.df[self.high] - self.df[self.low]) / 5
92
+ self.df['_boxmin'] = self.df[self.low] - self.df['_boxheight']
93
+ self.df['_boxmax'] = self.df[self.high] + self.df['_boxheight']
94
+ if self.volume: self.df['_volumeboxmax'] = self.df[self.volume] * 1.13
90
95
  return
91
96
 
92
- def set_text_coordante(self, vmin, vmax, pmin, pmax, volmax):
93
- # 주가, 거래량 텍스트 x 위치
94
- x_distance = (vmax - vmin) / 30
95
- self.v0, self.v1 = (vmin + x_distance, vmax - x_distance)
96
- self.text_price.set_x(self.v0)
97
- self.text_volume.set_x(self.v0)
97
+ def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
98
+ super()._set_lim(xmin, xmax, simpler, set_ma)
98
99
 
99
- self.vmin, self.vmax = (vmin, vmax)
100
- self.vmiddle = vmax - int((vmax - vmin) / 2)
100
+ psub = (self.price_ymax - self.price_ymin)
101
+ self.min_candleboxheight = psub / 8
101
102
 
102
- psub = pmax - pmin
103
- self.min_psub = psub / 12
103
+ pydistance = psub / 20
104
+ self.text_date_price.set_y(self.price_ymin + pydistance)
104
105
 
105
- # 주가 날짜 텍스트 y 위치
106
- y = (psub) / 20 + pmin
107
- self.text_date_price.set_y(y)
108
- # 주가 정보 y 위치
109
- y = pmax - (psub) / 20
110
- self.text_price_info.set_y(y)
106
+ self.min_volumeboxheight = self.volume_ymax / 4
111
107
 
112
- # 거래량 날짜 텍스트 y 위치
113
- y = volmax * 0.85
114
- self.text_date_volume.set_y(y)
115
- # 거래량 정보 y 위치
116
- self.text_volume_info.set_y(y)
108
+ vxsub = self.vxmax - self.vxmin
109
+ self.vmiddle = self.vxmax - int((vxsub) / 2)
117
110
 
118
- return
111
+ vxdistance = vxsub / 50
112
+ self.v0, self.v1 = (self.vxmin + vxdistance, self.vxmax - vxdistance)
113
+ self.vsixth = self.vxmin + int((vxsub) / 6)
114
+ self.veighth = self.vxmin + int((vxsub) / 8)
115
+
116
+ yvolume = self.volume_ymax * 0.85
117
+ self.text_date_volume.set_y(yvolume)
119
118
 
119
+ # 정보 텍스트박스
120
+ self.text_price_info.set_y(self.price_ymax - pydistance)
121
+ self.text_volume_info.set_y(yvolume)
122
+ return
120
123
 
121
- class LineMixin(DataMixin):
122
- in_slider, in_price, in_volume = (False, False, False)
123
124
 
124
- intx, in_index = (None, False)
125
- _in_candle, _in_volumebar = (False, False)
125
+ class EventMixin(DataMixin):
126
+ in_price_chart, in_volume_chart = (False, False)
127
+ intx = None
126
128
 
127
129
  def _connect_event(self):
128
130
  super()._connect_event()
129
- self.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
131
+ self.figure.canvas.mpl_connect('motion_notify_event', lambda x: self._on_move(x))
130
132
  return
131
133
 
132
- def _blit(self):
133
- self.canvas.blit()
134
+ def _on_move(self, e):
135
+ self._on_move_action(e)
134
136
  return
135
137
 
136
- def set_data(self, df, sort_df=True, calc_ma=True, change_lim=True, calc_info=True, *args, **kwargs):
137
- return super().set_data(df, sort_df, calc_ma, change_lim, calc_info=calc_info, *args, **kwargs)
138
- def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True, calc_info=True, *args, **kwargs):
139
- super()._set_data(df, sort_df, calc_ma, change_lim, calc_info=calc_info, *args, **kwargs)
138
+ def _on_move_action(self, e: MouseEvent):
139
+ self._check_ax(e)
140
140
 
141
- self.vmin, self.vmax = (self.xmin, self.xmax)
141
+ self.intx = None
142
+ if self.in_price_chart or self.in_volume_chart: self._get_x(e)
142
143
  return
143
144
 
144
- def _on_move(self, e):
145
- self._restore_region()
146
-
147
- self._on_move_action(e)
148
-
149
- if self.in_slider or self.in_price or self.in_volume:
150
- self._slider_move_action(e)
151
- if self.in_price or self.in_volume:
152
- self._chart_move_action(e)
145
+ def _check_ax(self, e: MouseEvent):
146
+ ax = e.inaxes
147
+ if not ax or e.xdata is None or e.ydata is None:
148
+ self.in_price_chart, self.in_volume_chart = (False, False)
149
+ else:
150
+ self.in_price_chart = ax is self.ax_price
151
+ self.in_volume_chart = False if self.in_price_chart else ax is self.ax_volume
153
152
 
154
- self._blit()
155
153
  return
156
154
 
157
- def _on_move_action(self, e: MouseEvent):
158
- if not e.inaxes:
159
- self.intx, self.in_index = (None, False)
155
+ def _get_x(self, e: MouseEvent):
156
+ self.intx = e.xdata.__int__()
157
+ if self.intx < 0: self.intx = None
160
158
  else:
161
- self._check_ax(e)
162
- x, y = (e.xdata, e.ydata)
163
- self.intx = x.__int__()
164
- if self.intx < 0: self.in_index = False
165
- else:
166
- try: self.df['x'][self.intx]
167
- except: self.in_index = False
168
- else: self.in_index = True
159
+ try: self.list_index[self.intx]
160
+ except: self.intx = None
169
161
  return
170
162
 
171
- def _check_ax(self, e: MouseEvent):
172
- ax = e.inaxes
173
163
 
174
- self.in_slider = ax is self.ax_slider
175
- self.in_price = False if self.in_slider else ax is self.ax_price
176
- self.in_volume = False if (self.in_slider or self.in_price) else ax is self.ax_volume
177
- return
164
+ class LineMixin(EventMixin):
165
+ digit_price, digit_volume = (0, 0)
166
+ in_candle, in_volumebar = (False, False)
167
+
168
+ def _on_move(self, e):
169
+ super()._on_move(e)
170
+
171
+ self._restore_region()
178
172
 
179
- def _slider_move_action(self, e: MouseEvent):
180
- x = e.xdata
173
+ if self.in_price_chart: self._on_move_price_chart(e)
174
+ elif self.in_volume_chart: self._on_move_volume_chart(e)
181
175
 
182
- # 수직선
183
- self.sliderline.set_segments([((x, self._slider_ymin), (x, self._slider_ymax))])
184
- self.ax_slider.draw_artist(self.sliderline)
176
+ self._blit()
185
177
  return
186
178
 
187
- def _chart_move_action(self, e: MouseEvent):
179
+ def _on_move_price_chart(self, e: MouseEvent):
188
180
  x, y = (e.xdata, e.ydata)
189
- if not y: return
190
- roundy = y.__round__()
191
181
 
192
- self.price_vline.set_segments([((x, self._price_ymin), (x, self._price_ymax))])
193
- self.volumeh_vline.set_segments([((x, 0), (x, self._vol_ymax))])
194
- self.ax_price.draw_artist(self.price_vline)
195
- self.ax_volume.draw_artist(self.volumeh_vline)
182
+ self.price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax)), ((self.vxmin, y), (self.vxmax, y))])
183
+ self.volume_crossline.set_segments([((x, 0), (x, self.volume_ymax))])
184
+ self._draw_crossline()
196
185
 
197
- if self.in_price: self._price_move_action(x, y, roundy)
198
- else: self._volume_move_action(x, y, roundy)
199
- return
200
-
201
- def _price_move_action(self, _, y, roundy):
202
- # 수평선
203
- self.price_hline.set_segments([((self.vmin, y), (self.vmax, y))])
204
- self.ax_price.draw_artist(self.price_hline)
186
+ renderer = self.figure.canvas.renderer
205
187
 
206
188
  # 가격
207
- self.text_price.set_text(f'{roundy:,}{self.unit_price}')
189
+ self.text_price.set_text(f'{float_to_str(y, self.digit_price)}{self.unit_price}')
190
+ self.text_price.set_x(self.v0 if self.veighth < x else self.vsixth)
208
191
  self.text_price.set_y(y)
209
- self.ax_price.draw_artist(self.text_price)
210
-
211
- # 캔들 강조
212
- if self.in_index:
213
- intx = self.intx
214
-
215
- h = self.df[self.high][intx]
216
- l = self.df[self.low][intx]
217
- sub = (h - l) / 2
218
- if sub < self.min_psub: sub = self.min_psub
219
- high = h + sub
220
- low = l - sub
221
- if high < y or y < low: self._in_candle = False
192
+ self.text_price.draw(renderer)
193
+
194
+ index = self.intx
195
+ if index is None: self.in_candle = False
196
+ else:
197
+ # 기준시간 표시
198
+ self.text_date_volume.set_text(f'{self.df[self.date][index]}')
199
+ self.text_date_volume.set_x(x)
200
+ self.text_date_volume.draw(renderer)
201
+
202
+ # 캔들 강조
203
+ low = self.df['_boxmin'][index]
204
+ high = self.df['_boxmax'][index]
205
+ sub = high - low
206
+ if sub < self.min_candleboxheight:
207
+ sub = (self.min_candleboxheight - sub) / 2
208
+ low -= sub
209
+ high += sub
210
+
211
+ if high < y or y < low: self.in_candle = False
222
212
  else:
223
- self._in_candle = True
224
- x1, x2 = (intx-0.3, intx+1.4)
213
+ self.in_candle = True
214
+ x1, x2 = (index-0.3, index+1.4)
225
215
  self.price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
226
- self.ax_price.draw_artist(self.price_box)
216
+ self.price_box.draw(renderer)
217
+ return
218
+
219
+ def _draw_crossline(self):
220
+ renderer = self.figure.canvas.renderer
221
+ self.price_crossline.draw(renderer)
222
+ self.volume_crossline.draw(renderer)
227
223
  return
228
224
 
229
- def _volume_move_action(self, _, y, roundy):
230
- # 수평선
231
- self.volume_hline.set_segments([((self.vmin, y), (self.vmax, y))])
232
- self.ax_volume.draw_artist(self.volume_hline)
225
+ def _on_move_volume_chart(self, e: MouseEvent):
226
+ x, y = (e.xdata, e.ydata)
227
+
228
+ self.price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax))])
229
+ self.volume_crossline.set_segments([((x, 0), (x, self.volume_ymax)), ((self.vxmin, y), (self.vxmax, y))])
230
+ self._draw_crossline()
231
+
232
+ if not self.volume: return
233
+
234
+ renderer = self.figure.canvas.renderer
233
235
 
234
236
  # 거래량
235
- self.text_volume.set_text(f'{roundy:,}{self.unit_volume}')
237
+ self.text_volume.set_text(f'{float_to_str(y, self.digit_volume)}{self.unit_volume}')
238
+ self.text_volume.set_x(self.v0 if self.veighth < x else self.vsixth)
236
239
  self.text_volume.set_y(y)
237
- self.ax_volume.draw_artist(self.text_volume)
240
+ self.text_volume.draw(renderer)
238
241
 
239
- # 거래량 강조
240
- if self.in_index:
241
- intx = self.intx
242
+ index = self.intx
243
+ if index is None: self.in_volumebar = False
244
+ else:
245
+ # 기준시간 표시
246
+ self.text_date_price.set_text(f'{self.df[self.date][index]}')
247
+ self.text_date_price.set_x(x)
248
+ self.text_date_price.draw(renderer)
242
249
 
243
- high = self.df[self.volume][intx] * 1.1
250
+ # 거래량 강조
251
+ high = self.df[self.volume][index] * 1.15
244
252
  low = 0
245
- self._volumerange = (0, high)
246
- if high < y or y < low: self._in_volumebar: False
253
+ if high < self.min_volumeboxheight: high = self.min_volumeboxheight
254
+
255
+ if high < y or y < low: self.in_volumebar = False
247
256
  else:
248
- self._in_volumebar = True
249
- x1, x2 = (intx-0.3, intx+1.4)
257
+ self.in_volumebar = True
258
+ x1, x2 = (index-0.3, index+1.4)
250
259
  self.volume_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
251
- self.ax_volume.draw_artist(self.volume_box)
260
+ self.volume_box.draw(renderer)
252
261
  return
253
262
 
254
263
 
264
+ format_candleinfo_ko = '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
265
+ format_volumeinfo_ko = '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
266
+ format_candleinfo_en = '{dt}\n\nclose: {close}\nrate: {rate}\ncompare: {compare}\nopen: {open}({rate_open})\nhigh: {high}({rate_high})\nlow: {low}({rate_low})\nvolume: {volume}({rate_volume})'
267
+ format_volumeinfo_en = '{dt}\n\nvolume: {volume}\nvolume rate: {rate_volume}'
268
+
255
269
  class InfoMixin(LineMixin):
256
270
  fraction = False
257
- candleformat = '{dt}\n\n종가:  {close}\n등락률: {rate}\n대비:  {compare}\n시가:  {open}({rate_open})\n고가:  {high}({rate_high})\n저가:  {low}({rate_low})\n거래량: {volume}({rate_volume})'
258
- volumeformat = '{dt}\n\n거래량   : {volume}\n거래량증가율: {rate_volume}'
259
- digit_price, digit_volume = (0, 0)
260
-
261
- def _set_data(self, df: pd.DataFrame, sort_df=True, calc_ma=True, change_lim=True, calc_info=True, *args, **kwargs):
262
- super()._set_data(df, sort_df, calc_ma, change_lim, calc_info, *args, **kwargs)
271
+ format_candleinfo = format_candleinfo_ko
272
+ format_volumeinfo = format_volumeinfo_ko
263
273
 
264
- # 슬라이더 날짜 텍스트 y 위치
265
- y = self._slider_ymax - (self._slider_ymax - self._slider_ymin) / 6
266
- self.slider_text.set_y(y)
267
-
268
- v = self.df[self.volume].max()
269
- self._length_text = len(f'{v:,}')
270
- self.set_text_coordante(self.xmin, self.xmax, self._price_ymin, self._price_ymax, self._vol_ymax)
274
+ def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, calc_info=True, *args, **kwargs):
275
+ super().set_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, calc_info, *args, **kwargs)
271
276
 
277
+ self._length_text = self.df[(self.volume if self.volume else self.high)].apply(lambda x: len(f'{x:,}')).max()
272
278
  return
273
279
 
274
- def _slider_move_action(self, e):
275
- super()._slider_move_action(e)
280
+ def _on_move_price_chart(self, e):
281
+ super()._on_move_price_chart(e)
276
282
 
277
- intx = self.intx
283
+ # 캔들 강조 확인
284
+ if not self.in_candle: return
278
285
 
279
- if self.in_slider and self.in_index:
280
- self.slider_text.set_text(f'{self.df[self.date][intx]}')
281
- self.slider_text.set_x(e.xdata)
282
- self.ax_slider.draw_artist(self.slider_text)
283
- return
286
+ # 캔들 정보
287
+ self.text_price_info.set_text(self._get_info(self.intx))
284
288
 
285
- def _price_move_action(self, x, y, roundy):
286
- super()._price_move_action(x, y, roundy)
287
- if not self.in_index: return
288
- intx = self.intx
289
-
290
- # 텍스트
291
- text = f'{self.df[self.date][intx]}'
292
- self.text_date_volume.set_text(text)
293
- self.text_date_volume.set_x(x)
294
- self.ax_volume.draw_artist(self.text_date_volume)
295
-
296
- # 캔들 강조
297
- if self.in_price and self._in_candle:
298
- # 캔들 정보
299
- self.text_price_info.set_text(self._get_info(intx))
300
- if x < self.vmiddle:
301
- # 텍스트박스 크기 가져오기
302
- bbox = self.text_price_info.get_window_extent().transformed(self.ax_price.transData.inverted())
303
- width = bbox.x1 - bbox.x0
304
- self.text_price_info.set_x(self.v1 - width)
305
- else:
306
- self.text_price_info.set_x(self.v0)
307
- self.text_price_info.set_horizontalalignment('left')
308
- self.ax_price.draw_artist(self.text_price_info)
289
+ if self.vmiddle < e.xdata: self.text_price_info.set_x(self.v0)
290
+ else:
291
+ # self.text_price_info.set_x(self.vmax - self.x_distance)
292
+ # self.text_price_info.set_horizontalalignment('right')
293
+ # 텍스트박스 크기 가져오기
294
+ bbox = self.text_price_info.get_window_extent().transformed(self.ax_price.transData.inverted())
295
+ width = bbox.x1 - bbox.x0
296
+ self.text_price_info.set_x(self.v1 - width)
297
+
298
+ self.text_price_info.draw(self.figure.canvas.renderer)
309
299
  return
310
300
 
311
- def _volume_move_action(self, x, y, roundy):
312
- super()._volume_move_action(x, y, roundy)
313
- if not self.in_index: return
314
- intx = self.intx
315
-
316
- text = f'{self.df[self.date][intx]}'
317
- self.text_date_price.set_text(text)
318
- self.text_date_price.set_x(x)
319
- self.ax_price.draw_artist(self.text_date_price)
320
-
321
- # 거래량 강조
322
- if self.in_volume and self._in_volumebar:
323
- # 거래량 정보
324
- if x < self.vmiddle:
325
- bbox = self.text_volume_info.get_window_extent().transformed(self.ax_price.transData.inverted())
326
- width = bbox.x1 - bbox.x0
327
- self.text_volume_info.set_x(self.v1 - width)
328
- else:
329
- self.text_volume_info.set_x(self.v0)
330
- self.text_volume_info.set_horizontalalignment('left')
331
- self.text_volume_info.set_text(self._get_info(intx, False))
332
- self.ax_volume.draw_artist(self.text_volume_info)
301
+ def _on_move_volume_chart(self, e):
302
+ super()._on_move_volume_chart(e)
303
+
304
+ # 거래량 강조 확인
305
+ if not self.in_volumebar: return
306
+
307
+ # 거래량 정보
308
+ self.text_volume_info.set_text(self._get_info(self.intx, is_price=False))
309
+
310
+ if self.vmiddle < e.xdata: self.text_volume_info.set_x(self.v0)
311
+ else:
312
+ # self.text_volume_info.set_x(self.vmax - self.x_distance)
313
+ # self.text_volume_info.set_horizontalalignment('right')
314
+ # 텍스트박스 크기 가져오기
315
+ bbox = self.text_volume_info.get_window_extent().transformed(self.ax_price.transData.inverted())
316
+ width = bbox.x1 - bbox.x0
317
+ self.text_volume_info.set_x(self.v1 - width)
318
+
319
+ self.text_volume_info.draw(self.figure.canvas.renderer)
333
320
  return
334
321
 
335
322
  def _get_info(self, index, is_price=True):
336
323
  dt = self.df[self.date][index]
337
- v = self.df[self.volume][index]
338
- v = float_to_str(v, self.digit_volume)
339
- vr = self.df['rate_volume'][index]
324
+ if not self.volume:
325
+ v, vr = ('-', '-%')
326
+ else:
327
+ v = self.df[self.volume][index]
328
+ v = float_to_str(v, self.digit_volume)
329
+ # if not v % 1: v = int(v)
330
+ vr = self.df['rate_volume'][index]
331
+ vr = f'{vr:+06,.2f}%'
332
+
340
333
  if is_price:
341
334
  o, h, l, c = (self.df[self.Open][index], self.df[self.high][index], self.df[self.low][index], self.df[self.close][index])
342
335
  rate, compare = (self.df['rate'][index], self.df['compare'][index])
@@ -348,27 +341,23 @@ class InfoMixin(LineMixin):
348
341
  cd = divmod(c, 1)
349
342
  if cd[1]: c = f'{float_to_str(cd[0])} {Fraction((cd[1]))}'
350
343
  else: c = float_to_str(cd[0])
351
-
352
344
  comd = divmod(compare, 1)
353
345
  if comd[1]: com = f'{float_to_str(comd[0], plus=True)} {Fraction(comd[1])}'
354
346
  else: com = float_to_str(comd[0], plus=True)
355
-
356
347
  o = o.__round__(self.digit_price)
357
348
  od = divmod(o, 1)
358
349
  if od[1]: o = f'{float_to_str(od[0])} {Fraction(od[1])}'
359
350
  else: o = float_to_str(od[0])
360
-
361
351
  h = h.__round__(self.digit_price)
362
352
  hd = divmod(h, 1)
363
353
  if hd[1]: h = f'{float_to_str(hd[0])} {Fraction(hd[1])}'
364
354
  else: h = float_to_str(hd[0])
365
-
366
355
  l = l.__round__(self.digit_price)
367
356
  ld = divmod(l, 1)
368
357
  if ld[1]: l = f'{float_to_str(ld[0])} {Fraction(ld[1])}'
369
358
  else: l = float_to_str(ld[0])
370
359
 
371
- text = self.candleformat.format(
360
+ text = self.format_candleinfo.format(
372
361
  dt=dt,
373
362
  close=f'{c:>{self._length_text}}{self.unit_price}',
374
363
  rate=f'{r:>{self._length_text}}%',
@@ -376,13 +365,13 @@ class InfoMixin(LineMixin):
376
365
  open=f'{o:>{self._length_text}}{self.unit_price}', rate_open=f'{Or:+06,.2f}%',
377
366
  high=f'{h:>{self._length_text}}{self.unit_price}', rate_high=f'{hr:+06,.2f}%',
378
367
  low=f'{l:>{self._length_text}}{self.unit_price}', rate_low=f'{lr:+06,.2f}%',
379
- volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=f'{vr:+06,.2f}%',
368
+ volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=vr,
380
369
  )
381
370
  else:
382
371
  o, h, l, c = (float_to_str(o, self.digit_price), float_to_str(h, self.digit_price), float_to_str(l, self.digit_price), float_to_str(c, self.digit_price))
383
372
  com = float_to_str(compare, self.digit_price, plus=True)
384
373
 
385
- text = self.candleformat.format(
374
+ text = self.format_candleinfo.format(
386
375
  dt=dt,
387
376
  close=f'{c:>{self._length_text}}{self.unit_price}',
388
377
  rate=f'{r:>{self._length_text}}%',
@@ -390,23 +379,35 @@ class InfoMixin(LineMixin):
390
379
  open=f'{o:>{self._length_text}}{self.unit_price}', rate_open=f'{Or:+06,.2f}%',
391
380
  high=f'{h:>{self._length_text}}{self.unit_price}', rate_high=f'{hr:+06,.2f}%',
392
381
  low=f'{l:>{self._length_text}}{self.unit_price}', rate_low=f'{lr:+06,.2f}%',
393
- volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=f'{vr:+06,.2f}%',
382
+ volume=f'{v:>{self._length_text}}{self.unit_volume}', rate_volume=vr,
394
383
  )
395
- else:
396
- vrate = f'{vr:+06,.2f}'
397
- text = self.volumeformat.format(
384
+ elif self.volume:
385
+ text = self.format_volumeinfo.format(
398
386
  dt=dt,
399
387
  volume=f'{v:>{self._length_text}}{self.unit_volume}',
400
- rate_volume=f'{vrate:>{self._length_text}}%',
388
+ rate_volume=f'{vr:>{self._length_text}}%',
401
389
  )
390
+ else: text = ''
402
391
  return text
403
392
 
404
393
 
405
- class CursorMixin(InfoMixin):
394
+ class BaseMixin(InfoMixin):
406
395
  pass
407
396
 
408
397
 
409
- class Chart(CursorMixin, CM, Mixin):
398
+ class Chart(BaseMixin, Mixin):
399
+ def _add_collection(self):
400
+ super()._add_collection()
401
+ return self.add_collection()
402
+
403
+ def _draw_artist(self):
404
+ super()._draw_artist()
405
+ return self.draw_artist()
406
+
407
+ def _get_segments(self):
408
+ self.generate_data()
409
+ return super()._get_segments()
410
+
410
411
  def _on_draw(self, e):
411
412
  super()._on_draw(e)
412
413
  return self.on_draw(e)
@@ -419,30 +420,3 @@ class Chart(CursorMixin, CM, Mixin):
419
420
  super()._on_move(e)
420
421
  return self.on_move(e)
421
422
 
422
-
423
- if __name__ == '__main__':
424
- import json
425
- from time import time
426
-
427
- import matplotlib.pyplot as plt
428
- from pathlib import Path
429
-
430
- file = Path(__file__).parent / 'data/samsung.txt'
431
- file = Path(__file__).parent / 'data/apple.txt'
432
- with open(file, 'r', encoding='utf-8') as txt:
433
- data = json.load(txt)
434
- n = 2600
435
- data = data[n:n+100]
436
- df = pd.DataFrame(data)
437
- print(f'{df.keys()=}')
438
-
439
- t = time()
440
- c = CursorMixin()
441
- c.unit_price = '$'
442
- # c.fraction = True
443
- c.set_data(df[['date', 'open', 'high', 'low', 'close', 'volume']])
444
- t2 = time() - t
445
- print(f'{t2=}')
446
- plt.show()
447
-
448
-