Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotly changes font size in 3D plot when updating via Dash

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.

Example image of issue

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.

like image 568
Raphael_Ratz Avatar asked Mar 14 '26 06:03

Raphael_Ratz


1 Answers

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)
like image 64
Alexey Lukyanov Avatar answered Mar 16 '26 20:03

Alexey Lukyanov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!