Heatmap for irregular shapes

Hey all,
I am trying to use Heatmaps to color a 2D finite element mesh according to some data defined on it. The way I am plotting the mesh itself (i.e. just the grids) is from this post. In there, I have created the connectivity array so I would know the coordinates of the 4 vertices that form one of the elements.
When the grid is regular, i.e. all rectangle shaped, like in the post, it is easy to then impose a heatmap onto it. However, what if the grids are irregular? For example, if I take â€śempetâ€ť 's code in the post, and just change the coordinates of the last node to `[0.5, 0.8]`, I get the mesh here:

Now the elements are trapezoids. I am wondering how can I color those with Heatmap. Thank you so much!

@axelwang
You can find in this thread how to associate a regular grid and interpolate the z-values at its points from the irregular ones: Heatmap complain NaN z values when there are actually valid z values

Hey @empet. Thanks for your help!
I am not sure though if what you suggest is what I am looking for. I am not looking for a smoothed heatmap color plot, but rather I would like the colors to strictly follow the boundaries. In other words, I simply want to color the 4 trapezoids shown above, and my knowns are the coordinates of the 4 corners forming one trapezoid, and a value corresponding to what this trapezoid should be colored to.
I am still unable to find a solution to this. I looked at the documentation of Heatmap and it seems like the issue is with how to define the 'cells" for each color?

Maybe what I ought to use is â€śScatterâ€ť rather than â€śHeatmapâ€ť. Then I can define the shape of each element and use the â€śfillâ€ť option from â€śScatterâ€ť, like in here. But the problem with this is I canâ€™t seem to find a way to change â€śfillcolorâ€ť from one element to another, but rather they will all be colored to one color.

``````import numpy as np
import plotly.graph_objs as go

nodes=[[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0.5, 0],
[1, 0.5],
[0.5, 1],
[0, 0.5],
[0.5, 0.8]]

elements= [[0, 4, 8, 7],
[7, 8, 6, 3],
[4, 1, 5, 8],
[8, 5, 2, 6]]

nodes=np.asarray(nodes)
xn, yn=nodes.T

field = [1,2,3,4]

y_plotly=[]
z_plotly=[]
for elem in elements:
elem.append(elem[0])
y_plotly.extend(xn[elem].tolist()+[None])
z_plotly.extend(yn[elem].tolist()+[None])

trace=dict(type='scatter',
x=y_plotly,
y=z_plotly,
mode='lines',
line=dict(color='blue', width=2),
fill='toself',
fillcolor="red")

layout=dict(width=400, height=300,
xaxis=dict(showgrid=False, zeroline=False),
yaxis=dict(showgrid=False, zeroline=False)
)
fig=go.FigureWidget(data=[trace], layout=layout)
``````

This fills all trapezoids to red. But what I really want is to fill them according with a custom colorscale that corresponds to `field`.

@axelwang
If you are calling go.Scatter to fill elements, then you should define a trace for each element. I updated your code to Plotly version 5.+, because the examples you followed on this forum are old:

``````import numpy as np
import plotly.graph_objects as go
nodes=[[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0.5, 0],
[1, 0.5],
[0.5, 1],
[0, 0.5],
[0.5, 0.8]]

elements= [[0, 4, 8, 7],
[7, 8, 6, 3],
[4, 1, 5, 8],
[8, 5, 2, 6]]

nodes=np.asarray(nodes)
xn, yn=nodes.T

field = [1,2,3,4]
colors = ['#183924', '#097d4a', '#72ba6c', '#d6f9cf']
f2color=dict(zip(field, colors))

data= []
for k, elem in enumerate(elements):
elem.append(elem[0])
data.append(go.Scatter(
x=xn[elem],
y=yn[elem],
mode='lines',
line=dict(color='rgb(150, 150, 150)', width=2),
fill='toself',
fillcolor=f2color[field[k]]))

fig = go.Figure(data)
d = dict(showline=False, zeroline=False)
fig.update_layout(width=400, height=300, showlegend=False,
xaxis=d, yaxis=d,
template="none")
``````

Since you didnâ€™t specify how many elements are you plotting in a figure, and whether they are colored with arbitrary discrete colors or colors from a colorscale, I gave here only the code for the four elements and the associated field, you posted above.

Hey @empet. Thank you so much for this. It has been very helpful!
Could you please help me one more time by showing how to actually do this with a colorscale, i.e. map the 4 values in `field` to an existing colorscale, say `"Viridis"`. I have been playing around with this for a bit but couldnâ€™t make it to work. My main confusion is how to create your `f2color` dictionary when `colors` is a built in colorscale.
Thank you very much!

@axelwang

Any graphic library works with normalized data, and to each normalized value, `nval in [0,1]`, one associates a color in the chosen colorscale. From a Plotly colorscale, as a list of lists:

``````pl_plasma=
[[0.0, '#eff821'],
[0.17, '#fdb32e'],
[0.25, '#f79341'],
[0.33, '#ec7853'],
[0.42, '#dd5f65'],
[0.5, '#ca4678'],
[0.58, '#b52e8c'],
[0.67, '#9b179e'],
[0.75, '#7c02a7'],
[0.83, '#5c00a5'],
[0.92, '#3a049a'],
[1.0, '#0c0786']]
``````

you cannot find which color corresponds to any normalized value in ([0,1]), because itâ€™s plotly.js which interpolates the colors. Thatâ€™s why you should import `matplotlib.cm` to find out the color in a coloscale corresponding to normalized field values. Hence to the previous code posted above we add a function that maps the filed values to a coloscale and returns the dictionary used above:

``````import numpy as np
import plotly.graph_objects as go
from matplotlib import cm

def get_field2color(field, mpl_cmap):
field = np.asarray(field)
vmin, vmax = field.min(), field.max()
if vmin == vmax:
raise ValueError("field contains the same values in each position")
norm_field = (field-vmin)/(vmax-vmin)
rgbcolors = (mpl_cmap(norm_field)*255).astype(np.uint8)
fcolors = [f"rgb{tuple(rgbc)[:3]}" for rgbc in rgbcolors]
return dict(zip(field, fcolors))

field =[1,2,3,4]
nodes=[[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0.5, 0],
[1, 0.5],
[0.5, 1],
[0, 0.5],
[0.5, 0.8]]

elements= [[0, 4, 8, 7],
[7, 8, 6, 3],
[4, 1, 5, 8],
[8, 5, 2, 6]]

nodes=np.asarray(nodes)
xn, yn=nodes.T
cmap = cm.viridis     #cm.Greens_r          #cm.Reds_r
f2color=get_field2color(field, cmap)

data= []

for k, elem in enumerate(elements):
elem.append(elem[0])
data.append(go.Scatter(
x=xn[elem],
y=yn[elem],
mode='lines',
line=dict(color='rgb(150, 150, 150)', width=2),
fill='toself',
fillcolor=f2color[field[k]]))

fig = go.Figure(data)
d = dict(showline=False, zeroline=False)
fig.update_layout(width=400, height=300, showlegend=False,
xaxis=d, yaxis=d,
template="none")
``````

Hey @empet.
This has been extremely helpful. Thank you so much.
I believe I have one last thing to ask for your help. How can I actually plot a color bar for the filled area plot? Again, I have tried myself for a while but couldnâ€™t figure this out. I think it is quite tricky because the elements are filled in one by one so the `go.Scatter()` doesnâ€™t see the full range of color at once. Also, seems the built-in color bar option for `go.Scatter()` can only work for `line` or `marker` as part of their dictionary, but not with respect to `fillcolor`.

I am thinking maybe the key really is to just take `f2color` and make a color bar from that, and then somehow append this to the `go.Scatter()` plot below. This is the part I get stuck now. Would be really grateful if you can help on this or offer another strategy to do it.

Thank you so much!

@axelwang
I modified the function that returnd the dict `f2color`, such that to define a discrete colorscale according to field values:

``````def get_field2color(field, cmap):
if 0 in field:
raise ValueError("the field code must be >0")

field = np.asarray(field)
vmin, vmax = field.min(), field.max()
if vmin == vmax:
raise ValueError("field contains the same values in each position")
norm_field = (field-vmin)/(vmax-vmin)
rgbcolors = (cmap(norm_field)*255).astype(np.uint8)
fcolors = [f"rgb{tuple(rgbc)[:3]}" for rgbc in rgbcolors]
d= dict(zip(field, fcolors))
vals = np.concatenate(([0], field))
vmin, vmax = vals.min(), vals.max()
nvals=(vals-vmin)/(vmax-vmin)
ids = np.argsort(nvals)
dcolorscale = [] #discrete colorscale
for k in range(len(fcolors)):
dcolorscale.extend([[nvals[ids[k]], fcolors[ids[k]]], [nvals[ids[k+1]], fcolors[ids[k]]]])
return d, dcolorscale
``````

Hence in the main code replace

``````f2color=get_field2color(field, cmap)
``````

by

``````f2color, dcolorscale= get_field2color(field, cmap)
``````

and to get displayed the colorbar, add a dummy trace to the initial one as follows:

``````ids = [el[0] for el in elements]