Using plotly with panel color issue

Hello,

I have come across an issue while using plotly express with panel. When plotting with plotly express there is the option to color the data by another column. If you choose to color by categorical data, the data being plotted is colored with discrete color options according to the categories chosen, as shown below.


If you choose to color the data by numerical data, the data being plotted is colored with a color bar spanning the range of the numerical data, as shown below.

I would like to look at my data in several different ways so I have created a selector widget that allows me to select how I want my data colored. However, I have noticed that when I make my plotting function dependent on the selector widget the colors for categorical data does not do what its supposed to do.

My first example is of the data being plotted correctly but the function that creates the plot is not dependent on the selector widget. Since it is not dependent I must manually execute the last cell to display my updated plot.


Code for this example:
import plotly.express as px
import panel as pn
pn.extension('plotly')

df = px.data.tips()

color_select = pn.widgets.Select(name='color', options=['smoker','size'], value='smoker')

def our_plot(c):
      return px.scatter(df,x="total_bill", y='tip', color=c)

color_select

pn.Column(our_plot(color_select.value))

My next example shows what happens to the color when you make the plotting function dependent on the selector widget using @pn.depends(color_select.param.value). The widget and plot are on the same panel so the plot can be automatically updated when I choose a new color. The first time the plot with discrete coloring is displayed it is how it should be. But once I switch to a color scale plot and back to a discrete color plot the first categorical value (in this example ‘No’) is given a continuous color scale while the other category (in this example ‘Yes’) is given a discrete color.


Code for this example:
import plotly.express as px
import panel as pn
pn.extension('plotly')

df = px.data.tips()

color_select = pn.widgets.Select(name='color', options=['smoker','size'], value='smoker')

@pn.depends(color_select.param.value)
def our_plot(c):
    return px.scatter(df,x="total_bill", y='tip', color=c)

pn.Column(color_select, our_plot)

As you can see in the video the plot is no longer correctly assigning colors to discrete values. I am not sure if this is a plotly or panel issue but things seem to go wrong once I add the @pn.depends() function to the code. If anyone has any ideas as to why this may be happening or have any solutions I would greatly appreciate it.

Thank you!

I don’t know exactly why this is happening, but it seems that there are some weird interactions between panel and plotly. A quick fix is to embed the px.scatter in a pn.pane.Plotly()

import plotly.express as px
import panel as pn
pn.extension('plotly')

df = px.data.tips()

color_select = pn.widgets.Select(name='color', options=['smoker','size'], value='smoker')

@pn.depends(color_select.param.value)
def our_plot(c):
    return pn.pane.Plotly(px.scatter(df.copy(),x="total_bill", y='tip', color=c))

pn.Column(color_select, our_plot)

That is truly bizarre. It’s hard to see which library to blame, as Panel doesn’t really have anything to do with the color of the points here, yet I can only get it to happen in the context of a Panel layout. If I pull the plot out after seeing the problem, it’s fine:

@philippjfr, any ideas?

Suspect this is related to the way Panel handles updates to the Plotly pane. If you do not wrap the returned figure in a Plotly pane, Panel takes an optimized codepath because it infers that it is safe to modify the internally created pane in-place. In this optimized codepath the figure will be updated with the Plotly.react JS function, which I’m guessing does not handle this case correctly.

1 Like

Is there a way a user can disable this optimization in Panel if they notice problems, at which point it becomes a Plotly rather than a Panel issue?

Yes, as @AurelienSciarra pointed out above wrapping the return value in pn.pane.Plotly is sufficient. Disabling the optimization globally is not really a good idea.

In any case, we need an issue about this on the Panel issue tracker.

1 Like

Thank you for the help! I was able to solve my issue using pn.pane.Plotly().

1 Like

Yes, this is definitely an issue rather than a Discourse topic. But I don’t consider using pn.pane.Plotly to be a defensible expression of disable the in-place updating logic; to me that change seems like it merely incidentally avoids the issue, and is neither discoverable nor semantically related to whether that optimization is being done.

Well we’re talking about a bug here. I’m not going to introduce mechanisms to work around internals that (most) users shouldn’t even know about just because one particular pane has a bug in the way it handles the optimized updates. The correct thing is to fix the bug, not to let users mess with internals.

1 Like

Yes, it’s a specific bug, but I think this particular bug is an example of a very large class of potential issues, many of which are still lurking out there. Maybe you’re more optimistic than I am, but I personally would be surprised if this is the last time we see a bug like this in an interactive backend that’s being fed partial updates. So I’m concerned that future users who hit such problems won’t have a good out.