Description
Environment
Arch Linux x86_64 6.12.1-arch1-1, using Brave Browser Version 1.73.91 Chromium: 131.0.6778.85 (Official Build) (64-bit) and Firefox 133.0 (64-bit).
# pip list | grep dash
dash 2.18.2
dash-core-components 2.0.0
dash-html-components 2.0.0
dash-table 5.0.0
# python -V
Python 3.12.7
Describe the bug
Bar charts draw nonexistent categories if a plot is updated with data that contains only a subset of the original categories. Here's a self-contained MWE demonstrating the problem.
from dash import Dash, dcc, callback, Output, Input
import plotly.express as px
app = Dash()
app.layout = [
dcc.RadioItems(["include foo", "exclude foo"], "include foo", id="choice"),
dcc.Graph(id="graph"),
]
@callback(
Output("graph", "figure"),
Input("choice", "value"),
)
def update(choice):
if choice == "include foo":
x = ["a", "b", "foo"]
y = [ 0 , 2 , 1 ]
else:
x = ["a", "b"]
y = [ 0 , 2 ]
p = px.bar(x=x, y=y)
p.update_xaxes(categoryorder="total ascending")
return p
app.run(debug=True)
Initially, or when include foo is selected, a bar chart is drawn with three categories a, b and foo, as expected:

If, however, exclude foo is selected afterwards and data for only two of the formerly three categories is used to generate the new plot, the excluded foo category is still retained in the plot and is displayed as having a value of zero.

From a little experimentation I gathered that this bug occurs only if the following conditions are met:
- The category order on the x-axis has to be set. For example, removing the call to
update_xaxes()
will make the bug not surface. - One of the non-ghost categories has to have a value of zero. For example, if for both choices the value of a is set to something other than zero everything works as expected.
Note that the data sent from the dash server to the browser client is correct for both paths, i.e. if exclude foo is selected only data for a and b is sent and received. So this bug seems to live in javascript-land, maybe some sort of caching issue during plot updates. Also, somebody else had (has?) a similar problem using streamlit's plotly bindings. I'll link it here for reference: streamlit/streamlit#5902
If there's more information I can provide, let me know.
Cheers.