Skip to content

Fix px opacity values misaligned when points are split into traces - #5825

Open
shivenaggarwal wants to merge 1 commit into
plotly:mainfrom
shivenaggarwal:px-opacity-per-trace
Open

shivenaggarwal wants to merge 1 commit into
plotly:mainfrom
shivenaggarwal:px-opacity-per-trace

Conversation

@shivenaggarwal

Copy link
Copy Markdown

Link to issue

Closes #4580

Description of change

Passing one opacity value per point to a Plotly Express function works for a single trace. Once the points are split into several traces (by color, symbol, facets, animation frames and so on), the whole array is copied into marker.opacity of every trace, so the values no longer match their markers. The length isn't checked either. The cause is that opacity goes into the shared trace patch as is, instead of going through the data frame like size or text.

A non-numeric opacity is now handled like the other data arguments. build_dataframe adds it as a column, so it is split with the rest of the data, and make_trace_kwargs sets each trace's marker.opacity from that trace's own rows. Column names now work too, and a list of the wrong length raises the usual "All arguments should have the same length" error. A number is still applied to all marks, as before. That includes NumPy scalars, and 1 when a column is named 1.

Demo

import plotly.express as px

fig = px.scatter(
    x=[1, 2, 3, 4],
    y=[1, 2, 3, 4],
    color=["a", "b", "a", "b"],
    opacity=[0.1, 0.2, 0.3, 0.4],
)
print([list(map(float, t.marker.opacity)) for t in fig.data])
# before: [[0.1, 0.2, 0.3, 0.4], [0.1, 0.2, 0.3, 0.4]]
# after:  [[0.1, 0.3], [0.2, 0.4]]

px.scatter(df, x="x", y="y", facet_col="f", opacity="alpha") used to raise Invalid value of type 'builtins.str' received for the 'opacity' property of scatter.marker. It now sets each facet's opacities from the alpha column.

Testing strategy

Added tests to tests/test_optional/test_px/test_px.py. They run with every dataframe backend:

  • a list split by color, for px.scatter, px.bar and px.scatter_matrix
  • a column name split by facet_row and facet_col
  • a number (0.5; 1 with an integer column named 1; a NumPy float) that is still set as a single value on every trace

The first two fail on main. pytest tests/test_optional/test_px passes locally.

Additional information (optional)

  • Hover labels don't change: opacity isn't added to them.
  • Charts that only support one opacity value (pie, funnel, funnel_area, density_map and scatter_3d) still reject per-point values through the same property validation as before, though the message now shows the generated column name rather than the list. I can add an explicit error for these if you prefer. px.histogram still passes the values to marker.opacity, and plotly.js applies them per bin, as on main.
  • I left the opacity docstring as "float". I'm happy to document per-point values if you'd like them to be part of the public API.
  • After this change, args["opacity"] can be a column name. This matters for Fix px error bars ignoring opacity on categorical traces #5781, which builds an rgba() color from that value.

Guidelines

When `opacity` was given one value per point, Plotly Express passed the
whole array to every trace, so once points were split by color, symbol,
facets or animation frames the values no longer matched their markers.

A non-numeric `opacity` is now handled like the other data arguments: it
is added to the data frame, split along with the rest of the data and
set as `marker.opacity` on each trace. This also makes column names work.
A number still applies to all marks, as before.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Opacity and color not working together in px.scatter

1 participant