tysonwu / dash-tradingview

Tradingview Lightweight Charts wrapper for Plotly Dash apps
MIT License
130 stars 22 forks source link

The dash-tradingview clientside_callback function calls a quick network response. #13

Closed WheatMaThink closed 7 months ago

WheatMaThink commented 7 months ago

app.py


import dash
from dash import html, Output, Input, State, ClientsideFunction, dcc
import dash_tvlwc

candlestick_data = [
    {'close': 97.56, 'high': 101.29, 'low': 95.07, 'open': 100, 'time': '2021-01-01'},
    {'close': 96.06, 'high': 99.06, 'low': 95.17, 'open': 97.56, 'time': '2021-01-02'},
    {'close': 92.06, 'high': 98.39, 'low': 90.72, 'open': 96.06, 'time': '2021-01-03'},
    {'close': 95.74, 'high': 97.87, 'low': 89.75, 'open': 92.06, 'time': '2021-01-04'},
    {'close': 92.44, 'high': 97.5, 'low': 88.56, 'open': 95.74, 'time': '2021-01-05'},
    {'close': 89.31, 'high': 93.1, 'low': 85.20, 'open': 92.44, 'time': '2021-01-06'},
    {'close': 85.10, 'high': 93.08, 'low': 82.23, 'open': 89.31, 'time': '2021-01-07'},
    {'close': 81.87, 'high': 88.34, 'low': 77.97, 'open': 85.10, 'time': '2021-01-08'},
    {'close': 79.55, 'high': 82.44, 'low': 76.08, 'open': 81.87, 'time': '2021-01-09'},
    {'close': 82.74, 'high': 84.01, 'low': 78, 'open': 79.55, 'time': '2021-01-10'}
]

line_data = [
    {'time': '2021-01-01', 'value': 100.35},
    {'time': '2021-01-02', 'value': 97.09},
    {'time': '2021-01-03', 'value': 95.74},
    {'time': '2021-01-04', 'value': 98.72},
    {'time': '2021-01-05', 'value': 100.3},
    {'time': '2021-01-06', 'value': 95.8},
    {'time': '2021-01-07', 'value': 91.22},
    {'time': '2021-01-08', 'value': 94.26},
    {'time': '2021-01-09', 'value': 94.9},
    {'time': '2021-01-10', 'value': 94.85}
]

main_panel = [
    dcc.Store(id='tw_operations_store', data={'data_name': {'0': "A", '1': "B"}}),  # data_name in seriesPrices 0,1 数据名称,和数据顺序对应

    html.Div(style={'position': 'relative', 'width': '100%', 'height': '100%'}, children=[
        html.Div(children=[
            dash_tvlwc.Tvlwc(
                id='tv-chart-1',
                seriesData=[candlestick_data, line_data],
                seriesTypes=['candlestick', 'line'],
                width='100%',
            ),

        ], style={'width': '100%', 'height': '100%', 'left': 0, 'top': 0}),

        html.Div(id='chart-info', children=[
            # https://tradingview.github.io/lightweight-charts/tutorials/how_to/tooltips
            html.Span(id='chart-tooltips',
                      style={'width': '120px', 'height': '100%', 'position': 'absolute', 'padding': '8px', 'fontSize': '12px', 'textAlign': 'left', 'zIndex': 999, 'top': '12px', 'left': '12px',
                             'pointerEvents': 'none',
                             'boxSizing': 'border-box',
                             'boxShadow': 'rgba(117, 134, 150, 0.45) 0px 2px 5px 0px',
                             'background': 'rgba(255, 255, 255, 0.25)',
                             'borderRadius': '4px 4px 0px 0px',
                             'borderTopColor': 'rgb(239, 83, 80)',
                             'borderRightColor': 'rgb(239, 83, 80)',
                             'borderLeftColor': 'rgb(239, 83, 80)',
                             'borderBottom': 'none',
                             'color': '#20262E',
                             'WebkitFontSmoothing': 'antialiased',
                             'fontFamily': '-apple-system, BlinkMacSystemFont, "Trebuchet MS", Roboto, Ubuntu, sans-serif',
                             }
                      ),
        ], style={'left': 0, 'top': 0, 'zIndex': 10})
    ]),

]

app = dash.Dash(__name__, suppress_callback_exceptions=True, )
app.layout = html.Div(className='container', children=[
    html.Div(className='main-container', children=[
        html.Div(children=main_panel),
    ]),
])

app.clientside_callback(
    ClientsideFunction(namespace="lightweight_chart", function_name="lightweight_chart_tooltips"),
    [Output("chart-tooltips", "children")],
    Input("tv-chart-1", "crosshair"),
    Input("chart-tooltips", "id"),
    prevent_initial_call=True
)

if __name__ == '__main__':
    app.run_server()

javascript file

function toStrWithTwoDecimals(num) {
    if (num === undefined) {
        return ""
    } else
        return num.toFixed(2);
}

function iterateDictValueWithKey(obj) {
    let str = "";
    for (const key in obj) {
        const value = obj[key];
        if (typeof value === "object") {
            for (const entry of Object.entries(value)) {
                str += `${entry[0]}:${entry[1]}<br>`;
            }
            str += '<br>'
        } else {
            str += `${toStrWithTwoDecimals(value)}<br>`;
        }
    }
    return str;
}

// Define a relatively long callback function in a stand-alone js script
window.dash_clientside = Object.assign({}, window.dash_clientside, {
    lightweight_chart: {
        lightweight_chart_tooltips: function (param, trigger_id) {

            let toolTip = document.getElementById(trigger_id)

            if (param.point === undefined || !param.time || param.point.x < 0) {
                toolTip.style.display = 'none';
            } else {

                const date_str = param.time.year.toString() + "-" + param.time.month.toString().padStart(2, "0") + "-" + param.time.day.toString().padStart(2, "0");
                const price_str = iterateDictValueWithKey(param.seriesPrices)

                toolTip.style.display = 'block';
                toolTip.innerHTML = `<div style="color: ${'rgba( 239, 83, 80, 1)'}">⬤ ABC Inc.</div>
                    <div style="font-size: 18px;color: ${'black'}">
                    ${date_str}
                    </div>
                    <div style="width:100%;font-size: 12px; margin: 1px; color: ${'black'}">
                         ${price_str}
                    </div>`;

                let left = param.point.x; // relative to timeScale
                left -= 60;
                toolTip.style.left = left + 'px';
                toolTip.style.top = 0 + 'px';
            }
            return window.dash_clientside.no_update;
        }
    }
});
image