Search code examples
pythonpandasplotly

How to customize the textposition of a bubblechart label


I managed to set a fixed label for each bubble in my chart. Here’s the code:

import plotly.graph_objects as go
import plotly.express as px 
import pandas as pd
margin_factor = 1.6
data = {'x': [1.5, 1.6, -1.2],
        'y': [21, -16, 46],
        'circle-size': [10, 5, 6],
        'circle-color': ["red","red","green"],
        'tttt': ["the last xt for MO","the last xt for MO pom","the last xt for MO %"]
        }
# Create DataFrame
df = pd.DataFrame(data)
fig = px.scatter(
    df,
    x="x", 
    y="y", 
    color="circle-color",
    size='circle-size'
)


fig.update_layout(
    {
        'xaxis': {
            "range": [-100, 100],
            'zerolinewidth': 3, 
            "zerolinecolor": "blue",
            "tick0": -100,
            "dtick": 25,
            'scaleanchor': 'y'
        },
        'yaxis': {
            "range": [-100, 100],
            'zerolinewidth': 3, 
            "zerolinecolor": "green",
            "tick0": -100,
            "dtick": 25
        },
        "width": 500,
        "height": 500
    }
)
x_pad = (max(df.x) - min(df.x)) / 8
y_pad = (max(df.y) - min(df.y)) / 30

for x0, y0 in zip(data['x'], data['y']):
    fig.add_shape(type="rect",
        x0=x0 + (x_pad)/5, 
        y0=y0 - y_pad, 
        x1=x0 + x_pad, 
        y1=y0 + y_pad,
        xref='x', yref='y',
        line=dict(
            color="black",
            width=2,
        ),
        fillcolor="#1CBE4F",
        layer="below"
    )

fig.add_trace(
    go.Scatter(
        x=df["x"].values + (x_pad)/2,
        y=df["y"],
        text=df["tttt"],
        mode="text",
        showlegend=False
    )
)
fig.show()

The result : enter image description here

Now what I’m trying to do is to put some css to these labels some this like this :

enter image description here

I know there is something called annotation, I tried it, but I did not successfully create annotations for each element.


Solution

  • You can add the rectangles using plotly shapes. I have adjusted the size and coordinates of the rectangle to scale to the range = max - min of your x and y values in your data since this is how plotly determines the scale of the figures (rather than hardcoding the size of the rectangles which won't work if your data changes). The length of the box in the x-direction is then scaled to the length of the text to be placed inside the box.

    import plotly.graph_objects as go
    import plotly.express as px 
    import pandas as pd
    
    
    data = {'x': [1.5, 1.6, -1.2],
            'y': [21, -16, 46],
            'circle-size': [10, 5, 6],
            'circle-color': ["red","red","green"],
            'tttt': ["ggg","vvvv","really really long string"],
    
            }
    
    # Create DataFrame
    df = pd.DataFrame(data)
    df['length'] = df['tttt'].str.len()
    fig = px.scatter(
        df,
        x="x", 
        y="y", 
        color="circle-color",
        size='circle-size',
        # text="tttt",
        # hover_name="tttt",
        color_discrete_map={"red": "red", "green": "green"}
    )
    fig.update_traces(textposition='middle right', textfont_size=14, textfont_color='black', textfont_family="Inter", hoverinfo="skip")
    
    xrange_min = -100
    xrange_max = 100
    yrange_min = -100
    yrange_max = 100
    
    fig.update_layout(
        {
            'xaxis': {
                "range": [xrange_min, xrange_max],
                'zerolinewidth': 3, 
                "zerolinecolor": "blue",
                "tick0": -100,
                "dtick": 25,
                'scaleanchor': 'y'
            },
            'yaxis': {
                "range": [yrange_min, yrange_max],
                'zerolinewidth': 3, 
                "zerolinecolor": "green",
                "tick0": -100,
                "dtick": 25
            },
            "width": 500,
            "height": 500
        }
    )
    
    x_pad = (xrange_max - xrange_min) / 25
    y_pad = (yrange_max - yrange_min) / 30
    
    for (x0, y0, text, length) in zip(df['x'], df['y'], df['tttt'], df['length']):
        fig.add_shape(type="rect",
            x0=x0 + 1.2*x_pad, 
            y0=y0 - y_pad, 
            x1=x0 + (length)*x_pad, 
            y1=y0 + y_pad,
            xref='x', yref='y',
            line=dict(
                color="black",
                width=2,
            ),
            fillcolor="#1CBE4F",
            layer="below"
        )
    
        fig.add_trace(
            go.Scatter(
                x=[x0 + 1.2*x_pad + (length*x_pad/4)],
                y=[y0 + (y_pad / 2)],
                text=[text],
                mode="text",
                showlegend=False
            )
        )
    
    fig.show()
    

    enter image description here