- Fixed a bug causing the library to throw errors when used with python 3.9.
- Fixed a bug that did not size the multi pane charts correctly. - Implementation of the ‘grid’ common method.
This commit is contained in:
@ -21,7 +21,7 @@ ___
|
||||
2. Blocking or non-blocking GUI.
|
||||
3. Streamlined for live data, with methods for updating directly from tick data.
|
||||
4. Support for PyQt and wxPython.
|
||||
5. Multi-Pane Charts using `SubCharts`.
|
||||
5. Multi-Pane Charts using the `SubChart` ([examples](https://lightweight-charts-python.readthedocs.io/en/latest/docs.html#subchart)).
|
||||
___
|
||||
|
||||
### 1. Display data from a csv:
|
||||
|
||||
@ -95,9 +95,9 @@ Config options for the chart.
|
||||
___
|
||||
|
||||
### `time_scale`
|
||||
`time_visible: bool` | `seconds_visible: bool`
|
||||
`visible: bool` | `time_visible: bool` | `seconds_visible: bool`
|
||||
|
||||
Options for the time scale of the chart.
|
||||
Time scale options for the chart.
|
||||
___
|
||||
|
||||
### `layout`
|
||||
@ -106,6 +106,12 @@ ___
|
||||
Global layout options for the chart.
|
||||
___
|
||||
|
||||
### `grid`
|
||||
`vert_enabled: bool` | `horz_enabled: bool` | `color: str` | `style: 'solid'/'dotted'/'dashed'/'large_dashed'/'sparse_dotted'`
|
||||
|
||||
Grid options for the chart.
|
||||
___
|
||||
|
||||
### `candle_style`
|
||||
`up_color: str` | `down_color: str` | `wick_enabled: bool` | `border_enabled: bool` | `border_up_color: str` | `border_down_color: str` | `wick_up_color: str` | `wick_down_color: str`
|
||||
|
||||
|
||||
@ -31,7 +31,8 @@ class Line:
|
||||
|
||||
class Chart:
|
||||
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, sub: bool = False, inner_width=1, inner_height=1):
|
||||
on_top: bool = False, debug: bool = False, sub: bool = False,
|
||||
inner_width: float = 1.0, inner_height: float = 1.0):
|
||||
self.debug = debug
|
||||
self.volume_enabled = volume_enabled
|
||||
self.width = width
|
||||
@ -179,6 +180,12 @@ class Chart:
|
||||
"""
|
||||
self._go('layout', background_color, text_color, font_size, font_family)
|
||||
|
||||
def grid(self, vert_enabled: bool = True, horz_enabled: bool = True, color: str = 'rgba(29, 30, 38, 5)', style: LINE_TYPE = 'solid'):
|
||||
"""
|
||||
Grid styling for the chart.
|
||||
"""
|
||||
self._go('grid', vert_enabled, horz_enabled, color, style)
|
||||
|
||||
def candle_style(self, up_color: str = 'rgba(39, 157, 130, 100)', down_color: str = 'rgba(200, 97, 100, 100)',
|
||||
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||
border_down_color: str = '', wick_up_color: str = '', wick_down_color: str = ''):
|
||||
@ -231,8 +238,8 @@ class Chart:
|
||||
self._go('subscribe_click', function)
|
||||
|
||||
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: bool | UUID = False):
|
||||
c_id = self._go_return('create_sub_chart', volume_enabled, position, width, height, sync)
|
||||
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||
c_id = self._go_return('create_subchart', volume_enabled, position, width, height, sync)
|
||||
return SubChart(self, c_id)
|
||||
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ class API:
|
||||
|
||||
|
||||
class LWC:
|
||||
def __init__(self, volume_enabled, inner_width=1, inner_height=1):
|
||||
def __init__(self, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
|
||||
self.id = uuid4()
|
||||
self.js_queue = []
|
||||
self.loaded = False
|
||||
@ -331,7 +331,7 @@ class LWC:
|
||||
if self._stored('config', mode, title, right_padding):
|
||||
return None
|
||||
|
||||
self.run_script(f'{self._chart_var}.chart.timeScale().scrollToPosition({right_padding}, false)') if right_padding else None
|
||||
self.run_script(f'{self._chart_var}.chart.timeScale().scrollToPosition({right_padding}, false)') if right_padding is not None else None
|
||||
self.run_script(f'{self._chart_var}.series.applyOptions({{title: "{title}"}})') if title else None
|
||||
self.run_script(
|
||||
f"{self._chart_var}.chart.priceScale().applyOptions({{mode: LightweightCharts.PriceScaleMode.{_price_scale_mode(mode)}}})") if mode else None
|
||||
@ -378,6 +378,31 @@ class LWC:
|
||||
{f'fontFamily: "{font_family}",' if font_family else ''}
|
||||
}}}})""")
|
||||
|
||||
def grid(self, vert_enabled: bool = True, horz_enabled: bool = True, color: str = 'rgba(29, 30, 38, 5)', style: LINE_TYPE = 'solid'):
|
||||
"""
|
||||
Grid styling for the chart.
|
||||
"""
|
||||
if self._stored('grid', vert_enabled, horz_enabled, color, style):
|
||||
return None
|
||||
|
||||
self.run_script(f"""
|
||||
{self._chart_var}.chart.applyOptions({{
|
||||
grid: {{
|
||||
{f'''vertLines: {{
|
||||
{f'visible: {_js_bool(vert_enabled)},' if vert_enabled is not None else ''}
|
||||
{f'color: "{color}",' if color else ''}
|
||||
{f'style: LightweightCharts.LineStyle.{_line_type(style)},' if style else ''}
|
||||
}},''' if vert_enabled is not None or color or style else ''}
|
||||
|
||||
{f'''horzLines: {{
|
||||
{f'visible: {_js_bool(horz_enabled)},' if horz_enabled is not None else ''}
|
||||
{f'color: "{color}",' if color else ''}
|
||||
{f'style: LightweightCharts.LineStyle.{_line_type(style)},' if style else ''}
|
||||
}},''' if horz_enabled is not None or color or style else ''}
|
||||
}}
|
||||
}})
|
||||
""")
|
||||
|
||||
def candle_style(self, up_color: str = 'rgba(39, 157, 130, 100)', down_color: str = 'rgba(200, 97, 100, 100)',
|
||||
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||
border_down_color: str = '', wick_up_color: str = '', wick_down_color: str = ''):
|
||||
@ -392,8 +417,8 @@ class LWC:
|
||||
{self._chart_var}.series.applyOptions({{
|
||||
{f'upColor: "{up_color}",' if up_color else ''}
|
||||
{f'downColor: "{down_color}",' if down_color else ''}
|
||||
{f'wickVisible: {_js_bool(wick_enabled)},' if wick_enabled else ''}
|
||||
{f'borderVisible: {_js_bool(border_enabled)},' if border_enabled else ''}
|
||||
{f'wickVisible: {_js_bool(wick_enabled)},' if wick_enabled is not None else ''}
|
||||
{f'borderVisible: {_js_bool(border_enabled)},' if border_enabled is not None else ''}
|
||||
{f'borderUpColor: "{border_up_color}",' if border_up_color else ''}
|
||||
{f'borderDownColor: "{border_down_color}",' if border_down_color else ''}
|
||||
{f'wickUpColor: "{wick_up_color}",' if wick_up_color else ''}
|
||||
@ -524,13 +549,13 @@ class LWC:
|
||||
{self._js_api_code}(data{var})
|
||||
}})''')
|
||||
|
||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: bool | UUID = False):
|
||||
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||
subchart = SubChart(self, volume_enabled, position, width, height, sync)
|
||||
self._subcharts[subchart.id] = subchart
|
||||
return subchart
|
||||
|
||||
def _pywebview_sub_chart(self, volume_enabled, position, width, height, sync, parent=None):
|
||||
def _pywebview_subchart(self, volume_enabled, position, width, height, sync, parent=None):
|
||||
subchart = PyWebViewSubChart(self if not parent else parent, volume_enabled, position, width, height, sync)
|
||||
self._subcharts[subchart.id] = subchart
|
||||
return subchart.id
|
||||
@ -543,7 +568,7 @@ class SubChart(LWC):
|
||||
self._parent = parent
|
||||
|
||||
self._rand = self._chart._rand
|
||||
self._chart_var = self._rand.generate()
|
||||
self._chart_var = f'window.{self._rand.generate()}'
|
||||
self._js_api = self._chart._js_api
|
||||
self._js_api_code = self._chart._js_api_code
|
||||
|
||||
@ -574,14 +599,14 @@ class SubChart(LWC):
|
||||
}});
|
||||
'''
|
||||
self.run_script(f'''
|
||||
var {self._chart_var}div = document.createElement('div')
|
||||
{self._chart_var}div = document.createElement('div')
|
||||
//{self._chart_var}div.style.position = 'relative'
|
||||
{self._chart_var}div.style.float = "{self.position}"
|
||||
|
||||
//chartsDiv.style.display = 'inline-block'
|
||||
chartsDiv.style.float = 'left'
|
||||
|
||||
var {self._chart_var} = {{}}
|
||||
{self._chart_var} = {{}}
|
||||
{self._chart_var}.scale = {{
|
||||
width: {self.inner_width},
|
||||
height: {self.inner_height}
|
||||
@ -616,9 +641,9 @@ class SubChart(LWC):
|
||||
|
||||
|
||||
class PyWebViewSubChart(SubChart):
|
||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: bool | UUID = False):
|
||||
return self._chart._pywebview_sub_chart(volume_enabled, position, width, height, sync, parent=self)
|
||||
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||
return self._chart._pywebview_subchart(volume_enabled, position, width, height, sync, parent=self)
|
||||
|
||||
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||
return super().create_line(color, width).id
|
||||
@ -698,14 +723,14 @@ function makeVolumeSeries(chart) {
|
||||
const chartsDiv = document.createElement('div')
|
||||
|
||||
var chart = {}
|
||||
|
||||
chart.chart = makeChart(window.innerWidth, window.innerHeight, chartsDiv)
|
||||
chart.series = makeCandlestickSeries(chart.chart)
|
||||
chart.volumeSeries = makeVolumeSeries(chart.chart)
|
||||
chart.scale = {
|
||||
width: __INNER_WIDTH__,
|
||||
height: __INNER_HEIGHT__
|
||||
}
|
||||
chart.chart = makeChart(window.innerWidth*chart.scale.width, window.innerHeight*chart.scale.height, chartsDiv)
|
||||
chart.series = makeCandlestickSeries(chart.chart)
|
||||
chart.volumeSeries = makeVolumeSeries(chart.chart)
|
||||
|
||||
|
||||
document.body.appendChild(chartsDiv)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Literal
|
||||
from typing import Literal, Union
|
||||
from uuid import UUID
|
||||
import webview
|
||||
from multiprocessing import Queue
|
||||
@ -52,9 +52,9 @@ class Webview(LWC):
|
||||
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||
return super().create_line(color, width).id
|
||||
|
||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: bool | UUID = False):
|
||||
return super()._pywebview_sub_chart(volume_enabled, position, width, height, sync)
|
||||
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
||||
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||
return super()._pywebview_subchart(volume_enabled, position, width, height, sync)
|
||||
|
||||
|
||||
def _loop(chart, controller=None):
|
||||
|
||||
@ -13,13 +13,13 @@ from lightweight_charts.js import LWC
|
||||
|
||||
|
||||
class WxChart(LWC):
|
||||
def __init__(self, parent, volume_enabled=True):
|
||||
def __init__(self, parent, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
|
||||
try:
|
||||
self.webview: wx.html2.WebView = wx.html2.WebView.New(parent)
|
||||
except NameError:
|
||||
raise ModuleNotFoundError('wx.html2 was not found, and must be installed to use WxChart.')
|
||||
|
||||
super().__init__(volume_enabled)
|
||||
super().__init__(volume_enabled, inner_width=inner_width, inner_height=inner_height)
|
||||
|
||||
self.webview.AddScriptMessageHandler('wx_msg')
|
||||
self._js_api_code = 'window.wx_msg.postMessage'
|
||||
@ -33,19 +33,24 @@ class WxChart(LWC):
|
||||
def _on_js_load(self, e):
|
||||
self.loaded = True
|
||||
for func, args, kwargs in self.js_queue:
|
||||
getattr(super(), func)(*args, **kwargs)
|
||||
if 'SUB' in func:
|
||||
c_id = args[0]
|
||||
args = args[1:]
|
||||
getattr(self._subcharts[c_id], func.replace('SUB', ''))(*args)
|
||||
else:
|
||||
getattr(self, func)(*args)
|
||||
|
||||
def get_webview(self): return self.webview
|
||||
|
||||
|
||||
class QtChart(LWC):
|
||||
def __init__(self, widget=None, volume_enabled=True):
|
||||
def __init__(self, widget=None, volume_enabled: bool = True, inner_width: float = 1.0, inner_height: float = 1.0):
|
||||
try:
|
||||
self.webview = QWebEngineView(widget)
|
||||
except NameError:
|
||||
raise ModuleNotFoundError('QWebEngineView was not found, and must be installed to use QtChart.')
|
||||
|
||||
super().__init__(volume_enabled)
|
||||
super().__init__(volume_enabled, inner_width=inner_width, inner_height=inner_height)
|
||||
|
||||
self.webview.loadFinished.connect(self._on_js_load)
|
||||
self.webview.page().setHtml(self._html)
|
||||
|
||||
Reference in New Issue
Block a user