Plotly.js: Per-group (e.g. legendgroup) trace styling

Created on 19 Jun 2018  Â·  17Comments  Â·  Source: plotly/plotly.js

If I want two traces on two sub-plots to share the same legend, I (think I) have to explicitly specify the trace color, like

import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode
init_notebook_mode()

style1 = dict(name='trace1', legendgroup='trace1', line=dict(color='red'))
style2 = dict(name='trace2', legendgroup='trace2', line=dict(color='blue'))

fig = go.Figure(
    data=[dict(y=[1, 2, 3], **style1),
          dict(y=[2, 1, 3], **style2),
          dict(y=[1, 2, 3], showlegend=False, xaxis='x2', **style1),
          dict(y=[1, 3, 2.5], showlegend=False, xaxis='x2', **style2)],
    layout=dict(xaxis=dict(domain=[0, .45]),
                xaxis2=dict(domain=[.55, 1]))
)
iplot(fig)

newplot-12

One issue with the above is that I lose the default plotly colors (see https://github.com/plotly/plotly.py/issues/1026 for getting them back).

Is there a more generic way to tell plotly that two traces should share the same legend/style, or even count as one? Thanks

feature ♥ NEEDS SPON$OR

Most helpful comment

You're correct, that ability doesn't exist today. I like the idea, and in v2 we could consider making this the default behavior, but for now it would need to be opt-in. Something like sharegroupstyle: true (though we have other kinds of groups too, it's getting a bit long but we may need sharelegendgroupstyle), would make this trace inherit style attributes from the first trace in the legend group before applying its own values. I'm a little uneasy with the order-dependence of that, could have some unexpected consequences upon reordering or adding/removing traces (and we'll have to be careful how it behaves on hiding/showing traces) but I think we can sort that out.

All 17 comments

You're correct, that ability doesn't exist today. I like the idea, and in v2 we could consider making this the default behavior, but for now it would need to be opt-in. Something like sharegroupstyle: true (though we have other kinds of groups too, it's getting a bit long but we may need sharelegendgroupstyle), would make this trace inherit style attributes from the first trace in the legend group before applying its own values. I'm a little uneasy with the order-dependence of that, could have some unexpected consequences upon reordering or adding/removing traces (and we'll have to be careful how it behaves on hiding/showing traces) but I think we can sort that out.

Yes, that would be very convenient. Do I understand well that the first trace in the sharedlegendgroupstyle would then define a legendstyle? Isn't this a concept you want to have outside of traces, then?
I've never faced the issue of reordering/removing traces, but your remark makes me think of the existing legendgroup and showlegend pair. Usually I have a single trace with showlegend=True within a legendgroup, does that resist well to trace updating?

This issue also probably belongs in https://github.com/plotly/plotly.py rather than plotly.js, right?

Well, you probably know better than I - still, the rationale for reporting here is that it is a question on the API, not on the python wrappers.

@jacobq The example is in Python, but any change to the trace properties would need to be implemented in Plotly.js first. Then plotly.py will get it automatically.

any update as of October 2018.

This would be great for subplots showing different data from the same elements. I'm having problems trying to workarround this.

I would love this feature too.

Created a github account just to pile on.

The way plotly currently works for this issue goes against the expected behavior, compared to most other common statistical / graphing programs I've used. Grouped legends is a crutch, but then manually setting colors and setting showlegend to false for an arbitrary number of traces becomes programmatically cumbersome. This is a major hassle when programming a dashboard for even simple data.

For anyone still encountering this issue and looking for a work around I'd suggest generating a color value based on a hash of your group name as per this stack exchange as per this stack exchange.

If you have similar group names (i.e group01, group 02, ...) you may want to consider shuffling or reversing your string to get clearly different values. This method has the advantage that using the hash function you can easily use the same color outside of the ploty ecosystem.

Piling up on this as well. Would love to have the option to share the same style for the same legend groups. This is very useful, for example, when showing the values and limits for the same datum.

Any update on enabling this feature?

I think i have found a workaround for this problem (if I understood the problem correctly). Forgive me for my formatting, I just started using GitHub today.

I have two subplots in which the legend is shared and the line colors should be the same for the same legend entry:
Capture

I had a lot of trouble achieving this but i ended up using Legendgroups based on the legend entry and controlling the colors of the lines using based on the legend entries:

import numpy as np
import pandas as pd
import plotly
from plotly.offline import plot
import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio
pio.renderers.default = "browser"
from plotly.subplots import make_subplots


#Dataframe:
df = pd.DataFrame(np.array([[1.4, 1.1, 1.3, "Name1"],[1, 2.3, 2.0, "Name2"],[2.3,1,7, "Name2"],[2.75, 2.8, 2.4, "Name1"]]),
                   columns=["Time",'Measurement_result1','Measurement_result2', 'Machine_ID'])

#Array of the machine IDs
machine_names = df.Machine_ID.unique()

#Make the figure
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.02)

#Colors
cols = plotly.colors.DEFAULT_PLOTLY_COLORS

#Loop over the machine names (legend entry) and put each machine name in a legend group with a specific color
for k,i in enumerate(machine_names):
    fig.add_trace(go.Scatter(x=df.loc[df['Machine_ID'] == i].Time, y=df.loc[df['Machine_ID'] == i].Measurement_result1, name = i,legendgroup = i,line=dict(width=2, color=cols[k])), row=1, col=1)
    fig.add_trace(go.Scatter(x=df.loc[df['Machine_ID'] == i].Time, y=df.loc[df['Machine_ID'] == i].Measurement_result2, name = i,legendgroup = i,showlegend=False,line=dict(width=2, color=cols[k])), row=2, col=1)

fig.update_layout(xaxis2_rangeslider_visible=True, xaxis2_rangeslider_thickness=0.1 )
fig.update_layout(legend_title_text='Machine ID',legend_itemclick = "toggleothers")#,legend_traceorder="grouped"
fig.show()

I hope this can help.

The following approach worked for me:

colors = {'group1': 'green',
          'group2': 'orange'}

    for index, name in enumerate(names):
       bars.append(go.Bar(name=name, x=x_array[index], y=y_array[index], marker={'color': colors[name]}))

This issue has been tagged with NEEDS SPON$OR

A community PR for this feature would certainly be welcome, but our experience is deeper features like this are difficult to complete without the Plotly maintainers leading the effort.

Sponsorship range: $10k-$15k

What Sponsorship includes:

  • Completion of this feature to the Sponsor's satisfaction, in a manner coherent with the rest of the Plotly.js library and API
  • Tests for this feature
  • Long-term support (continued support of this feature in the latest version of Plotly.js)
  • Documentation at plotly.com/javascript
  • Possibility of integrating this feature with Plotly Graphing Libraries (Python, R, F#, Julia, MATLAB, etc)
  • Possibility of integrating this feature with Dash
  • Feature announcement on community.plotly.com with shout out to Sponsor (or can remain anonymous)
  • Gratification of advancing the world's most downloaded, interactive scientific graphing libraries (>50M downloads across supported languages)

Please include the link to this issue when contacting us to discuss.

The following approach worked for me:

colors = {'group1': 'green',
          'group2': 'orange'}

    for index, name in enumerate(names):
       bars.append(go.Bar(name=name, x=x_array[index], y=y_array[index], marker={'color': colors[name]}))

I needed this for an arbitrary number of groups but in that case it is not practical to define a color list.
I solved the problem by using HSL colors, the first group get hue = 0 and each new group the hue is increased by 360/length(groups).
This in principle gives 360 relatively distinctly colored traces which is probably fine because to be honest plotting more traces in the same plot gives a chaotic plot anyway.

For anyone still encountering this issue and looking for a work around I'd suggest generating a color value based on a hash of your group name as per this stack exchange as per this stack exchange.

If you have similar group names (i.e group01, group 02, ...) you may want to consider shuffling or reversing your string to get clearly different values. This method has the advantage that using the hash function you can easily use the same color outside of the ploty ecosystem.

Thanks! This is by far the fastest work-around I've tried. For python, here's a module that hash strings into hex color code directly:

https://pypi.org/project/colorhash/

Was this page helpful?
0 / 5 - 0 ratings