I am trying to create a 3D plot with plotly. In the plotting area, I want a grid of floating text annotations. This is my MWE:
import plotly.graph_objs as go
import dash
from dash import html, dcc, Input, Output
import random
import itertools
def generate_figure(data):
text_positions = [[[-1,-1,0], [-1,1,0]], [[1,-1,0], [1,1,0]]]
fig = go.Figure()
fig.add_trace(go.Scatter3d(
x=[v[0] for rows in text_positions for v in rows],
y=[v[1] for rows in text_positions for v in rows],
z=[v[2] for rows in text_positions for v in rows],
text=[f"{f'{data['row'][row]}' if data['row'][row] is not None else 'N/A'} x {f'{data['col'][col]}' if data['col'][col] is not None else 'N/A'}" for row, rows in enumerate(text_positions) for col, _ in enumerate(rows)],
mode='text',
textposition="middle center",
textfont=dict(size=12, color='black'),
))
fig.update_layout(
scene=dict(
xaxis=dict(range=[-2, 2]),
yaxis=dict(range=[-2, 2]),
zaxis=dict(range=[-1, 1])
)
)
return fig
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Graph(id='graph'),
html.Button('Refresh data', id='btn')
])
@app.callback(
Output('graph', 'figure'),
Input('btn', 'n_clicks')
)
def update_graph(n_clicks):
data = [0,1,2,3]
col = list(random.choice(list(itertools.combinations(data, 2))))
row = [item for item in data if item not in col]
return generate_figure({'col': col, 'row': row})
if __name__ == '__main__':
app.run(debug=True)
I've added a button to randomize the order of the numbers. The plot updates as expected, but the font size of some of the annotations gets smaller. Obviously, this is not what I want.

I've already tried to use different traces for every annotation, which didn't work. In addition, I've tried to use different text formats, but that didn't help either.
Any help would be very much appreciated.
I wasn't able to identify the reason why this problem is happening, but I was able to find a workaround. Instead of adding a 3D scatter trace with text data you can add annotations to a figure with the fig.update_layout() call. Now, every time you click "Refresh data" button all the annotations should have the same size. Below is the code that tested on Python 3.12.11 with plotly==6.2.0 and dash==3.2.0.
import plotly.graph_objs as go
import dash
from dash import html, dcc, Input, Output
import random
import itertools
def generate_figure(data):
text_positions = [
[[-1, -1, 0], [-1, 1, 0]],
[[1, -1, 0], [1, 1, 0]]
]
text=[
f"{f'{data['row'][row]}' if data['row'][row] is not None else 'N/A'} "
"x "
f"{f'{data['col'][col]}' if data['col'][col] is not None else 'N/A'}"
for row, rows in enumerate(text_positions) for col, _ in enumerate(rows)
]
x_coords = [v[0] for rows in text_positions for v in rows]
y_coords = [v[1] for rows in text_positions for v in rows]
z_coords = [v[2] for rows in text_positions for v in rows]
# Create a list of annotations separately
annotations=[
dict(
showarrow=False,
x=x,
y=y,
z=z,
text=t,
font=dict(
family='Arial, sans-serif',
size=18,
color='black'
),
) for x, y, z, t in zip(x_coords, y_coords, z_coords, text)
]
fig = go.Figure()
fig.update_layout(
scene=dict(
xaxis=dict(range=[-2, 2]),
yaxis=dict(range=[-2, 2]),
zaxis=dict(range=[-1, 1]),
annotations=annotations, # add annotations to the figure
)
)
return fig
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Graph(id='graph'),
html.Button('Refresh data', id='btn')
])
@app.callback(
Output('graph', 'figure'),
Input('btn', 'n_clicks')
)
def update_graph(n_clicks):
data = [0, 1, 2, 3]
col = list(random.choice(list(itertools.combinations(data, 2))))
row = [item for item in data if item not in col]
return generate_figure({"col": col, "row": row})
if __name__ == '__main__':
app.run(debug=True)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With