diff --git a/README.md b/README.md index 2a42fad..9846502 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,14 @@ lightweight-charts-python aims to provide a simple and pythonic way to access an ``` pip install lightweight-charts ``` -* White screen? Having issues with pywebview? Click [here](https://github.com/louisnw01/lightweight-charts-python/issues?q=label%3A%22pywebview+issue%22+). ___ ## Features 1. Streamlined for live data, with methods for updating directly from tick data. -2. Multi-pane charts using [Subcharts](https://lightweight-charts-python.readthedocs.io/en/latest/common_methods.html#create-subchart-subchart). -3. The [Toolbox](https://lightweight-charts-python.readthedocs.io/en/latest/toolbox.html), allowing for trendlines, rays and horizontal lines to be drawn directly onto charts. -4. [Callbacks](https://lightweight-charts-python.readthedocs.io/en/latest/callbacks.html) allowing for timeframe selectors (1min, 5min, 30min etc.), searching, hotkeys, and more. -5. [Tables](https://lightweight-charts-python.readthedocs.io/en/latest/tables.html) for watchlists, order entry, and trade management. +2. Multi-pane charts using [Subcharts](https://lightweight-charts-python.readthedocs.io/en/latest/reference/abstract_chart.html#AbstractChart.create_subchart). +3. The [Toolbox](https://lightweight-charts-python.readthedocs.io/en/latest/reference/toolbox.html), allowing for trendlines, rays and horizontal lines to be drawn directly onto charts. +4. [Events](https://lightweight-charts-python.readthedocs.io/en/latest/tutorials/events.html) allowing for timeframe selectors (1min, 5min, 30min etc.), searching, hotkeys, and more. +5. [Tables](https://lightweight-charts-python.readthedocs.io/en/latest/reference/tables.html) for watchlists, order entry, and trade management. 6. Direct integration of market data through [Polygon.io's](https://polygon.io/?utm_source=affiliate&utm_campaign=pythonlwcharts) market data API. __Supports:__ Jupyter Notebooks, PyQt5, PySide6, wxPython, Streamlit, and asyncio. diff --git a/docs/source/conf.py b/docs/source/conf.py index 27551a3..41e9062 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,7 +3,7 @@ import os.path project = 'lightweight-charts-python' copyright = '2023, louisnw' author = 'louisnw' -release = '1.0.17' +release = '1.0.17.1' extensions = [ "myst_parser", diff --git a/docs/source/reference/abstract_chart.md b/docs/source/reference/abstract_chart.md index 97e2879..cddb08e 100644 --- a/docs/source/reference/abstract_chart.md +++ b/docs/source/reference/abstract_chart.md @@ -67,7 +67,7 @@ ___ -```{py:function} lines() -> List[Line] +```{py:method} lines() -> List[Line] Returns a list of all lines for the chart. @@ -76,7 +76,7 @@ ___ -```{py:function} trend_line(start_time: str | datetime, start_value: NUM, end_time: str | datetime, end_value: NUM, color: COLOR, width: int) -> Line +```{py:method} trend_line(start_time: str | datetime, start_value: NUM, end_time: str | datetime, end_value: NUM, color: COLOR, width: int) -> Line Creates a trend line, drawn from the first point (`start_time`, `start_value`) to the last point (`end_time`, `end_value`). @@ -85,7 +85,7 @@ ___ -```{py:function} ray_line(start_time: str | datetime, value: NUM, color: COLOR, width: int) -> Line +```{py:method} ray_line(start_time: str | datetime, value: NUM, color: COLOR, width: int) -> Line Creates a ray line, drawn from the first point (`start_time`, `value`) and onwards. @@ -94,7 +94,39 @@ ___ -```{py:function} marker(time: datetime, position: MARKER_POSITION, shape: MARKER_SHAPE, color: COLOR, text: str) -> str +```{py:method} vertical_span(start_time: TIME, end_time: TIME = None, color: COLOR = 'rgba(252, 219, 3, 0.2)') + +Creates and returns a `VerticalSpan` object. + +If `end_time` is not given, then a single vertical line will be placed at `start_time`. + +This should be used after calling [`set`](#AbstractChart.set). +``` +___ + + + +```{py:method} set_visible_range(self, start_time: TIME, end_time: TIME) + +Sets the visible range of the chart. +``` +___ + + + +```{py:method} resize(self, width: float = None, height: float = None) + +Resizes the chart within the window. + +Dimensions should be given as a float between or equal to 0 and 1. + +Both `width` and `height` do not need to be provided if only one axis is to be changed. +``` +___ + + + +```{py:method} marker(time: datetime, position: MARKER_POSITION, shape: MARKER_SHAPE, color: COLOR, text: str) -> str Adds a marker to the chart, and returns its id. @@ -107,7 +139,7 @@ ___ -```{py:function} remove_marker(marker_id: str) +```{py:method} remove_marker(marker_id: str) Removes the marker with the given id. @@ -116,7 +148,7 @@ ___ -```{py:function} horizontal_line(price: NUM, color: COLOR, width: int, style: LINE_STYLE, text: str, axis_label_visible: bool, func: callable= None) -> HorizontalLine +```{py:method} horizontal_line(price: NUM, color: COLOR, width: int, style: LINE_STYLE, text: str, axis_label_visible: bool, func: callable= None) -> HorizontalLine Places a horizontal line at the given price, and returns a [`HorizontalLine`] object. @@ -127,7 +159,7 @@ ___ -```{py:function} remove_horizontal_line(price: NUM) +```{py:method} remove_horizontal_line(price: NUM) Removes a horizontal line at the given price. ``` @@ -135,7 +167,7 @@ ___ -```{py:function} clear_markers() +```{py:method} clear_markers() Clears the markers displayed on the data. ``` @@ -143,7 +175,7 @@ ___ -```{py:function} clear_horizontal_lines +```{py:method} clear_horizontal_lines() Clears the horizontal lines displayed on the data. ``` @@ -151,7 +183,7 @@ ___ -```{py:function} precision(precision: int) +```{py:method} precision(precision: int) Sets the precision of the chart based on the given number of decimal places. @@ -160,7 +192,7 @@ ___ -```{py:function} price_scale(mode: PRICE_SCALE_MODE, align_labels: bool, border_visible: bool, border_color: COLOR, text_color: COLOR, entire_text_only: bool, ticks_visible: bool, scale_margin_top: float, scale_margin_bottom: float) +```{py:method} price_scale(mode: PRICE_SCALE_MODE, align_labels: bool, border_visible: bool, border_color: COLOR, text_color: COLOR, entire_text_only: bool, ticks_visible: bool, scale_margin_top: float, scale_margin_bottom: float) Price scale options for the chart. ``` @@ -168,7 +200,7 @@ ___ -```{py:function} time_scale(right_offset: int, min_bar_spacing: float, visible: bool, time_visible: bool, seconds_visible: bool, border_visible: bool, border_color: COLOR) +```{py:method} time_scale(right_offset: int, min_bar_spacing: float, visible: bool, time_visible: bool, seconds_visible: bool, border_visible: bool, border_color: COLOR) Timescale options for the chart. ``` @@ -176,7 +208,7 @@ ___ -```{py:function} layout(background_color: COLOR, text_color: COLOR, font_size: int, font_family: str) +```{py:method} layout(background_color: COLOR, text_color: COLOR, font_size: int, font_family: str) Global layout options for the chart. ``` @@ -184,7 +216,7 @@ ___ -```{py:function} grid(vert_enabled: bool, horz_enabled: bool, color: COLOR, style: LINE_STYLE) +```{py:method} grid(vert_enabled: bool, horz_enabled: bool, color: COLOR, style: LINE_STYLE) Grid options for the chart. ``` @@ -192,7 +224,7 @@ ___ -```{py:function} candle_style(up_color: COLOR, down_color: COLOR, wick_enabled: bool, border_enabled: bool, border_up_color: COLOR, border_down_color: COLOR, wick_up_color: COLOR, wick_down_color: COLOR) +```{py:method} candle_style(up_color: COLOR, down_color: COLOR, wick_enabled: bool, border_enabled: bool, border_up_color: COLOR, border_down_color: COLOR, wick_up_color: COLOR, wick_down_color: COLOR) Candle styling for each of the candle's parts (border, wick). ``` @@ -200,7 +232,7 @@ ___ -```{py:function} volume_config(scale_margin_top: float, scale_margin_bottom: float, up_color: COLOR, down_color: COLOR) +```{py:method} volume_config(scale_margin_top: float, scale_margin_bottom: float, up_color: COLOR, down_color: COLOR) Volume config options. @@ -211,7 +243,7 @@ ___ -```{py:function} crosshair(mode, vert_visible: bool, vert_width: int, vert_color: COLOR, vert_style: LINE_STYLE, vert_label_background_color: COLOR, horz_visible: bool, horz_width: int, horz_color: COLOR, horz_style: LINE_STYLE, horz_label_background_color: COLOR) +```{py:method} crosshair(mode, vert_visible: bool, vert_width: int, vert_color: COLOR, vert_style: LINE_STYLE, vert_label_background_color: COLOR, horz_visible: bool, horz_width: int, horz_color: COLOR, horz_style: LINE_STYLE, horz_label_background_color: COLOR) Crosshair formatting for its vertical and horizontal axes. ``` @@ -219,7 +251,7 @@ ___ -```{py:function} watermark(text: str, font_size: int, color: COLOR) +```{py:method} watermark(text: str, font_size: int, color: COLOR) Overlays a watermark on top of the chart. ``` @@ -227,7 +259,7 @@ ___ -```{py:function} legend(visible: bool, ohlc: bool, percent: bool, lines: bool, color: COLOR, font_size: int, font_family: str) +```{py:method} legend(visible: bool, ohlc: bool, percent: bool, lines: bool, color: COLOR, font_size: int, font_family: str) Configures the legend of the chart. ``` @@ -235,7 +267,7 @@ ___ -```{py:function} spinner(visible: bool) +```{py:method} spinner(visible: bool) Shows a loading spinner on the chart, which can be used to visualise the loading of large datasets, API calls, etc. @@ -246,7 +278,7 @@ ___ -```{py:function} price_line(label_visible: bool, line_visible: bool, title: str) +```{py:method} price_line(label_visible: bool, line_visible: bool, title: str) Configures the visibility of the last value price line and its label. ``` @@ -254,7 +286,7 @@ ___ -```{py:function} fit() +```{py:method} fit() Attempts to fit all data displayed on the chart within the viewport (`fitContent()`). ``` @@ -262,7 +294,7 @@ ___ -```{py:function} show_data() +```{py:method} show_data() Shows the hidden candles on the chart. ``` @@ -270,7 +302,7 @@ ___ -```{py:function} hide_data() +```{py:method} hide_data() Hides the candles on the chart. ``` @@ -278,7 +310,7 @@ ___ -```{py:function} hotkey(modifier: 'ctrl' | 'alt' | 'shift' | 'meta', key: 'str' | 'int' | 'tuple', func: callable) +```{py:method} hotkey(modifier: 'ctrl' | 'alt' | 'shift' | 'meta', key: 'str' | 'int' | 'tuple', func: callable) Adds a global hotkey to the chart window, which will execute the method or function given. @@ -288,7 +320,7 @@ ___ -```{py:function} create_table(width: NUM, height: NUM, headings: Tuple[str], widths: Tuple[float], alignments: Tuple[str], position: FLOAT, draggable: bool, func: callable) -> Table +```{py:method} create_table(width: NUM, height: NUM, headings: Tuple[str], widths: Tuple[float], alignments: Tuple[str], position: FLOAT, draggable: bool, func: callable) -> Table Creates and returns a [`Table`](https://lightweight-charts-python.readthedocs.io/en/latest/tables.html) object. @@ -297,7 +329,7 @@ ___ -````{py:function} create_subchart(position: FLOAT, width: float, height: float, sync: bool | str, scale_candles_only: bool, toolbox: bool) -> AbstractChart +````{py:method} create_subchart(position: FLOAT, width: float, height: float, sync: bool | str, scale_candles_only: bool, toolbox: bool) -> AbstractChart Creates and returns a Chart object, placing it adjacent to the previous Chart. This allows for the use of multiple chart panels within the same window. @@ -316,6 +348,7 @@ Creates and returns a Chart object, placing it adjacent to the previous Chart. T Charts are arranged horizontally from left to right. When the available space is no longer sufficient, the subsequent Chart will be positioned on a new row, starting from the left side. +[Subchart examples](../examples/subchart.md) ```` ````` diff --git a/docs/source/reference/charts.md b/docs/source/reference/charts.md index e21f572..85643fc 100644 --- a/docs/source/reference/charts.md +++ b/docs/source/reference/charts.md @@ -6,10 +6,12 @@ They inherit from [AbstractChart](#AbstractChart). ___ -`````{py:class} Chart(width: int, height: int, x: int, y: int, on_top: bool, maximize: bool, debug: bool, toolbox: bool, inner_width: float, inner_height: float, scale_candles_only: bool) +`````{py:class} Chart(width: int, height: int, x: int, y: int, screen: int, on_top: bool, maximize: bool, debug: bool, toolbox: bool, inner_width: float, inner_height: float, scale_candles_only: bool) The main object used for the normal functionality of lightweight-charts-python, built on the pywebview library. +The `screen` parameter defines which monitor the chart window will open on, given as an index (primary monitor = 0). + ```{important} The `Chart` object should be defined within an `if __name__ == '__main__'` block. ``` diff --git a/docs/source/reference/line.md b/docs/source/reference/line.md index db9b25b..5da6fd5 100644 --- a/docs/source/reference/line.md +++ b/docs/source/reference/line.md @@ -12,7 +12,7 @@ ___ -```{py:function} set(data: pd.DataFrame) +```{py:method} set(data: pd.DataFrame) Sets the data for the line. @@ -25,7 +25,7 @@ ___ -```{py:function} update(series: pd.Series) +```{py:method} update(series: pd.Series) Updates the data for the line. @@ -36,7 +36,7 @@ This should be given as a Series object, with labels akin to the `line.set()` fu ___ -```{py:function} line.delete() +```{py:method} line.delete() Irreversibly deletes the line. diff --git a/docs/source/tutorials/getting_started.md b/docs/source/tutorials/getting_started.md index 6595576..998e5ad 100644 --- a/docs/source/tutorials/getting_started.md +++ b/docs/source/tutorials/getting_started.md @@ -10,6 +10,8 @@ pip install lightweight-charts Pywebview's installation can differ depending on OS. Please refer to their [documentation](https://pywebview.flowrl.com/guide/installation.html#installation). +When using Docker or WSL, you may need to update your language tags; see [this](https://github.com/louisnw01/lightweight-charts-python/issues/63#issuecomment-1670473651) issue. + ___ ## A simple static chart diff --git a/lightweight_charts/abstract.py b/lightweight_charts/abstract.py index f99508e..7793e9c 100644 --- a/lightweight_charts/abstract.py +++ b/lightweight_charts/abstract.py @@ -91,17 +91,13 @@ class Window: return Table(self, width, height, headings, widths, alignments, position, draggable, func) def create_subchart(self, position: FLOAT = 'left', width: float = 0.5, height: float = 0.5, - sync: str = None, scale_candles_only: bool = False, toolbox: bool = False + sync_id: str = None, scale_candles_only: bool = False, toolbox: bool = False ) -> 'AbstractChart': subchart = AbstractChart(self, width, height, scale_candles_only, toolbox, position=position) - if not sync: + if not sync_id: return subchart - sync_id = sync self.run_script(f''' - syncCrosshairs({subchart.id}.chart, {sync_id}.chart) - {sync_id}.chart.timeScale().subscribeVisibleLogicalRangeChange((timeRange) => {{ - {subchart.id}.chart.timeScale().setVisibleLogicalRange(timeRange) - }}) + syncCharts({subchart.id}, {sync_id}) {subchart.id}.chart.timeScale().setVisibleLogicalRange( {sync_id}.chart.timeScale().getVisibleLogicalRange() ) @@ -313,6 +309,40 @@ class HorizontalLine(Pane): del self +class VerticalSpan(Pane): + def __init__(self, chart: 'AbstractChart', start_time: TIME, end_time: TIME = None, + color: str = 'rgba(252, 219, 3, 0.2)'): + super().__init__(chart.win) + self._chart = chart + start_date, end_date = pd.to_datetime(start_time), pd.to_datetime(end_time) + self.run_script(f''' + {self.id} = {chart.id}.chart.addHistogramSeries({{ + color: '{color}', + priceFormat: {{type: 'volume'}}, + priceScaleId: 'vertical_line', + lastValueVisible: false, + priceLineVisible: false, + }}) + {self.id}.priceScale('').applyOptions({{ + scaleMargins: {{top: 0, bottom: 0}} + }}) + ''') + if end_date is None: + self.run_script(f'{self.id}.setData([{{time: {start_date.timestamp()}, value: 1}}])') + else: + self.run_script(f''' + {self.id}.setData(calculateTrendLine( + {start_date.timestamp()}, 1, {end_date.timestamp()}, 1, + {chart._interval.total_seconds() * 1000}, {chart.id})) + ''') + + def delete(self): + """ + Irreversibly deletes the vertical span. + """ + self.run_script(f'{self._chart.id}.chart.removeSeries({self.id}.series); delete {self.id}') + + class Line(SeriesCommon): def __init__(self, chart, name, color, style, width, price_line, price_label, crosshair_marker=True): super().__init__(chart) @@ -345,7 +375,7 @@ class Line(SeriesCommon): {self._chart.id}.legend.lines.push({self._chart.id}.legend.makeLineRow({self.id})) }}''') - def set(self, df: pd.DataFrame): + def set(self, df: pd.DataFrame = None): """ Sets the line data.\n :param df: If the name parameter is not used, the columns should be named: date/time, value. @@ -373,15 +403,13 @@ class Line(SeriesCommon): self.run_script(f'{self.id}.series.update({series.to_dict()})') def _set_trend(self, start_time, start_value, end_time, end_value, ray=False): - def time_format(time_val): - time_val = pd.to_datetime(time_val).timestamp() - return f"'{time_val}'" if isinstance(time_val, str) else time_val - self.run_script(f''' {self._chart.id}.chart.timeScale().applyOptions({{shiftVisibleRangeOnNewBar: false}}) {self.id}.series.setData( - calculateTrendLine({time_format(start_time)}, {start_value}, {time_format(end_time)}, {end_value}, - {self._chart._interval.total_seconds() * 1000}, {self._chart.id}, {jbool(ray)})) + calculateTrendLine({pd.to_datetime(start_time).timestamp()}, {start_value}, + {pd.to_datetime(end_time).timestamp()}, {end_value}, + {self._chart._interval.total_seconds() * 1000}, + {self._chart.id}, {jbool(ray)})) {self._chart.id}.chart.timeScale().applyOptions({{shiftVisibleRangeOnNewBar: true}}) ''') @@ -399,7 +427,6 @@ class Line(SeriesCommon): }} delete {self.id} ''') - del self class Candlestick(SeriesCommon): @@ -556,20 +583,22 @@ class Candlestick(SeriesCommon): class AbstractChart(Candlestick, Pane): - def __init__(self, window: Window, inner_width: float = 1.0, inner_height: float = 1.0, + def __init__(self, window: Window, width: float = 1.0, height: float = 1.0, scale_candles_only: bool = False, toolbox: bool = False, autosize: bool = True, position: FLOAT = 'left'): Pane.__init__(self, window) self._lines = [] self._scale_candles_only = scale_candles_only + self._width = width + self._height = height self.events: Events = Events(self) from lightweight_charts.polygon import PolygonAPI self.polygon: PolygonAPI = PolygonAPI(self) self.run_script( - f'{self.id} = new Chart("{self.id}", {inner_width}, {inner_height}, "{position}", {jbool(autosize)})') + f'{self.id} = new Chart("{self.id}", {width}, {height}, "{position}", {jbool(autosize)})') Candlestick.__init__(self, self) @@ -614,6 +643,37 @@ class AbstractChart(Candlestick, Pane): line._set_trend(start_time, value, start_time, value, ray=True) return line + def vertical_span(self, start_time: TIME, end_time: TIME = None, color: str = 'rgba(252, 219, 3, 0.2)'): + """ + Creates a vertical line or span across the chart. + :param start_time: Start time of the span. + :param end_time: End time of the span (can be omitted for a single vertical line). + :param color: CSS color. + :return: + """ + return VerticalSpan(self, start_time, end_time, color) + + def set_visible_range(self, start_time: TIME, end_time: TIME): + self.run_script(f''' + {self.id}.chart.timeScale().setVisibleRange({{ + from: {pd.to_datetime(start_time).timestamp()}, + to: {pd.to_datetime(end_time).timestamp()} + }}) + ''') + + def resize(self, width: float = None, height: float = None): + """ + Resizes the chart within the window. + Dimensions should be given as a float between 0 and 1. + """ + self._width = width if width is not None else self._width + self._height = height if height is not None else self._height + self.run_script(f''' + {self.id}.scale.width = {self._width} + {self.id}.scale.height = {self._height} + {self.id}.reSize() + ''') + def time_scale(self, right_offset: int = 0, min_bar_spacing: float = 0.5, visible: bool = True, time_visible: bool = True, seconds_visible: bool = False, border_visible: bool = True, border_color: str = None): diff --git a/lightweight_charts/chart.py b/lightweight_charts/chart.py index 237638f..aa0fecd 100644 --- a/lightweight_charts/chart.py +++ b/lightweight_charts/chart.py @@ -17,7 +17,7 @@ class CallbackAPI: class PyWV: def __init__(self, q, start_ev, exit_ev, loaded, emit_queue, return_queue, html, debug, - width, height, x, y, on_top, maximize): + width, height, x, y, screen, on_top, maximize): self.queue = q self.return_queue = return_queue self.exit = exit_ev @@ -26,18 +26,20 @@ class PyWV: self.html = html self.windows = [] - self.create_window(width, height, x, y, on_top, maximize) + self.create_window(width, height, x, y, screen, on_top, maximize) start_ev.wait() webview.start(debug=debug) self.exit.set() - def create_window(self, width, height, x, y, on_top, maximize): + def create_window(self, width, height, x, y, screen=0, on_top=False, maximize=False): + screen = webview.screens[screen] if maximize: - width, height = webview.screens[0].width, webview.screens[0].height + width, height = screen.width, screen.height self.windows.append(webview.create_window( - '', html=self.html, on_top=on_top, js_api=self.callback_api, - width=width, height=height, x=x, y=y, background_color='#000000')) + '', html=self.html, js_api=self.callback_api, + width=width, height=height, x=x, y=y, screen=screen, + on_top=on_top, background_color='#000000')) self.windows[-1].events.loaded += lambda: self.loop(self.loaded[len(self.windows)-1]) def loop(self, loaded): @@ -68,29 +70,29 @@ class Chart(abstract.AbstractChart): _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, + def __init__(self, width: int = 800, height: int = 600, x: int = None, y: int = None, screen: int = 0, 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): 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 + window = abstract.Window(lambda s: self._q.put((self._i, s)), 'pywebview.api.callback') 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, + width, height, x, y, screen, on_top, maximize, ), daemon=True) self._process.start() 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))) + self._q.put(('create_window', (width, height, x, y, screen, on_top, maximize))) def show(self, block: bool = False): """ diff --git a/lightweight_charts/js/funcs.js b/lightweight_charts/js/funcs.js index ac61d9b..11075aa 100644 --- a/lightweight_charts/js/funcs.js +++ b/lightweight_charts/js/funcs.js @@ -63,7 +63,7 @@ if (!window.Chart) { window.addEventListener('resize', () => this.reSize()) } reSize() { - let topBarOffset = 'topBar' in this ? this.topBar.offsetHeight : 0 + let topBarOffset = 'topBar' in this && this.scale.height !== 0 ? this.topBar.offsetHeight : 0 this.chart.resize(window.innerWidth * this.scale.width, (window.innerHeight * this.scale.height) - topBarOffset) } makeCandlestickSeries() { @@ -80,7 +80,7 @@ if (!window.Chart) { this.volumeSeries = this.chart.addHistogramSeries({ color: '#26a69a', priceFormat: {type: 'volume'}, - priceScaleId: '', + priceScaleId: 'volume_scale', }) this.series.priceScale().applyOptions({ scaleMargins: {top: 0.2, bottom: 0.2}, @@ -219,6 +219,12 @@ if (!window.Chart) { }); } + toJSON() { + // Exclude the chart attribute from serialization + const {lines, ...serialized} = this; + return serialized; + } + makeLines(chart) { this.lines = [] if (this.linesEnabled) chart.lines.forEach(line => this.lines.push(this.makeLineRow(line))) @@ -291,6 +297,10 @@ if (!window.Chart) { window.Legend = Legend } +function syncCharts(childChart, parentChart) { + syncCrosshairs(childChart.chart, parentChart.chart) + syncRanges(childChart, parentChart) +} function syncCrosshairs(childChart, parentChart) { function crosshairHandler (e, thisChart, otherChart, otherHandler) { thisChart.applyOptions({crosshair: { horzLine: { @@ -328,6 +338,20 @@ function syncCrosshairs(childChart, parentChart) { parentChart.subscribeCrosshairMove(parentCrosshairHandler) childChart.subscribeCrosshairMove(childCrosshairHandler) } +function syncRanges(childChart, parentChart) { + let setChildRange = (timeRange) => childChart.chart.timeScale().setVisibleLogicalRange(timeRange) + let setParentRange = (timeRange) => parentChart.chart.timeScale().setVisibleLogicalRange(timeRange) + + parentChart.wrapper.addEventListener('mouseover', (event) => { + childChart.chart.timeScale().unsubscribeVisibleLogicalRangeChange(setParentRange) + parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange) + }) + childChart.wrapper.addEventListener('mouseover', (event) => { + parentChart.chart.timeScale().unsubscribeVisibleLogicalRangeChange(setChildRange) + childChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setParentRange) + }) + parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange) +} function stampToDate(stampOrBusiness) { return new Date(stampOrBusiness*1000) diff --git a/lightweight_charts/util.py b/lightweight_charts/util.py index 6196023..493c736 100644 --- a/lightweight_charts/util.py +++ b/lightweight_charts/util.py @@ -67,7 +67,7 @@ def price_scale_mode(mode: PRICE_SCALE_MODE): def marker_shape(shape: MARKER_SHAPE): - return shape[:shape.index('_')]+shape[shape.index('_')+1:].title() if '_' in shape else shape.title() + return shape[:shape.index('_')]+shape[shape.index('_')+1:].title() if '_' in shape else shape def marker_position(p: MARKER_POSITION): diff --git a/setup.py b/setup.py index ad5db66..ccf23de 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,12 @@ with open('README.md', 'r', encoding='utf-8') as f: setup( name='lightweight_charts', - version='1.0.17', + version='1.0.17.1', packages=find_packages(), python_requires='>=3.8', install_requires=[ 'pandas', - 'pywebview', + 'pywebview>=4.3', ], package_data={ 'lightweight_charts': ['js/*.js'],