How to update/extend plotly chart

Hey everybody!
Is there a way to transfer of the current position to the window via callbacks or make an event if the user has reached the start value of figure for loading data?

What I have now:

import datetime as dt
import time
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objects as go
from dash.dependencies import Output, Input
from dateutil.relativedelta import relativedelta
from django_plotly_dash import DjangoDash

inc_color = '#15aaba'
dec_color = '#7F7F7F'


config = {
    'scrollZoom': True,
    'displayModeBar': True,
    'displaylogo': False,
    'responsive': True,
    'modeBarButtonsToRemove': ['toImage',
                               'select2d',
                               'lasso2d',
                               'resetScale2d',
                               'hoverCompareCartesian',
                               'hoverClosestCartesian',
                               'toggleSpikelines',
                               ],
}

ohlc = DjangoDash('Plot',
                  add_bootstrap_links=True)


def generate_layout():
    return html.Div([
        dcc.Graph(id='graph',
                  animate=True,
                  config=config),
        
        html.Div(id='data-frame',
                 style={'display': 'none'}),
    ])


ohlc.layout = generate_layout()


@ohlc.expanded_callback(Output('graph', 'figure'),
                        [Input('data-frame', 'children')],
                        )
def load_graph(*args, **kwargs):
    df = pd.read_json(kwargs['session_state']['django_to_dash_context'], orient='split')

    return go.Figure(
        data=[go.Candlestick(
            x=df['x_axis'],
            open=df['open'], high=df['high'], low=df['low'], close=df['close'],
            increasing_line_color=inc_color, decreasing_line_color=dec_color,
        )],
        layout=dict(
            xaxis=dict(
                autorange=True,
                rangeslider=dict(
                    visible=False,
                ),
            ),
            yaxis=dict(
                overlaying="y",
                side="right",
                position=1,  # An int or float in the interval [0, 1]
                tickformat=".2f",
                ticksuffix="₽",
            ),
            margin=dict(
                t=0, b=0, r=40, l=0
            ),
            autosize=False,
            hovermode='x',
            dragmode='pan',
        ),
    )

Okay, I find a way to transfer of the current position to the window via callbacks.
I make it using [Input('graph', 'relayoutData')] then I take args of current window position

 x_start = relayout_data['xaxis.range[0]']
 x_end = relayout_data['xaxis.range[1]']

Now I have next question: How to extend data of figure? I’m trying a lot… But all futile efforts, I beat my head against the keyboard(((

All this is followed by error:


Here 's how looks my updateData in the debugger:

Please help me a little with python :snake: code :pray: :pray: :pray: not links with JS code…

Full code here:

import datetime as dt

import pandas as pd
import requests
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Output, Input, State
from dateutil.relativedelta import relativedelta
from django_plotly_dash import DjangoDash

from shares.services import get_board_candles

inc_color = '#15aaba'
dec_color = '#7F7F7F'

config = {
    'scrollZoom': True,
    'displayModeBar': True,
    'displaylogo': False,
    'responsive': True,
    'modeBarButtonsToRemove': ['toImage',
                               'select2d',
                               'lasso2d',
                               'resetScale2d',
                               'hoverCompareCartesian',
                               'hoverClosestCartesian',
                               'toggleSpikelines',
                               ],
}

ohlc = DjangoDash('Plot',
                  add_bootstrap_links=True)

ohlc.layout = html.Div([
    dcc.Graph(id='graph',
              animate=True,
              config=config),
    html.Div(id='data-frame',
             style={'display': 'none'}),
])


@ohlc.expanded_callback(Output('graph', 'figure'),
                        [Input('data-frame', 'children')],
                        [State('graph', 'figure')])
def load_graph(children, *args, **kwargs):
    df = pd.read_json(kwargs['session_state']['django_to_dash_context'], orient='split')
    data_from_store = children
    figure = go.Figure(
        data=[go.Candlestick(
            x=df['x_axis'],
            open=df['open'], high=df['high'], low=df['low'], close=df['close'],
            increasing_line_color=inc_color, decreasing_line_color=dec_color,
        )],
        layout=dict(
            xaxis=dict(
                autorange=True,
                rangeslider=dict(
                    visible=False,
                ),
            ),
            yaxis=dict(
                overlaying="y",
                side="right",
                position=1,
                tickformat=".2f",
                ticksuffix="₽",
            ),
            margin=dict(
                t=0, b=0, r=40, l=0
            ),
            autosize=False,
            hovermode='x',
            dragmode='pan',
        ),
    )
    return figure


@ohlc.expanded_callback(Output('data-frame', 'children'),
                        [Input('graph', 'relayoutData')],
                        )
def save_in_session_store(relayout_data, *args, **kwargs):
    data = None
    security = kwargs['session_state']['security']
    try:
        x_start = relayout_data['xaxis.range[0]']
        x_end = relayout_data['xaxis.range[1]']
    except (TypeError, KeyError):
        pass
    else:
        interval = 1
        if x_start <= 0:
            days_end = abs(x_start // 525) + 1
            days_start = abs(x_end // 525) + 1
            while not data:
                start = (dt.datetime.today() -
                         relativedelta(days=days_end)).strftime('%Y-%m-%d')
                end = (dt.datetime.today() -
                       relativedelta(days=days_start)).strftime('%Y-%m-%d')
                with requests.Session() as session:
                    data = get_board_candles(session, security, interval, start, end)
                if not data:
                    days_start += 1
        try:
            df = pd.DataFrame(data)
        except Exception as _:
            pass
        else:
            df['volume'] = pd.to_numeric(df.value)
            df['datetime'] = pd.to_datetime(df.begin, format='%Y-%m-%d %H:%M')
            del df['begin'], df['value']
            if df['datetime'][0].minute == 59:
                df['datetime'] += pd.Timedelta('1 minute')
            df['x_axis'] = [date.strftime('%H:%M | %d') for _, date in
                            enumerate(df['datetime'])]  # '%d %b %H:%M'
            df = df.to_json(orient='split')
            return df


@ohlc.expanded_callback(Output('graph', 'extendData'),
                        [Input('data-frame', 'children')],
                        [State('graph', 'figure')])
def save_in_session_store(children, *args, **kwargs):
    if children:
        df = pd.read_json(children, orient='split')
        df_dict = df.to_dict(orient='list')
        return [df_dict, 0, 500]  # [updateData, traceIndices, maxPoints]

Hi Kireal, I think the syntax of the returned extendData is not quite right, the trace number must be an array so [0] as described in https://dash.plotly.com/dash-core-components/graph. You have an example of Python code using extendData on https://github.com/plotly/dash-core-components/pull/461.

1 Like

Thank you for your answer Emmanuelle, I would not be able to find the links you gave.
But I still cannot understand how I can expand the data if I have more values added than ‘x’ and ‘y’.
I need to expand:

x = list(df['x_axis'])
open = list(df['open'])
high = list(df['high'])
low = list(df['low'])
close = list(df['close'])

Considering that my x data is string. I have categorical type of x axis.

Based on the links, what code I made, but it not working :frowning: :

@ohlc.callback(Output('graph', 'extendData'),
               [Input('store', 'data')],
               [State('graph', 'figure')])
def update_extend_then_add(data, existing, **kwargs):
    if data is None:
        raise PreventUpdate
    df = pd.read_json(data, orient='split')
    x = list(df['x_axis'])
    opens = list(df['open'])
    high = list(df['high'])
    low = list(df['low'])
    closes = list(df['close'])
    retval = [dict(
        close=[closes], decreasing={'line': {'color': '#7F7F7F'}}, high=[high],
        increasing={'line': {'color': '#15aaba'}}, low=[low], open=[opens], x=[x]
    )]
    return retval

Here 's what I have in debugger:
for ‘existing’


for ‘retval’

P.S:
I very appreciate your help. Before Dash I used a very mediocre library bokeh. Dash is work of art in comparsion with bokeh

1 Like

Hey @Kireal , in extendData I believe you can only pass data points, so close, high, low, open and x are ok, but it will use the existing linecolor for decreasing and increasing you cannot pass a new value. If you need more help, please include a standalone app with dummy data, because otherwise I can only guess :-). Oh, and there are really good things with bokeh, but I’m happy that Dash suits your needs!

1 Like

Could you give a link to how to include a standalone app?

And I think I’ve got it.
Thank you so much @Emmanuelle

Now I have bugs with autoscroll
That’s what it looks like:

My full code:

import datetime as dt

import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objects as go
import requests
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate
from dateutil.relativedelta import relativedelta
from django_plotly_dash import DjangoDash
from shares.services import get_board_candles

config = {
    'scrollZoom': True,
    'staticPlot ': True,
    'autosizable': False,
    'showAxisRangeEntryBoxes ': True,
    'displayModeBar': True,
    'displaylogo': False,
    'responsive': True,
    'modeBarButtonsToRemove': ['toImage',
                               'select2d',
                               'lasso2d',
                               'resetScale2d',
                               'hoverCompareCartesian',
                               'hoverClosestCartesian',
                               'toggleSpikelines',
                               ],
}


def get_data_from_moex(security, start=None, end=None):
    interval = 1
    data = None
    if start is None and end is None:
        start = dt.datetime.today()
    if start <= dt.datetime.today():
        start = start.replace(hour=0, minute=0, second=0)
        while not data:
            with requests.Session() as session:
                data = get_board_candles(session, security, interval, start, end)
            if not data:
                start = start - relativedelta(days=1)
                end = start + relativedelta(days=1)
        # Dataframe
        df = pd.DataFrame(data)
        df['datetime'] = pd.to_datetime(df.begin, format='%Y-%m-%d %H:%M')
        if df['datetime'][0].minute == 59:
            df['datetime'] += pd.Timedelta('1 minute')
        del df['begin'], df['value']
        jsonified_df = df.to_json(orient='split')
        return jsonified_df


def load_figure(jsonified_df, graph=None):
    df = pd.read_json(jsonified_df, orient='split')
    fig = go.Figure(
        data=[go.Candlestick(
            dict(x=df['datetime'], open=df['open'], high=df['high'], low=df['low'],
                 close=df['close']))],
        layout=dict(xaxis=dict(autorange=True, rangeslider=dict(visible=False),
                               rangebreaks=[
                                   dict(pattern='hour', bounds=[19, 10])]
                               ),
                    yaxis=dict(autorange=True, overlaying='y', side='right',
                               position=1, tickformat='.2f', ticksuffix='₽'),
                    margin=dict(t=0, b=0, r=40, l=0),
                    autosize=True,
                    hovermode='x', dragmode='pan'
                    )
    )
    return fig


ohlc = DjangoDash('Plot', serve_locally=True, add_bootstrap_links=True)


def serve_layout():
    return html.Div([
        dcc.Graph(id='graph', animate=True, config=config),
        dcc.Store(id='signal', storage_type='media'),
        dcc.Interval(
            id='interval',
            interval=1000,
            n_intervals=0),
    ])


ohlc.layout = serve_layout


@ohlc.callback(Output('graph', 'figure'),
               [Input('signal', 'data')])
def load_graph(signal, *args, **kwargs):
    if signal:
        fig = load_figure(signal)
        return fig


@ohlc.expanded_callback(Output('signal', 'data'),
                        [Input('graph', 'relayoutData')])
def save_in_store(relayout_data, *args, **kwargs):
    security = kwargs['session_state']['security']
    start = None
    end = None
    try:
        x_start = relayout_data['xaxis.range[0]'].split(sep=' ')
        x_end = relayout_data['xaxis.range[1]'].split(sep=' ')
    except (TypeError, KeyError, AttributeError):
        jsonified_df = get_data_from_moex(security)
        return jsonified_df
    else:
        x_start = f'{x_start[0]} {x_start[1].split(sep=":")[0]}'
        x_start = dt.datetime.strptime(x_start, '%Y-%m-%d %H')
        x_end = f'{x_end[0]} {x_end[1].split(sep=":")[0]}'
        x_end = dt.datetime.strptime(x_end, '%Y-%m-%d %H')
        if x_start <= x_start.replace(hour=10):
            start = x_start - relativedelta(days=1)
            end = start + relativedelta(days=2)
        elif x_end >= x_end.replace(hour=19):
            start = x_start + relativedelta(days=1)
            end = start + relativedelta(days=2)
        jsonified_df = get_data_from_moex(security, start, end)
        return jsonified_df


@ohlc.callback(Output('graph', 'extendData'),
               [Input('signal', 'data'), Input('interval', 'n_intervals')],
               [State('graph', 'figure')])
def update_extend_then_add(jsonified_df, n_intervals, graph, *args, **kwargs):
    if graph is None:
        raise PreventUpdate
    if jsonified_df:
        # print((len(jsonified_df.encode("utf-8"))) / 1024, 'kBytes')
        df = pd.read_json(jsonified_df, orient='split')
        x = list(df['datetime'])
        opens = list(df['open'])
        high = list(df['high'])
        low = list(df['low'])
        closes = list(df['close'])
        retval = [dict(
            close=[closes],
            high=[high],
            low=[low],
            open=[opens],
            x=[x]
        )]
        return retval