Enhancements and Bug Fixes
Tables Feature - Added the `create_table` method, which returns a `Table` object. This can be used to display watchlists, order windows, position windows and more. - See the new page on the docs for more information! Bugs - Fixed a bug preventing the named column of a line to not work as a label of the series. - Fixed a bug causing drawings loaded from the minute timeframe to not show on a daily timeframe. - Fixed a bug causing `chart.exit` to not work. - Fixed a bug preventing the chart from being moved after placing a ray. - Fixed the ‘price in hoveringOver’ web console error. Enhancements - The date/time column can also be the `name` of the passed series object. - Added the `label` method to `HorizontalLine`, allowing for the price line label of horizontal lines to be updated. - `None` or an empty DataFrame can now be passed to `line.set` as a means to clear it. - Seperate Chart objects will now run on the same pywebview instance. This means that any Chart objects created after the first will inherit the first Chart’s API. - Reorganized the documentation for clarity.
This commit is contained in:
@ -5,6 +5,10 @@ import webview
|
||||
from lightweight_charts.abstract import LWC
|
||||
|
||||
|
||||
chart = None
|
||||
num_charts = 0
|
||||
|
||||
|
||||
class CallbackAPI:
|
||||
def __init__(self, emit_queue, return_queue):
|
||||
self.emit_q, self.return_q = emit_queue, return_queue
|
||||
@ -17,33 +21,43 @@ class CallbackAPI:
|
||||
|
||||
|
||||
class PyWV:
|
||||
def __init__(self, q, exit, loaded, html, width, height, x, y, on_top, maximize, debug, emit_queue, return_queue):
|
||||
def __init__(self, q, start: mp.Event, exit, loaded, html, width, height, x, y, on_top, maximize, debug, emit_queue, return_queue):
|
||||
if maximize:
|
||||
width, height = webview.screens[0].width, webview.screens[0].height
|
||||
self.queue = q
|
||||
self.exit = exit
|
||||
self.loaded = loaded
|
||||
self.debug = debug
|
||||
js_api = CallbackAPI(emit_queue, return_queue)
|
||||
self.webview = webview.create_window('', html=html, on_top=on_top, js_api=js_api, width=width, height=height,
|
||||
x=x, y=y, background_color='#000000')
|
||||
self.webview.events.loaded += self.on_js_load
|
||||
self.loop()
|
||||
self.callback_api = CallbackAPI(emit_queue, return_queue)
|
||||
self.loaded: list = loaded
|
||||
|
||||
def loop(self):
|
||||
self.windows = []
|
||||
self.create_window(html, on_top, width, height, x, y)
|
||||
|
||||
start.wait()
|
||||
webview.start(debug=debug)
|
||||
self.exit.set()
|
||||
|
||||
def create_window(self, html, on_top, width, height, x, y):
|
||||
self.windows.append(webview.create_window(
|
||||
'', html=html, on_top=on_top, js_api=self.callback_api,
|
||||
width=width, height=height, x=x, y=y, background_color='#000000'))
|
||||
self.windows[-1].events.loaded += lambda: self.loop(self.loaded[len(self.windows)-1])
|
||||
|
||||
def loop(self, loaded):
|
||||
loaded.set()
|
||||
while 1:
|
||||
arg = self.queue.get()
|
||||
if arg in ('start', 'show', 'hide', 'exit'):
|
||||
webview.start(debug=self.debug) if arg == 'start' else getattr(self.webview, arg)()
|
||||
self.exit.set() if arg in ('start', 'exit') else None
|
||||
i, arg = self.queue.get()
|
||||
if i == 'create_window':
|
||||
self.create_window(*arg)
|
||||
elif arg in ('show', 'hide'):
|
||||
getattr(self.windows[i], arg)()
|
||||
elif arg == 'exit':
|
||||
self.exit.set()
|
||||
else:
|
||||
try:
|
||||
self.webview.evaluate_js(arg)
|
||||
self.windows[i].evaluate_js(arg)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
def on_js_load(self): self.loaded.set(), self.loop()
|
||||
|
||||
|
||||
class Chart(LWC):
|
||||
def __init__(self, volume_enabled: bool = True, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
||||
@ -51,14 +65,30 @@ class Chart(LWC):
|
||||
api: object = None, topbar: bool = False, searchbox: bool = False, toolbox: bool = False,
|
||||
inner_width: float = 1.0, inner_height: float = 1.0, dynamic_loading: bool = False, scale_candles_only: bool = False):
|
||||
super().__init__(volume_enabled, inner_width, inner_height, dynamic_loading, scale_candles_only, topbar, searchbox, toolbox, 'pywebview.api.callback')
|
||||
self._q, self._emit_q, self._return_q = (mp.Queue() for _ in range(3))
|
||||
self._exit, self._loaded = mp.Event(), mp.Event()
|
||||
self._script_func = self._q.put
|
||||
self._api = api
|
||||
self._process = mp.Process(target=PyWV, args=(self._q, self._exit, self._loaded, self._html,
|
||||
width, height, x, y, on_top, maximize, debug,
|
||||
self._emit_q, self._return_q), daemon=True)
|
||||
self._process.start()
|
||||
global chart, num_charts
|
||||
|
||||
if chart:
|
||||
self._q, self._exit, self._start, self._process = chart._q, chart._exit, chart._start, chart._process
|
||||
self._emit_q, self._return_q = mp.Queue(), mp.Queue()
|
||||
chart._charts[self.id] = self
|
||||
self._api = chart._api
|
||||
self._loaded = chart._loaded_list[num_charts]
|
||||
self._q.put(('create_window', (self._html, on_top, width, height, x, y)))
|
||||
else:
|
||||
self._api = api
|
||||
self._q, self._emit_q, self._return_q = (mp.Queue() for _ in range(3))
|
||||
self._loaded_list = [mp.Event() for _ in range(10)]
|
||||
self._loaded = self._loaded_list[0]
|
||||
self._exit, self._start = (mp.Event() for _ in range(2))
|
||||
self._process = mp.Process(target=PyWV, args=(self._q, self._start, self._exit, self._loaded_list, self._html,
|
||||
width, height, x, y, on_top, maximize, debug,
|
||||
self._emit_q, self._return_q), daemon=True)
|
||||
self._process.start()
|
||||
chart = self
|
||||
|
||||
self.i = num_charts
|
||||
num_charts += 1
|
||||
self._script_func = lambda s: self._q.put((self.i, s))
|
||||
|
||||
def show(self, block: bool = False):
|
||||
"""
|
||||
@ -66,11 +96,11 @@ class Chart(LWC):
|
||||
:param block: blocks execution until the chart is closed.
|
||||
"""
|
||||
if not self.loaded:
|
||||
self._q.put('start')
|
||||
self._start.set()
|
||||
self._loaded.wait()
|
||||
self._on_js_load()
|
||||
else:
|
||||
self._q.put('show')
|
||||
self._q.put((self.i, 'show'))
|
||||
if block:
|
||||
asyncio.run(self.show_async(block=True))
|
||||
|
||||
@ -88,13 +118,15 @@ class Chart(LWC):
|
||||
return
|
||||
elif not self._emit_q.empty():
|
||||
name, chart_id, arg = self._emit_q.get()
|
||||
self._api.chart = self._charts[chart_id]
|
||||
if name == 'save_drawings':
|
||||
self._api.chart.toolbox._save_drawings(arg)
|
||||
continue
|
||||
fixed_callbacks = ('on_search', 'on_horizontal_line_move')
|
||||
func = self._methods[name] if name not in fixed_callbacks else getattr(self._api, name)
|
||||
if hasattr(self._api.chart, 'topbar') and (widget := self._api.chart.topbar._widget_with_method(name)):
|
||||
if self._api:
|
||||
self._api.chart = self._charts[chart_id]
|
||||
if self._api and name == 'save_drawings':
|
||||
func = self._api.chart.toolbox._save_drawings
|
||||
elif name in ('on_search', 'on_horizontal_line_move'):
|
||||
func = getattr(self._api, name)
|
||||
else:
|
||||
func = self._methods[name]
|
||||
if self._api and hasattr(self._api.chart, 'topbar') and (widget := self._api.chart.topbar._widget_with_method(name)):
|
||||
widget.value = arg
|
||||
await func() if asyncio.iscoroutinefunction(func) else func()
|
||||
else:
|
||||
@ -106,18 +138,22 @@ class Chart(LWC):
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
|
||||
def hide(self):
|
||||
"""
|
||||
Hides the chart window.\n
|
||||
"""
|
||||
self._q.put('hide')
|
||||
self._q.put((self.i, 'hide'))
|
||||
|
||||
def exit(self):
|
||||
"""
|
||||
Exits and destroys the chart window.\n
|
||||
"""
|
||||
self._q.put('exit')
|
||||
self._exit.wait()
|
||||
if not self.loaded:
|
||||
global num_charts, chart
|
||||
chart = None
|
||||
num_charts = 0
|
||||
else:
|
||||
self._q.put((self.i, 'exit'))
|
||||
self._exit.wait()
|
||||
self._process.terminate()
|
||||
del self
|
||||
|
||||
Reference in New Issue
Block a user