seolpyo-mplchart 0.1.3.1__py3-none-any.whl → 2.0.0.3__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 (50) hide show
  1. seolpyo_mplchart/__init__.py +164 -99
  2. seolpyo_mplchart/_base.py +117 -0
  3. seolpyo_mplchart/_chart/__init__.py +137 -0
  4. seolpyo_mplchart/_chart/_base.py +217 -0
  5. seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
  6. seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
  7. seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
  8. seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
  9. seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
  10. seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
  11. seolpyo_mplchart/_chart/_draw/_data.py +314 -0
  12. seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
  13. seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
  14. seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
  15. seolpyo_mplchart/_chart/_slider/_base.py +268 -0
  16. seolpyo_mplchart/_chart/_slider/_data.py +105 -0
  17. seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
  18. seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
  19. seolpyo_mplchart/_chart/test.py +121 -0
  20. seolpyo_mplchart/_config/__init__.py +3 -0
  21. seolpyo_mplchart/_config/ax.py +28 -0
  22. seolpyo_mplchart/_config/candle.py +30 -0
  23. seolpyo_mplchart/_config/config.py +21 -0
  24. seolpyo_mplchart/_config/cursor.py +49 -0
  25. seolpyo_mplchart/_config/figure.py +41 -0
  26. seolpyo_mplchart/_config/format.py +51 -0
  27. seolpyo_mplchart/_config/ma.py +15 -0
  28. seolpyo_mplchart/_config/slider/__init__.py +2 -0
  29. seolpyo_mplchart/_config/slider/config.py +24 -0
  30. seolpyo_mplchart/_config/slider/figure.py +20 -0
  31. seolpyo_mplchart/_config/slider/nav.py +9 -0
  32. seolpyo_mplchart/_config/unit.py +19 -0
  33. seolpyo_mplchart/_config/utils.py +67 -0
  34. seolpyo_mplchart/_config/volume.py +26 -0
  35. seolpyo_mplchart/_cursor.py +559 -0
  36. seolpyo_mplchart/_draw.py +634 -0
  37. seolpyo_mplchart/_slider.py +634 -0
  38. seolpyo_mplchart/base.py +70 -67
  39. seolpyo_mplchart/cursor.py +308 -271
  40. seolpyo_mplchart/draw.py +449 -237
  41. seolpyo_mplchart/slider.py +451 -396
  42. seolpyo_mplchart/test.py +173 -24
  43. seolpyo_mplchart/utils.py +15 -4
  44. seolpyo_mplchart/xl_to_dict.py +47 -0
  45. seolpyo_mplchart-2.0.0.3.dist-info/METADATA +710 -0
  46. seolpyo_mplchart-2.0.0.3.dist-info/RECORD +50 -0
  47. {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/WHEEL +1 -1
  48. seolpyo_mplchart-0.1.3.1.dist-info/METADATA +0 -49
  49. seolpyo_mplchart-0.1.3.1.dist-info/RECORD +0 -13
  50. {seolpyo_mplchart-0.1.3.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,634 @@
1
+ from matplotlib.backend_bases import PickEvent
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.lines import Line2D
4
+ from matplotlib.text import Text
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+
9
+ from ._base import Base
10
+
11
+
12
+ class Mixin:
13
+ def draw_artist(self):
14
+ "This method work before ```figure.canvas.blit()```."
15
+ return
16
+
17
+ def on_change_xlim(self, xmin, xmax, simpler=False, set_ma=True):
18
+ "This method work if xlim change."
19
+ return
20
+
21
+ def on_draw(self, e):
22
+ "If draw event active, This method work."
23
+ return
24
+
25
+ def on_pick(self, e):
26
+ "If draw pick active, This method work."
27
+ return
28
+
29
+
30
+ class CollectionMixin(Base):
31
+ facecolor_volume, edgecolor_volume = ('#1f77b4', 'k')
32
+ watermark = 'seolpyo mplchart'
33
+
34
+ def __init__(self, *args, **kwargs):
35
+ super().__init__(*args, **kwargs)
36
+
37
+ self._add_collection()
38
+ return
39
+
40
+ def _add_collection(self):
41
+ self.artist_watermark = Text(
42
+ x=0.5, y=0.5, text=self.watermark,
43
+ animated=True,
44
+ fontsize=20, color=self.color_tick_label, alpha=0.2,
45
+ horizontalalignment='center', verticalalignment='center',
46
+ transform=self.ax_price.transAxes
47
+ )
48
+ self.ax_price.add_artist(self.artist_watermark)
49
+
50
+ self.collection_candle = LineCollection([], animated=True, linewidths=0.8)
51
+ self.ax_price.add_collection(self.collection_candle)
52
+
53
+ self.collection_ma = LineCollection([], animated=True, linewidths=1)
54
+ self.ax_price.add_collection(self.collection_ma)
55
+
56
+ self.collection_volume = LineCollection([], animated=True, linewidths=1)
57
+ self.ax_volume.add_collection(self.collection_volume)
58
+ return
59
+
60
+ def change_background_color(self, color):
61
+ self.figure.set_facecolor(color)
62
+ self.ax_price.set_facecolor(color)
63
+ self.ax_volume.set_facecolor(color)
64
+ legends = self.ax_legend.get_legend()
65
+ if legends: legends.get_frame().set_facecolor(color)
66
+ return
67
+
68
+ def change_tick_color(self, color):
69
+ for ax in (self.ax_price, self.ax_volume):
70
+ for i in ('top', 'bottom', 'left', 'right'): ax.spines[i].set_color(self.color_tick)
71
+ ax.tick_params(colors=color)
72
+ ax.tick_params(colors=color)
73
+
74
+ legends = self.ax_legend.get_legend()
75
+ if legends: legends.get_frame().set_edgecolor(color)
76
+
77
+ self.change_text_color(color)
78
+ return
79
+
80
+ def change_text_color(self, color):
81
+ self.artist_watermark.set_color(color)
82
+ legends = self.ax_legend.get_legend()
83
+ if legends:
84
+ for i in legends.texts: i.set_color(color)
85
+ return
86
+
87
+ def change_line_color(self, color): return
88
+
89
+
90
+ class DrawMixin(CollectionMixin):
91
+ candle_on_ma = True
92
+
93
+ def __init__(self, *args, **kwargs):
94
+ super().__init__(*args, **kwargs)
95
+
96
+ self._connect_event()
97
+ return
98
+
99
+ def _connect_event(self):
100
+ self.figure.canvas.mpl_connect('draw_event', lambda x: self._on_draw(x))
101
+ return
102
+
103
+ def _on_draw(self, e):
104
+ self._draw_artist()
105
+ self.figure.canvas.blit()
106
+ return
107
+
108
+ def _draw_artist(self):
109
+ self._draw_ax_price()
110
+ self._draw_ax_volume()
111
+ return
112
+
113
+ def _draw_ax_price(self):
114
+ renderer = self.figure.canvas.renderer
115
+
116
+ self.ax_price.xaxis.draw(renderer)
117
+ self.ax_price.yaxis.draw(renderer)
118
+
119
+ if self.candle_on_ma:
120
+ self.collection_ma.draw(renderer)
121
+ self.collection_candle.draw(renderer)
122
+ else:
123
+ self.collection_candle.draw(renderer)
124
+ self.collection_ma.draw(renderer)
125
+
126
+ if self.watermark:
127
+ self.artist_watermark.set_text(self.watermark)
128
+ self.artist_watermark.draw(renderer)
129
+ return
130
+
131
+ def _draw_ax_volume(self):
132
+ renderer = self.figure.canvas.renderer
133
+
134
+ self.ax_volume.xaxis.draw(renderer)
135
+ self.ax_volume.yaxis.draw(renderer)
136
+
137
+ self.collection_volume.draw(renderer)
138
+ return
139
+
140
+
141
+ _set_key = {
142
+ 'x', 'zero', 'close_pre', 'ymax_volume',
143
+ 'is_up',
144
+ 'top_candle', 'bottom_candle',
145
+ 'left_candle', 'right_candle',
146
+ 'left_volume', 'right_volume',
147
+ }
148
+
149
+ class DataMixin(DrawMixin):
150
+ df: pd.DataFrame
151
+
152
+ date = 'date'
153
+ Open, high, low, close = ('open', 'high', 'low', 'close')
154
+ volume = 'volume'
155
+ list_ma = (5, 20, 60, 120, 240)
156
+
157
+ candle_width_half, volume_width_half = (0.24, 0.36)
158
+ color_up, color_down = ('#FF2400', '#1E90FF')
159
+ color_flat = 'k'
160
+ color_up_down, color_down_up = ('w', 'w')
161
+
162
+ color_volume_up, color_volume_down = ('#FF5555', '#5CA8F4')
163
+ color_volume_flat = '#909090'
164
+
165
+ set_candlecolor, set_volumecolor = (True, True)
166
+
167
+ def _generate_data(self, df: pd.DataFrame, sort_df, calc_ma, set_candlecolor, set_volumecolor, *_, **__):
168
+ self.set_candlecolor = set_candlecolor
169
+ self.set_volumecolor = set_volumecolor
170
+
171
+ self.chart_price_ymax = df[self.high].max() * 1.3
172
+ self.chart_volume_ymax = df[self.volume].max() * 1.3
173
+
174
+ self._validate_column_key(df)
175
+
176
+ # 오름차순 정렬
177
+ if sort_df: df = df.sort_values([self.date])
178
+ df = df.reset_index(drop=True)
179
+
180
+ self.list_index = df.index.tolist()
181
+ self.xmin, self.xmax = (0, self.list_index[-1])
182
+
183
+ if not self.list_ma: self.list_ma = tuple()
184
+ else:
185
+ self.list_ma = sorted(self.list_ma)
186
+ # 가격이동평균선 계산
187
+ if calc_ma:
188
+ for i in self.list_ma: df[f'ma{i}'] = df[self.close].rolling(i).mean()
189
+ else:
190
+ set_key = set(self.df.keys())
191
+ for i in self.list_ma:
192
+ key = f'ma{i}'
193
+ if key not in set_key:
194
+ raise KeyError(f'"{key}" column not found.\nset calc_ma=True or add "{key}" column.')
195
+
196
+ df['x'] = df.index + 0.5
197
+ df['left_candle'] = df['x'] - self.candle_width_half
198
+ df['right_candle'] = df['x'] + self.candle_width_half
199
+ df['left_volume'] = df['x'] - self.volume_width_half
200
+ df['right_volume'] = df['x'] + self.volume_width_half
201
+ df.loc[:, 'zero'] = 0
202
+
203
+ df['is_up'] = np.where(df[self.Open] < df[self.close], True, False)
204
+ df['top_candle'] = np.where(df['is_up'], df[self.close], df[self.Open])
205
+ df['bottom_candle'] = np.where(df['is_up'], df[self.Open], df[self.close])
206
+
207
+ df['close_pre'] = df[self.close].shift(1)
208
+ if self.volume: df['ymax_volume'] = df[self.volume] * 1.2
209
+
210
+ self.df = df
211
+ return
212
+
213
+ def _validate_column_key(self, df: pd.DataFrame):
214
+ for i in ('date', 'Open', 'high', 'low', 'close', 'volume'):
215
+ v = getattr(self, i)
216
+ if v in _set_key: raise Exception(f'you can not set "{i}" to column key.\nself.{i}={v!r}')
217
+
218
+ if not self.set_candlecolor:
219
+ keys = set(df.keys())
220
+ for i in ('facecolor', 'edgecolor', 'facecolor_volume', 'edgecolor_volume',):
221
+ if i not in keys:
222
+ raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_candlecolor=True.')
223
+
224
+ if not self.set_volumecolor:
225
+ keys = set(df.keys())
226
+ for i in ('facecolor_volume', 'edgecolor_volume',):
227
+ if i not in keys:
228
+ raise Exception(f'"{i}" column not in DataFrame.\nadd column or set set_volumecolor=True.')
229
+ return
230
+
231
+
232
+ class CandleSegmentMixin(DataMixin):
233
+ color_priceline = 'k'
234
+ limit_candle = 800
235
+
236
+ def get_candle_segment(self, *, is_up, x, left, right, top, bottom, high, low):
237
+ """
238
+ get candle segment
239
+
240
+ Args:
241
+ is_up (bool): (True if open < close else False)
242
+ x (float): center of candle
243
+ left (float): left of candle
244
+ right (float): right of candle
245
+ top (float): top of candle(close if open < close else open)
246
+ bottom (float): bottom of candle(open if open < close else close)
247
+ high (float): top of candle wick
248
+ low (float): bottom of candle wick
249
+
250
+ Returns:
251
+ tuple[tuple[float, float]]: candle segment
252
+ """
253
+ return (
254
+ (x, top),
255
+ (left, top), (left, bottom),
256
+ (x, bottom), (x, low), (x, bottom),
257
+ (right, bottom), (right, top),
258
+ (x, top), (x, high)
259
+ )
260
+
261
+ def _create_candle_segments(self):
262
+ # 캔들 세그먼트
263
+ segment_candle = []
264
+ for x, left, right, top, bottom, is_up, high, low in zip(
265
+ self.df['x'].to_numpy().tolist(),
266
+ self.df['left_candle'].to_numpy().tolist(), self.df['right_candle'].to_numpy().tolist(),
267
+ self.df['top_candle'].to_numpy().tolist(), self.df['bottom_candle'].to_numpy().tolist(),
268
+ self.df['is_up'].to_numpy().tolist(),
269
+ self.df[self.high].to_numpy().tolist(), self.df[self.low].to_numpy().tolist(),
270
+ ):
271
+ segment_candle.append(
272
+ self.get_candle_segment(
273
+ is_up=is_up,
274
+ x=x, left=left, right=right,
275
+ top=top, bottom=bottom,
276
+ high=high, low=low,
277
+ )
278
+ )
279
+ self.segment_candle = np.array(segment_candle)
280
+
281
+ # 심지 세그먼트
282
+ segment_wick = self.df[[
283
+ 'x', self.high,
284
+ 'x', self.low,
285
+ ]].values
286
+ self.segment_candle_wick = segment_wick.reshape(segment_wick.shape[0], 2, 2)
287
+
288
+ # 종가 세그먼트
289
+ segment_priceline = segment_wick = self.df[['x', self.close]].values
290
+ self.segment_priceline = segment_priceline.reshape(1, *segment_wick.shape)
291
+
292
+ self._create_candle_color_segments()
293
+ return
294
+
295
+ def add_candle_color_column(self):
296
+ columns = ['facecolor', 'edgecolor']
297
+ # 양봉
298
+ self.df.loc[:, columns] = (self.color_up, self.color_up)
299
+ if self.color_up != self.color_down:
300
+ # 음봉
301
+ self.df.loc[self.df[self.close] < self.df[self.Open], columns] = (self.color_down, self.color_down)
302
+ if self.color_up != self.color_flat and self.color_down != self.color_flat:
303
+ # 보합
304
+ self.df.loc[self.df[self.close] == self.df[self.Open], columns] = (self.color_flat, self.color_flat)
305
+ if self.color_up != self.color_up_down:
306
+ # 양봉(비우기)
307
+ self.df.loc[(self.df['facecolor'] == self.color_up) & (self.df[self.close] <= self.df['close_pre']), 'facecolor'] = self.color_up_down
308
+ if self.color_down != self.color_down_up:
309
+ # 음봉(비우기)
310
+ self.df.loc[(self.df['facecolor'] == self.color_down) & (self.df['close_pre'] <= self.df[self.close]), ['facecolor']] = self.color_down_up
311
+ return
312
+
313
+ def _create_candle_color_segments(self):
314
+ if self.set_candlecolor: self.add_candle_color_column()
315
+
316
+ self.facecolor_candle = self.df['facecolor'].values
317
+ self.edgecolor_candle = self.df['edgecolor'].values
318
+ return
319
+
320
+ def _set_candle_segments(self, index_start, index_end):
321
+ self.collection_candle.set_segments(self.segment_candle[index_start:index_end])
322
+ self.collection_candle.set_facecolor(self.facecolor_candle[index_start:index_end])
323
+ self.collection_candle.set_edgecolor(self.edgecolor_candle[index_start:index_end])
324
+ return
325
+
326
+ def _set_candle_wick_segments(self, index_start, index_end):
327
+ self.collection_candle.set_segments(self.segment_candle_wick[index_start:index_end])
328
+ self.collection_candle.set_facecolor([])
329
+ self.collection_candle.set_edgecolor(self.edgecolor_candle[index_start:index_end])
330
+ return
331
+
332
+ def _set_priceline_segments(self, index_start, index_end):
333
+ self.collection_candle.set_segments(self.segment_priceline[:, index_start:index_end])
334
+ self.collection_candle.set_facecolor([])
335
+ self.collection_candle.set_edgecolor(self.color_priceline)
336
+ return
337
+
338
+
339
+ class MaSegmentMixin(CandleSegmentMixin):
340
+ format_ma = '{}일선'
341
+ # https://matplotlib.org/stable/gallery/color/named_colors.html
342
+ list_macolor = ('#006400', '#8B008B', '#FFA500', '#0000FF', '#FF0000')
343
+
344
+ _visible_ma = set()
345
+
346
+ def _create_ma_segments(self):
347
+ # 주가 차트 가격이동평균선
348
+ key_ma = []
349
+ for i in reversed(self.list_ma):
350
+ key_ma.append('x')
351
+ key_ma.append(f'ma{i}')
352
+ if key_ma:
353
+ segment_ma = self.df[key_ma].values
354
+ self.segment_ma = segment_ma.reshape(segment_ma.shape[0], len(self.list_ma), 2).swapaxes(0, 1)
355
+
356
+ self._create_ma_color_segments()
357
+ return
358
+
359
+ def _create_ma_color_segments(self):
360
+ # 기존 legend 제거
361
+ legends = self.ax_legend.get_legend()
362
+ if legends: legends.remove()
363
+
364
+ self._visible_ma.clear()
365
+
366
+ list_handle, list_label, list_color = ([], [], [])
367
+ arr = [0, 1]
368
+ for n, i in enumerate(self.list_ma):
369
+ try: c = self.list_macolor[n]
370
+ except: c = self.color_priceline
371
+ list_color.append(c)
372
+
373
+ list_handle.append(Line2D(arr, arr, color=c, linewidth=5, label=i))
374
+ list_label.append(self.format_ma.format(i))
375
+
376
+ self._visible_ma.add(i)
377
+ self.edgecolor_ma = list(reversed(list_color))
378
+
379
+ # 가격이동평균선 legend 생성
380
+ if list_handle:
381
+ legends = self.ax_legend.legend(
382
+ list_handle, list_label, loc='lower left', ncol=10,
383
+ facecolor=self.color_background, edgecolor=self.color_tick,
384
+ labelcolor=self.color_tick_label,
385
+ )
386
+ for i in legends.legend_handles: i.set_picker(5)
387
+ return
388
+
389
+ def _set_ma_segments(self, index_start, index_end, set_ma):
390
+ if not set_ma: self.collection_ma.set_segments([])
391
+ else:
392
+ self.collection_ma.set_segments(self.segment_ma[:, index_start:index_end])
393
+ self.collection_ma.set_edgecolor(self.edgecolor_ma)
394
+ return
395
+
396
+
397
+ class VolumeSegmentMixin(MaSegmentMixin):
398
+ limit_volume = 200
399
+
400
+ def get_volume_segment(self, *, x, left, right, top):
401
+ """
402
+ get volume bar segment
403
+
404
+ Args:
405
+ x (float): center of volume bar
406
+ left (float): left of volume bar
407
+ right (float): right of volume bar
408
+ top (float): top of volume bar
409
+
410
+ Returns:
411
+ tuple[tuple[float, float]]: volume bar segment
412
+ """
413
+ return (
414
+ (left, 0), (left, top),
415
+ (right, top), (right, 0),
416
+ )
417
+
418
+ def _create_volume_segments(self):
419
+ # 거래량 바 세그먼트
420
+ segment_volume = []
421
+ for x, left, right, top in zip(
422
+ self.df['x'].to_numpy().tolist(),
423
+ self.df['left_volume'].to_numpy().tolist(), self.df['right_volume'].to_numpy().tolist(),
424
+ self.df[self.volume].to_numpy().tolist(),
425
+ ):
426
+ segment_volume.append(
427
+ self.get_volume_segment(x=x, left=left, right=right, top=top)
428
+ )
429
+ self.segment_volume = np.array(segment_volume)
430
+
431
+ # 거래량 심지 세그먼트
432
+ segment_volume_wick = self.df[[
433
+ 'x', 'zero',
434
+ 'x', self.volume,
435
+ ]].values
436
+ self.segment_volume_wick = segment_volume_wick.reshape(segment_volume_wick.shape[0], 2, 2)
437
+
438
+ self._create_volume_color_segments()
439
+ return
440
+
441
+ def add_volume_color_column(self):
442
+ columns = ['facecolor_volume', 'edgecolor_volume']
443
+ # 거래량
444
+ self.df.loc[:, columns] = (self.color_volume_up, self.color_volume_up)
445
+ if self.color_up != self.color_down:
446
+ # 전일대비 하락
447
+ condition = self.df[self.close] < self.df['close_pre']
448
+ self.df.loc[condition, columns] = (self.color_volume_down, self.color_volume_down)
449
+ if self.color_up != self.color_flat:
450
+ # 전일과 동일
451
+ condition = self.df[self.close] == self.df['close_pre']
452
+ self.df.loc[condition, columns] = (self.color_volume_flat, self.color_volume_flat)
453
+ return
454
+
455
+ def _create_volume_color_segments(self):
456
+ if self.set_volumecolor: self.add_volume_color_column()
457
+
458
+ self.facecolor_volume = self.df['facecolor_volume'].values
459
+ self.edgecolor_volume = self.df['edgecolor_volume'].values
460
+ return
461
+
462
+ def _set_volume_segments(self, index_start, index_end):
463
+ self.collection_volume.set_segments(self.segment_volume[index_start:index_end])
464
+ self.collection_volume.set_linewidth(0.7)
465
+ self.collection_volume.set_facecolor(self.facecolor_volume[index_start:index_end])
466
+ self.collection_volume.set_edgecolor(self.edgecolor_volume[index_start:index_end])
467
+ return
468
+
469
+ def _set_volume_wick_segments(self, index_start, index_end, simpler):
470
+ seg_volume = self.segment_volume_wick[index_start:index_end]
471
+ seg_facecolor_volume = self.facecolor_volume[index_start:index_end]
472
+ seg_edgecolor_volume = self.edgecolor_volume[index_start:index_end]
473
+ if simpler:
474
+ values = seg_volume[:, 1, 1]
475
+ top_index = np.argsort(-values)[:self.limit_volume]
476
+ seg_volume = seg_volume[top_index]
477
+ seg_facecolor_volume = seg_facecolor_volume[top_index]
478
+ seg_edgecolor_volume = seg_edgecolor_volume[top_index]
479
+ self.collection_volume.set_segments(seg_volume)
480
+ self.collection_volume.set_linewidth(1.3)
481
+ self.collection_volume.set_facecolor(seg_facecolor_volume)
482
+ self.collection_volume.set_edgecolor(seg_edgecolor_volume)
483
+ return
484
+
485
+
486
+ class SegmentMixin(VolumeSegmentMixin):
487
+ limit_wick = 4_000
488
+
489
+ def _create_segments(self):
490
+ self._create_candle_segments()
491
+ self._create_ma_segments()
492
+ if self.volume: self._create_volume_segments()
493
+ return
494
+
495
+ def _set_collection_segments(self, index_start, index_end, indsub, simpler, set_ma=True):
496
+ if indsub < self.limit_candle:
497
+ self._set_candle_segments(index_start, index_end)
498
+ self._set_ma_segments(index_start, index_end, set_ma)
499
+ if self.volume: self._set_volume_segments(index_start, index_end)
500
+ elif indsub < self.limit_wick:
501
+ self._set_candle_wick_segments(index_start, index_end)
502
+ self._set_ma_segments(index_start, index_end, set_ma)
503
+ if self.volume: self._set_volume_wick_segments(index_start, index_end, simpler)
504
+ else:
505
+ self._set_priceline_segments(index_start, index_end)
506
+ self._set_ma_segments(index_start, index_end, set_ma)
507
+ if self.volume: self._set_volume_wick_segments(index_start, index_end, simpler)
508
+ return
509
+
510
+
511
+ class EventMixin(SegmentMixin):
512
+ def _connect_event(self):
513
+ super()._connect_event()
514
+
515
+ self.figure.canvas.mpl_connect('pick_event', lambda x: self._on_pick(x))
516
+ return
517
+
518
+ def _on_pick(self, e):
519
+ self._pick_ma_action(e)
520
+ return
521
+
522
+ def _pick_ma_action(self, e: PickEvent):
523
+ handle = e.artist
524
+
525
+ visible = handle.get_alpha() == 0.2
526
+ handle.set_alpha(1.0 if visible else 0.2)
527
+
528
+ n = int(handle.get_label())
529
+ if visible: self._visible_ma = {i for i in self.list_ma if i in self._visible_ma or i == n}
530
+ else: self._visible_ma = {i for i in self._visible_ma if i != n}
531
+
532
+ alphas = [(1 if i in self._visible_ma else 0) for i in reversed(self.list_ma)]
533
+ self.collection_ma.set_alpha(alphas)
534
+
535
+ self.figure.canvas.draw()
536
+ return
537
+
538
+
539
+ class LimMixin(EventMixin):
540
+ candle_on_ma = True
541
+
542
+ def set_data(self, df, sort_df=True, calc_ma=True, set_candlecolor=True, set_volumecolor=True, *args, **kwargs):
543
+ self._generate_data(df, sort_df, calc_ma, set_candlecolor, set_volumecolor, *args, **kwargs)
544
+ self._create_segments()
545
+
546
+ vmin, vmax = self.get_default_lim()
547
+ self._set_lim(vmin, vmax)
548
+ return
549
+
550
+ def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
551
+ self.vxmin, self.vxmax = (xmin, xmax)
552
+ if xmin < 0: xmin = 0
553
+ if xmax < 1: xmax = 1
554
+
555
+ ymin, ymax = (self.df[self.low][xmin:xmax].min(), self.df[self.high][xmin:xmax].max())
556
+ yspace = (ymax - ymin) / 15
557
+ # 주가 차트 ymin, ymax
558
+ self.price_ymin, self.price_ymax = (ymin-yspace, ymax+yspace)
559
+
560
+ # 거래량 차트 ymax
561
+ self.volume_ymax = self.df['ymax_volume'][xmin:xmax].max() if self.volume else 1
562
+
563
+ self._change_lim(self.vxmin, self.vxmax)
564
+ xsub = xmax - xmin
565
+ self._set_collection_segments(xmin, xmax, xsub, simpler, set_ma)
566
+ return
567
+
568
+ def _change_lim(self, xmin, xmax):
569
+ # 주가 차트 xlim
570
+ self.ax_price.set_xlim(xmin, xmax)
571
+ # 거래량 차트 xlim
572
+ self.ax_volume.set_xlim(xmin, xmax)
573
+
574
+ # 주가 차트 ylim
575
+ self.ax_price.set_ylim(self.price_ymin, self.price_ymax)
576
+ # 거래량 차트 ylim
577
+ self.ax_volume.set_ylim(0, self.volume_ymax)
578
+ return
579
+
580
+ def get_default_lim(self):
581
+ return (0, self.list_index[-1]+1)
582
+
583
+
584
+ class BackgroundMixin(LimMixin):
585
+ background = None
586
+
587
+ _creating_background = False
588
+
589
+ def _create_background(self):
590
+ if self._creating_background: return
591
+
592
+ self._creating_background = True
593
+ self._copy_bbox()
594
+ self._creating_background = False
595
+ return
596
+
597
+ def _copy_bbox(self):
598
+ self._draw_artist()
599
+ self.background = self.figure.canvas.renderer.copy_from_bbox(self.figure.bbox)
600
+ return
601
+
602
+ def _on_draw(self, e):
603
+ self.background = None
604
+ self._restore_region()
605
+ return
606
+
607
+ def _restore_region(self):
608
+ if not self.background: self._create_background()
609
+
610
+ self.figure.canvas.renderer.restore_region(self.background)
611
+ return
612
+
613
+
614
+ class BaseMixin(BackgroundMixin):
615
+ pass
616
+
617
+
618
+ class Chart(BaseMixin, Mixin):
619
+ def _draw_artist(self):
620
+ super()._draw_artist()
621
+ return self.draw_artist()
622
+
623
+ def _set_lim(self, xmin, xmax, simpler=False, set_ma=True):
624
+ super()._set_lim(xmin, xmax, simpler, set_ma)
625
+ return self.on_change_xlim(xmin, xmax, simpler, set_ma)
626
+
627
+ def _on_draw(self, e):
628
+ super()._on_draw(e)
629
+ return self.on_draw(e)
630
+
631
+ def _on_pick(self, e):
632
+ self.on_pick(e)
633
+ return super()._on_pick(e)
634
+