Refactoring/Enhancements/Fixes

Breaking Changes:
- Removed the `api` parameter; callbacks no longer need to be in a specific class.
- Topbar callbacks now take a chart as an argument (see updated callback examples)
- Removed the `topbar` parameter from chart declaration. The Topbar will be automatically created upon declaration of a topbar widget.
- Removed the `searchbox` parameter from charts. It will be created upon subscribing to it in `chart.events`.
- Removed `dynamic_loading`.
- Removed ‘volume_enabled’ parameter. Volume will be enabled if the volumn column is present in the dataframe.
- Widgets’ `func` parameter is now declared last.
- Switchers take a tuple of options rather than a variable number of arguments.
- `add_hotkey` renamed to `hotkey`
- Horizontal lines now take a `func` argument rather than `interactive`. This event will emit the Line object that was moved.
- Removed the `name` parameter from `line.set`. Line object names are now declared upon creation.

Enhancements:
- Added the `button` widget to the Topbar.
- Added the color picker to the drawing context menu.
- Charts now have a `candle_data` method, which returns the current data displayed on the chart as a DataFrame.
- Fixed callbacks are now located in the `chart.events` object:
    - search (e.g `chart.events.search += on_search`)
    - new_bar
    - range_change
- Added volume to the legend
- Drawings can now be accessed through `chart.toolbox.drawings`
- added the `style` and `name` parameters to `create_line`

Bug Fixes:
- Fixed a bug causing new charts not to load after `exit` was called.
- Refactored rayline placement to ensure they do not move the visible range.
- Fixed a bug causing the visible range to shift when trendlines are moved past the final candlestick.
- Fixed a bug preventing trendlines and raylines on irregular timeframes.
- Fixed a bug causing the legend to prevent mouse input into the chart.
This commit is contained in:
louisnw
2023-08-14 16:06:16 +01:00
parent 06b605d3a7
commit 34ce3f7199
22 changed files with 1024 additions and 784 deletions

View File

@ -1,74 +1,92 @@
# Callbacks
# Events
The `Chart` object allows for asynchronous and synchronous callbacks to be passed back to python, allowing for more sophisticated chart layouts including searching, timeframe selectors text boxes, and hotkeys using the `add_hotkey` method.
`QtChart`and `WxChart` can also use callbacks.
A variety of the parameters below should be passed to the Chart upon decaration.
* `api`: The class object that the fixed callbacks will always be emitted to.
* `topbar`: Adds a [TopBar](#topbar) to the `Chart` or `SubChart` and allows use of the `create_switcher` method.
* `searchbox`: Adds a search box onto the `Chart` or `SubChart` that is activated by typing.
Events allow asynchronous and synchronous callbacks to be passed back into python.
___
## How to use Callbacks
## `chart.events`
Fixed Callbacks are emitted to the class given as the `api` parameter shown above.
`events.search` `->` `chart` | `string`: Fires upon searching. Searchbox will be automatically created.
`events.new_bar` `->` `chart`: Fires when a new candlestick is added to the chart.
`events.range_change` `->` `chart` | `bars_before` | `bars_after`: Fires when the range (visibleLogicalRange) changes.
Chart events can be subscribed to using: `chart.events.<name> += <callable>`
___
## How to use Events
Take a look at this minimal example:
```python
class API:
def __init__(self):
self.chart = None
from lightweight_charts import Chart
def on_search(chart, string):
print(f'Search Text: "{string}" | Chart/SubChart ID: "{chart.id}"')
if __name__ == '__main__':
chart = Chart()
chart.events.search += on_search
chart.show(block=True)
def on_search(self, string):
print(f'Search Text: "{string}" | Chart/SubChart ID: "{self.chart.id}"')
```
Upon searching in a pane, the expected output would be akin to:
```
Search Text: "AAPL" | Chart/SubChart ID: "window.blyjagcr"
```
The ID shown above will change depending upon which pane was used to search, due to the instance of `self.chart` dynamically updating to the latest pane which triggered the callback.
`self.chart` will update upon each callback, allowing for access to the specific pane in question.
The ID shown above will change depending upon which pane was used to search, allowing for access to the object in question.
```{important}
* When using `show` rather than `show_async`, block should be set to `True` (`chart.show(block=True)`).
* `API` class methods can be either coroutines or normal methods.
* Non fixed callbacks (switchers, hotkeys) can be methods, coroutines, or regular functions.
* Event callables can be either coroutines, methods, or functions.
```
There are certain callbacks which are always emitted to a specifically named method of API:
* Search callbacks: `on_search`
* Interactive Horizontal Line callbacks: `on_horizontal_line_move`
___
## `TopBar`
The `TopBar` class represents the top bar shown on the chart when using callbacks:
The `TopBar` class represents the top bar shown on the chart:
![topbar](https://i.imgur.com/Qu2FW9Y.png)
This class is accessed from the `topbar` attribute of the chart object (`chart.topbar.<method>`), after setting the topbar parameter to `True` upon declaration of the chart.
This object is accessed from the `topbar` attribute of the chart object (`chart.topbar.<method>`).
Switchers and text boxes can be created within the top bar, and their instances can be accessed through the `topbar` dictionary. For example:
Switchers, text boxes and buttons can be added to the top bar, and their instances can be accessed through the `topbar` dictionary. For example:
```python
chart = Chart(api=api, topbar=True)
chart.topbar.textbox('symbol', 'AAPL') # Declares a textbox displaying 'AAPL'.
print(chart.topbar['symbol'].value) # Prints the value within ('AAPL')
chart.topbar['symbol'].set('MSFT') # Sets the 'symbol' textbox to 'MSFT'
print(chart.topbar['symbol'].value) # Prints the value again ('MSFT')
```
Events can also be emitted from the topbar. For example:
```python
from lightweight_charts import Chart
def on_button_press(chart):
new_button_value = 'On' if chart.topbar['my_button'].value == 'Off' else 'Off'
chart.topbar['my_button'].set(new_button_value)
print(f'Turned something {new_button_value.lower()}.')
if __name__ == '__main__':
chart = Chart()
chart.topbar.button('my_button', 'Off', func=on_button_press)
chart.show(block=True)
```
___
### `switcher`
`name: str` | `method: function` | `*options: str` | `default: str`
`name: str` | `options: tuple` | `default: str` | `func: callable`
* `name`: the name of the switcher which can be used to access it from the `topbar` dictionary.
* `method`: The function from the `api` class given to the constructor that will receive the callback.
* `options`: The strings to be displayed within the switcher. This may be a variety of timeframes, security types, or whatever needs to be updated directly from the chart.
* `options`: The options for each switcher item.
* `default`: The initial switcher option set.
___
@ -79,6 +97,15 @@ ___
* `initial_text`: The text to show within the text box.
___
### `button`
`name: str` | `button_text: str` | `separator: bool` | `func: callable`
* `name`: the name of the text box to access it from the `topbar` dictionary.
* `button_text`: Text to show within the button.
* `separator`: places a separator line to the right of the button.
* `func`: The event handler which will be executed upon a button click.
___
## Callbacks Example:
```python
@ -93,40 +120,37 @@ def get_bar_data(symbol, timeframe):
return pd.read_csv(f'../examples/6_callbacks/bar_data/{symbol}_{timeframe}.csv')
class API:
def __init__(self):
self.chart = None # Changes after each callback.
def on_search(chart, searched_string):
new_data = get_bar_data(searched_string, chart.topbar['timeframe'].value)
if new_data.empty:
return
chart.topbar['symbol'].set(searched_string)
chart.set(new_data)
def on_search(self, searched_string): # Called when the user searches.
new_data = get_bar_data(searched_string, self.chart.topbar['timeframe'].value)
if new_data.empty:
return
self.chart.topbar['symbol'].set(searched_string)
self.chart.set(new_data)
def on_timeframe_selection(chart):
new_data = get_bar_data(chart.topbar['symbol'].value, chart.topbar['timeframe'].value)
if new_data.empty:
return
chart.set(new_data, True)
def on_timeframe_selection(self): # Called when the user changes the timeframe.
new_data = get_bar_data(self.chart.topbar['symbol'].value, self.chart.topbar['timeframe'].value)
if new_data.empty:
return
self.chart.set(new_data, True)
def on_horizontal_line_move(self, line_id, price):
print(f'Horizontal line moved to: {price}')
def on_horizontal_line_move(chart, line):
print(f'Horizontal line moved to: {line.price}')
if __name__ == '__main__':
api = API()
chart = Chart(api=api, topbar=True, searchbox=True, toolbox=True)
chart = Chart(toolbox=True)
chart.legend(True)
chart.topbar.textbox('symbol', 'TSLA')
chart.topbar.switcher('timeframe', api.on_timeframe_selection, '1min', '5min', '30min', default='5min')
chart.topbar.switcher('timeframe', ('1min', '5min', '30min'), default='5min',
func=on_timeframe_selection)
df = get_bar_data('TSLA', '5min')
chart.set(df)
chart.horizontal_line(200, interactive=True)
chart.horizontal_line(200, func=on_horizontal_line_move)
chart.show(block=True)
```