Use conditional coloring on a plotly surface

I am using plotly via R for the first time and trying to create a surface from a grid and color it based on a calculation.

For example, I would like to use the surface from `data(volcano)`, as in

``````library(plotly)
``````

But instead of color based on the z-value (altitude), let's just say I wanted to color based on distance from my house on the little mesa at (20,60) .

``````house_loc <- c(20,60,150) # (x,y,z) of my house
dist_to_house <- Vectorize(function(x,y,z){sqrt(sum( (c(x,y,z)-house_loc)^2 ))})
``````

So far I have tried:

``````color_me <-function(x){
colorRampPalette(c('tan','blue')
)(24L)[findInterval(x,seq(0,1,length.out=25),
all.inside=TRUE)]
}

library(dplyr)
library(reshape2)
volcano %>%
melt( varnames=c('y','x'),value.name='z' ) %>%
mutate( d = dist_to_house(x, y, z) ,
d_rel = d/max(d),
d_color = color_me(d_rel)
) -> df

plot_ly(df,
type='scatter3d',
mode='none', # no markers, just surface
x=~x,
y=~y,
z=~z,
surfaceaxis=2,
surfacecolor=~d_color) # last argument seems not to work
``````

Which just returns:

The desired result would color the landscape tan in the region of the house and gradually fade to blue in the regions far from the house.

Somewhat related question uses `mesh3d` code found elsewhere and doesn't explain how to calculate (i, j, k)

6

Your code virtually has everything you need, just use a `surface` plot and use your distance array as the `color`.

``````library(plotly)
library(dplyr)
library(reshape2)

house_loc <- c(20,60,150)
dist_to_house <- Vectorize(function(x,y,z){sqrt(sum( (c(x,y,z)-house_loc)^2 ))})

volcano %>%
melt( varnames=c('y','x'),value.name='z' ) %>%
mutate( d = dist_to_house(x, y, z) ,
d_rel = d/max(d)
) -> df

color <- df\$d_rel
dim(color) <- dim(volcano)

plot_ly(df,
type='surface',
z=volcano,
surfacecolor=color,
colors=c('tan','blue'))
``````

Saturday, May 14, 2022
1

Sometimes writing a question helps to find the answer to it. The manipulation needed is a spatial interpolation (kriging) program of some kind. This stackoverflow Q and A explains the basics and gives specific examples. With the data set described in the question above, the following lines provide a solution. The link also has other approaches as well.

``````library(akima)
library(plotly)
s = interp(x = df\$x, y = df\$y, z = df\$z)
p <- plot_ly(x = s\$x, y = s\$y, z = s\$z) %>% add_surface()
``````
Friday, July 30, 2021
4

I understand you need to solve two problems:

• Find the plane that fits a collection of points
• Project a second collection of points onto that plane along a specific direction

The second problem has been fully addressed in another answer, so I'm contributing a more generic approach to the first problem.

It's true that when you positively know that all your points lie on a plane, you may just select three non-aligned ones and calculate the plane. But your points may come from real measurements with some noise, and you may wish to find the plane that best fists your points.

The following function solves the general problem of finding the plane that best fits a collection of points. See the explanations in the comments:

``````import numpy as np
PRECISION = 1e-8    # Arbitrary zero for real-world purposes

def plane_from_points(points):
# The adjusted plane crosses the centroid of the point collection
centroid = np.mean(points, axis=0)

# Use SVD to calculate the principal axes of the point collection
# (eigenvectors) and their relative size (eigenvalues)
_, values, vectors = np.linalg.svd(points - centroid)

# Each singular value is paired with its vector and they are sorted from
# largest to smallest value.
# The adjusted plane plane must contain the eigenvectors corresponding to
# the two largest eigenvalues. If only one eigenvector is different
# from zero, then points are aligned and they don't define a plane.
if values[1] < PRECISION:
raise ValueError("Points are aligned, can't define a plane")

# So the plane normal is the eigenvector with the smallest eigenvalue
normal = vectors[2]

# Calculate the coefficients (a,b,c,d) of the plane's equation ax+by+cz+d=0.
# The first three coefficients are given by the normal, and the fourth
# one (d) is the plane's signed distance to the origin of coordinates
d = -np.dot(centroid, normal)
plane = np.append(normal, d)

# If the smallest eigenvector is close to zero, the collection of
# points is perfectly flat. The larger the eigenvector, the less flat.
# You may wish to know this.
thickness = values[2]

return plane, thickness
``````

You can check this:

``````>>> surface_maker=np.array([[50., 15., 46.04750574], [50., 5., 45.56400925], [44.83018398, 5., 25.], [44.76296902, 15., 25.], [50., 25., 45.56400925], [44.83018398, 25., 25.], [59.8336792, 5., 75.], [59.71483707, 15., 75.], [59.8336792, 25., 75.]])
>>> plane, thickness = plane_from_points(surface_maker)
>>> print(plane)
[-0.95725318  0.          0.28925136 35.2806339 ]
>>> print(thickness)
1.3825669490602308
``````

So, in fact, your point distribution is not flat (thickness clearly different from zero), and you can't just select three arbitrary points to solve your problem.

Saturday, July 31, 2021
4

You ran into a bug, which we now fixed it.

Please reinstall and reload the "plotly" package, re-instantiate your `py` object and it should work now: https://plot.ly/~marianne2/38/or-illn-vs-or-edu/

Saturday, August 7, 2021
1

To disable zoom and panning you need to set: `layout.xaxis.fixedrange = true` and `layout.yaxis.fixedrange = true`.

To hide the controls you need to set `displayModeBar = false`.

In Python this would for example look like this:

``````dcc.Graph(
id='my-id',
config={'displayModeBar': False},
'layout': go.Layout(
xaxis={'title': 'x-axis','fixedrange':True},
yaxis={'title': 'y-axis','fixedrange':True})
)
``````
Monday, August 23, 2021