- Fixed a bug causing loading times for large amounts of data to be increased significantly.
- BETA: Dynamic candlestick loading
- the config method has been removed, and its methods can now be found in various places:
- right_padding: moved to the ‘time_scale’ method.
- mode: moved to the ‘price_scale’ method.
- title: declared in the new ‘title’ method.
- It is now possible to update titles, horizontal_lines, and markers within Line objects.
This commit is contained in:
@ -12,7 +12,7 @@ class PyWV:
|
|||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.js_api = js_api
|
self.js_api = js_api
|
||||||
self.webview = webview.create_window('', html=html, on_top=on_top, js_api=js_api,
|
self.webview = webview.create_window('', html=html, on_top=on_top, js_api=js_api,
|
||||||
width=width, height=height, x=x, y=y)
|
width=width, height=height, x=x, y=y, background_color='#000000')
|
||||||
self.webview.events.loaded += self.on_js_load
|
self.webview.events.loaded += self.on_js_load
|
||||||
self.loop()
|
self.loop()
|
||||||
|
|
||||||
@ -38,8 +38,8 @@ class PyWV:
|
|||||||
class Chart(LWC):
|
class Chart(LWC):
|
||||||
def __init__(self, volume_enabled: bool = True, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
def __init__(self, volume_enabled: bool = True, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
||||||
on_top: bool = False, debug: bool = False,
|
on_top: bool = False, debug: bool = False,
|
||||||
inner_width: float = 1.0, inner_height: float = 1.0):
|
inner_width: float = 1.0, inner_height: float = 1.0, dynamic_loading: bool = False):
|
||||||
super().__init__(volume_enabled, inner_width, inner_height)
|
super().__init__(volume_enabled, inner_width, inner_height, dynamic_loading)
|
||||||
self._js_api_code = 'pywebview.api.onClick'
|
self._js_api_code = 'pywebview.api.onClick'
|
||||||
self._q = mp.Queue()
|
self._q = mp.Queue()
|
||||||
self._script_func = self._q.put
|
self._script_func = self._q.put
|
||||||
|
|||||||
@ -7,15 +7,128 @@ from lightweight_charts.util import LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, C
|
|||||||
MissingColumn, _js_bool, _price_scale_mode, PRICE_SCALE_MODE, _marker_position, _marker_shape, IDGen
|
MissingColumn, _js_bool, _price_scale_mode, PRICE_SCALE_MODE, _marker_position, _marker_shape, IDGen
|
||||||
|
|
||||||
|
|
||||||
class Line:
|
class SeriesCommon:
|
||||||
|
def _set_interval(self, df: pd.DataFrame):
|
||||||
|
common_interval = pd.to_datetime(df['time']).diff().value_counts()
|
||||||
|
self._interval = common_interval.index[0]
|
||||||
|
|
||||||
|
def _df_datetime_format(self, df: pd.DataFrame):
|
||||||
|
if 'date' in df.columns:
|
||||||
|
df = df.rename(columns={'date': 'time'})
|
||||||
|
self._set_interval(df)
|
||||||
|
# df['time'] = df['time'].apply(self._datetime_format)
|
||||||
|
df['time'] = self._datetime_format(df['time'])
|
||||||
|
return df
|
||||||
|
|
||||||
|
def _series_datetime_format(self, series):
|
||||||
|
if 'date' in series.keys():
|
||||||
|
series = series.rename({'date': 'time'})
|
||||||
|
series['time'] = self._datetime_format(series['time'])
|
||||||
|
return series
|
||||||
|
|
||||||
|
def _datetime_format(self, arg):
|
||||||
|
arg = pd.to_datetime(arg)
|
||||||
|
if self._interval != timedelta(days=1):
|
||||||
|
arg = arg.astype('int64') // 10 ** 9 if isinstance(arg, pd.Series) else arg.timestamp()
|
||||||
|
arg = self._interval.total_seconds() * (arg // self._interval.total_seconds())
|
||||||
|
else:
|
||||||
|
arg = arg.dt.strftime('%Y-%m-%d') if isinstance(arg, pd.Series) else arg.strftime('%Y-%m-%d')
|
||||||
|
return arg
|
||||||
|
|
||||||
|
def marker(self, time: datetime = None, position: MARKER_POSITION = 'below', shape: MARKER_SHAPE = 'arrow_up',
|
||||||
|
color: str = '#2196F3', text: str = '') -> str:
|
||||||
|
"""
|
||||||
|
Creates a new marker.\n
|
||||||
|
:param time: The time that the marker will be placed at. If no time is given, it will be placed at the last bar.
|
||||||
|
:param position: The position of the marker.
|
||||||
|
:param color: The color of the marker (rgb, rgba or hex).
|
||||||
|
:param shape: The shape of the marker.
|
||||||
|
:param text: The text to be placed with the marker.
|
||||||
|
:return: The id of the marker placed.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
time = self._last_bar['time'] if not time else self._datetime_format(time)
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('Chart marker created before data was set.')
|
||||||
|
marker_id = self._rand.generate()
|
||||||
|
self.run_script(f"""
|
||||||
|
{self.id}.markers.push({{
|
||||||
|
time: {time if isinstance(time, float) else f"'{time}'"},
|
||||||
|
position: '{_marker_position(position)}',
|
||||||
|
color: '{color}',
|
||||||
|
shape: '{_marker_shape(shape)}',
|
||||||
|
text: '{text}',
|
||||||
|
id: '{marker_id}'
|
||||||
|
}});
|
||||||
|
{self.id}.series.setMarkers({self.id}.markers)""")
|
||||||
|
return marker_id
|
||||||
|
|
||||||
|
def remove_marker(self, marker_id: str):
|
||||||
|
"""
|
||||||
|
Removes the marker with the given id.\n
|
||||||
|
"""
|
||||||
|
self.run_script(f'''
|
||||||
|
{self.id}.markers.forEach(function (marker) {{
|
||||||
|
if ('{marker_id}' === marker.id) {{
|
||||||
|
{self.id}.markers.splice({self.id}.markers.indexOf(marker), 1)
|
||||||
|
{self.id}.series.setMarkers({self.id}.markers)
|
||||||
|
}}
|
||||||
|
}});''')
|
||||||
|
|
||||||
|
def horizontal_line(self, price: Union[float, int], color: str = 'rgb(122, 146, 202)', width: int = 1,
|
||||||
|
style: LINE_STYLE = 'solid', text: str = '', axis_label_visible=True):
|
||||||
|
"""
|
||||||
|
Creates a horizontal line at the given price.\n
|
||||||
|
"""
|
||||||
|
var = self._rand.generate()
|
||||||
|
self.run_script(f"""
|
||||||
|
let priceLine{var} = {{
|
||||||
|
price: {price},
|
||||||
|
color: '{color}',
|
||||||
|
lineWidth: {width},
|
||||||
|
lineStyle: {_line_style(style)},
|
||||||
|
axisLabelVisible: {_js_bool(axis_label_visible)},
|
||||||
|
title: '{text}',
|
||||||
|
}};
|
||||||
|
let line{var} = {{
|
||||||
|
line: {self.id}.series.createPriceLine(priceLine{var}),
|
||||||
|
price: {price},
|
||||||
|
}};
|
||||||
|
{self.id}.horizontal_lines.push(line{var})""")
|
||||||
|
|
||||||
|
def remove_horizontal_line(self, price: Union[float, int]):
|
||||||
|
"""
|
||||||
|
Removes a horizontal line at the given price.
|
||||||
|
"""
|
||||||
|
self.run_script(f'''
|
||||||
|
{self.id}.horizontal_lines.forEach(function (line) {{
|
||||||
|
if ({price} === line.price) {{
|
||||||
|
{self.id}.series.removePriceLine(line.line);
|
||||||
|
{self.id}.horizontal_lines.splice({self.id}.horizontal_lines.indexOf(line), 1)
|
||||||
|
}}
|
||||||
|
}});''')
|
||||||
|
|
||||||
|
def title(self, title: str):
|
||||||
|
self.run_script(f'{self.id}.series.applyOptions({{title: "{title}"}})')
|
||||||
|
|
||||||
|
|
||||||
|
class Line(SeriesCommon):
|
||||||
def __init__(self, parent, color, width):
|
def __init__(self, parent, color, width):
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self.id = self._parent._rand.generate()
|
self._rand = self._parent._rand
|
||||||
|
self.id = self._rand.generate()
|
||||||
|
self.run_script = self._parent.run_script
|
||||||
|
|
||||||
self._parent.run_script(f'''
|
self._parent.run_script(f'''
|
||||||
var {self.id} = {self._parent.id}.chart.addLineSeries({{
|
var {self.id} = {{
|
||||||
color: '{color}',
|
series: {self._parent.id}.chart.addLineSeries({{
|
||||||
lineWidth: {width},
|
color: '{color}',
|
||||||
}})''')
|
lineWidth: {width},
|
||||||
|
}}),
|
||||||
|
markers: [],
|
||||||
|
horizontal_lines: [],
|
||||||
|
}}
|
||||||
|
''')
|
||||||
|
|
||||||
def set(self, data: pd.DataFrame):
|
def set(self, data: pd.DataFrame):
|
||||||
"""
|
"""
|
||||||
@ -23,7 +136,8 @@ class Line:
|
|||||||
:param data: columns: date/time, value
|
:param data: columns: date/time, value
|
||||||
"""
|
"""
|
||||||
df = self._parent._df_datetime_format(data)
|
df = self._parent._df_datetime_format(data)
|
||||||
self._parent.run_script(f'{self.id}.setData({df.to_dict("records")})')
|
self._last_bar = df.iloc[-1]
|
||||||
|
self.run_script(f'{self.id}.series.setData({df.to_dict("records")})')
|
||||||
|
|
||||||
def update(self, series: pd.Series):
|
def update(self, series: pd.Series):
|
||||||
"""
|
"""
|
||||||
@ -31,9 +145,8 @@ class Line:
|
|||||||
:param series: labels: date/time, value
|
:param series: labels: date/time, value
|
||||||
"""
|
"""
|
||||||
series = self._parent._series_datetime_format(series)
|
series = self._parent._series_datetime_format(series)
|
||||||
self._parent.run_script(f'{self.id}.update({series.to_dict()})')
|
self._last_bar = series
|
||||||
|
self.run_script(f'{self.id}.series.update({series.to_dict()})')
|
||||||
def marker(self): pass
|
|
||||||
|
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
@ -49,52 +162,29 @@ class API:
|
|||||||
click_func(data) if click_func else None
|
click_func(data) if click_func else None
|
||||||
|
|
||||||
|
|
||||||
class LWC:
|
class LWC(SeriesCommon):
|
||||||
def __init__(self, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
|
def __init__(self, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0, dynamic_loading: bool = False):
|
||||||
self._volume_enabled = volume_enabled
|
self._volume_enabled = volume_enabled
|
||||||
self._inner_width = inner_width
|
self._inner_width = inner_width
|
||||||
self._inner_height = inner_height
|
self._inner_height = inner_height
|
||||||
self._position = 'left'
|
self._dynamic_loading = dynamic_loading
|
||||||
self.loaded = False
|
|
||||||
self._rand = IDGen()
|
self._rand = IDGen()
|
||||||
self.id = self._rand.generate()
|
self.id = self._rand.generate()
|
||||||
|
self._position = 'left'
|
||||||
|
self.loaded = False
|
||||||
|
self._html = HTML
|
||||||
self._append_js = f'document.body.append({self.id}.div)'
|
self._append_js = f'document.body.append({self.id}.div)'
|
||||||
self._scripts = []
|
self._scripts = []
|
||||||
self._script_func = None
|
self._script_func = None
|
||||||
self._html = HTML
|
|
||||||
self._last_bar = None
|
self._last_bar = None
|
||||||
self._interval = None
|
self._interval = None
|
||||||
self._background_color = '#000000'
|
|
||||||
self._volume_up_color = 'rgba(83,141,131,0.8)'
|
|
||||||
self._volume_down_color = 'rgba(200,127,130,0.8)'
|
|
||||||
self._js_api = API()
|
self._js_api = API()
|
||||||
self._js_api_code = None
|
self._js_api_code = None
|
||||||
|
|
||||||
def _set_interval(self, df: pd.DataFrame):
|
self._background_color = '#000000'
|
||||||
common_interval = pd.to_datetime(df['time']).diff().value_counts()
|
self._volume_up_color = 'rgba(83,141,131,0.8)'
|
||||||
self._interval = common_interval.index[0]
|
self._volume_down_color = 'rgba(200,127,130,0.8)'
|
||||||
|
|
||||||
def _df_datetime_format(self, df: pd.DataFrame):
|
|
||||||
if 'date' in df.columns:
|
|
||||||
df = df.rename(columns={'date': 'time'})
|
|
||||||
self._set_interval(df)
|
|
||||||
df['time'] = df['time'].apply(self._datetime_format)
|
|
||||||
return df
|
|
||||||
|
|
||||||
def _series_datetime_format(self, series):
|
|
||||||
if 'date' in series.keys():
|
|
||||||
series = series.rename({'date': 'time'})
|
|
||||||
series['time'] = self._datetime_format(series['time'])
|
|
||||||
return series
|
|
||||||
|
|
||||||
def _datetime_format(self, string):
|
|
||||||
string = pd.to_datetime(string)
|
|
||||||
if self._interval != timedelta(days=1):
|
|
||||||
string = string.timestamp()
|
|
||||||
string = self._interval.total_seconds() * (string // self._interval.total_seconds())
|
|
||||||
else:
|
|
||||||
string = string.strftime('%Y-%m-%d')
|
|
||||||
return string
|
|
||||||
|
|
||||||
def _on_js_load(self):
|
def _on_js_load(self):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
@ -135,7 +225,43 @@ class LWC:
|
|||||||
bars = bars.drop(columns=['volume'])
|
bars = bars.drop(columns=['volume'])
|
||||||
|
|
||||||
bars = bars.to_dict(orient='records')
|
bars = bars.to_dict(orient='records')
|
||||||
self.run_script(f'{self.id}.series.setData({bars})')
|
self.run_script(f'''
|
||||||
|
{self.id}.candleData = {bars}
|
||||||
|
{self.id}.shownData = ({self.id}.candleData.length >= 190) ? {self.id}.candleData.slice(-190) : {self.id}.candleData
|
||||||
|
{self.id}.series.setData({self.id}.shownData);
|
||||||
|
|
||||||
|
var timer = null;
|
||||||
|
{self.id}.chart.timeScale().subscribeVisibleLogicalRangeChange(() => {{
|
||||||
|
if (timer !== null) {{
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
timer = setTimeout(() => {{
|
||||||
|
let chart = {self.id}
|
||||||
|
let logicalRange = chart.chart.timeScale().getVisibleLogicalRange();
|
||||||
|
if (logicalRange !== null) {{
|
||||||
|
let barsInfo = chart.series.barsInLogicalRange(logicalRange);
|
||||||
|
if (barsInfo === null || barsInfo.barsBefore === null || barsInfo.barsAfter === null) {{return}}
|
||||||
|
if (barsInfo !== null && barsInfo.barsBefore < 20 || barsInfo.barsAfter < 20) {{
|
||||||
|
let newBeginning = chart.candleData.indexOf(chart.shownData[0])+Math.round(barsInfo.barsBefore)-20
|
||||||
|
let newEnd = chart.candleData.indexOf(chart.shownData[chart.shownData.length-2])-Math.round(barsInfo.barsAfter)+20
|
||||||
|
if (newBeginning < 0) {{
|
||||||
|
newBeginning = 0
|
||||||
|
}}
|
||||||
|
chart.shownData = chart.candleData.slice(newBeginning, newEnd)
|
||||||
|
if (newEnd-17 <= chart.candleData.length-1) {{
|
||||||
|
chart.shownData[chart.shownData.length - 1] = Object.assign({{}}, chart.shownData[chart.shownData.length - 1]);
|
||||||
|
chart.shownData[chart.shownData.length - 1].open = chart.candleData[chart.candleData.length - 1].close;
|
||||||
|
chart.shownData[chart.shownData.length - 1].high = chart.candleData[chart.candleData.length - 1].close;
|
||||||
|
chart.shownData[chart.shownData.length - 1].low = chart.candleData[chart.candleData.length - 1].close;
|
||||||
|
chart.shownData[chart.shownData.length - 1].close = chart.candleData[chart.candleData.length - 1].close;
|
||||||
|
}}
|
||||||
|
chart.series.setData(chart.shownData);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
timer = null;
|
||||||
|
}}, 50);
|
||||||
|
}});
|
||||||
|
''') if self._dynamic_loading else self.run_script(f'{self.id}.series.setData({bars})')
|
||||||
|
|
||||||
def update(self, series, from_tick=False):
|
def update(self, series, from_tick=False):
|
||||||
"""
|
"""
|
||||||
@ -153,7 +279,29 @@ class LWC:
|
|||||||
volume['color'] = self._volume_up_color if series['close'] > series['open'] else self._volume_down_color
|
volume['color'] = self._volume_up_color if series['close'] > series['open'] else self._volume_down_color
|
||||||
self.run_script(f'{self.id}.volumeSeries.update({volume.to_dict()})')
|
self.run_script(f'{self.id}.volumeSeries.update({volume.to_dict()})')
|
||||||
series = series.drop(['volume'])
|
series = series.drop(['volume'])
|
||||||
self.run_script(f'{self.id}.series.update({series.to_dict()})')
|
bar = series.to_dict()
|
||||||
|
self.run_script(f'''
|
||||||
|
|
||||||
|
let logicalRange = {self.id}.chart.timeScale().getVisibleLogicalRange();
|
||||||
|
let barsInfo = {self.id}.series.barsInLogicalRange(logicalRange);
|
||||||
|
|
||||||
|
if ({self.id}.candleData[{self.id}.candleData.length-1].time === {bar['time']}) {{
|
||||||
|
|
||||||
|
{self.id}.shownData[{self.id}.shownData.length-1] = {bar}
|
||||||
|
{self.id}.candleData[{self.id}.candleData.length-1] = {bar}
|
||||||
|
}}
|
||||||
|
else {{
|
||||||
|
if (barsInfo.barsAfter > 0) {{
|
||||||
|
{self.id}.shownData[{self.id}.shownData.length-1] = {bar}
|
||||||
|
}}
|
||||||
|
else {{
|
||||||
|
{self.id}.shownData.push({bar})
|
||||||
|
}}
|
||||||
|
|
||||||
|
{self.id}.candleData.push({bar})
|
||||||
|
}}
|
||||||
|
{self.id}.series.update({self.id}.shownData[{self.id}.shownData.length-1])
|
||||||
|
''') if self._dynamic_loading else self.run_script(f'{self.id}.series.update({bar})')
|
||||||
|
|
||||||
def update_from_tick(self, series):
|
def update_from_tick(self, series):
|
||||||
"""
|
"""
|
||||||
@ -184,105 +332,38 @@ class LWC:
|
|||||||
"""
|
"""
|
||||||
return Line(self, color, width)
|
return Line(self, color, width)
|
||||||
|
|
||||||
def marker(self, time: datetime = None, position: MARKER_POSITION = 'below', shape: MARKER_SHAPE = 'arrow_up',
|
def price_scale(self, mode: PRICE_SCALE_MODE = 'normal', align_labels: bool = True, border_visible: bool = False,
|
||||||
color: str = '#2196F3', text: str = '') -> str:
|
border_color: str = None, text_color: str = None, entire_text_only: bool = False, ticks_visible: bool = False):
|
||||||
"""
|
|
||||||
Creates a new marker.\n
|
|
||||||
:param time: The time that the marker will be placed at. If no time is given, it will be placed at the last bar.
|
|
||||||
:param position: The position of the marker.
|
|
||||||
:param color: The color of the marker (rgb, rgba or hex).
|
|
||||||
:param shape: The shape of the marker.
|
|
||||||
:param text: The text to be placed with the marker.
|
|
||||||
:return: The id of the marker placed.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
time = self._last_bar['time'] if not time else self._datetime_format(time)
|
|
||||||
except TypeError:
|
|
||||||
raise TypeError('Chart marker created before data was set.')
|
|
||||||
marker_id = self._rand.generate()
|
|
||||||
self.run_script(f"""
|
|
||||||
markers.push({{
|
|
||||||
time: {time if isinstance(time, float) else f"'{time}'"},
|
|
||||||
position: '{_marker_position(position)}',
|
|
||||||
color: '{color}',
|
|
||||||
shape: '{_marker_shape(shape)}',
|
|
||||||
text: '{text}',
|
|
||||||
id: '{marker_id}'
|
|
||||||
}});
|
|
||||||
{self.id}.series.setMarkers(markers)""")
|
|
||||||
return marker_id
|
|
||||||
|
|
||||||
def remove_marker(self, marker_id: str):
|
|
||||||
"""
|
|
||||||
Removes the marker with the given id.\n
|
|
||||||
"""
|
|
||||||
self.run_script(f'''
|
self.run_script(f'''
|
||||||
markers.forEach(function (marker) {{
|
{self.id}.chart.priceScale('right').applyOptions({{
|
||||||
if ('{marker_id}' === marker.id) {{
|
mode: {_price_scale_mode(mode)},
|
||||||
markers.splice(markers.indexOf(marker), 1)
|
alignLabels: {_js_bool(align_labels)},
|
||||||
{self.id}.series.setMarkers(markers)
|
borderVisible: {_js_bool(border_visible)},
|
||||||
}}
|
{f'borderColor: "{border_color}",' if border_color else ''}
|
||||||
}});''')
|
{f'textColor: "{text_color}",' if text_color else ''}
|
||||||
|
entireTextOnly: {_js_bool(entire_text_only)},
|
||||||
|
ticksVisible: {_js_bool(ticks_visible)},
|
||||||
|
}})''')
|
||||||
|
|
||||||
def horizontal_line(self, price: Union[float, int], color: str = 'rgb(122, 146, 202)', width: int = 1,
|
def time_scale(self, right_offset: int = 0, min_bar_spacing: float = 0.5,
|
||||||
style: LINE_STYLE = 'solid', text: str = '', axis_label_visible=True):
|
visible: bool = True, time_visible: bool = True, seconds_visible: bool = False,
|
||||||
"""
|
border_visible: bool = True, border_color: str = None):
|
||||||
Creates a horizontal line at the given price.\n
|
|
||||||
"""
|
|
||||||
var = self._rand.generate()
|
|
||||||
self.run_script(f"""
|
|
||||||
let priceLine{var} = {{
|
|
||||||
price: {price},
|
|
||||||
color: '{color}',
|
|
||||||
lineWidth: {width},
|
|
||||||
lineStyle: {_line_style(style)},
|
|
||||||
axisLabelVisible: {_js_bool(axis_label_visible)},
|
|
||||||
title: '{text}',
|
|
||||||
}};
|
|
||||||
let line{var} = {{
|
|
||||||
line: {self.id}.series.createPriceLine(priceLine{var}),
|
|
||||||
price: {price},
|
|
||||||
}};
|
|
||||||
horizontal_lines.push(line{var})""")
|
|
||||||
|
|
||||||
def remove_horizontal_line(self, price: Union[float, int]):
|
|
||||||
"""
|
|
||||||
Removes a horizontal line at the given price.
|
|
||||||
"""
|
|
||||||
self.run_script(f'''
|
|
||||||
horizontal_lines.forEach(function (line) {{
|
|
||||||
if ({price} === line.price) {{
|
|
||||||
{self.id}.series.removePriceLine(line.line);
|
|
||||||
horizontal_lines.splice(horizontal_lines.indexOf(line), 1)
|
|
||||||
}}
|
|
||||||
}});''')
|
|
||||||
|
|
||||||
def config(self, mode: PRICE_SCALE_MODE = 'normal', title: str = None, right_padding: float = None):
|
|
||||||
"""
|
|
||||||
:param mode: Chart price scale mode.
|
|
||||||
:param title: Last price label text.
|
|
||||||
:param right_padding: How many bars of empty space to the right of the last bar.
|
|
||||||
"""
|
|
||||||
self.run_script(f'{self.id}.chart.timeScale().scrollToPosition({right_padding}, false)') if right_padding is not None else None
|
|
||||||
self.run_script(f'{self.id}.series.applyOptions({{title: "{title}"}})') if title else None
|
|
||||||
self.run_script(f"{self.id}.chart.priceScale().applyOptions({{mode: {_price_scale_mode(mode)}}})")
|
|
||||||
|
|
||||||
def time_scale(self, visible: bool = True, time_visible: bool = True, seconds_visible: bool = False):
|
|
||||||
"""
|
"""
|
||||||
Options for the time scale of the chart.
|
Options for the time scale of the chart.
|
||||||
:param visible: Time scale visibility control.
|
|
||||||
:param time_visible: Time visibility control.
|
|
||||||
:param seconds_visible: Seconds visibility control.
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
self.run_script(f'''
|
self.run_script(f'''
|
||||||
{self.id}.chart.applyOptions({{
|
{self.id}.chart.applyOptions({{
|
||||||
timeScale: {{
|
timeScale: {{
|
||||||
visible: {_js_bool(visible)},
|
rightOffset: {right_offset},
|
||||||
timeVisible: {_js_bool(time_visible)},
|
minBarSpacing: {min_bar_spacing},
|
||||||
secondsVisible: {_js_bool(seconds_visible)},
|
visible: {_js_bool(visible)},
|
||||||
}}
|
timeVisible: {_js_bool(time_visible)},
|
||||||
}})''')
|
secondsVisible: {_js_bool(seconds_visible)},
|
||||||
|
borderVisible: {_js_bool(border_visible)},
|
||||||
|
{f'borderColor: "{border_color}",' if border_color else ''}
|
||||||
|
|
||||||
|
}}
|
||||||
|
}})''')
|
||||||
|
|
||||||
def layout(self, background_color: str = None, text_color: str = None, font_size: int = None,
|
def layout(self, background_color: str = None, text_color: str = None, font_size: int = None,
|
||||||
font_family: str = None):
|
font_family: str = None):
|
||||||
@ -470,7 +551,7 @@ class SubChart(LWC):
|
|||||||
self._append_js = f'{self._parent.id}.div.parentNode.insertBefore({self.id}.div, {self._parent.id}.div.nextSibling)'
|
self._append_js = f'{self._parent.id}.div.parentNode.insertBefore({self.id}.div, {self._parent.id}.div.nextSibling)'
|
||||||
self._js_api = self._chart._js_api
|
self._js_api = self._chart._js_api
|
||||||
self._js_api_code = self._chart._js_api_code
|
self._js_api_code = self._chart._js_api_code
|
||||||
|
self.run_script = self._chart.run_script
|
||||||
self._create_chart()
|
self._create_chart()
|
||||||
if not sync:
|
if not sync:
|
||||||
return
|
return
|
||||||
@ -478,25 +559,29 @@ class SubChart(LWC):
|
|||||||
self.run_script(f'''
|
self.run_script(f'''
|
||||||
{sync_parent_var}.chart.timeScale().subscribeVisibleLogicalRangeChange((timeRange) => {{
|
{sync_parent_var}.chart.timeScale().subscribeVisibleLogicalRangeChange((timeRange) => {{
|
||||||
{self.id}.chart.timeScale().setVisibleLogicalRange(timeRange)
|
{self.id}.chart.timeScale().setVisibleLogicalRange(timeRange)
|
||||||
}});''')
|
}});
|
||||||
|
''')
|
||||||
def run_script(self, script): self._chart.run_script(script)
|
|
||||||
|
|
||||||
|
|
||||||
SCRIPT = """
|
SCRIPT = """
|
||||||
document.body.style.backgroundColor = '#000000'
|
document.body.style.backgroundColor = '#000000'
|
||||||
const markers = []
|
|
||||||
const horizontal_lines = []
|
|
||||||
const up = 'rgba(39, 157, 130, 100)'
|
const up = 'rgba(39, 157, 130, 100)'
|
||||||
const down = 'rgba(200, 97, 100, 100)'
|
const down = 'rgba(200, 97, 100, 100)'
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div')
|
||||||
|
document.body.appendChild(wrapper)
|
||||||
|
|
||||||
function makeChart(innerWidth, innerHeight) {
|
function makeChart(innerWidth, innerHeight) {
|
||||||
let chart = {}
|
let chart = {
|
||||||
chart.scale = {
|
markers: [],
|
||||||
width: innerWidth,
|
horizontal_lines: [],
|
||||||
height: innerHeight
|
div: document.createElement('div'),
|
||||||
|
legend: document.createElement('div'),
|
||||||
|
scale: {
|
||||||
|
width: innerWidth,
|
||||||
|
height: innerHeight
|
||||||
|
},
|
||||||
}
|
}
|
||||||
chart.div = document.createElement('div')
|
|
||||||
chart.chart = LightweightCharts.createChart(chart.div, {
|
chart.chart = LightweightCharts.createChart(chart.div, {
|
||||||
width: window.innerWidth*innerWidth,
|
width: window.innerWidth*innerWidth,
|
||||||
height: window.innerHeight*innerHeight,
|
height: window.innerHeight*innerHeight,
|
||||||
@ -535,7 +620,9 @@ function makeChart(innerWidth, innerHeight) {
|
|||||||
priceFormat: {type: 'volume'},
|
priceFormat: {type: 'volume'},
|
||||||
priceScaleId: '',
|
priceScaleId: '',
|
||||||
})
|
})
|
||||||
chart.legend = document.createElement('div')
|
chart.chart.priceScale('').applyOptions({
|
||||||
|
scaleMargins: {top: 0.8, bottom: 0},
|
||||||
|
});
|
||||||
chart.legend.style.position = 'absolute'
|
chart.legend.style.position = 'absolute'
|
||||||
chart.legend.style.zIndex = 1000
|
chart.legend.style.zIndex = 1000
|
||||||
chart.legend.style.width = `${(chart.scale.width*100)-8}vw`
|
chart.legend.style.width = `${(chart.scale.width*100)-8}vw`
|
||||||
@ -545,10 +632,6 @@ function makeChart(innerWidth, innerHeight) {
|
|||||||
chart.legend.style.fontSize = '11px'
|
chart.legend.style.fontSize = '11px'
|
||||||
chart.legend.style.color = 'rgb(191, 195, 203)'
|
chart.legend.style.color = 'rgb(191, 195, 203)'
|
||||||
chart.div.appendChild(chart.legend)
|
chart.div.appendChild(chart.legend)
|
||||||
|
|
||||||
chart.chart.priceScale('').applyOptions({
|
|
||||||
scaleMargins: {top: 0.8, bottom: 0}
|
|
||||||
});
|
|
||||||
return chart
|
return chart
|
||||||
}
|
}
|
||||||
function legendItemFormat(num) {
|
function legendItemFormat(num) {
|
||||||
|
|||||||
Reference in New Issue
Block a user