Files
lightweight-charts-python/lightweight_charts/widgets.py

208 lines
8.4 KiB
Python

import asyncio
import html
from .util import parse_event_message
from lightweight_charts import abstract
try:
import wx.html2
except ImportError:
wx = None
try:
using_pyside6 = False
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtCore import QObject, pyqtSlot as Slot, QUrl, QTimer
except ImportError:
using_pyside6 = True
try:
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWebChannel import QWebChannel
from PySide6.QtCore import Qt, QObject, Slot, QUrl, QTimer
except ImportError:
try:
using_pyside6 = False
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtCore import QObject, pyqtSlot as Slot, QUrl, QTimer
except ImportError:
QWebEngineView = None
if QWebEngineView:
class Bridge(QObject):
def __init__(self, chart):
super().__init__()
self.win = chart.win
@Slot(str)
def callback(self, message):
emit_callback(self.win, message)
try:
from streamlit.components.v1 import html as sthtml
except ImportError:
sthtml = None
try:
from IPython.display import HTML, display
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="IPython.core.display")
except ImportError:
HTML = None
def emit_callback(window, string):
func, args = parse_event_message(window, string)
asyncio.create_task(func(*args)) if asyncio.iscoroutinefunction(func) else func(*args)
class WxChart(abstract.AbstractChart):
def __init__(self, parent, inner_width: float = 1.0, inner_height: float = 1.0,
scale_candles_only: bool = False, toolbox: bool = False):
# this isn't available at the moment
raise ModuleNotFoundError('WxChart is not available in lightweight charts 2.0; please downgrade to an earlier version.')
if wx is None:
raise ModuleNotFoundError('wx.html2 was not found, and must be installed to use WxChart.')
self.webview: wx.html2.WebView = wx.html2.WebView.New(parent)
super().__init__(abstract.Window(self.webview.RunScript, 'window.wx_msg.postMessage.bind(window.wx_msg)'),
inner_width, inner_height, scale_candles_only, toolbox)
self.webview.Bind(wx.html2.EVT_WEBVIEW_LOADED, lambda e: wx.CallLater(1000, self.win.on_js_load))
self.webview.Bind(wx.html2.EVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, lambda e: emit_callback(self, e.GetString()))
self.webview.AddScriptMessageHandler('wx_msg')
self.webview.LoadURL(f'file:///{abstract.INDEX}')
# with open(abstract.INDEX, 'r') as f:
# html = f.read()
# self.webview.SetPage(html, '/Users/louis/Projects/lightweight-charts-python/lightweight_charts/js/')
# with open('/Users/louis/Projects/lightweight-charts-python/lightweight_charts/js/bundle.js', 'r') as f:
# self.webview.AddUserScript(f.read())
# self.webview.AddUserScript(abstract.JS['toolbox']) if toolbox else None
def get_webview(self): return self.webview
class QtChart(abstract.AbstractChart):
def __init__(self, widget=None, inner_width: float = 1.0, inner_height: float = 1.0,
scale_candles_only: bool = False, toolbox: bool = False):
if QWebEngineView is None:
raise ModuleNotFoundError('QWebEngineView was not found, and must be installed to use QtChart.')
self.webview = QWebEngineView(widget)
super().__init__(abstract.Window(self.webview.page().runJavaScript, 'window.pythonObject.callback'),
inner_width, inner_height, scale_candles_only, toolbox)
self.web_channel = QWebChannel()
self.bridge = Bridge(self)
self.web_channel.registerObject('bridge', self.bridge)
self.webview.page().setWebChannel(self.web_channel)
self.webview.loadFinished.connect(lambda: self.webview.page().runJavaScript('''
let scriptElement = document.createElement("script")
scriptElement.src = 'qrc:///qtwebchannel/qwebchannel.js'
scriptElement.onload = function() {
var bridge = new QWebChannel(qt.webChannelTransport, function(channel) {
var pythonObject = channel.objects.bridge
window.pythonObject = pythonObject
})
}
document.head.appendChild(scriptElement)
'''))
self.webview.loadFinished.connect(lambda: QTimer.singleShot(200, self.win.on_js_load))
if using_pyside6:
self.webview.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
self.webview.load(QUrl.fromLocalFile(abstract.INDEX))
def get_webview(self): return self.webview
class StaticLWC(abstract.AbstractChart):
def __init__(self, width=None, height=None, inner_width=1, inner_height=1,
scale_candles_only: bool = False, toolbox=False, autosize=True):
# this isn't available at the moment
raise ModuleNotFoundError('Streamlit & Jupyter Charts are unavailable in lightweight charts 2.0; please downgrade to an earlier version.')
with open(abstract.INDEX.replace("test.html", 'styles.css'), 'r') as f:
css = f.read()
with open(abstract.INDEX.replace("test.html", 'bundle.js'), 'r') as f:
js = f.read()
with open(abstract.INDEX.replace("test.html", 'lightweight-charts.js'), 'r') as f:
lwc = f.read()
with open(abstract.INDEX, 'r') as f:
self._html = f.read().replace('</body>\n</html>', f'<script type="module">{lwc}</script><script type="module">{js}')
self._html = self._html.replace("</style>", f"{css}</style>")
self._html = self._html.replace('<script type="module" src="./bundle.js"></script>',
f'')
super().__init__(abstract.Window(run_script=self.run_script), inner_width, inner_height,
scale_candles_only, toolbox, autosize)
self.width = width
self.height = height
def run_script(self, script, run_last=False):
if run_last:
self.win.final_scripts.append(script)
else:
self._html += '\n' + script
def load(self):
if self.win.loaded:
return
self.win.loaded = True
for script in self.win.final_scripts:
self._html += '\n' + script
self._load()
def _load(self): pass
class StreamlitChart(StaticLWC):
def __init__(self, width=None, height=None, inner_width=1, inner_height=1, scale_candles_only: bool = False, toolbox: bool = False):
super().__init__(width, height, inner_width, inner_height, scale_candles_only, toolbox)
def _load(self):
if sthtml is None:
raise ModuleNotFoundError('streamlit.components.v1.html was not found, and must be installed to use StreamlitChart.')
sthtml(f'{self._html}</script></body></html>', width=self.width, height=self.height)
class JupyterChart(StaticLWC):
def __init__(self, width: int = 800, height=350, inner_width=1, inner_height=1, scale_candles_only: bool = False, toolbox: bool = False):
super().__init__(width, height, inner_width, inner_height, scale_candles_only, toolbox, 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):
if HTML is None:
raise ModuleNotFoundError('IPython.display.HTML was not found, and must be installed to use JupyterChart.')
html_code = html.escape(f"{self._html}</script></body></html>")
iframe = f'<iframe width="{self.width}" height="{self.height}" frameBorder="0" srcdoc="{html_code}"></iframe>'
display(HTML(iframe))