JP-Ellis
JP-Ellis

Reputation: 437

Access Color from Plotly Color Scale

Is there a way in Plotly to access colormap colours at any value along its range?

I know I can access the defining colours for a colourscale from

plotly.colors.PLOTLY_SCALES["Viridis"]

but I am unable to find how to access intermediate / interpolated values.

The equivalent in Matplotlib is shown in this question. There is also another question that address a similar question from the colorlover library, but neither offers a nice solution.

Upvotes: 12

Views: 11769

Answers (6)

stroblme
stroblme

Reputation: 1366

You may want to checkout plotly.colors.sample_colorscale (see docs here). It allows you to specify low and high values, and can also handle lists of sample points to convert to interpolated colors. Note, however, that a list will be returned even if you specify a single sample point.

As an example:

plotly.colors.sample_colorscale(plotly.colors.sequential.Viridis, samplepoints=0.25)

returns ['rgb(59, 81, 138)'], or

plotly.colors.sample_colorscale(plotly.colors.sequential.Viridis, samplepoints=0.25, colortype='tuple')

returns [(0.23039215686274508, 0.31666666666666665, 0.542156862745098)] if you prefer floats.

Upvotes: 0

BetterCallMe
BetterCallMe

Reputation: 768

import plotly.express as px
color_list = list(name_of_color_scale)

# name_of_color_scale could be any in-built colorscale like px.colors.qualitative.D3.

Output:
color_list = 
['#1F77B4',
 '#FF7F0E',
 '#2CA02C',
 '#D62728',
 '#9467BD',
 '#8C564B',
 '#E377C2',
 '#7F7F7F',
 '#BCBD22',
 '#17BECF']

Upvotes: -2

matthias
matthias

Reputation: 181

There is a built in method from plotly.express.colors to sample_colorscale which would provide the color samples:

from plotly.express.colors import sample_colorscale

import plotly.graph_objects as go
import numpy as np

x = np.linspace(0, 1, 25)
c = sample_colorscale('jet', list(x))

fig = go.FigureWidget()
fig.add_trace(
    go.Bar(x=x, y=y, marker_color=c)
)
fig.show()

See the output figure -> sampled_colors

Upvotes: 18

Davide_sd
Davide_sd

Reputation: 13170

This answer extend the already good one provided by Adam. In particular, it deals with the inconsistency of Plotly's color scales.

In Plotly, you specify a built-in color scale by writing colorscale="name_of_the_colorscale". This suggests that Plotly already has a built-in tool that somehow convert the color scale to an appropriate value and is capable of dealing with these inconsistencies. By searching Plotly's source code we find the useful ColorscaleValidator class. Let's see how to use it:

def get_color(colorscale_name, loc):
    from _plotly_utils.basevalidators import ColorscaleValidator
    # first parameter: Name of the property being validated
    # second parameter: a string, doesn't really matter in our use case
    cv = ColorscaleValidator("colorscale", "")
    # colorscale will be a list of lists: [[loc1, "rgb1"], [loc2, "rgb2"], ...] 
    colorscale = cv.validate_coerce(colorscale_name)
    
    if hasattr(loc, "__iter__"):
        return [get_continuous_color(colorscale, x) for x in loc]
    return get_continuous_color(colorscale, loc)
        

# Identical to Adam's answer
import plotly.colors
from PIL import ImageColor

def get_continuous_color(colorscale, intermed):
    """
    Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
    color for any value in that range.

    Plotly doesn't make the colorscales directly accessible in a common format.
    Some are ready to use:
    
        colorscale = plotly.colors.PLOTLY_SCALES["Greens"]

    Others are just swatches that need to be constructed into a colorscale:

        viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
        colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)

    :param colorscale: A plotly continuous colorscale defined with RGB string colors.
    :param intermed: value in the range [0, 1]
    :return: color in rgb string format
    :rtype: str
    """
    if len(colorscale) < 1:
        raise ValueError("colorscale must have at least one color")

    hex_to_rgb = lambda c: "rgb" + str(ImageColor.getcolor(c, "RGB"))

    if intermed <= 0 or len(colorscale) == 1:
        c = colorscale[0][1]
        return c if c[0] != "#" else hex_to_rgb(c)
    if intermed >= 1:
        c = colorscale[-1][1]
        return c if c[0] != "#" else hex_to_rgb(c)

    for cutoff, color in colorscale:
        if intermed > cutoff:
            low_cutoff, low_color = cutoff, color
        else:
            high_cutoff, high_color = cutoff, color
            break

    if (low_color[0] == "#") or (high_color[0] == "#"):
        # some color scale names (such as cividis) returns:
        # [[loc1, "hex1"], [loc2, "hex2"], ...]
        low_color = hex_to_rgb(low_color)
        high_color = hex_to_rgb(high_color)

    return plotly.colors.find_intermediate_color(
        lowcolor=low_color,
        highcolor=high_color,
        intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
        colortype="rgb",
    )

At this point, all you have to do is:

get_color("phase", 0.5)
# 'rgb(123.99999999999999, 112.00000000000001, 236.0)'

import numpy as np
get_color("phase", np.linspace(0, 1, 256))
# ['rgb(167, 119, 12)',
#  'rgb(168.2941176470588, 118.0078431372549, 13.68235294117647)',
#  ...

Edit: improvements to deal with special cases.

Upvotes: 9

Adam
Adam

Reputation: 17339

Plotly does not appear to have such a method, so I wrote one:

import plotly.colors

def get_continuous_color(colorscale, intermed):
    """
    Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
    color for any value in that range.

    Plotly doesn't make the colorscales directly accessible in a common format.
    Some are ready to use:
    
        colorscale = plotly.colors.PLOTLY_SCALES["Greens"]

    Others are just swatches that need to be constructed into a colorscale:

        viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
        colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)

    :param colorscale: A plotly continuous colorscale defined with RGB string colors.
    :param intermed: value in the range [0, 1]
    :return: color in rgb string format
    :rtype: str
    """
    if len(colorscale) < 1:
        raise ValueError("colorscale must have at least one color")

    if intermed <= 0 or len(colorscale) == 1:
        return colorscale[0][1]
    if intermed >= 1:
        return colorscale[-1][1]

    for cutoff, color in colorscale:
        if intermed > cutoff:
            low_cutoff, low_color = cutoff, color
        else:
            high_cutoff, high_color = cutoff, color
            break

    # noinspection PyUnboundLocalVariable
    return plotly.colors.find_intermediate_color(
        lowcolor=low_color, highcolor=high_color,
        intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
        colortype="rgb")

The challenge is that the built-in Plotly colorscales are not consistently exposed. Some are defined as a colorscale already, others as just a list of color swatches that must be converted to a color scale first.

The Viridis colorscale is defined with hex values, which the Plotly color manipulation methods don't like, so it's easiest to construct it from swatches like this:

viridis_colors, _ = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
colorscale = plotly.colors.make_colorscale(viridis_colors)

get_continuous_color(colorscale, intermed=0.25)
# rgb(58.75, 80.75, 138.25)

Upvotes: 16

r-beginners
r-beginners

Reputation: 35155

The official reference explains. Here

import plotly.express as px

print(px.colors.sequential.Viridis)
['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725']

print(px.colors.sequential.Viridis[0])
#440154

Upvotes: 0

Related Questions