✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

How to plot polygons with color assigned according to a continuous colorscale

Hi,

I am trying to plot polygons that are arbitrarily placed in the XY plane and assign a color to each polygon according to a colorscale. So far, I thought of using Heatmap but that didn’t work well because I want to plot arbitrary polygons. I also tried Choropleth but it looks like polygons are overlaid over the world map. The closest I got to achieve what I want is with the following code:

x_polygons = [[20.0, 30.0, 30.0, 20.0, 20.0], [0, 10, 10, 0, 0]]
y_polygons=[[2.5, 2.5, -2.5, -2.5, 2.5], [2.5, 2.5, -2.5, -2.5, 2.5]]

vals = [1, 2]

fig = go.Figure()
            
for (x,y, v) in zip(x_polygons, y_polygons, vals):
    
    fig.add_trace( go.Scatter(x=x, y=y, fill="toself", 
                              mode = 'none',
                              fillcolor=px.colors.sequential.Viridis[v],                              
                              name = f"v ={v}",                               
                             )
                  
                 )


fig.show()

but I need to interpolate between discrete color values for non-integer values of vals and this doesn’t show a colorscale on the side. There must be a better way to achieve what I want but I couldn’t figure it out so far. Any idea?

Hi @bastien,

Welcome to Plotly forum!
plotly.py doesn’t provide a function that maps a value to a color in a colorscale. All these mappings are performed by plotly.js. Recently a similar question was addressed , and these are the proposed solutions: Hover background color on scatter 3d.

Thanks @empet!

I could get what I want after adapting the code you pointed at to my use case. There’s no colorscale on the side of the plot but I can show the value on hover and I suppose I could make up one using subplot if I really needed to. Here is the modified code snippet:

####### Functions to color polygons according to a colorscale ######
def hex_to_rgb(value):
    """Convert a hex-formatted color to rgb, ignoring alpha values."""
    value = value.lstrip("#")
    return [int(value[i:i + 2], 16) for i in range(0, 6, 2)]


def rbg_to_hex(c):
    """Convert an rgb-formatted color to hex, ignoring alpha values."""
    return f"#{c[0]:02x}{c[1]:02x}{c[2]:02x}"


def get_colors_for_vals(vals, vmin, vmax, colorscale, return_hex=True):
    """Given a float array vals, interpolate based on a colorscale to obtain
    rgb or hex colors. Inspired by
    `user empet's answer in \
    <community.plotly.com/t/hover-background-color-on-scatter-3d/9185/6>`_."""
    from numbers import Number
    from ast import literal_eval

    if vmin >= vmax:
        raise ValueError("`vmin` should be < `vmax`.")

    if (len(colorscale[0]) == 2) and isinstance(colorscale[0][0], Number):
        scale, colors = zip(*colorscale)
    else:
        scale = np.linspace(0, 1, num=len(colorscale))
        colors = colorscale
    scale = np.asarray(scale)

    if colors[0][:3] == "rgb":
        colors = np.asarray([literal_eval(color[3:]) for color in colors],
                            dtype=np.float_)
    elif colors[0][0] == "#":
        colors = np.asarray(list(map(hex_to_rgb, colors)), dtype=np.float_)
    else:
        raise ValueError("This colorscale is not supported.")

    colorscale = np.hstack([scale.reshape(-1, 1), colors])
    colorscale = np.vstack([colorscale, colorscale[0, :]])
    colorscale_diffs = np.diff(colorscale, axis=0)
    colorscale_diff_ratios = colorscale_diffs[:, 1:] / colorscale_diffs[:, [0]]
    colorscale_diff_ratios[-1, :] = np.zeros(3)

    vals_scaled = (vals - vmin) / (vmax - vmin)

    left_bin_indices = np.digitize(vals_scaled, scale) - 1
    left_endpts = colorscale[left_bin_indices]
    vals_scaled -= left_endpts[:, 0]
    diff_ratios = colorscale_diff_ratios[left_bin_indices]

    vals_rgb = (
            left_endpts[:, 1:] + diff_ratios * vals_scaled[:, np.newaxis] + 0.5
    ).astype(np.uint8)

    if return_hex:
        return list(map(rbg_to_hex, vals_rgb))
    return [f"rgb{tuple(v)}" for v in vals_rgb]

#####################
import plotly
import numpy as np
import plotly.graph_objects as go

x_polygons = [[20.0, 30.0, 30.0, 20.0, 20.0], [0, 10, 10, 0, 0]]
y_polygons=[[2.5, 2.5, -2.5, -2.5, 2.5], [2.5, 2.5, -2.5, -2.5, 2.5]]

values = [1.1 , 2.2]
vmin = 0 #values.min()
vmax = 10 #values.max()

pl_colors = plotly.colors.sequential.Viridis

fig = go.Figure()
            
for (x, y, v) in zip(x_polygons, y_polygons, values):    
    fig.add_trace( go.Scatter(x=x, y=y, fill="toself", 
                              mode = 'none',
                              fillcolor = str(get_colors_for_vals(np.array([v]), vmin, vmax, pl_colors)[0]),                            
                              name = f"v ={v}",                               
                             )
                  
                 )
fig.show()

@bastien

If your polygons are rectangular and do not have other shapes you can define them as bars:

fig= go.Figure(go.Bar(x=[5, 25], y = [2.5, 2.5], marker_color= [1.1, 2.2], width=10,
                      marker_cmin=0, marker_cmax=10, marker_colorscale='Viridis',
                     marker_showscale=True))