seolpyo-mplchart 1.4.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.
- seolpyo_mplchart/__init__.py +144 -308
- seolpyo_mplchart/_chart/__init__.py +137 -0
- seolpyo_mplchart/_chart/_base.py +217 -0
- seolpyo_mplchart/_chart/_cursor/__init__.py +2 -0
- seolpyo_mplchart/_chart/_cursor/_artist.py +217 -0
- seolpyo_mplchart/_chart/_cursor/_cursor.py +165 -0
- seolpyo_mplchart/_chart/_cursor/_info.py +187 -0
- seolpyo_mplchart/_chart/_draw/__init__.py +2 -0
- seolpyo_mplchart/_chart/_draw/_artist.py +50 -0
- seolpyo_mplchart/_chart/_draw/_data.py +314 -0
- seolpyo_mplchart/_chart/_draw/_draw.py +103 -0
- seolpyo_mplchart/_chart/_draw/_lim.py +265 -0
- seolpyo_mplchart/_chart/_slider/__init__.py +1 -0
- seolpyo_mplchart/_chart/_slider/_base.py +268 -0
- seolpyo_mplchart/_chart/_slider/_data.py +105 -0
- seolpyo_mplchart/_chart/_slider/_mouse.py +176 -0
- seolpyo_mplchart/_chart/_slider/_nav.py +204 -0
- seolpyo_mplchart/_chart/test.py +121 -0
- seolpyo_mplchart/_config/__init__.py +3 -0
- seolpyo_mplchart/_config/ax.py +28 -0
- seolpyo_mplchart/_config/candle.py +30 -0
- seolpyo_mplchart/_config/config.py +21 -0
- seolpyo_mplchart/_config/cursor.py +49 -0
- seolpyo_mplchart/_config/figure.py +41 -0
- seolpyo_mplchart/_config/format.py +51 -0
- seolpyo_mplchart/_config/ma.py +15 -0
- seolpyo_mplchart/_config/slider/__init__.py +2 -0
- seolpyo_mplchart/_config/slider/config.py +24 -0
- seolpyo_mplchart/_config/slider/figure.py +20 -0
- seolpyo_mplchart/_config/slider/nav.py +9 -0
- seolpyo_mplchart/_config/unit.py +19 -0
- seolpyo_mplchart/_config/utils.py +67 -0
- seolpyo_mplchart/_config/volume.py +26 -0
- seolpyo_mplchart/_cursor.py +27 -25
- seolpyo_mplchart/_draw.py +7 -18
- seolpyo_mplchart/_slider.py +26 -20
- seolpyo_mplchart/test.py +172 -56
- seolpyo_mplchart/xl_to_dict.py +47 -0
- seolpyo_mplchart-2.0.0.3.dist-info/METADATA +710 -0
- seolpyo_mplchart-2.0.0.3.dist-info/RECORD +50 -0
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/WHEEL +1 -1
- seolpyo_mplchart-1.4.1.dist-info/METADATA +0 -57
- seolpyo_mplchart-1.4.1.dist-info/RECORD +0 -17
- {seolpyo_mplchart-1.4.1.dist-info → seolpyo_mplchart-2.0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
format_candleinfo_ko = """\
|
|
4
|
+
{dt}
|
|
5
|
+
|
|
6
|
+
종가: {close}
|
|
7
|
+
등락률: {rate}
|
|
8
|
+
대비: {compare}
|
|
9
|
+
시가: {open}({rate_open})
|
|
10
|
+
고가: {high}({rate_high})
|
|
11
|
+
저가: {low}({rate_low})
|
|
12
|
+
거래량: {volume}({rate_volume})\
|
|
13
|
+
"""
|
|
14
|
+
format_volumeinfo_ko = """\
|
|
15
|
+
{dt}
|
|
16
|
+
|
|
17
|
+
거래량: {volume}
|
|
18
|
+
거래량증가율: {rate_volume}
|
|
19
|
+
대비: {compare}\
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
class FormatData:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.candle = format_candleinfo_ko
|
|
25
|
+
self.volume = format_volumeinfo_ko
|
|
26
|
+
|
|
27
|
+
FORMAT = FormatData()
|
|
28
|
+
|
|
29
|
+
format_candleinfo_en = """\
|
|
30
|
+
{dt}
|
|
31
|
+
|
|
32
|
+
close: {close}
|
|
33
|
+
rate: {rate}
|
|
34
|
+
compare: {compare}
|
|
35
|
+
open: {open}({rate_open})
|
|
36
|
+
high: {high}({rate_high})
|
|
37
|
+
low: {low}({rate_low})
|
|
38
|
+
volume: {volume}({rate_volume})\
|
|
39
|
+
"""
|
|
40
|
+
format_volumeinfo_en = """\
|
|
41
|
+
{dt}
|
|
42
|
+
|
|
43
|
+
volume: {volume}
|
|
44
|
+
volume rate: {rate_volume}
|
|
45
|
+
compare: {compare}\
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
FORMAT_EN = FormatData()
|
|
49
|
+
FORMAT_EN.candle = format_candleinfo_en
|
|
50
|
+
FORMAT_EN.volume = format_volumeinfo_en
|
|
51
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class MaData:
|
|
4
|
+
"https://matplotlib.org/stable/gallery/color/named_colors.html"
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.color_default: str|tuple[float, float, float, float] = 'k'
|
|
7
|
+
self.format = '{}일선'
|
|
8
|
+
self.color_list: list[str|tuple[float, float, float, float]] = ['#8B00FF', '#008000', '#A0522D', '#008B8B', '#FF0080']
|
|
9
|
+
self.ma_list = (5, 20, 60, 120, 240)
|
|
10
|
+
|
|
11
|
+
MA = MaData()
|
|
12
|
+
|
|
13
|
+
MA_EN = MaData()
|
|
14
|
+
MA_EN.format = 'ma{}'
|
|
15
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .. import config
|
|
2
|
+
from .figure import FIGURE, SliderFigureData
|
|
3
|
+
from .nav import NAVIGATOR
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SliderData:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.NAVIGATOR = NAVIGATOR
|
|
9
|
+
|
|
10
|
+
SLIDER = SliderData()
|
|
11
|
+
|
|
12
|
+
class SliderConfigData(config.ConfigData):
|
|
13
|
+
FIGURE: SliderFigureData
|
|
14
|
+
SLIDER: SliderData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
SLIDERCONFIG: SliderConfigData = config.DEFAULTCONFIG
|
|
18
|
+
SLIDERCONFIG.FIGURE = FIGURE
|
|
19
|
+
SLIDERCONFIG.SLIDER = SLIDER
|
|
20
|
+
|
|
21
|
+
SLIDERCONFIG_EN: SliderConfigData = config.DEFAULTCONFIG_EN
|
|
22
|
+
SLIDERCONFIG_EN.FIGURE = FIGURE
|
|
23
|
+
SLIDERCONFIG_EN.SLIDER = SLIDER
|
|
24
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .. import figure
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RatioData:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.legend = 2
|
|
7
|
+
self.price = 18
|
|
8
|
+
self.volume = 5
|
|
9
|
+
self.slider = 3
|
|
10
|
+
self.none = 2
|
|
11
|
+
|
|
12
|
+
RATIO = RatioData()
|
|
13
|
+
|
|
14
|
+
class SliderFigureData(figure.FigureData):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.RATIO: RatioData = RATIO
|
|
18
|
+
|
|
19
|
+
FIGURE = SliderFigureData()
|
|
20
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .utils import convert_unit, convert_unit_en
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UnitData:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.price = '원'
|
|
7
|
+
self.volume = '주'
|
|
8
|
+
self.digit = 0
|
|
9
|
+
self.digit_volume = 0
|
|
10
|
+
self.func = convert_unit
|
|
11
|
+
|
|
12
|
+
UNIT = UnitData()
|
|
13
|
+
|
|
14
|
+
UNIT_EN = UnitData()
|
|
15
|
+
UNIT_EN.price = ' $'
|
|
16
|
+
UNIT_EN.volume = ' Vol'
|
|
17
|
+
UNIT_EN.digit = 2
|
|
18
|
+
UNIT_EN.func = convert_unit_en
|
|
19
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def convert_num(num):
|
|
4
|
+
if isinstance(num, float) and num % 1:
|
|
5
|
+
return num
|
|
6
|
+
return int(num)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def float_to_str(num: float, *, digit=0, plus=False):
|
|
10
|
+
if 0 < digit:
|
|
11
|
+
num.__round__(digit)
|
|
12
|
+
text = f'{num:+,.{digit}f}' if plus else f'{num:,.{digit}f}'
|
|
13
|
+
else:
|
|
14
|
+
num = round(num, digit).__int__()
|
|
15
|
+
text = f'{num:+,}' if plus else f'{num:,}'
|
|
16
|
+
return text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
unit_ko = {
|
|
20
|
+
'경': 10_000_000_000_000_000,
|
|
21
|
+
'조': 1_000_000_000_000,
|
|
22
|
+
'억': 100_000_000,
|
|
23
|
+
'만': 10_000,
|
|
24
|
+
}
|
|
25
|
+
def convert_unit(value, *, digit=0, word='원', unit_data: dict[str, int]=None):
|
|
26
|
+
if not unit_data:
|
|
27
|
+
unit_data = unit_ko
|
|
28
|
+
# print('ko')
|
|
29
|
+
# print(f'{value=:,}')
|
|
30
|
+
v = abs(value)
|
|
31
|
+
for unit, n in unit_data.items():
|
|
32
|
+
if n <= v:
|
|
33
|
+
# print(f'{n=:,}')
|
|
34
|
+
# print(f'{unit=}')
|
|
35
|
+
num = value / n
|
|
36
|
+
if word.startswith(' '):
|
|
37
|
+
return f'{float_to_str(num, digit=digit)}{unit}{word}'
|
|
38
|
+
return f'{float_to_str(num, digit=digit)}{unit} {word}'
|
|
39
|
+
|
|
40
|
+
if not value % 1:
|
|
41
|
+
value = int(value)
|
|
42
|
+
text = f'{float_to_str(value, digit=digit)}{word}'
|
|
43
|
+
# print(f'{text=}')
|
|
44
|
+
return text
|
|
45
|
+
|
|
46
|
+
unit_en = {
|
|
47
|
+
'Qd': 1_000_000_000_000_000,
|
|
48
|
+
'T': 1_000_000_000_000,
|
|
49
|
+
'B': 1_000_000_000,
|
|
50
|
+
'M': 1_000_000,
|
|
51
|
+
'K': 1_000,
|
|
52
|
+
}
|
|
53
|
+
def convert_unit_en(value, *, digit=0, word='$', unit_data: dict[str, int]=None):
|
|
54
|
+
if not unit_data:
|
|
55
|
+
unit_data = unit_en
|
|
56
|
+
# print('en')
|
|
57
|
+
# print(f'{value=:,}')
|
|
58
|
+
return convert_unit(value, digit=digit, word=word, unit_data=unit_data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
a = 456.123
|
|
63
|
+
print(float_to_str(a))
|
|
64
|
+
print(float_to_str(a, 2))
|
|
65
|
+
print(float_to_str(a, 6))
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class VolumeFaceColorData:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self.rise: str|tuple[float, float, float, float] = '#F27663'
|
|
6
|
+
self.fall: str|tuple[float, float, float, float] = '#70B5F2'
|
|
7
|
+
self.doji: str|tuple[float, float, float, float] = '#BEBEBE'
|
|
8
|
+
|
|
9
|
+
VOLUMEFACECOLOR = VolumeFaceColorData()
|
|
10
|
+
|
|
11
|
+
class VolumeEdgeColorData:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.rise: str|tuple[float, float, float, float] = '#F27663'
|
|
14
|
+
self.fall: str|tuple[float, float, float, float] = '#70B5F2'
|
|
15
|
+
self.doji: str|tuple[float, float, float, float] = '#BEBEBE'
|
|
16
|
+
|
|
17
|
+
VOLUMEEDGECOLOR = VolumeEdgeColorData()
|
|
18
|
+
|
|
19
|
+
class VolumeData:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.half_width = 0.36
|
|
22
|
+
self.FACECOLOR = VOLUMEFACECOLOR
|
|
23
|
+
self.EDGECOLOR = VOLUMEEDGECOLOR
|
|
24
|
+
|
|
25
|
+
VOLUME = VolumeData()
|
|
26
|
+
|
seolpyo_mplchart/_cursor.py
CHANGED
|
@@ -105,7 +105,7 @@ class CollectionMixin(BM):
|
|
|
105
105
|
_set_key = {
|
|
106
106
|
'compare', 'rate',
|
|
107
107
|
'rate_open', 'rate_high', 'rate_low',
|
|
108
|
-
'compare_volume', 'rate_volume',
|
|
108
|
+
'volume_pre', 'compare_volume', 'rate_volume',
|
|
109
109
|
'space_box_candle',
|
|
110
110
|
'bottom_box_candle', 'top_box_candle',
|
|
111
111
|
'max_box_volume',
|
|
@@ -136,13 +136,14 @@ class DataMixin(CollectionMixin):
|
|
|
136
136
|
raise Exception(f'"{i}" column not in DataFrame.\nadd column or set calc_info=True.')
|
|
137
137
|
else:
|
|
138
138
|
self.df['compare'] = (self.df[self.close] - self.df['close_pre']).fillna(0)
|
|
139
|
-
self.df['rate'] = (self.df['compare'] / self.df[
|
|
140
|
-
self.df['rate_open'] = ((self.df[self.Open] - self.df['close_pre']) / self.df[
|
|
141
|
-
self.df['rate_high'] = ((self.df[self.high] - self.df['close_pre']) / self.df[
|
|
142
|
-
self.df['rate_low'] = ((self.df[self.low] - self.df['close_pre']) / self.df[
|
|
139
|
+
self.df['rate'] = (self.df['compare'] * 100 / self.df['close_pre']).__round__(2).fillna(0)
|
|
140
|
+
self.df['rate_open'] = ((self.df[self.Open] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
|
|
141
|
+
self.df['rate_high'] = ((self.df[self.high] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
|
|
142
|
+
self.df['rate_low'] = ((self.df[self.low] - self.df['close_pre']) * 100 / self.df['close_pre']).__round__(2).fillna(0)
|
|
143
143
|
if self.volume:
|
|
144
|
-
self.df['
|
|
145
|
-
self.df['
|
|
144
|
+
self.df['volume_pre'] = self.df[self.volume].shift(1)
|
|
145
|
+
self.df['compare_volume'] = (self.df[self.volume] - self.df['volume_pre']).fillna(0)
|
|
146
|
+
self.df['rate_volume'] = (self.df['compare_volume'] * 100 / self.df['volume_pre']).__round__(2).fillna(0)
|
|
146
147
|
|
|
147
148
|
self.df['space_box_candle'] = (self.df[self.high] - self.df[self.low]) / 5
|
|
148
149
|
self.df['bottom_box_candle'] = self.df[self.low] - self.df['space_box_candle']
|
|
@@ -233,7 +234,7 @@ class CrossLineMixin(EventMixin):
|
|
|
233
234
|
|
|
234
235
|
if self.in_price_chart or self.in_volume_chart:
|
|
235
236
|
self._restore_region()
|
|
236
|
-
self._draw_crossline(e
|
|
237
|
+
self._draw_crossline(e)
|
|
237
238
|
self.figure.canvas.blit()
|
|
238
239
|
else:
|
|
239
240
|
if self._erase_crossline():
|
|
@@ -248,10 +249,10 @@ class CrossLineMixin(EventMixin):
|
|
|
248
249
|
return True
|
|
249
250
|
return False
|
|
250
251
|
|
|
251
|
-
def _draw_crossline(self, e: MouseEvent
|
|
252
|
+
def _draw_crossline(self, e: MouseEvent):
|
|
252
253
|
x, y = (e.xdata, e.ydata)
|
|
253
254
|
|
|
254
|
-
if in_price_chart:
|
|
255
|
+
if self.in_price_chart:
|
|
255
256
|
self.collection_price_crossline.set_segments([((x, self.price_ymin), (x, self.price_ymax)), ((self.vxmin, y), (self.vxmax, y))])
|
|
256
257
|
self.collection_volume_crossline.set_segments([((x, 0), (x, self.volume_ymax))])
|
|
257
258
|
else:
|
|
@@ -262,14 +263,14 @@ class CrossLineMixin(EventMixin):
|
|
|
262
263
|
self.collection_price_crossline.draw(renderer)
|
|
263
264
|
self.collection_volume_crossline.draw(renderer)
|
|
264
265
|
|
|
265
|
-
self._draw_text_artist(e
|
|
266
|
+
self._draw_text_artist(e)
|
|
266
267
|
return
|
|
267
268
|
|
|
268
|
-
def _draw_text_artist(self, e: MouseEvent
|
|
269
|
+
def _draw_text_artist(self, e: MouseEvent):
|
|
269
270
|
x, y = (e.xdata, e.ydata)
|
|
270
271
|
|
|
271
272
|
renderer = self.figure.canvas.renderer
|
|
272
|
-
if in_price_chart:
|
|
273
|
+
if self.in_price_chart:
|
|
273
274
|
# 가격
|
|
274
275
|
self.artist_text_price.set_text(f'{float_to_str(y, self.digit_price)}{self.unit_price}')
|
|
275
276
|
self.artist_text_price.set_x(self.v0 if self.veighth < x else self.vsixth)
|
|
@@ -297,17 +298,20 @@ class CrossLineMixin(EventMixin):
|
|
|
297
298
|
|
|
298
299
|
|
|
299
300
|
class BoxMixin(CrossLineMixin):
|
|
300
|
-
def _draw_crossline(self, e
|
|
301
|
-
super()._draw_crossline(e
|
|
302
|
-
self._draw_box_artist(e
|
|
301
|
+
def _draw_crossline(self, e):
|
|
302
|
+
super()._draw_crossline(e)
|
|
303
|
+
self._draw_box_artist(e)
|
|
303
304
|
return
|
|
304
305
|
|
|
305
|
-
def _draw_box_artist(self, e: MouseEvent
|
|
306
|
+
def _draw_box_artist(self, e: MouseEvent):
|
|
306
307
|
y = e.ydata
|
|
307
308
|
|
|
308
309
|
renderer = self.figure.canvas.renderer
|
|
310
|
+
|
|
311
|
+
self.in_candle = False
|
|
312
|
+
self.in_volumebar = False
|
|
309
313
|
if self.intx is not None:
|
|
310
|
-
if in_price_chart:
|
|
314
|
+
if self.in_price_chart:
|
|
311
315
|
# 박스 크기
|
|
312
316
|
high = self.df['top_box_candle'][self.intx]
|
|
313
317
|
low = self.df['bottom_box_candle'][self.intx]
|
|
@@ -317,21 +321,19 @@ class BoxMixin(CrossLineMixin):
|
|
|
317
321
|
high, low = (high+sub, low-sub)
|
|
318
322
|
|
|
319
323
|
# 커서가 캔들 사이에 있는지 확인
|
|
320
|
-
if
|
|
321
|
-
else:
|
|
324
|
+
if low <= y and y <= high:
|
|
322
325
|
# 캔들 강조
|
|
323
326
|
self.in_candle = True
|
|
324
327
|
x1, x2 = (self.intx-0.3, self.intx+1.3)
|
|
325
328
|
self.collection_price_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
|
|
326
329
|
self.collection_price_box.draw(renderer)
|
|
327
|
-
elif self.volume:
|
|
330
|
+
elif self.in_volume_chart and self.volume:
|
|
328
331
|
# 거래량 강조
|
|
329
332
|
high = self.df['max_box_volume'][self.intx]
|
|
330
333
|
low = 0
|
|
331
334
|
if high < self.min_height_box_volume: high = self.min_height_box_volume
|
|
332
335
|
|
|
333
|
-
if
|
|
334
|
-
else:
|
|
336
|
+
if low <= y and y <= high:
|
|
335
337
|
self.in_volumebar = True
|
|
336
338
|
x1, x2 = (self.intx-0.3, self.intx+1.3)
|
|
337
339
|
self.collection_volume_box.set_segments([((x1, high), (x2, high), (x2, low), (x1, low), (x1, high))])
|
|
@@ -394,8 +396,8 @@ class InfoMixin(BoxMixin):
|
|
|
394
396
|
self._length_text = lenth_high if lenth_volume < lenth_high else lenth_volume
|
|
395
397
|
return
|
|
396
398
|
|
|
397
|
-
def _draw_box_artist(self, e
|
|
398
|
-
super()._draw_box_artist(e
|
|
399
|
+
def _draw_box_artist(self, e):
|
|
400
|
+
super()._draw_box_artist(e)
|
|
399
401
|
|
|
400
402
|
if self.intx is not None:
|
|
401
403
|
if self.in_candle: self._draw_candle_info_artist(e)
|
seolpyo_mplchart/_draw.py
CHANGED
|
@@ -251,18 +251,11 @@ class CandleSegmentMixin(DataMixin):
|
|
|
251
251
|
tuple[tuple[float, float]]: candle segment
|
|
252
252
|
"""
|
|
253
253
|
return (
|
|
254
|
-
(x, high),
|
|
255
|
-
(x, top),
|
|
256
|
-
(left, top),
|
|
257
|
-
(left, bottom),
|
|
258
|
-
(x, bottom),
|
|
259
|
-
(x, low),
|
|
260
|
-
(x, bottom),
|
|
261
|
-
(right, bottom),
|
|
262
|
-
(right, top),
|
|
263
|
-
(x, top),
|
|
264
|
-
(x, high),
|
|
265
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)
|
|
266
259
|
)
|
|
267
260
|
|
|
268
261
|
def _create_candle_segments(self):
|
|
@@ -278,8 +271,7 @@ class CandleSegmentMixin(DataMixin):
|
|
|
278
271
|
segment_candle.append(
|
|
279
272
|
self.get_candle_segment(
|
|
280
273
|
is_up=is_up,
|
|
281
|
-
x=x,
|
|
282
|
-
left=left, right=right,
|
|
274
|
+
x=x, left=left, right=right,
|
|
283
275
|
top=top, bottom=bottom,
|
|
284
276
|
high=high, low=low,
|
|
285
277
|
)
|
|
@@ -419,11 +411,8 @@ class VolumeSegmentMixin(MaSegmentMixin):
|
|
|
419
411
|
tuple[tuple[float, float]]: volume bar segment
|
|
420
412
|
"""
|
|
421
413
|
return (
|
|
422
|
-
(left, top),
|
|
423
|
-
(
|
|
424
|
-
(right, 0),
|
|
425
|
-
(right, top),
|
|
426
|
-
(left, top),
|
|
414
|
+
(left, 0), (left, top),
|
|
415
|
+
(right, top), (right, 0),
|
|
427
416
|
)
|
|
428
417
|
|
|
429
418
|
def _create_volume_segments(self):
|
seolpyo_mplchart/_slider.py
CHANGED
|
@@ -131,7 +131,9 @@ class CollectionMixin(PlotMixin):
|
|
|
131
131
|
keys.append('x')
|
|
132
132
|
keys.append(f'ma{i}')
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
series = self.df[keys + ['x', self.close]]
|
|
135
|
+
series['x'] = series['x'] - 0.5
|
|
136
|
+
segment_slider = series.values
|
|
135
137
|
segment_slider = segment_slider.reshape(segment_slider.shape[0], len(self.list_ma)+1, 2).swapaxes(0, 1)
|
|
136
138
|
self.collection_slider.set_segments(segment_slider)
|
|
137
139
|
linewidth = [1 for _ in self.list_ma]
|
|
@@ -300,7 +302,7 @@ class MouseMoveMixin(BackgroundMixin):
|
|
|
300
302
|
self.figure.canvas.blit()
|
|
301
303
|
elif self.in_price_chart or self.in_volume_chart:
|
|
302
304
|
self._restore_region()
|
|
303
|
-
self._draw_crossline(e
|
|
305
|
+
self._draw_crossline(e)
|
|
304
306
|
self.figure.canvas.blit()
|
|
305
307
|
else:
|
|
306
308
|
if self._erase_crossline():
|
|
@@ -327,13 +329,16 @@ class MouseMoveMixin(BackgroundMixin):
|
|
|
327
329
|
navleft, navright = self.navcoordinate
|
|
328
330
|
if navleft == navright: return
|
|
329
331
|
|
|
330
|
-
x = e.xdata
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
x = e.xdata.__round__()
|
|
333
|
+
|
|
334
|
+
leftmin = navleft - self._navLineWidth
|
|
335
|
+
leftmax = navleft + self._navLineWidth_half
|
|
336
|
+
rightmin = navright - self._navLineWidth_half
|
|
337
|
+
rightmax = navright + self._navLineWidth
|
|
333
338
|
if x < leftmin: self.figure.canvas.set_cursor(cursors.POINTER)
|
|
334
|
-
elif x
|
|
339
|
+
elif x <= leftmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
335
340
|
elif x < rightmin: self.figure.canvas.set_cursor(cursors.MOVE)
|
|
336
|
-
elif x
|
|
341
|
+
elif x <= rightmax: self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
337
342
|
else: self.figure.canvas.set_cursor(cursors.POINTER)
|
|
338
343
|
return
|
|
339
344
|
|
|
@@ -373,10 +378,10 @@ class MouseMoveMixin(BackgroundMixin):
|
|
|
373
378
|
self.artist_text_slider.draw(renderer)
|
|
374
379
|
return
|
|
375
380
|
|
|
376
|
-
def _draw_crossline(self, e: MouseEvent
|
|
381
|
+
def _draw_crossline(self, e: MouseEvent):
|
|
377
382
|
self.collection_slider_vline.set_segments([((e.xdata, self.slider_ymin), (e.xdata, self.slider_ymax))])
|
|
378
383
|
self.collection_slider_vline.draw(self.figure.canvas.renderer)
|
|
379
|
-
return super()._draw_crossline(e
|
|
384
|
+
return super()._draw_crossline(e)
|
|
380
385
|
|
|
381
386
|
|
|
382
387
|
class ClickMixin(MouseMoveMixin):
|
|
@@ -400,25 +405,26 @@ class ClickMixin(MouseMoveMixin):
|
|
|
400
405
|
self.is_click_slider = True
|
|
401
406
|
self.figure.canvas.set_cursor(cursors.RESIZE_HORIZONTAL)
|
|
402
407
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
leftmax =
|
|
407
|
-
rightmin =
|
|
408
|
+
navleft, navright = self.navcoordinate
|
|
409
|
+
x = e.xdata.__round__()
|
|
410
|
+
|
|
411
|
+
leftmax = navleft + self._navLineWidth_half
|
|
412
|
+
rightmin = navright - self._navLineWidth_half
|
|
408
413
|
|
|
409
|
-
grater_than_left
|
|
414
|
+
grater_than_left = leftmax < x
|
|
415
|
+
less_then_right = x < rightmin
|
|
410
416
|
if grater_than_left and less_then_right:
|
|
411
417
|
self.is_move = True
|
|
412
418
|
self.x_click = x
|
|
413
419
|
else:
|
|
414
|
-
leftmin =
|
|
415
|
-
rightmax =
|
|
420
|
+
leftmin = navleft - self._navLineWidth
|
|
421
|
+
rightmax = navright + self._navLineWidth
|
|
416
422
|
if not grater_than_left and leftmin <= x:
|
|
417
423
|
self.click_navleft = True
|
|
418
|
-
self.x_click =
|
|
424
|
+
self.x_click = navright
|
|
419
425
|
elif not less_then_right and x <= rightmax:
|
|
420
426
|
self.click_navright = True
|
|
421
|
-
self.x_click =
|
|
427
|
+
self.x_click = navleft
|
|
422
428
|
else:
|
|
423
429
|
self.x_click = x
|
|
424
430
|
return
|
|
@@ -561,7 +567,7 @@ class ChartClickMixin(ReleaseMixin):
|
|
|
561
567
|
elif self.in_price_chart or self.in_volume_chart:
|
|
562
568
|
self._restore_region(self.is_click_chart)
|
|
563
569
|
if not self.is_click_chart:
|
|
564
|
-
self._draw_crossline(e
|
|
570
|
+
self._draw_crossline(e)
|
|
565
571
|
else: self._move_chart(e)
|
|
566
572
|
self.figure.canvas.blit()
|
|
567
573
|
else:
|