- 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.
|
2. Blocking or non-blocking GUI.
|
||||||
3. Streamlined for live data, with methods for updating directly from tick data.
|
3. Streamlined for live data, with methods for updating directly from tick data.
|
||||||
4. Support for PyQt and wxPython.
|
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:
|
### 1. Display data from a csv:
|
||||||
|
|||||||
@ -95,9 +95,9 @@ Config options for the chart.
|
|||||||
___
|
___
|
||||||
|
|
||||||
### `time_scale`
|
### `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`
|
### `layout`
|
||||||
@ -106,6 +106,12 @@ ___
|
|||||||
Global layout options for the chart.
|
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`
|
### `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`
|
`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:
|
class Chart:
|
||||||
def __init__(self, volume_enabled: bool = True, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
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.debug = debug
|
||||||
self.volume_enabled = volume_enabled
|
self.volume_enabled = volume_enabled
|
||||||
self.width = width
|
self.width = width
|
||||||
@ -179,6 +180,12 @@ class Chart:
|
|||||||
"""
|
"""
|
||||||
self._go('layout', background_color, text_color, font_size, font_family)
|
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)',
|
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 = '',
|
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||||
border_down_color: str = '', wick_up_color: str = '', wick_down_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)
|
self._go('subscribe_click', function)
|
||||||
|
|
||||||
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
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):
|
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||||
c_id = self._go_return('create_sub_chart', volume_enabled, position, width, height, sync)
|
c_id = self._go_return('create_subchart', volume_enabled, position, width, height, sync)
|
||||||
return SubChart(self, c_id)
|
return SubChart(self, c_id)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class API:
|
|||||||
|
|
||||||
|
|
||||||
class LWC:
|
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.id = uuid4()
|
||||||
self.js_queue = []
|
self.js_queue = []
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
@ -331,7 +331,7 @@ class LWC:
|
|||||||
if self._stored('config', mode, title, right_padding):
|
if self._stored('config', mode, title, right_padding):
|
||||||
return None
|
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}.series.applyOptions({{title: "{title}"}})') if title else None
|
||||||
self.run_script(
|
self.run_script(
|
||||||
f"{self._chart_var}.chart.priceScale().applyOptions({{mode: LightweightCharts.PriceScaleMode.{_price_scale_mode(mode)}}})") if mode else None
|
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 ''}
|
{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)',
|
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 = '',
|
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||||
border_down_color: str = '', wick_up_color: str = '', wick_down_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({{
|
{self._chart_var}.series.applyOptions({{
|
||||||
{f'upColor: "{up_color}",' if up_color else ''}
|
{f'upColor: "{up_color}",' if up_color else ''}
|
||||||
{f'downColor: "{down_color}",' if down_color else ''}
|
{f'downColor: "{down_color}",' if down_color else ''}
|
||||||
{f'wickVisible: {_js_bool(wick_enabled)},' if wick_enabled else ''}
|
{f'wickVisible: {_js_bool(wick_enabled)},' if wick_enabled is not None else ''}
|
||||||
{f'borderVisible: {_js_bool(border_enabled)},' if border_enabled 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'borderUpColor: "{border_up_color}",' if border_up_color else ''}
|
||||||
{f'borderDownColor: "{border_down_color}",' if border_down_color else ''}
|
{f'borderDownColor: "{border_down_color}",' if border_down_color else ''}
|
||||||
{f'wickUpColor: "{wick_up_color}",' if wick_up_color else ''}
|
{f'wickUpColor: "{wick_up_color}",' if wick_up_color else ''}
|
||||||
@ -524,13 +549,13 @@ class LWC:
|
|||||||
{self._js_api_code}(data{var})
|
{self._js_api_code}(data{var})
|
||||||
}})''')
|
}})''')
|
||||||
|
|
||||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
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):
|
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||||
subchart = SubChart(self, volume_enabled, position, width, height, sync)
|
subchart = SubChart(self, volume_enabled, position, width, height, sync)
|
||||||
self._subcharts[subchart.id] = subchart
|
self._subcharts[subchart.id] = subchart
|
||||||
return 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)
|
subchart = PyWebViewSubChart(self if not parent else parent, volume_enabled, position, width, height, sync)
|
||||||
self._subcharts[subchart.id] = subchart
|
self._subcharts[subchart.id] = subchart
|
||||||
return subchart.id
|
return subchart.id
|
||||||
@ -543,7 +568,7 @@ class SubChart(LWC):
|
|||||||
self._parent = parent
|
self._parent = parent
|
||||||
|
|
||||||
self._rand = self._chart._rand
|
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 = self._chart._js_api
|
||||||
self._js_api_code = self._chart._js_api_code
|
self._js_api_code = self._chart._js_api_code
|
||||||
|
|
||||||
@ -574,15 +599,15 @@ class SubChart(LWC):
|
|||||||
}});
|
}});
|
||||||
'''
|
'''
|
||||||
self.run_script(f'''
|
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.position = 'relative'
|
||||||
{self._chart_var}div.style.float = "{self.position}"
|
{self._chart_var}div.style.float = "{self.position}"
|
||||||
|
|
||||||
//chartsDiv.style.display = 'inline-block'
|
//chartsDiv.style.display = 'inline-block'
|
||||||
chartsDiv.style.float = 'left'
|
chartsDiv.style.float = 'left'
|
||||||
|
|
||||||
var {self._chart_var} = {{}}
|
{self._chart_var} = {{}}
|
||||||
{self._chart_var}.scale= {{
|
{self._chart_var}.scale = {{
|
||||||
width: {self.inner_width},
|
width: {self.inner_width},
|
||||||
height: {self.inner_height}
|
height: {self.inner_height}
|
||||||
}}
|
}}
|
||||||
@ -616,9 +641,9 @@ class SubChart(LWC):
|
|||||||
|
|
||||||
|
|
||||||
class PyWebViewSubChart(SubChart):
|
class PyWebViewSubChart(SubChart):
|
||||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
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):
|
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||||
return self._chart._pywebview_sub_chart(volume_enabled, position, width, height, sync, parent=self)
|
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):
|
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||||
return super().create_line(color, width).id
|
return super().create_line(color, width).id
|
||||||
@ -698,14 +723,14 @@ function makeVolumeSeries(chart) {
|
|||||||
const chartsDiv = document.createElement('div')
|
const chartsDiv = document.createElement('div')
|
||||||
|
|
||||||
var chart = {}
|
var chart = {}
|
||||||
|
|
||||||
chart.chart = makeChart(window.innerWidth, window.innerHeight, chartsDiv)
|
|
||||||
chart.series = makeCandlestickSeries(chart.chart)
|
|
||||||
chart.volumeSeries = makeVolumeSeries(chart.chart)
|
|
||||||
chart.scale = {
|
chart.scale = {
|
||||||
width: __INNER_WIDTH__,
|
width: __INNER_WIDTH__,
|
||||||
height: __INNER_HEIGHT__
|
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)
|
document.body.appendChild(chartsDiv)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Literal
|
from typing import Literal, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import webview
|
import webview
|
||||||
from multiprocessing import Queue
|
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):
|
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||||
return super().create_line(color, width).id
|
return super().create_line(color, width).id
|
||||||
|
|
||||||
def create_sub_chart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
|
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):
|
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
|
||||||
return super()._pywebview_sub_chart(volume_enabled, position, width, height, sync)
|
return super()._pywebview_subchart(volume_enabled, position, width, height, sync)
|
||||||
|
|
||||||
|
|
||||||
def _loop(chart, controller=None):
|
def _loop(chart, controller=None):
|
||||||
|
|||||||
@ -13,13 +13,13 @@ from lightweight_charts.js import LWC
|
|||||||
|
|
||||||
|
|
||||||
class WxChart(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:
|
try:
|
||||||
self.webview: wx.html2.WebView = wx.html2.WebView.New(parent)
|
self.webview: wx.html2.WebView = wx.html2.WebView.New(parent)
|
||||||
except NameError:
|
except NameError:
|
||||||
raise ModuleNotFoundError('wx.html2 was not found, and must be installed to use WxChart.')
|
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.webview.AddScriptMessageHandler('wx_msg')
|
||||||
self._js_api_code = 'window.wx_msg.postMessage'
|
self._js_api_code = 'window.wx_msg.postMessage'
|
||||||
@ -33,19 +33,24 @@ class WxChart(LWC):
|
|||||||
def _on_js_load(self, e):
|
def _on_js_load(self, e):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
for func, args, kwargs in self.js_queue:
|
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
|
def get_webview(self): return self.webview
|
||||||
|
|
||||||
|
|
||||||
class QtChart(LWC):
|
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:
|
try:
|
||||||
self.webview = QWebEngineView(widget)
|
self.webview = QWebEngineView(widget)
|
||||||
except NameError:
|
except NameError:
|
||||||
raise ModuleNotFoundError('QWebEngineView was not found, and must be installed to use QtChart.')
|
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.loadFinished.connect(self._on_js_load)
|
||||||
self.webview.page().setHtml(self._html)
|
self.webview.page().setHtml(self._html)
|
||||||
|
|||||||
Reference in New Issue
Block a user