louisnw01 / lightweight-charts-python

Python framework for TradingView's Lightweight Charts JavaScript library.
MIT License
1.1k stars 204 forks source link

[BUG] crash on chart.events.click on empty area #405

Closed Iss-in closed 6 days ago

Iss-in commented 3 months ago

Expected Behavior

on using

chart.events.click += some_func

chart is crashing when click is made on some empty area, left or right of candlesticks area

Current Behaviour

chart is crashing ,

File /venv/lib/python3.11/site-packages/lightweight_charts-2.0-py3.11.egg/lightweight_charts/chart.py", line 221, in show_async await func(*args) if asyncio.iscoroutinefunction(func) else func(*args) ^^^^^^^^^^^ File /venv/lib/python3.11/site-packages/lightweight_charts-2.0-py3.11.egg/lightweight_charts/util.py", line 129, in final_wrapper other(self._chart, *arg) if not self._wrapper else self._wrapper(other, self._chart, *arg) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File /venv/lib/python3.11/site-packages/lightweight_charts-2.0-py3.11.egg/lightweight_charts/util.py", line 173, in <lambda> wrapper=lambda func, c, *args: func(c, *[float(a) for a in args]) ^^^^^^^^^^^^^^^^^^^^^^^^ File /venv/lib/python3.11/site-packages/lightweight_charts-2.0-py3.11.egg/lightweight_charts/util.py", line 173, in <listcomp> wrapper=lambda func, c, *args: func(c, *[float(a) for a in args]) ^^^^^^^^ ValueError: could not convert string to float: 'null'

Reproducible Example

using 2.0 branch, 

add  chart.events.click += on_chart_click

and click on some empty area

Environment

- OS:linux
- Library:2.0 branch
JiteshMule17 commented 3 months ago

Replace the Events class in util.py with the code provided below, and the issue will be resolved.

class Events:
    def __init__(self, chart):
        self.new_bar = Emitter()
        self.search = JSEmitter(chart, f'search{chart.id}',
            lambda o: chart.run_script(f'''
            Lib.Handler.makeSpinner({chart.id})
            {chart.id}.search = Lib.Handler.makeSearchBox({chart.id})
            ''')
        )
        salt = chart.id[chart.id.index('.')+1:]
        self.range_change = JSEmitter(chart, f'range_change{salt}',
            lambda o: chart.run_script(f'''
            let checkLogicalRange{salt} = (logical) => {{
                {chart.id}.chart.timeScale().unsubscribeVisibleLogicalRangeChange(checkLogicalRange{salt})

                let barsInfo = {chart.id}.series.barsInLogicalRange(logical)
                if (barsInfo) window.callbackFunction(`range_change{salt}_~_${{barsInfo.barsBefore}};;;${{barsInfo.barsAfter}}`)

                setTimeout(() => {chart.id}.chart.timeScale().subscribeVisibleLogicalRangeChange(checkLogicalRange{salt}), 50)
            }}
            {chart.id}.chart.timeScale().subscribeVisibleLogicalRangeChange(checkLogicalRange{salt})
            '''),
            wrapper=lambda o, c, *arg: o(c, *[self.safe_float_conversion(a) for a in arg])
        )

        self.click = JSEmitter(chart, f'subscribe_click{salt}',
            lambda o: chart.run_script(f'''
            let clickHandler{salt} = (param) => {{
                if (!param.point) return;
                const time = {chart.id}.chart.timeScale().coordinateToTime(param.point.x)
                const price = {chart.id}.series.coordinateToPrice(param.point.y);
                window.callbackFunction(`subscribe_click{salt}_~_${{time}};;;${{price}}`)
            }}
            {chart.id}.chart.subscribeClick(clickHandler{salt})
            '''),
            wrapper=lambda func, c, *args: func(c, *[self.safe_float_conversion(a) for a in args])
        )

    @staticmethod
    def safe_float_conversion(value):
        try:
            return float(value)
        except (ValueError, TypeError):
            return float('nan')  # or any other default value like None
Iss-in commented 1 month ago

thanks a lot