Axis labeling with dynamic updates

Hi,

I have a an that dynamically updates a graph with data received from another application. My Dash app periodically polls for new data and adds it to the plot extending it.

The X axis of the graph is time, which is being recorded as the time since the epoch. I want to convert this time to a more user-readable format. I can easily do that with 'time.strftime()` and display it in the form “HH:MM:SS”.

However, if I do that then the graph does not show a smooth curve but rather steps that correspond to the individual seconds on the axis.

Is there a way that I can add the data points as frequently as I need for the curve to be smooth but have the X axis labels be on a second granularity?

Thank you.

I thought about it a little bit.

Basically, what I want is to configure how often the X axis display a label. That way I can add any many point on X that are needed for a smooth curve but only display labels at a certain frequency.

I would appreciate any help with this.

Thank you.

Hello @voidtrance,

Is it possible to create a minimal app that will perform the task for people to look into what are you wanting to do?

Here you go:

#!/usr/bin/env python3
import plotly.graph_objects as go
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
import time
import threading

app = Dash("Emulator Trace")

tfig = go.Figure()
t1 = go.Scatter(x=[], y=[], mode="lines", name=f"test1")
t2 = go.Scatter(x=[], y=[], mode="lines", name=f"test2")
tfig.add_trace(t1)
tfig.add_trace(t2)

app.layout = html.Div([
    html.Div([
        dcc.Graph(id="graph", figure=tfig),
        dcc.Interval(id="graph-interval-component", interval=100, n_intervals=0)
    ])
])

start_time = time.time()
global_value1 = 1.
global_value2 = 2.

@app.callback(Output('graph', 'extendData'),
              Input('graph-interval-component', 'n_intervals'))
def update_temp_data(n):
    global global_value1, global_value2
    runtime = round(time.time() - start_time, 3)
    runtime = time.strftime("%H:%M:%S", time.localtime(runtime))
    data = {'x': [[runtime], [runtime]], 'y':[[global_value1], [global_value2]]}
    return data, [0, 1], 100
    
do_run = True
def update():
    global global_value1, do_run
    increase = 0.02
    while do_run:
        global_value1 += increase
        time.sleep(0.02)

t = threading.Thread(None, update)
t.start()

app.run(debug=True)

As you can see from running this, the Y axis rises as the data is updated but it only rises on the mark lines. When a new marker is displayed, it jumps to the next mark.

What I would really want is to have the X axis to keep updating but the markers be only on a second boundary.

Here, try this and see if you like it:

#!/usr/bin/env python3
import plotly.graph_objects as go
from dash import Dash, dcc, html, Input, Output, Patch
import time
import threading

app = Dash("Emulator Trace")

tfig = go.Figure()
t1 = go.Scatter(x=[], y=[], mode="lines", name=f"test1")
t2 = go.Scatter(x=[], y=[], mode="lines", name=f"test2")
tfig.add_trace(t1)
tfig.add_trace(t2)
tfig.update_xaxes(
    rangeslider_visible=True,
    tickformat="%H:%M:%S"
)

app.layout = html.Div([
    html.Div([
        dcc.Graph(id="graph", figure=tfig),
        dcc.Interval(id="graph-interval-component", interval=100, n_intervals=0)
    ])
])

start_time = time.time()
global_value1 = 1.
global_value2 = 2.


@app.callback(Output('graph', 'extendData'),
                Output('graph', 'figure'),
              Input('graph-interval-component', 'n_intervals'))
def update_temp_data(n):
    global global_value1, global_value2
    fig = Patch()
    runtime = round(time.time() - start_time, 3)
    # Extract milliseconds
    milliseconds = int((runtime - int(runtime)) * 1000)
    # Format the time without milliseconds
    formatted_time = time.strftime("%H:%M:%S", time.localtime(runtime))
    # Combine formatted time with milliseconds
    formatted_runtime = f"1900-01-01 {formatted_time}.{milliseconds:03d}"
    data = {'x': [[formatted_runtime], [formatted_runtime]], 'y': [[global_value1], [global_value2]]}
    return (data, [0, 1], 100), fig


do_run = True


def update():
    global global_value1, do_run
    increase = 0.02
    while do_run:
        global_value1 += increase
        time.sleep(0.02)


t = threading.Thread(None, update)
t.start()

app.run(debug=True)

Thanks, that does work (although, I am not sure how to use the range slider yet).

Is there a way that I can configure the X axis to scroll on a second granularity but also to keep a certain number of second ticks visible on the screen? For example, display only the last 180 seconds of data.

I think this is possible, however, I’m not sure what was making the extendData keep going and scrolling.

Do you mean scrolling off the chart?

If so, it should be the last value in the tuple of the return value. That value is supposed to be the number of samples to keep/show.