✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

Strange behavior during data updating

Hi,

I’m having some wired behavior during updating data to one of my graphs. This plot is dynamically updated - and data are being extended okay, without any issues. The problem appears when I want to update the layout of the graph (for example change Y axes range) and after layout update, it looks like on this gif.
After I delete and recreate the plot, everything looks perfect until I change layout properties.
Also, I don’t get any errors or exceptions. Did anyone also face this issue?

Hi @manius.cezar,

Without code it is impossible to express an opinion on your issue :frowning:

Hi @empet,

Sorry for a long time without an answer, but after some time I solved it myself. Anyway, the issue turned out to be animate property of dcc.Grpah object.
To duplicate it, please run below code snippet. Run it with the animate option enabled and disabled (line #124). When it will be set to True, you will be able to see the issue.
Below you can find the code snippet with a data.csv file for storing the data.

data.csv:

timestamp,value
2020-06-24 13:49:58.930175,0.6804218634026458

Dash app:

import dash
from dash.dependencies import Input, Output, State, MATCH
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objects as go
import random
from dash.exceptions import PreventUpdate
import pandas as pd
import datetime
import csv, json


app = dash.Dash(__name__)


app.layout = html.Div([
    html.Div([
        html.H3('Extend trace'),
        html.Button('Add graph', id='add_graph'),
        html.Div(id='graph_section'),
    ]),
    dcc.Interval(
        id='generator',
        interval=1000,
        n_intervals=0),
    html.Div(
        id='hidden_div',
        hidden=True 
    )
])


@app.callback(
    Output('hidden_div', 'children'),
    [Input('generator', 'n_intervals')]
)
def gen_data(n_intervals):
    with open('data.csv', 'a', newline='') as csv_file:
        writer = csv.DictWriter(csv_file, ['timestamp', 'value'])
        writer.writerow({'timestamp': str(datetime.datetime.now()), 'value': random.random()})
    return 'anything'


@app.callback(
    Output('graph_section', 'children'),
    [Input('add_graph', 'n_clicks')]
)
def add_graph(click):
    if click is None:
        raise PreventUpdate
    else:
        df = read_csv()
        data = [
            go.Scatter(
                x=df['timestamp'],
                y=df['value'],
                mode='lines',
                name='some name',
                line={'color': '#FF0000'},
                text='some text'
            )
        ]
        data.append(
            go.Scatter(
                x=df['timestamp'],
                y=df['value'] * (-1),
                mode='lines',
                name='some name',
                line={'color': '#00FF00'},
                text='some text'
            )
        )
        children = []
        for i in range(click):
            div = html.Div(
                [
                    dcc.Graph(
                        id={'index': i, 'type': 'graph'},
                        figure=dict(
                            data=data,
                            layout={
                                'yaxis': {
                                    'title': {'text': 'some titile'},
                                    'range': [-1,1],
                                    'autorange': False
                                },
                                'xaxis': {
                                    'title': {'text': 'Time (& date) [hh:mm:SS  Month Day, Year]'},
                                    'autorange': True,
                                    'rangemode': 'normal',
                                    'rangeselector': {
                                        'buttons': [
                                            dict(
                                                count=10,
                                                label='  10 seconds  ',
                                                step='second',
                                                stepmode='backward'
                                            ),
                                            dict(
                                                count=1,
                                                label='  1 minute  ',
                                                step='minute',
                                                stepmode='backward'
                                            ),
                                            dict(
                                                count=10,
                                                label='  10 minutes  ',
                                                step='minute',
                                                stepmode='backward'
                                            )
                                        ]
                                    },
                                    'rangeslider': {
                                        'visible': True,
                                        'yaxis': {
                                            'rangemode': 'auto'
                                        }
                                    },
                                    'type':'date'
                                },
                                'showlegend': True
                            }
                        ),
                        animate=True
                    ),
                    dcc.Interval(
                        id={'index': i, 'type': 'interval'},
                        interval=1000,
                        n_intervals=0),
                    dcc.Input(id={'index': i, 'type': 'input'}, type='number',
                                    placeholder='Set graph scale', value=1,
                                    min=1, step=1, max=10)
                ]
            )
            children.append(div)
        return children


def read_csv(from_date: datetime.datetime = None):
    if from_date is None:
        return pd.read_csv('data.csv')
    else:
        df = pd.read_csv('data.csv')
        return df[df['timestamp'] > from_date]


@app.callback(
    Output({'index': MATCH, 'type': 'graph'}, 'extendData'),
    [Input({'index': MATCH, 'type': 'interval'}, 'n_intervals')],
    [State({'index': MATCH, 'type': 'graph'}, 'figure')]
)
def update_extend_traces_traceselect(n_intervals, existing):
    if n_intervals is None:
        raise PreventUpdate
    old_x = existing['data'][0]['x']
    old_data = {'timestamp': old_x}
    old_df = pd.DataFrame(old_data)
    last_update_timestamp = old_df.iloc[-1]['timestamp']

    new_df = read_csv(last_update_timestamp)
    if new_df is None:
        raise PreventUpdate

    data = dict(
        x = [new_df['timestamp'].to_list(), new_df['timestamp'].to_list()],
        y = [new_df['value'].to_list(), (new_df['value'] * (-1)).to_list()]
    )

    return [data, [0, 1]]


@app.callback(
    Output({'index': MATCH, 'type': 'graph'}, 'figure'),
    [Input({'index': MATCH, 'type': 'input'}, 'value')],
    [State({'index': MATCH, 'type': 'graph'}, 'figure')]
)
def change_y_max_range(value, figure):
    if value is None:
        raise PreventUpdate
    else:
        print(figure['layout'])
        figure['layout']['yaxis']['range'] = [-value, value]

        return figure


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

Please let me know if you can also duplicate this. Do you think it is a plotly bug, or I used this option incorrectly?

Thanks!