Hi everyone,
I’m trying to build my first streamlit app, which will include a plotly graph objects line chart that displays both forecast and actual data.
When using the plotly x unified hovermode, I noticed a weird issue where the value for the first forecast year value is displayed in the tooltip for the last actual year (but no line is displayed, correctly). The opposite also occurs with the last actual year value displaying in the tooltip for the first forecast year.
E.g. in the below screenshot, 2022 should only have an ‘actual’ value, however the forecast value of 15 for 2023 is being displayed in the 2022 tooltip.
I played around with different year ranges and it seems to be related to the number of values to be plotted and the space in which to do so. E.g. the same range of values to be plotted may be ok with the legend hidden, but show the error when the legend is visible. Adding more values so that the x axis tick width is smaller can also result in the last 2 & first 2 hovers being duplicated, instead of just the last 1 & first 1.
python: 3.11.8
streamlit: 1.31.0
plotly: 5.18.0
I’m unable to reproduce the error when using the same code to display the chart in an ipynb without streamlit being involved.
Does anyone know why this may be occurring and how to fix it?
Code to reproduce is below. I’ve included a number of options for the forecast dataframe that show/don’t show the error.
import streamlit as st
import pandas as pd
import plotly.graph_objects as go
def create_trajectory_chart(actual_df, forecast_df):
combined_df = pd.concat([actual_df, forecast_df], ignore_index=True)
# Create an empty figure
fig = go.Figure()
# Add traces for each status
for status in ['Actual', 'Forecast']:
df_subset = combined_df[combined_df['status'] == status]
if not df_subset.empty:
fig.add_trace(go.Scatter(
x=df_subset['year'],
y=df_subset['percentage'],
name=status
))
# Update layout
fig.update_layout(
showlegend=False,
hovermode="x unified"
)
return fig
# Actual data range
actual_data = {
'year': [2018, 2019, 2020, 2021, 2022],
'percentage': [10, 11, 12, 13, 14]
}
# This range does not produce error
forecast_data1 = {
'year': [2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042],
'percentage': [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
}
# This range does not produce error when legend is hidden, however error occurs when legend is shown
forecast_data2 = {
'year': [2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046],
'percentage': [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38]
}
# This range produces an error even when the legend is hidden
forecast_data3 = {
'year': [2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047],
'percentage': [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
}
# This range shows the error on the last two actual data points and first two forecast data points, even when the legend is hidden
forecast_data4 = {
'year': [2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077],
'percentage': [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
}
# Create the dataframes
actual_df = pd.DataFrame(actual_data)
forecast_df = pd.DataFrame(forecast_data3)
actual_df['status'] = 'Actual'
forecast_df['status'] = 'Forecast'
# Create the chart
fig_trajectory_chart = create_trajectory_chart(actual_df, forecast_df)
st.plotly_chart(fig_trajectory_chart)