seolpyo-mplchart 1.4.1__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. seolpyo_mplchart/__init__.py +53 -333
  2. seolpyo_mplchart/_chart/__init__.py +145 -0
  3. seolpyo_mplchart/_chart/_base.py +217 -0
  4. seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
  5. seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
  6. seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
  7. seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
  8. seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
  9. seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
  10. seolpyo_mplchart/_chart/_draw/_data.py +314 -0
  11. seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
  12. seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
  13. seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
  14. seolpyo_mplchart/_chart/_slider/_base.py +268 -0
  15. seolpyo_mplchart/_chart/_slider/_data.py +105 -0
  16. seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
  17. seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
  18. seolpyo_mplchart/_chart/base/__init__.py +111 -0
  19. seolpyo_mplchart/_chart/base/a_canvas.py +250 -0
  20. seolpyo_mplchart/_chart/base/b_artist.py +143 -0
  21. seolpyo_mplchart/_chart/base/c_draw.py +100 -0
  22. seolpyo_mplchart/_chart/base/d_segment.py +262 -0
  23. seolpyo_mplchart/_chart/base/e_axis.py +267 -0
  24. seolpyo_mplchart/_chart/base/f_background.py +62 -0
  25. seolpyo_mplchart/_chart/base/g_event.py +66 -0
  26. seolpyo_mplchart/_chart/base/h_data.py +138 -0
  27. seolpyo_mplchart/_chart/base/test.py +58 -0
  28. seolpyo_mplchart/_chart/cursor/__init__.py +125 -0
  29. seolpyo_mplchart/_chart/cursor/b_artist.py +130 -0
  30. seolpyo_mplchart/_chart/cursor/c_draw.py +96 -0
  31. seolpyo_mplchart/_chart/cursor/d_segment.py +359 -0
  32. seolpyo_mplchart/_chart/cursor/e_axis.py +65 -0
  33. seolpyo_mplchart/_chart/cursor/g_event.py +233 -0
  34. seolpyo_mplchart/_chart/cursor/h_data.py +61 -0
  35. seolpyo_mplchart/_chart/cursor/test.py +69 -0
  36. seolpyo_mplchart/_chart/slider/__init__.py +169 -0
  37. seolpyo_mplchart/_chart/slider/a_canvas.py +260 -0
  38. seolpyo_mplchart/_chart/slider/b_artist.py +91 -0
  39. seolpyo_mplchart/_chart/slider/c_draw.py +54 -0
  40. seolpyo_mplchart/_chart/slider/d_segment.py +166 -0
  41. seolpyo_mplchart/_chart/slider/e_axis.py +70 -0
  42. seolpyo_mplchart/_chart/slider/f_background.py +37 -0
  43. seolpyo_mplchart/_chart/slider/g_event.py +353 -0
  44. seolpyo_mplchart/_chart/slider/h_data.py +102 -0
  45. seolpyo_mplchart/_chart/slider/test.py +71 -0
  46. seolpyo_mplchart/_chart/test.py +121 -0
  47. seolpyo_mplchart/_config/__init__.py +3 -0
  48. seolpyo_mplchart/_config/ax.py +28 -0
  49. seolpyo_mplchart/_config/candle.py +31 -0
  50. seolpyo_mplchart/_config/config.py +21 -0
  51. seolpyo_mplchart/_config/cursor.py +49 -0
  52. seolpyo_mplchart/_config/figure.py +40 -0
  53. seolpyo_mplchart/_config/format.py +51 -0
  54. seolpyo_mplchart/_config/ma.py +17 -0
  55. seolpyo_mplchart/_config/slider/__init__.py +2 -0
  56. seolpyo_mplchart/_config/slider/config.py +24 -0
  57. seolpyo_mplchart/_config/slider/figure.py +19 -0
  58. seolpyo_mplchart/_config/slider/nav.py +10 -0
  59. seolpyo_mplchart/_config/unit.py +19 -0
  60. seolpyo_mplchart/_config/utils.py +67 -0
  61. seolpyo_mplchart/_config/volume.py +27 -0
  62. seolpyo_mplchart/_cursor.py +27 -25
  63. seolpyo_mplchart/_draw.py +7 -18
  64. seolpyo_mplchart/_slider.py +26 -20
  65. seolpyo_mplchart/_utils/__init__.py +10 -0
  66. seolpyo_mplchart/_utils/nums.py +67 -0
  67. seolpyo_mplchart/_utils/theme/__init__.py +15 -0
  68. seolpyo_mplchart/_utils/theme/dark.py +57 -0
  69. seolpyo_mplchart/_utils/theme/light.py +56 -0
  70. seolpyo_mplchart/_utils/utils.py +28 -0
  71. seolpyo_mplchart/_utils/xl/__init__.py +15 -0
  72. seolpyo_mplchart/_utils/xl/csv.py +46 -0
  73. seolpyo_mplchart/_utils/xl/xlsx.py +49 -0
  74. seolpyo_mplchart/sample/apple.txt +6058 -0
  75. seolpyo_mplchart/sample/samsung.txt +5938 -0
  76. seolpyo_mplchart/test.py +172 -56
  77. seolpyo_mplchart/xl_to_dict.py +47 -0
  78. seolpyo_mplchart-2.1.0.dist-info/METADATA +718 -0
  79. seolpyo_mplchart-2.1.0.dist-info/RECORD +89 -0
  80. {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/WHEEL +1 -1
  81. seolpyo_mplchart-1.4.1.dist-info/METADATA +0 -57
  82. seolpyo_mplchart-1.4.1.dist-info/RECORD +0 -17
  83. {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
1
+ import pandas as pd
2
+
3
+ from ..._config import ConfigData
4
+
5
+ class Base:
6
+ CONFIG: ConfigData
7
+
8
+ key_volume: str
9
+ df: pd.DataFrame
10
+
11
+ _add_columns: callable
12
+ set_variables: callable
13
+
14
+
15
+ class Mixin(Base):
16
+ def _add_columns(self):
17
+ super()._add_columns()
18
+
19
+ self.df['compare'] = (self.df['close'] - self.df['pre_close']).fillna(0)
20
+ self.df['rate'] = (self.df['compare'] * 100 / self.df['pre_close']).__round__(2).fillna(0)
21
+ self.df['rate_open'] = ((self.df['open'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
22
+ self.df['rate_high'] = ((self.df['high'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
23
+ self.df['rate_low'] = ((self.df['low'] - self.df['pre_close']) * 100 / self.df['pre_close']).__round__(2).fillna(0)
24
+ if self.key_volume:
25
+ self.df['pre_volume'] = self.df['volume'].shift(1)
26
+ self.df['compare_volume'] = (self.df['volume'] - self.df['pre_volume']).fillna(0)
27
+ self.df['rate_volume'] = (self.df['compare_volume'] * 100 / self.df['pre_volume']).__round__(2).fillna(0)
28
+
29
+ self.df['space_box_candle'] = (self.df['high'] - self.df['low']) / 5
30
+ self.df['box_candle_top'] = self.df['high'] + self.df['space_box_candle']
31
+ self.df['box_candle_bottom'] = self.df['low'] - self.df['space_box_candle']
32
+ self.df['box_candle_height'] = self.df['box_candle_top'] - self.df['box_candle_bottom']
33
+
34
+ if self.key_volume:
35
+ self.df['box_volume_top'] = self.df['volume'] * 1.15
36
+
37
+ return
38
+
39
+ def set_variables(self):
40
+ super().set_variables()
41
+
42
+ self._set_length_text()
43
+ return
44
+
45
+ def _set_length_text(self):
46
+ func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit, word=self.CONFIG.UNIT.price))
47
+ self._length_text = self.df['high'].apply(func).max()
48
+
49
+ if self.key_volume:
50
+ func = lambda x: len(self.CONFIG.UNIT.func(x, digit=self.CONFIG.UNIT.digit_volume, word=self.CONFIG.UNIT.volume))
51
+ lenth_volume = self.df['volume'].apply(func).max()
52
+ # print(f'{self._length_text=}')
53
+ # print(f'{lenth_volume=}')
54
+ if self._length_text < lenth_volume:
55
+ self._length_text = lenth_volume
56
+ return
57
+
58
+
59
+ class DataMixin(Mixin):
60
+ _length_text: int
61
+
@@ -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
+
@@ -0,0 +1,260 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.axes import Axes
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.text import Text
5
+
6
+ from ..._config import SLIDERCONFIG, SliderConfigData
7
+ from ..base.a_canvas import CanvasMixin as BaseMixin, Figure
8
+
9
+
10
+ class Base(BaseMixin):
11
+ CONFIG: SliderConfigData
12
+
13
+ def add_axes(self):
14
+ if not self.figure:
15
+ self.figure, *_ = plt.subplots(
16
+ 7, # row 수
17
+ figsize=self.CONFIG.FIGURE.figsize, # 기본 크기
18
+ height_ratios=(
19
+ 0,
20
+ 0,
21
+ 0,
22
+ self.CONFIG.FIGURE.RATIO.price,
23
+ self.CONFIG.FIGURE.RATIO.volume,
24
+ 0,
25
+ 0,
26
+ ) # row 크기 비율
27
+ )
28
+
29
+ self._ax_slider_top, self.ax_none_top, self.ax_legend, self.ax_price, self.ax_volume, self.ax_none_bottom, self._ax_slider_bottom = self.figure.axes
30
+
31
+ return
32
+
33
+
34
+ class FigureMixin(Base):
35
+ key_volume: str
36
+ ax_legend: Axes
37
+
38
+ slider_top = True
39
+
40
+ def _set_figure_ratios(self):
41
+ gs = self.figure.axes[0].get_subplotspec().get_gridspec()
42
+
43
+ ratio_volume = self.CONFIG.FIGURE.RATIO.volume
44
+ if not self.key_volume:
45
+ ratio_volume = 0
46
+
47
+ legend = self.ax_legend.get_legend()
48
+ if not legend:
49
+ if self.slider_top:
50
+ ratios = [
51
+ self.CONFIG.FIGURE.RATIO.slider,
52
+ 0,
53
+ 0,
54
+ self.CONFIG.FIGURE.RATIO.price, ratio_volume,
55
+ 0,
56
+ 0,
57
+ ]
58
+ else:
59
+ ratios = [
60
+ 0,
61
+ 0,
62
+ 0,
63
+ self.CONFIG.FIGURE.RATIO.price, ratio_volume,
64
+ self.CONFIG.FIGURE.RATIO.none,
65
+ self.CONFIG.FIGURE.RATIO.slider,
66
+ ]
67
+ else:
68
+ fig_heihgt = self.figure.get_figheight()
69
+ fig_px = fig_heihgt * (1-self.CONFIG.FIGURE.ADJUST.hspace*2) * self.figure.dpi
70
+ # print(f'{(fig_heihgt, fig_px)=}')
71
+
72
+ # Legend에 Axes 높이 맞추기
73
+ bbox = legend.get_window_extent().transformed(self.figure.transFigure.inverted())
74
+ ax_pos = self.ax_legend.get_position()
75
+ self.ax_legend.set_position([ax_pos.x0, ax_pos.y0, ax_pos.width, bbox.height])
76
+
77
+ legend_height = bbox.height
78
+ legend_px = legend.get_window_extent().height
79
+ # print(f'{(legend_height, legend_px)=}')
80
+
81
+ chart_px = fig_px - legend_height
82
+ chart_ratio = self.CONFIG.FIGURE.RATIO.price + ratio_volume
83
+
84
+ # print(f'{self.CONFIG.FIGURE.RATIO.__dict__=}')
85
+ ratio_none = 0 if self.slider_top else self.CONFIG.FIGURE.RATIO.none
86
+ chart_ratio += self.CONFIG.FIGURE.RATIO.slider
87
+ div_chart = chart_px / chart_ratio
88
+ price_px = div_chart * self.CONFIG.FIGURE.RATIO.price
89
+ volume_px = div_chart * ratio_volume
90
+ slider_px = div_chart * self.CONFIG.FIGURE.RATIO.slider
91
+ none_px = div_chart * ratio_none
92
+ # print(f'{none_px=}')
93
+
94
+ if self.slider_top:
95
+ # 차트 비율
96
+ ratios = [
97
+ slider_px,
98
+ 0,
99
+ legend_px * 3,
100
+ price_px, volume_px,
101
+ 0,
102
+ 0,
103
+ ]
104
+ else:
105
+ ratios = [
106
+ 0,
107
+ 0,
108
+ legend_px * 1.2,
109
+ price_px, volume_px,
110
+ none_px,
111
+ slider_px,
112
+ ]
113
+
114
+ # print(f'{ratios=}')
115
+ gs.set_height_ratios(ratios)
116
+
117
+ self.figure.tight_layout()
118
+
119
+ # 플롯간 간격 설정(Configure subplots)
120
+ self.figure.subplots_adjust(**self.CONFIG.FIGURE.ADJUST.__dict__)
121
+
122
+ return
123
+
124
+ def _set_figure(self):
125
+ self.figure.canvas.manager.set_window_title('Seolpyo MPLChart')
126
+
127
+ # print(f'{self.CONFIG.FIGURE.RATIO.volume=}')
128
+ # print(f'{gs.get_height_ratios()=}')
129
+
130
+ self._set_figure_ratios()
131
+
132
+ self.figure.set_facecolor(self.CONFIG.FIGURE.facecolor)
133
+ return
134
+
135
+
136
+ class AxesMixin(Base):
137
+ slider_top = True
138
+
139
+ ax_slider: Axes = None
140
+
141
+ collection_slider: LineCollection
142
+ collection_nav: LineCollection
143
+ collection_slider_vline: LineCollection
144
+ artist_text_slider: Text
145
+
146
+ def _set_axes(self):
147
+ super()._set_axes()
148
+
149
+ if self.slider_top:
150
+ ax_slider = self._ax_slider_top
151
+ else:
152
+ ax_slider = self._ax_slider_bottom
153
+
154
+ if self.ax_slider and ax_slider is not self.ax_slider:
155
+ # print('move artist')
156
+ # ax_slider 위치가 변경된 경우 artist 이동하기
157
+ artists: list[LineCollection|Text] = [
158
+ self.collection_slider,
159
+ self.collection_nav,
160
+ self.collection_slider_vline,
161
+ self.artist_text_slider,
162
+ ]
163
+ for artist in artists:
164
+ artist.remove()
165
+ artist.set_transform(ax_slider.transData)
166
+ ax_slider.add_artist(artist)
167
+ # axis
168
+ ax_slider.set_xlim(*self.ax_slider.get_xlim())
169
+ ax_slider.set_ylim(*self.ax_slider.get_ylim())
170
+ # tick label
171
+ ax_slider.set_xticks(self.ax_slider.get_xticks(minor=True), minor=True)
172
+ ax_slider.set_xticklabels(self.ax_slider.get_xticklabels(minor=True), minor=True)
173
+
174
+ self.ax_slider = ax_slider
175
+
176
+ self._ax_slider_top.set_label('top slider ax')
177
+ self._ax_slider_bottom.set_label('bottom slider ax')
178
+ self.ax_none_top.set_label('top none ax')
179
+ self.ax_none_bottom.set_label('bottom none ax')
180
+
181
+ self.ax_slider.set_label('slider ax')
182
+
183
+ for ax in (self.ax_none_top, self.ax_none_bottom):
184
+ ax.set_animated(True)
185
+ ax.set_axis_off()
186
+
187
+ self._set_axes_slider()
188
+
189
+ return
190
+
191
+ def _set_axes_slider(self):
192
+ # print(f'{self.slider_top=}')
193
+ formatter = lambda x, _: self.CONFIG.UNIT.func(
194
+ x,
195
+ word=self.CONFIG.UNIT.price,
196
+ digit=self.CONFIG.UNIT.digit+2
197
+ )
198
+ # 공통 설정
199
+ for ax in (self._ax_slider_top, self._ax_slider_bottom):
200
+ ax.yaxis.set_major_formatter(formatter)
201
+ # x tick 외부 눈금 표시하지 않기
202
+ ax.xaxis.set_ticks_position('none')
203
+ # x tick label 제거
204
+ ax.set_xticklabels([])
205
+ # y tick 눈금 표시하지 않기
206
+ ax.yaxis.set_ticks_position('none')
207
+
208
+ # 차트 영역 배경 색상
209
+ ax.set_facecolor(self.CONFIG.AX.facecolor)
210
+
211
+ # Axes 외곽선 색 변경(틱 색과 일치)
212
+ for i in ['top', 'bottom', 'left', 'right']:
213
+ ax.spines[i].set_color(self.CONFIG.AX.TICK.edgecolor)
214
+ # 틱 색상
215
+ ax.tick_params('both', colors=self.CONFIG.AX.TICK.edgecolor)
216
+ # 틱 라벨 색상
217
+ ticklabels: list[Text] = ax.get_xticklabels() + ax.get_yticklabels()
218
+ for ticklabel in ticklabels:
219
+ ticklabel.set_color(self.CONFIG.AX.TICK.fontcolor)
220
+
221
+ # Axes grid(구분선, 격자) 그리기
222
+ # 어째서인지 grid의 zorder 값을 선언해도 1.6을 값으로 한다.
223
+ ax.grid(**self.CONFIG.AX.GRID.__dict__)
224
+
225
+ # major tick mark 길이를 0으로 만들어 튀어나오지 않게 하기
226
+ ax.tick_params('x', which='major', length=0)
227
+ # minor tick mark 색상 변경
228
+ ax.tick_params('x', which='minor', colors=self.CONFIG.AX.TICK.edgecolor)
229
+ # 틱 라벨 색상
230
+ ticklabels: list[Text] = ax.get_xticklabels(minor=True) + ax.get_yticklabels()
231
+ for ticklabel in ticklabels:
232
+ ticklabel.set_color(self.CONFIG.AX.TICK.fontcolor)
233
+
234
+ ax.yaxis.set_ticks_position('right')
235
+ # 상단 슬라이더의 x tick 하단 설정
236
+ self._ax_slider_top.xaxis.set_ticks_position('bottom')
237
+ # 하단 슬라이더의 x tick 상단 설정
238
+ self._ax_slider_bottom.xaxis.set_ticks_position('top')
239
+
240
+ return
241
+
242
+
243
+ class CanvasMixin(FigureMixin, AxesMixin):
244
+ slider_top = True
245
+
246
+ figure: Figure
247
+
248
+ ax_legend: Axes
249
+ ax_price: Axes
250
+ ax_volume: Axes
251
+ ax_slider: Axes
252
+ ax_none: Axes
253
+ _ax_slider_top: Axes
254
+ _ax_slider_bottom: Axes
255
+
256
+ def __init__(self, config=SLIDERCONFIG):
257
+ super().__init__(config=config)
258
+ return
259
+
260
+
@@ -0,0 +1,91 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+
5
+ from ..._config import SliderConfigData
6
+
7
+
8
+ class Base:
9
+ CONFIG: SliderConfigData
10
+
11
+ ax_slider: Axes
12
+ ax_price: Axes
13
+ ax_volume: Axes
14
+ ax_legend: Axes
15
+
16
+ add_artists: callable
17
+ set_artists: callable
18
+
19
+
20
+ class CollectionMixin(Base):
21
+ def add_artists(self):
22
+ super().add_artists()
23
+
24
+ self.collection_slider = LineCollection([])
25
+ self.collection_nav = LineCollection([])
26
+ self.collection_slider_vline = LineCollection([])
27
+ self.artist_text_slider = Text('')
28
+
29
+ for artist in [
30
+ self.collection_slider,
31
+ self.collection_nav,
32
+ self.collection_slider_vline,
33
+ self.artist_text_slider
34
+ ]:
35
+ self.ax_slider.add_artist(artist)
36
+ return
37
+
38
+ def set_artists(self):
39
+ super().set_artists()
40
+
41
+ self._set_slider_artists()
42
+ self._set_slider_test()
43
+ return
44
+
45
+ def _set_slider_artists(self):
46
+ self.collection_slider.set_animated(True)
47
+ self.collection_slider.set_transform(self.ax_slider.transData)
48
+
49
+ color_overay = self.CONFIG.SLIDER.NAV.facecolor
50
+ color_line = self.CONFIG.SLIDER.NAV.edgecolor
51
+ colors = [color_overay, color_overay, color_line, color_line]
52
+ # print(f'{colors=}')
53
+ self.collection_nav.set_linewidth(0.1)
54
+ self.collection_nav.set_facecolor(colors)
55
+
56
+ # facecolor alpha값 제거
57
+ colors = []
58
+ for c in self.collection_nav.get_facecolor():
59
+ # print(c)
60
+ colors.append(c[:3])
61
+ self.collection_nav.set_facecolor(colors)
62
+
63
+ alpha = self.CONFIG.SLIDER.NAV.alpha
64
+ self.collection_nav.set_alpha([alpha, alpha, 1, 1])
65
+
66
+ self.collection_nav.set_edgecolor([(0, 0, 0, 0) for _ in colors])
67
+ self.collection_nav.set_animated(True)
68
+ self.collection_nav.set_transform(self.ax_slider.transData)
69
+
70
+ kwargs = self.CONFIG.CURSOR.CROSSLINE.__dict__
71
+ kwargs.update({'segments': [], 'animated': True})
72
+ self.collection_slider_vline.set(**kwargs)
73
+
74
+ kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
75
+ kwargs.update({'text': ' ', 'animated': True})
76
+ self.artist_text_slider.set(**kwargs)
77
+ return
78
+
79
+ def _set_slider_test(self):
80
+ kwargs = self.CONFIG.CURSOR.TEXT.to_dict()
81
+ kwargs.update(animated=True, verticalalignment='top', horizontalalignment='center')
82
+ self.artist_text_slider.set(**kwargs)
83
+ return
84
+
85
+
86
+ class ArtistMixin(CollectionMixin):
87
+ collection_slider: LineCollection
88
+ collection_nav: LineCollection
89
+ collection_slider_vline: LineCollection
90
+ artist_text_slider: Text
91
+
@@ -0,0 +1,54 @@
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.collections import LineCollection
3
+ from matplotlib.text import Text
4
+
5
+ from .a_canvas import Figure
6
+
7
+
8
+ class Base:
9
+ figure: Figure
10
+
11
+ ax_slider: Axes
12
+
13
+ collection_slider: LineCollection
14
+ collection_nav: LineCollection
15
+ collection_slider_vline: LineCollection
16
+ artist_text_slider: Text
17
+
18
+
19
+ class SliderMixin(Base):
20
+ def _draw_ax_slider(self):
21
+ renderer = self.figure.canvas.renderer
22
+
23
+ self.ax_slider.xaxis.draw(renderer)
24
+ self.ax_slider.yaxis.draw(renderer)
25
+
26
+ self.collection_slider.draw(renderer)
27
+ c = self.collection_slider.get_edgecolor()
28
+ # print(f'{c=}')
29
+ s = self.collection_slider.get_segments()
30
+ # print(f'{s=}')
31
+ return
32
+
33
+ def _draw_nav(self):
34
+ renderer = self.figure.canvas.renderer
35
+
36
+ self.collection_nav.draw(renderer)
37
+ return
38
+
39
+ def _draw_slider_vline(self):
40
+ renderer = self.figure.canvas.renderer
41
+
42
+ self.collection_slider_vline.draw(renderer)
43
+ return
44
+
45
+ def _draw_slider_text(self):
46
+ renderer = self.figure.canvas.renderer
47
+
48
+ self.artist_text_slider.draw(renderer)
49
+ return
50
+
51
+
52
+ class DrawMixin(SliderMixin):
53
+ pass
54
+