- Moved ChartAsync’s methods into the Chart object.

- Removed ChartAsync.
- Added the `show_async` method to `Chart`.
- Refactored how the TopBar is used. The docs explain this in detail, but a basic rundown is:
    - `corner_text` and `create_switcher` are no longer methods. The `topbar` attribute of `chart` should be used instead.
    - switchers and textboxes, now created with `chart.topbar.textbox` and `chart.topbar.switcher` require a name to be passed to them, which is used to access its instance (e.g `chart.topbar[‘timeframe’]`)
    - If you have any questions about these changes, or potential enhancements, feel free to raise an issue and I will get back to you ASAP :)

- PtQt and Wx can now use either synchronous or asynchronous callback functions

- BETA: Support for Jupyter Notebooks

- Fixed a bug causing the ‘date’ column of DataFrames passed to `set`, `update`, and `update_from_tick` to be modified.
This commit is contained in:
louisnw
2023-06-04 14:38:58 +01:00
parent a58f1e306c
commit 3a7832e0d4
27 changed files with 566 additions and 459 deletions

View File

@ -1,3 +1,6 @@
import asyncio
from inspect import iscoroutinefunction
try:
import wx.html2
except ImportError:
@ -15,16 +18,18 @@ try:
@pyqtSlot(str)
def callback(self, message):
_widget_message(self.chart, message)
except ImportError:
pass
try:
from streamlit.components.v1 import html
except ImportError:
pass
try:
from IPython.display import HTML, display
except ImportError:
pass
from lightweight_charts.chartasync import LWCAsync, ASYNC_SCRIPT
from lightweight_charts.js import LWC
from lightweight_charts.js import LWC, TopBar, CALLBACK_SCRIPT
def _widget_message(chart, string):
@ -32,12 +37,13 @@ def _widget_message(chart, string):
name, chart_id = messages[:2]
args = messages[2:]
chart.api.chart = chart._charts[chart_id]
getattr(chart.api, name)(*args)
method = getattr(chart.api, name)
asyncio.create_task(getattr(chart.api, name)(*args)) if iscoroutinefunction(method) else method(*args)
class WxChart(LWCAsync):
def __init__(self, parent, api: object = None, top_bar: bool = False, search_box: bool = False,
volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
class WxChart(LWC):
def __init__(self, parent, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0,
api: object = None, topbar: bool = False, searchbox: bool = False):
try:
self.webview: wx.html2.WebView = wx.html2.WebView.New(parent)
except NameError:
@ -48,20 +54,21 @@ class WxChart(LWCAsync):
self._script_func = self.webview.RunScript
self._js_api_code = 'window.wx_msg.postMessage.bind(window.wx_msg)'
self.webview.Bind(wx.html2.EVT_WEBVIEW_LOADED, lambda e: wx.CallLater(200, self._on_js_load))
self.webview.Bind(wx.html2.EVT_WEBVIEW_LOADED, lambda e: wx.CallLater(500, self._on_js_load))
self.webview.Bind(wx.html2.EVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, lambda e: _widget_message(self, e.GetString()))
self.webview.AddScriptMessageHandler('wx_msg')
self.webview.SetPage(self._html, '')
self.webview.AddUserScript(ASYNC_SCRIPT)
self._create_chart(top_bar)
self._make_search_box() if search_box else None
self.webview.AddUserScript(CALLBACK_SCRIPT)
self._create_chart()
self.topbar = TopBar(self) if topbar else None
self._make_search_box() if searchbox else None
def get_webview(self): return self.webview
class QtChart(LWCAsync):
def __init__(self, widget=None, api: object = None, top_bar: bool = False, search_box: bool = False,
class QtChart(LWC):
def __init__(self, widget=None, api: object = None, topbar: bool = False, searchbox: bool = False,
volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
try:
self.webview = QWebEngineView(widget)
@ -89,22 +96,21 @@ class QtChart(LWCAsync):
{self._html[85:]}
'''
self.webview.page().setHtml(self._html)
self.run_script(ASYNC_SCRIPT)
self._create_chart(top_bar)
self._make_search_box() if search_box else None
self.run_script(CALLBACK_SCRIPT)
self._create_chart()
self.topbar = TopBar(self) if topbar else None
self._make_search_box() if searchbox else None
def get_webview(self): return self.webview
class StreamlitChart(LWC):
class StaticLWC(LWC):
def __init__(self, volume_enabled=True, width=None, height=None, inner_width=1, inner_height=1):
super().__init__(volume_enabled, inner_width, inner_height)
self.width = width
self.height = height
self._html = self._html.replace('</script>\n</body>\n</html>', '')
self._create_chart()
def run_script(self, script): self._html += '\n' + script
@ -112,8 +118,44 @@ class StreamlitChart(LWC):
if self.loaded:
return
self.loaded = True
self._load()
def _load(self): pass
class StreamlitChart(StaticLWC):
def __init__(self, volume_enabled=True, width=None, height=None, inner_width=1, inner_height=1):
super().__init__(volume_enabled, width, height, inner_width, inner_height)
self._create_chart()
def _load(self):
try:
html(f'{self._html}</script></body></html>', width=self.width, height=self.height)
except NameError:
raise ModuleNotFoundError('streamlit.components.v1.html was not found, and must be installed to use StreamlitChart.')
class JupyterChart(StaticLWC):
def __init__(self, volume_enabled=True, width=800, height=350, inner_width=1, inner_height=1):
super().__init__(volume_enabled, width, height, inner_width, inner_height)
self._position = ""
self._create_chart(autosize=False)
self.run_script(f'''
for (var i = 0; i < document.getElementsByClassName("tv-lightweight-charts").length; i++) {{
var element = document.getElementsByClassName("tv-lightweight-charts")[i];
element.style.overflow = "visible"
}}
document.getElementById('wrapper').style.overflow = 'hidden'
document.getElementById('wrapper').style.borderRadius = '10px'
document.getElementById('wrapper').style.width = '{self.width}px'
document.getElementById('wrapper').style.height = '100%'
''')
self.run_script(f'{self.id}.chart.resize({width}, {height})')
def _load(self):
try:
display(HTML(f'{self._html}</script></body></html>'))
except NameError:
raise ModuleNotFoundError('IPython.display.HTML was not found, and must be installed to use JupyterChart.')