Yep, uirevision
is implemented entirely on the javascript side, so itāll work just as well from R as from Python. Just add uirevision=TRUE
to your graphās figure$layout
.
Thank you
If I have a figure with a go.Heatmap
trace plus some other overlayed traces, how can I make the heatmap always visible while still allowing the user to toggle the visibility of the other traces via the clickable legend? The unwanted scenario is when the heatmap gets hidden when another trace gets double-clicked to show that other trace in isolation. Thanks!
Thanks for bringing this up! Iād call that behavior a bug -> https://github.com/plotly/plotly.js/issues/4389
A post was split to a new topic: Uirevision and animation slider
Hi @alexcjohnson Iām facing the same issue. Iām using uirevision to preserve the UI state in graph. I am also using dcc.Tabs⦠When I change the tab and come back to the original tab, the graph is getting reset to the default state. Could you please help me in this?
@balagdivya I donāt know that we have a good solution for this use case yet but itās come up before. There may be a way by not actually removing the graph when you switch tabs, just hiding it via CSS. But that could be really awkward depending on how many tabs you have and how complex they are. Iāve made an issue to look into a natural solution https://github.com/plotly/dash-core-components/issues/806
Hi @alexcjohnson, any update on the issue https://github.com/plotly/dash-core-components/issues/806 ??
A post was split to a new topic: Trigger callback when legend is clicked
Hey @crosenth, what do you mean that āthe user MUST update the figureā? I am having the issue where I am updating some traces of the figure, but the view still resets to where it was when I triggered the callback the first time the callback was called.
Hi @jvgomez, what I meant to say is the user must āinteractā the figure in order to preserve . This feature is meant to preserve the userās interactions. If you are adding traces to the plot it will reset unless you also interact (Zoom in, out, autoscale, etc).
There used to be a way to preserve a plotsā axes without interaction as described in this Issue (https://github.com/plotly/dash-core-components/issues/321) but I suspect it was accidentally squashed by a PR back in 2018.
@crosenth Not sure I follow. I have the case for example that I manually move the camera and click on the figure that triggers a callback that modifies one of the existing traces. So far so good.
Then I move the camera again, I click again on the figure to trigger the callback, and the camera resets to the prose it had on the previous click. So I understand user interactions are not preserved. Is this supposed to be fixed with uirevision
? Because i try and didnāt work.
@jvgomez - That should work, but maybe there is an issue with 3D interactions. Any chance you create a really simple, small, reproducible example with dummy data? Thatāll help us create the bug report.
@crosenth I tried to reproduce the issue with the following code which represents my real application. Basically, a callback creates a figure and updates it. This callback is triggered when we click in a point on the figure and updates one of the traces to highlight the point clicked.
import json
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly
import plotly.graph_objects as go
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
data = [1,2,3,4,5]
# Layout
app.layout = html.Div([
# Invisible div for data caching
html.Div(id='point-data', style={'display': 'none'}),
# 3D graph div
html.Div([
dcc.Graph(id='3d-graph', style={'height': '60vh'})
]),
])
# Callbacks
@app.callback(Output('3d-graph', 'figure'),
[Input('point-data', 'children')],
[State('3d-graph', 'figure')])
def on_figure_update(cache_data, fig):
# On start, create the figure with a placeholder trace for future updates
if not fig:
trace1 = go.Scatter3d(x=data, y=data, z=data,
mode="markers", marker=dict(size=3, color='blue'),
name="trace1")
trace2 = go.Scatter3d(x=[], y=[], z=[],
mode="markers", marker=dict(size=5, color='red'),
name="trace2")
# This is causing the camera issue!
scene = dict(camera=dict(
up=dict(x=0, y=-1, z=0),
center=dict(x=0, y=0, z=0),
eye=dict(x=1, y=-1, z=1))
)
layout = go.Layout(scene=scene)
fig = go.Figure(data=[trace1, trace2], layout=layout)
fig['layout']['uirevision'] = 'Do not change'
else:
# Update figure
if not cache_data:
return fig
idx = json.loads(cache_data)
fig['data'][1]['x'] = [data[idx]]
fig['data'][1]['y'] = [data[idx]]
fig['data'][1]['z'] = [data[idx]]
return fig
# Clicking on the 3D map
@app.callback(Output('point-data', 'children'),
[Input('3d-graph', 'clickData')])
def on_3d_click(clickData):
if clickData is None:
return ''
return json.dumps(clickData['points'][0]['pointNumber'])
if __name__ == '__main__':
app.run_server(debug=True)
So I realized that the default camera is causing the issue. The code above produces strange behavior of resetting camera to a previous position (I think it is the camera on the first callback), as shown in the image:
If you remove the custom layout when creating the figure, the behavior seems to be mostly OK. There are still small issues such as the first time the figure updates the camera returns to the initial position, or when zoom is changed, the camera readjusts it a bit when updated:
For my application the camera change is relatively important, any suggestion on how to deal with it?
Thanks!
Hello everyone,
I am having an issue with the uirevision parameter in my live chart. I believe I am using it correctly however I cannot maintain zoom or panning. every other second the UI resets back to the default and I lose zoom and chart position. Any suggestions are much appreciated. My end goal is to have a live candlestick chart similar to trading view
thank you
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import plotly.graph_objs as go
from collections import deque
from binance.client import Client
from binance.websockets import BinanceSocketManager
import time
from datetime import datetime
import pandas as pd
data = pd.DataFrame(columns=['Open', "High", "Low", "Close", 'Date'])
y_candle = []
index = 0
app = dash.Dash(__name__)
app.layout = html.Div([dcc.Graph(id='live-graph', animate=True, style={'height': '1080px'}),
dcc.Interval(id='graph-update', interval=1000, n_intervals=0)])
@app.callback(Output('live-graph', 'figure'),
[Input('graph-update', 'n_intervals')])
def update_graph_scatter(n):
candle_graph = go.Candlestick(
x=data['Date'],
open=data['Open'],
high=data['High'],
low=data['Low'],
close=data['Close'],
name='candles')
return dict(data=[candle_graph], layout=go.Layout(title='BTC/USDT',
showlegend=True, xaxis=dict(range=[data["Date"].iloc[0], data["Date"].iloc[-1]]),
yaxis=dict(range=[min(data["Low"]), max(data["High"])]), autosize=True,
uirevision=True, template="plotly_dark"))
def handle_message(msg):
global index
timestamp = msg['T'] / 1000
new_time = datetime.fromtimestamp(timestamp)
new_time.strftime('%Y-%m-%d %H:%M:%S')
y_candle.append(msg['p'])
if (len(y_candle) % 100 == 0) and (len(y_candle) > 0):
temp = {}
temp['Open'] = y_candle[0]
temp['High'] = max(y_candle)
temp['Low'] = min(y_candle)
temp['Close'] = y_candle[-1]
temp['Date'] = new_time
data.loc[index] = temp
index += 1
#df.append(pd.Series(temp), ignore_index=True)
#print(df)
y_candle.clear()
if __name__ == '__main__':
bm = BinanceSocketManager(client)
conn_key = bm.start_trade_socket('BTCUSDT', handle_message)
bm.start()
time.sleep(10)
app.run_server(debug=True)
#
#
# bm.stop_socket(conn_key)
I believe this is a bug in Plotly.js, or a combination of multiple bugs. Iāve logged them as https://github.com/plotly/plotly.js/issues/5050 and https://github.com/plotly/plotly.js/issues/5005 and https://github.com/plotly/plotly.js/issues/5004
Ah ok I appreciate the feedback.
Thank you
A post was split to a new topic: Vue & uirevision
Hi Everyone,
I am building a streaming application. So, every 5 minutes my data is refreshed in the database and I am pulling the updated data from the database in the dash application. So, my customer wants to update the data tables but not the graphs because they will be seeing a trend if there are abnormal behaviour in the lines.
I have tried using uirevision but it not working. Can someone help in this? Thanks in advance
#call back for All Plating Lines
@app.callback(
dash.dependencies.Output('table', 'children'),
[dash.dependencies.Input('lineSelect', 'value'),
dash.dependencies.Input('refresh-table', 'n_intervals'),])
def filter_table(input,num):
if num == 99999999999:
raise PreventUpdate
else:
print ("The code is inside the function")
df2 = pd.read_sql_query(query_3, engine)
df2['location'] = df2['location'].replace(regex='AmpsActual',value='')
df2['line'] = np.where(df2.location.str[3] == '1' ,df2.line+' 1',df2.line)
df2['line'] = np.where(df2.location.str[3] == '2' ,df2.line+' 2',df2.line)
tableData = df2.pivot_table('alarmflag',['line','linespeed'],'location',aggfunc = 'first')
tableData = pd.DataFrame(tableData.to_records())
tableData.columns = [hdr.replace("('alarmflag',","").replace(")","") for hdr in tableData.columns]
td1 = tableData
td1['linespeed'] = td1['linespeed'].round(1)
td1['linespeed'] = td1['linespeed'].astype(str)
for j in td1.columns:
if j not in ['line','linespeed']:
td1[j] = j + td1[j]
sort0_cols = td1.columns[td1.columns.str.contains(pat="EC") ]
sort1_cols = td1.columns[td1.columns.str.contains(pat="Ni") ]
sort2_cols = td1.columns[td1.columns.str.contains(pat="Ag") ]
sort3_cols = td1.columns[td1.columns.str.contains(pat="Sn") ]
sort4_cols = td1.columns[(~td1.columns.str.contains(pat="EC") & ~td1.columns.str.contains(pat="Ni") &
~td1.columns.str.contains(pat="Ag") & ~td1.columns.str.contains(pat="Sn") &
~td1.columns.str.contains(pat="line") ) ]
td1 = pd.concat([td1['line'],td1['linespeed'],td1[sort0_cols],td1[sort1_cols],td1[sort2_cols],td1[sort3_cols],td1[sort4_cols]],axis=1)
td1['new'] = td1.apply(lambda x: ','.join(x.dropna()),axis=1)
td2 = td1['new'].apply(lambda x:pd.Series(x.split(',')))
td2.rename(columns = {0:'line'},inplace=True)
df = pd.DataFrame(data = td2)
dfFiltered = pd.DataFrame()
temp = input
dfFiltered = df[df.line.isin(temp)]
return html.Div([
dash_table.DataTable(
id='tab',
columns = [{"name": i, "id": i} for i in dfFiltered.columns],
data = dfFiltered.to_dict('records'),
style_cell={'maxWidth': 0,
'overflow': 'hidden',
'textOverflow': 'ellipsis',
'textAlign': 'center',
'font-family': 'Verdana',
'margin':'10px',
'font-weight': 'bold',
},
style_header = {'display': 'none'},
style_data_conditional = ([{'if':{'filter_query': '{{{col}}} is blank'.format(col=col),
'column_id' : col},
'backgroundColor': 'white',
'color': 'white' } for col in dfFiltered.columns] +
[{'if':{'filter_query': '{{{col}}} contains "."'.format(col=col),
'column_id' : col},
'backgroundColor': '#CC0000',
'color': 'white' } for col in dfFiltered.columns] +
[{'if':{'filter_query': '{{{col}}} contains "`"'.format(col=col),
'column_id' : col},
'backgroundColor': 'white',
'color': 'green' } for col in dfFiltered.columns] +
[{'if':{'filter_query': '{{{col}}} contains " "'.format(col=col),
'column_id' : col},
'backgroundColor': '#D3D3D3',
'color': 'black' } for col in dfFiltered.columns]
+ [{'if':{
'column_id' : 1},
'backgroundColor': '#D3D3D3',
'color': 'black' }]
),
)
, dcc.Graph(id="graph3", style={"width": "50%", "display": "inline-block"}),
dcc.Graph(id="graph4", style={"width": "50%", "display": "inline-block"}),])
#call back for selection on All Plating Lines
@app.callback(
[Output('graph3', 'figure'),Output('graph4', 'figure')],
[Input('tab', 'derived_virtual_data'),Input('tab', 'active_cell')])
def show_graph(rows, selection):
dff = pd.DataFrame(rows)
if selection is None:
n_intervals = 0
return {}, {}
else:
n_intervals = 99999999999
loc = dff.iloc[selection['row']][selection['column']-1]
loc = loc.replace('.','').replace('`','').replace(' ','').replace('AmpsActual','')
input1 = loc + 'AmpsActual'
t1 = dff.iloc[selection['row']]['line']
input2 = t2 = t1[:3]
t1 = 'USMLXLIUP_' + t2
connection_string_details()
cursor = cnxn_1.cursor()
query_4 = """Select COLUMN_NAME from information_schema.columns where TABLE_NAME = '{}'
and TABLE_SCHEMA = 'ppqa' and
(COLUMN_NAME like '{}%' or COLUMN_NAME = 'DateTime' )
order by ORDINAL_POSITION"""
table_columns = pd.read_sql_query(query_4.format(t1,loc), cnxn_1)
col_list = [i for i in table_columns['COLUMN_NAME']]
col_string = ', '.join([str(i) for i in col_list])
query_5 = """SELECT {}, '{}' as line, NOW() as lastrefreshdate
FROM ppqa.{}
where DateTime >= now() - interval 24 hour
order by DateTime"""
df = pd.read_sql_query(query_5.format(col_string,t2,t1), cnxn_1)
if len(df) == 0:
return {}, {}
else:
df['data point i'] = df.index + 1
filename = t2
volt_col = input1.replace('Amp','Volt')
tolerance_inputs = pd.read_excel("C:/Users/ISLAMS/Downloads/Work/Project Fusion/Files/"+filename +".xlsx", sheet_name = 'Sheet1')
current_tolerance = int(tolerance_inputs["Current Tolerance for " + input1].loc[0])
voltage_tolerance = int(tolerance_inputs["Voltage Tolerance for Standard Deviation for " + volt_col].loc[0])
standard_dev_15_rows = df[df['data point i'] <= 15][volt_col].std()
df['rolling_average'] = df[volt_col].rolling(15).mean()
def func_voltage_mu_o(df):
if df['data point i'] <= 15:
return (df[volt_col])
else:
return (df['rolling_average'])
df['voltage_mu_o'] = df.apply(func_voltage_mu_o, axis = 1)
if input1.startswith("Ni"):
Voltage_SD = 0.045
elif input1.startswith("Sn"):
Voltage_SD = 0.050
elif input1.startswith("ECln"):
Voltage_SD = 0.055
elif input1.startswith("Ag"):
Voltage_SD = 0.055
else:
Voltage_SD = 0.055
df['voltage_sigma'] = Voltage_SD
fig=go.Figure()
fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'AmpsSetpoint'],name = 'Current Set Point',line=dict(color='black',width=4,dash='dash')))
fig.add_trace(go.Scatter(x=df['DateTime'],y=df[input1],name = 'Actual Current',line=dict(color='black',width=4)))
fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'AmpsSetpoint']+df[loc+'AmpsSetpoint']*current_tolerance/100,name = 'Upper Limit',line=dict(color='red',width=4)))
fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'AmpsSetpoint']-df[loc+'AmpsSetpoint']*current_tolerance/100,name = 'Lower Limit',line=dict(color='red',width=4)))
fig.update_layout(title= input2+' '+input1[:5]+ ' : Current Vs Time',xaxis_title = 'Time',yaxis_title = 'Current (Amp)')
fig['layout']['uirevision'] = 'Do not change'
fig2=go.Figure()
fig2.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'VoltsActual'],name = 'Actual Voltage',line=dict(color='black',width=4)))
fig2.add_trace(go.Scatter(x=df['DateTime'],y=df['voltage_mu_o'] + df['voltage_sigma'] * voltage_tolerance,name = 'Voltage Red UCL',line=dict(color='red',width=4)))
fig2.add_trace(go.Scatter(x=df['DateTime'],y=df['voltage_mu_o'] - df['voltage_sigma'] * voltage_tolerance,name = 'Voltage Red LCL',line=dict(color='red',width=4)))
fig2.update_layout(title= input2+' '+input1[:5]+ ' : Voltage Vs Time',xaxis_title = 'Time',yaxis_title = 'Voltage (Volt)')
fig2['layout']['uirevision'] = 'Do not change'
return fig, fig2
@app.callback(
Output('refresh-table','n_intervals'),
Input('tab', 'active_cell'))
def stop( selection):
if selection is None:
n_intervals = 0
return n_intervals
else:
n_intervals = 99999999999
return n_intervals
Hi all,
I am also facing a problem with uirevision. In my application, which is based on a 3D scatter plot, I set uirevision to preserve the UI state but I found a case where it does not work: when I click on a legend trace, the camera change to a prior position.
Any idea of I can fix that?
Thank you.