plotly / plotly.py

The interactive graphing library for Python :sparkles: This project now includes Plotly Express!
https://plotly.com/python/
MIT License
16.09k stars 2.54k forks source link

Error bars in px.scatter should inherit color and opacity from markers by default #4353

Open rickynilsson opened 1 year ago

rickynilsson commented 1 year ago

Error bars do correctly show with the same color (not opacity) as markers when using categorical data to set the color parameter:

import plotly.express as px
df = px.data.iris()
df["e_plus"] = df["sepal_width"]/100
df["e_minus"] = df["sepal_width"]/40
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species",
                 error_y="e_plus", error_y_minus="e_minus")

fig.show()
image

However, setting the opacity parameter only changes the opacity of the markers:

fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species",
                 error_y="e_plus", error_y_minus="e_minus", opacity=0.6)

fig.show()
image

And using numerical data to set the color parameter, the error bars will not have the same color as the markers, but instead renders in black:

fig = px.scatter(df, x="sepal_width", y="sepal_length", color="petal_width",
                 error_y="e_plus", error_y_minus="e_minus")

fig.show()
image

Fixing this would be super helpful, since the only current solution seems to be to manually add or update the trace for every single data point to set rgba-values for the error bars. My scatter plot had a few thousand data points, making that a very time consuming for-loop.

rickynilsson commented 1 year ago

Here's a minimal example showing what I would like to accomplish:

import plotly.express as px
import plotly.graph_objects as go
df = px.data.iris()
df["e_plus"] = df["sepal_width"]/100
df["e_minus"] = df["sepal_width"]/40
alpha = 0.6
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="petal_width", opacity=alpha)

df['norm_petal_width'] = (df['petal_width']-df['petal_width'].min())/(df['petal_width'].max()-df['petal_width'].min())
colors = px.colors.sample_colorscale(colorscale='Magma', samplepoints=df['norm_petal_width'].to_numpy().tolist())
for i, point in enumerate(df['e_plus']):
    color = 'rgba' + colors[i][3:-1]  + ', ' + str(alpha) + ')'
    fig.add_trace(go.Scatter(x=[df['sepal_width'].iloc[i]], y=[df['sepal_length'].iloc[i]], \
                             error_y=dict(color=color, array=[point], arrayminus=[df['e_minus'].iloc[i]], \
                            type='data', symmetric=False), marker=dict(color='rgba(0,0,0,0)', size=6), \
                             showlegend=False, hoverinfo='skip'))

fig.show()
image

Unfortunately, this approach isn't feasible when dealing with thousands of data points in a Dash application. It takes several seconds for the graph to refresh after a user updates any of the input data.