Polygon:
- Added async methods to polygon. - The `requests` library is no longer required, with `urllib` being used instead. - Added the `get_bar_data` function, which returns a dataframe of aggregate data from polygon. - Opened up the `subscribe` and `unsubscribe` functions Enhancements: - Tables will now scroll when the rows exceed table height. Bugs: - Fixed a bug preventing async functions being used with horizontal line event. - Fixed a bug causing the legend to show duplicate lines if the line was created after the legend. - Fixed a bug causing the line hide icon to persist within the legend after deletion (#75) - Fixed a bug causing the search box to be unfocused when the chart is loaded.
This commit is contained in:
@ -1,42 +1,42 @@
|
||||
import asyncio
|
||||
import multiprocessing as mp
|
||||
from base64 import b64decode
|
||||
import webview
|
||||
|
||||
from lightweight_charts.abstract import LWC
|
||||
|
||||
|
||||
chart = None
|
||||
num_charts = 0
|
||||
from lightweight_charts import abstract
|
||||
from .util import parse_event_message
|
||||
|
||||
|
||||
class CallbackAPI:
|
||||
def __init__(self, emit_queue, return_queue):
|
||||
self.emit_q, self.return_q = emit_queue, return_queue
|
||||
def __init__(self, emit_queue):
|
||||
self.emit_q = emit_queue
|
||||
|
||||
def callback(self, message: str):
|
||||
name, args = message.split('_~_')
|
||||
self.return_q.put(*args) if name == 'return' else self.emit_q.put((name, args.split(';;;')))
|
||||
self.emit_q.put(message)
|
||||
|
||||
|
||||
class PyWV:
|
||||
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
|
||||
def __init__(self, q, start_ev, exit_ev, loaded, emit_queue, return_queue, html, debug,
|
||||
width, height, x, y, on_top, maximize):
|
||||
self.queue = q
|
||||
self.exit = exit
|
||||
self.callback_api = CallbackAPI(emit_queue, return_queue)
|
||||
self.return_queue = return_queue
|
||||
self.exit = exit_ev
|
||||
self.callback_api = CallbackAPI(emit_queue)
|
||||
self.loaded: list = loaded
|
||||
self.html = html
|
||||
|
||||
self.windows = []
|
||||
self.create_window(html, on_top, width, height, x, y)
|
||||
self.create_window(width, height, x, y, on_top, maximize)
|
||||
|
||||
start.wait()
|
||||
start_ev.wait()
|
||||
webview.start(debug=debug)
|
||||
self.exit.set()
|
||||
|
||||
def create_window(self, html, on_top, width, height, x, y):
|
||||
def create_window(self, width, height, x, y, on_top, maximize):
|
||||
if maximize:
|
||||
width, height = webview.screens[0].width, webview.screens[0].height
|
||||
self.windows.append(webview.create_window(
|
||||
'', html=html, on_top=on_top, js_api=self.callback_api,
|
||||
'', html=self.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])
|
||||
|
||||
@ -47,57 +47,62 @@ class PyWV:
|
||||
if i == 'create_window':
|
||||
self.create_window(*arg)
|
||||
elif arg in ('show', 'hide'):
|
||||
getattr(self.windows[i], arg)()
|
||||
getattr(self.windows[i], arg)()
|
||||
elif arg == 'exit':
|
||||
self.exit.set()
|
||||
else:
|
||||
try:
|
||||
self.windows[i].evaluate_js(arg)
|
||||
if '_~_~RETURN~_~_' in arg:
|
||||
self.return_queue.put(self.windows[i].evaluate_js(arg[14:]))
|
||||
else:
|
||||
self.windows[i].evaluate_js(arg)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
|
||||
class Chart(LWC):
|
||||
class Chart(abstract.AbstractChart):
|
||||
MAX_WINDOWS = 10
|
||||
_window_num = 0
|
||||
_main_window_handlers = None
|
||||
_exit, _start = (mp.Event() for _ in range(2))
|
||||
_q, _emit_q, _return_q = (mp.Queue() for _ in range(3))
|
||||
_loaded_list = [mp.Event() for _ in range(MAX_WINDOWS)]
|
||||
|
||||
def __init__(self, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
||||
on_top: bool = False, maximize: bool = False, debug: bool = False, toolbox: bool = False,
|
||||
inner_width: float = 1.0, inner_height: float = 1.0, scale_candles_only: bool = False):
|
||||
super().__init__(inner_width, inner_height, scale_candles_only, toolbox, 'pywebview.api.callback')
|
||||
global chart, num_charts
|
||||
self._i = Chart._window_num
|
||||
self._loaded = Chart._loaded_list[self._i]
|
||||
window = abstract.Window(lambda s: self._q.put((self._i, s)), 'pywebview.api.callback')
|
||||
abstract.Window._return_q = Chart._return_q
|
||||
Chart._window_num += 1
|
||||
self.is_alive = True
|
||||
|
||||
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()
|
||||
for key, val in self._handlers.items():
|
||||
chart._handlers[key] = val
|
||||
self._handlers = chart._handlers
|
||||
self._loaded = chart._loaded_list[num_charts]
|
||||
self._q.put(('create_window', (self._html, on_top, width, height, x, y)))
|
||||
else:
|
||||
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)
|
||||
if self._i == 0:
|
||||
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox)
|
||||
Chart._main_window_handlers = self.win.handlers
|
||||
self._process = mp.Process(target=PyWV, args=(
|
||||
self._q, self._start, self._exit, Chart._loaded_list,
|
||||
self._emit_q, self._return_q, abstract.TEMPLATE, debug,
|
||||
width, height, x, y, on_top, maximize,
|
||||
), 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))
|
||||
else:
|
||||
window.handlers = Chart._main_window_handlers
|
||||
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox)
|
||||
self._q.put(('create_window', (abstract.TEMPLATE, on_top, width, height, x, y)))
|
||||
|
||||
def show(self, block: bool = False):
|
||||
"""
|
||||
Shows the chart window.\n
|
||||
:param block: blocks execution until the chart is closed.
|
||||
"""
|
||||
if not self.loaded:
|
||||
if not self.win.loaded:
|
||||
self._start.set()
|
||||
self._loaded.wait()
|
||||
self._on_js_load()
|
||||
self.win.on_js_load()
|
||||
else:
|
||||
self._q.put((self.i, 'show'))
|
||||
self._q.put((self._i, 'show'))
|
||||
if block:
|
||||
asyncio.run(self.show_async(block=True))
|
||||
|
||||
@ -107,20 +112,19 @@ class Chart(LWC):
|
||||
asyncio.create_task(self.show_async(block=True))
|
||||
return
|
||||
try:
|
||||
from lightweight_charts import polygon
|
||||
[asyncio.create_task(self.polygon.async_set(*args)) for args in polygon._set_on_load]
|
||||
while 1:
|
||||
while self._emit_q.empty() and not self._exit.is_set() and self.polygon._q.empty():
|
||||
while self._emit_q.empty() and not self._exit.is_set():
|
||||
await asyncio.sleep(0.05)
|
||||
if self._exit.is_set():
|
||||
self._exit.clear()
|
||||
self.is_alive = False
|
||||
return
|
||||
elif not self._emit_q.empty():
|
||||
name, args = self._emit_q.get()
|
||||
func = self._handlers[name]
|
||||
func, args = parse_event_message(self.win, self._emit_q.get())
|
||||
await func(*args) if asyncio.iscoroutinefunction(func) else func(*args)
|
||||
continue
|
||||
value = self.polygon._q.get()
|
||||
func, args = value[0], value[1:]
|
||||
func(*args)
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
@ -128,16 +132,26 @@ class Chart(LWC):
|
||||
"""
|
||||
Hides the chart window.\n
|
||||
"""
|
||||
self._q.put((self.i, 'hide'))
|
||||
self._q.put((self._i, 'hide'))
|
||||
|
||||
def exit(self):
|
||||
"""
|
||||
Exits and destroys the chart window.\n
|
||||
"""
|
||||
global num_charts, chart
|
||||
chart = None
|
||||
num_charts = 0
|
||||
self._q.put((self.i, 'exit'))
|
||||
self._exit.wait()
|
||||
self._q.put((self._i, 'exit'))
|
||||
self._exit.wait() if self.win.loaded else None
|
||||
self._process.terminate()
|
||||
del self
|
||||
|
||||
Chart._main_window_handlers = None
|
||||
Chart._window_num = 0
|
||||
Chart._q = mp.Queue()
|
||||
self.is_alive = False
|
||||
|
||||
def screenshot(self) -> bytes:
|
||||
"""
|
||||
Takes a screenshot. This method can only be used after the chart window is visible.
|
||||
:return: a bytes object containing a screenshot of the chart.
|
||||
"""
|
||||
self.run_script(f'_~_~RETURN~_~_{self.id}.chart.takeScreenshot().toDataURL()')
|
||||
serial_data = self.win._return_q.get()
|
||||
return b64decode(serial_data.split(',')[1])
|
||||
|
||||
Reference in New Issue
Block a user