Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
152 views
in Technique[技术] by (71.8m points)

r - Heatmap within a triangle

Consider the following example:

triangle_lines <- data.frame(
  X   = c(0,0,1,1,0.5,0.5),
  Y   = c(0,0,0,0,1,  1),
  grp = c(1,2,1,3,2,3)
)

df <- 
  matrix(c(c(0.2,0.5,0.8), c(0.3,0.5,0.1), c(1,5,10)), ncol=3) %>%
  as.data.frame()
colnames(df) <- c("x","y", "amount")

ggplot(df, aes(x=x, y=y)) +
  geom_point(aes(colour = amount)) +
  geom_line(data=triangle_lines, aes(X, Y, group = grp)) +
  theme_void() 

Plot

We have three values: the x coordinates, the y coordinates, and amount that decides the colour values of the scatter points. The points are not equally spaced apart.

Also, I have a triangle. I am attempting to create a gradient heatmap inside this triangle only, filling the entire triangle with heatmap colours based on df$amount. Any idea how to solve this?

P.S. While I am exemplifying with only three points here, this plot will have tens or hundreds of points in real applications.

question from:https://stackoverflow.com/questions/65941146/heatmap-within-a-triangle

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Not the cleanest solution, but it works.

First we initiate an empty list.

Then we create a sequence of lambda values, one for each axis, that is used to create a weighted average between two points on that scale (namely, between 0 and 1). In this example, there are 101 lambdas per axis, creating 101^2 iterations.

We then check if the coordinate generated lies inside the triangle with sp::point.in.polygon. Note that this method applies to more shapes than just a triangle, so this solution is general for multiple shapes.

If the coordinate generated from the lambda values lies inside the polygon, then we calculate the distances from this coordinate to every coordinate in df. Note that we take sqrt(2) minus the distance because the lower the distance, the more weight that point should carry. Hence, we take the maximum distance (sqrt(2)) and subtract the distance. sqrt(2) is not a definite number that must be set, but it does prevent negative values. Other values provide other results.

In the next step, we scale the distances so that they sum to 1. That allows us to create a weighted average, which is defined in amount.

After running the loops, we bind the lists into a data frame and create the plot.

To ensure that the edges are smooth, we make somewhat thick white lines that have rounded edges.

gradient_list <- list()
for (lambda_x in seq(0,1,by=0.01)) {
  for (lambda_y in seq(0,1,by=0.01)) {
    x_value <- lambda_x*0 + (1-lambda_x)*1
    y_value <- lambda_y*0 + (1-lambda_y)*1
    
    inside_polygon <- sp::point.in.polygon(x_value, y_value, triangle_lines$X, triangle_lines$Y) %>% as.logical()
    
    if (inside_polygon) {
      point <- c(x_value, y_value)
      distances <- sqrt(2) - sqrt((scatters$x - point[1])^2 + (scatters$y - point[2])^2)
      weighted_distances <- distances/sum(distances)
      amount <- sum(weighted_distances * df$z)
      gradient_list <- append(gradient_list, list(c(point, amount)))
    }
  }
}

gradient_df <- do.call(rbind, gradient_list) %>% as.data.frame()
colnames(gradient_df) <- c("x","y","amount")


ggplot(gradient_df, aes(x=x, y=y)) + 
  geom_point(aes(colour = amount), size=2) +
  theme_void() + 
  geom_line(data=triangle_lines, aes(X, Y, group = grp), size=3, colour="white", lineend="round")

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...