plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.9k stars 1.85k forks source link

support arrays of color and colorscale for line and fillcolor of `plotly.graph_objects.Scatter` #5259

Open jleaves opened 3 years ago

jleaves commented 3 years ago

Coming from plotly.py. Since this is about the plotly.graph_objects module, the issue is posted here.

Currently, only plotly.graph_objects.Scatter.marker, plotly.graph_objects.Scatter.marker.gradient and plotly.graph_objects.Scatter.marker.line supports assigning colors by arrays of color along with colorscale.

Could the same color-setting techniques be allowed for plotly.graph_objects.Scatter.line and plotly.graph_objects.Scatter.fillcolor as well?

p.s. Links to the current documentation:

  1. The following supports arrays of color with colorscale:
  2. The following doesn't support arrays of color with colorscale:
nicolaskruchten commented 3 years ago

These are both good ideas although a bit trickier to implement because these arrays wouldn't have the same lengths as the others in the trace: the line.color would be 1 element shorter, and the fillcolor one would be different again. I'm actually not clear what kind of plot you'd like to make with variable fillcolor :)

alexcjohnson commented 3 years ago

I'm guessing the variable fillcolor would be like https://stackoverflow.com/a/59950664/9188800 Q6kC7gQ

The line case is covered in https://github.com/plotly/plotly.js/issues/581 - short answer: big pain in SVG, more manageable in scattergl. I'd say the same goes for variable fillcolor

@nicolaskruchten I guess it could either be 1 element shorter or the same length, depending on exactly what you want the color to mean, and whether you want a smooth variation or a single color per segment. There may be use cases for both, and neither is particularly easy in SVG.

jleaves commented 3 years ago

These are both good ideas although a bit trickier to implement because these arrays wouldn't have the same lengths as the others in the trace: the line.color would be 1 element shorter, and the fillcolor one would be different again. I'm actually not clear what kind of plot you'd like to make with variable fillcolor :)

Sorry for the late reply.

I was trying to plot shapes with different colors. Each polygon (plotly.graph_objects.Scatter.fillcolor) comes with a numeric property which I want to illustrate by the "fill" color. Furthermore, the common boundary of two polygons (plotly.graph_objects.Scatter.line) also has a numeric property which I want to illustrate (now maybe by "width", but support for "color" may be helpful as well).

The simplist graph would look like this:

newplot

Currently, I have to write my own "color mapping" function, something like the function map_z2color in this section. With regards to my specific application, I successfully work around this issue with the above method.

But still, I guess my naive method may have performance issues as I have to manually produce a string of color for every polygon and every boundary. So maybe the "feature request" is still a good improvement? Any suggestions?

ryani commented 3 years ago

I'm also interested in this feature. I'd like the lines to change color via something like the heatmap z component. I'd actually prefer gradient lines (blending between the values at each point) but using the z value of the start or end point would be fine.

My use case is scatter lines representing 2d data over time, and I want the trail to fade out as the points get further in the past.

yotkadata commented 1 year ago

It would be really great to be able to use arrays/lists of colors for line and fillcolor. I am currently working on an interactive version of my MeteoHist App and I could've really used this feature. Instead, I ended up adding 365 traces to my Plot - one for every day of the year. This might serve as a workaround for others:

With a bar chart, I could get more or less what I want in one trace (code from inside a class):

# Define opacity depending on whether peak alpha is enabled
opacity = (
    self.df_t[f"{self.year}_alpha"]
    if self.settings["peak_alpha"]
    else np.ones(len(self.df_t))
)

# Get colorscale from method
# Results in an array of colors the same length as self.df_t
colors = self.get_colorscale()

# Display a simpler and faster plot if chart_type is "bar"
if chart_type == "bar":
    fig.add_trace(
        go.Bar(
            x=self.df_t["date"],
            y=self.df_t[f"{self.year}_diff"],
            base=self.df_t["mean"],
            marker=dict(
                color=colors,
                line_width=0,
                opacity=opacity,
            ),
        )
    )

    return fig

key-west-united-states-temperature-mean-2023-ref-1961-1990-interactive

But I like much better to draw an area between the mean and the current value. Since I can't use the color array directly, I have to create many "shapes" (actually they are Scatter traces):

# For each day, add a filled area between the mean and the year's value
for i in range(len(self.df_t) - 1):
    # Define x and y values to draw a polygon between mean and values of today and tomorrow
    date_today = self.df_t["date"].iloc[i]
    date_tomorrow = self.df_t["date"].iloc[i + 1]
    mean_today = self.df_t["mean"].iloc[i]
    mean_tomorrow = self.df_t["mean"].iloc[i + 1]
    value_today = self.df_t[f"{self.year}"].iloc[i]
    value_tomorrow = self.df_t[f"{self.year}"].iloc[i + 1]

    # If one day is above and the other below the mean, set the value to the mean
    if (value_today > mean_today) ^ (value_tomorrow > mean_tomorrow):
        value_tomorrow = mean_tomorrow

    fig.add_trace(
        go.Scatter(
            name=f"Daily value {self.df_t['date'].iloc[i].strftime('%d.%m.%Y')}",
            x=[date_today, date_today, date_tomorrow, date_tomorrow],
            y=[mean_today, value_today, value_tomorrow, mean_tomorrow],
            line_width=0,
            fill="toself",
            fillcolor=colors[i],
            showlegend=False,
            mode="lines",
            opacity=opacity[i],
            hoverinfo="skip",
        )
    )

return fig

key-west-united-states-temperature-mean-2023-ref-1961-1990-interactive

In the end this works, but it's quite slower performance-wise and also feels more "hacky".