three-way color gradient fill in r
Here is one way to do it - it's a bit of a hack, using points to plot the gradient piece by piece:
plot(NA,NA,xlim=c(0,1),ylim=c(0,1),asp=1,bty="n",axes=F,xlab="",ylab="")
segments(0,0,0.5,sqrt(3)/2)
segments(0.5,sqrt(3)/2,1,0)
segments(1,0,0,0)
# sm - how smooth the plot is. Higher values will plot very slowly
sm <- 500
for (y in 1:(sm*sqrt(3)/2)/sm){
for (x in (y*sm/sqrt(3)):(sm-y*sm/sqrt(3))/sm){
## distance from base line:
d.red = y
## distance from line y = sqrt(3) * x:
d.green = abs(sqrt(3) * x - y) / sqrt(3 + 1)
## distance from line y = - sqrt(3) * x + sqrt(3):
d.blue = abs(- sqrt(3) * x - y + sqrt(3)) / sqrt(3 + 1)
points(x, y, col=rgb(1-d.red,1 - d.green,1 - d.blue), pch=19)
}
}
And the output:
Did you want to use these gradients to represent data? If so, it may be possible to alter d.red
, d.green
, and d.blue
to do it - I haven't tested anything like that yet though. I hope this is somewhat helpful, but a proper solution using colorRamp
, for example, will probably be better.
EDIT: As per baptiste's suggestion, this is how you would store the information in vectors and plot it all at once. It is considerably faster (especially with sm
set to 500, for example):
plot(NA,NA,xlim=c(0,1),ylim=c(0,1),asp=1,bty="n",axes=F,xlab="",ylab="")
sm <- 500
x <- do.call(c, sapply(1:(sm*sqrt(3)/2)/sm,
function(i) (i*sm/sqrt(3)):(sm-i*sm/sqrt(3))/sm))
y <- do.call(c, sapply(1:(sm*sqrt(3)/2)/sm,
function(i) rep(i, length((i*sm/sqrt(3)):(sm-i*sm/sqrt(3))))))
d.red = y
d.green = abs(sqrt(3) * x - y) / sqrt(3 + 1)
d.blue = abs(- sqrt(3) * x - y + sqrt(3)) / sqrt(3 + 1)
points(x, y, col=rgb(1-d.red,1 - d.green,1 - d.blue), pch=19)
R how to specify custom color gradients with breakpoints
You can set the exact points where a particular colour should be by using the values
argument of the scale. In the example below, we want "darkblue"
at 10, "lightblue"
at 20 and "yellow"
at 30.
The only catch is that the values
argument works for rescaled values between 0 and 1, so we ought to fix the limits and apply the same rescaling to our breaks.
Lastly, because now values outside the 10-30 range are undefined, it might be best to repeat the colours at the extremes at the (rescaled) values 0 and 1.
library(ggplot2)
colour_breaks <- c(10, 20, 30)
colours <- c("darkblue", "lightblue", "yellow")
ggplot(mpg, aes(displ, hwy, colour = cty)) +
geom_point() +
scale_colour_gradientn(
limits = range(mpg$cty),
colours = colours[c(1, seq_along(colours), length(colours))],
values = c(0, scales::rescale(colour_breaks, from = range(mpg$cty)), 1),
)
Created on 2021-10-13 by the reprex package (v2.0.1)
As a small note: in your description it is unclear what the colour should be at 50: in one gradient it should be blue and in the other it should be lightblue. At best, you can set one of the colours (let's say blue) to 50 and use a 50 + a miniscule offset (e.g. .Machine$double.eps
) for the lightblue break.
Add a gradient fill to geom_col
You can do this fairly easily with a bit of data manipulation. You need to give each group in your original data frame a sequential number that you can associate with the fill scale, and another column the value of 1. Then you just plot using position_stack
library(ggplot2)
library(dplyr)
diamonds %>%
group_by(cut) %>%
mutate(fill_col = seq_along(cut), height = 1) %>%
ggplot(aes(x = cut, y = height, fill = fill_col)) +
geom_col(position = position_stack()) +
scale_fill_viridis_c(option = "plasma")
Gradient fill area under curve
The following should be close to what you're looking for. The trick is to use scale_color_identity
for the geom_segment
, and passing to the color aesthetic an RGB string that represents each wavelength in your data frame.
ggplot(bq, aes(x=w.length, y=s.e.irrad)) +
geom_segment(aes(xend=w.length, yend=0, colour = nm_to_RGB(w.length)),
size = 1) +
geom_line() +
scale_colour_identity()
Or if you want a more muted appearance:
ggplot(bq, aes(x=w.length, y=s.e.irrad)) +
geom_area(fill = "black") +
geom_segment(aes(xend=w.length, yend=0,
colour = nm_to_RGB(w.length)),
size = 1, alpha = 0.3) +
geom_line() +
scale_colour_identity()
The only drawback being that you need to define nm_to_RGB
: the function that converts a wavelength of light into a hex-string to represent a color. I'm not sure there's a "right" way to do this, but one possible implementation (that I translated from the javascript function here) would be:
nm_to_RGB <- function(wavelengths){
sapply(wavelengths, function(wavelength) {
red <- green <- blue <- 0
if((wavelength >= 380) & (wavelength < 440)){
red <- -(wavelength - 440) / (440 - 380)
blue <- 1
}else if((wavelength >= 440) & (wavelength<490)){
green <- (wavelength - 440) / (490 - 440)
blue <- 1
}else if((wavelength >= 490) && (wavelength<510)){
green <- 1
blue = -(wavelength - 510) / (510 - 490)
}else if((wavelength >= 510) && (wavelength<580)){
red = (wavelength - 510) / (580 - 510)
green <- 1
}else if((wavelength >= 580) && (wavelength<645)){
red = 1
green <- -(wavelength - 645) / (645 - 580)
}else if((wavelength >= 645) && (wavelength<781)){
red = 1
}
if((wavelength >= 380) && (wavelength<420)){
fac <- 0.3 + 0.7*(wavelength - 380) / (420 - 380)
}else if((wavelength >= 420) && (wavelength<701)){
fac <- 1
}else if((wavelength >= 701) && (wavelength<781)){
fac <- 0.3 + 0.7*(780 - wavelength) / (780 - 700)
}else{
fac <- 0
}
do.call(rgb, as.list((c(red, green, blue) * fac)^0.8))
})
}
Obviously, I don't have your data set, but the following code creates a plausible set of data over the correct ranges:
Data
set.seed(10)
bq <- setNames(as.data.frame(density(sample(rnorm(5, 600, 120)))[c("x", "y")]),
c("w.length", "s.e.irrad"))
bq$s.e.irrad <- bq$s.e.irrad * 1e5
Gradient of n colors ranging from color 1 and color 2
colorRampPalette
could be your friend here:
colfunc <- colorRampPalette(c("black", "white"))
colfunc(10)
# [1] "#000000" "#1C1C1C" "#383838" "#555555" "#717171" "#8D8D8D" "#AAAAAA"
# [8] "#C6C6C6" "#E2E2E2" "#FFFFFF"
And just to show it works:
plot(rep(1,10),col=colfunc(10),pch=19,cex=3)
How merge two different scale color gradient with ggplot
Yes you could if you use the ggnewscale
package:
a <- sample(nrow(iris), 75)
df1 <- iris[a,]
df2 <- iris[-a,]
library(ggnewscale)
ggplot(mapping = aes(Sepal.Width, Sepal.Length)) +
geom_point(data = df1, aes(colour = Petal.Length)) +
scale_colour_gradientn(colours = c("red", "black")) +
# Important: define a colour/fill scale before calling a new_scale_* function
new_scale_colour() +
geom_point(data = df2, aes(colour = Petal.Width)) +
scale_colour_gradientn(colours = c("blue", "white"))
Alternatives are the relayer package, or the scale_colour_multi
/scale_listed
from ggh4x (full disclaimer: I wrote ggh4x).
EDIT: Here are the alternatives:
library(ggh4x)
# ggh4x scale_colour_multi (for gradientn-like scales)
ggplot(mapping = aes(Sepal.Width, Sepal.Length)) +
geom_point(data = df1, aes(length = Petal.Length)) +
geom_point(data = df2, aes(width = Petal.Width)) +
scale_colour_multi(colours = list(c("red", "black"), c("blue", "white")),
aesthetics = c("length", "width"))
# ggh4x scale_listed (for any non-position scale (in theory))
ggplot(mapping = aes(Sepal.Width, Sepal.Length)) +
geom_point(data = df1, aes(length = Petal.Length)) +
geom_point(data = df2, aes(width = Petal.Width)) +
scale_listed(list(
scale_colour_gradientn(colours = c("red", "black"), aesthetics = "length"),
scale_colour_gradientn(colours = c("blue", "white"), aesthetics = "width")
), replaces = c("colour", "colour"))
library(relayer)
# relayer
ggplot(mapping = aes(Sepal.Width, Sepal.Length)) +
rename_geom_aes(geom_point(data = df1, aes(length = Petal.Length)),
new_aes = c("colour" = "length")) +
rename_geom_aes(geom_point(data = df2, aes(width = Petal.Width)),
new_aes = c("colour" = "width")) +
scale_colour_gradientn(colours = c("red", "black"), aesthetics = "length",
guide = guide_colourbar(available_aes = "length")) +
scale_colour_gradientn(colours = c("blue", "white"), aesthetics = "width",
guide = guide_colourbar(available_aes = "width"))
All the alternatives give warnings about unknown aesthetics, but this doesn't matter for the resulting plots. It is just a line of code in ggplot's layer()
function that produces this warning and you can't go around this without either re-coding every geom
wrapper or, as ggnewscale does, renaming the old aesthetic instead of providing a new aesthetic. The plots all look near-identical, so I figured I wouldn't have to post them again.
Gradient-color a line in ggplot2 R
One option to achieve that would be via ggforce::geom_link2
:
library(ggforce)
#> Loading required package: ggplot2
d <- tibble::tibble(
`Country Name` = c('Estados Unidos', 'Estados Unidos'),
`Country Code` = c('USA', 'USA'),
Year = c(1961, 2020),
`CrecimientoPBI (%)` = c(2.3, -3.49),
`Inflación (%)` = c(1.07, 1.23),
`Desempleo (%)` = c(6.7, 8.05)
)
ggplot(d, aes(x = `Inflación (%)`, y = `Desempleo (%)`, color = factor(Year))) +
geom_point(aes(size = `CrecimientoPBI (%)`)) +
geom_link2(aes(group = 1)) +
scale_color_manual(values = c(`1961` = "gold", `2020` = "darkgreen")) +
theme_minimal() +
guides(size = "none")
Using a custom gradient fill based on two different columns
Here is an option without changing too much your code. I would divide the Apo
by 3 instead of multiplying, like so all Apo
are <5 and Qt
>10:
> unique(usc_map_ex$Qt)
[1] NA 10 20 30 40 50 60 70 80 90 100 110 120 140
> unique(usc_map_ex$Apo)/3
[1] 0.3333333 NA 0.6666667 1.0000000 1.3333333 1.6666667 2.0000000 2.3333333 2.6666667 3.0000000 3.3333333 3.6666667 4.0000000 4.3333333
Then for the plot itself perhaps solid colors would be better than the palette so I just set the color to the last of your brewer.pal()
call.
mcolor <- c(rep("#31A354", 3), rep("#3182BD", 3))
## plot
ggplot() +
# polygon for Qt
geom_polygon(data = {usc_map_ex %>% filter(is.na(Apo))},
aes(x,y, group = group, fill = (Qt), color = "")) +
# polygon for Apo (multiplyong by three to have a different range of values than Qt)
geom_polygon(data = {usc_map_ex %>% filter(is.na(Qt))},
aes(x,y, group = group, fill = (Apo)/3, color = "")) +
# adding the outline of each state
geom_polygon(data = us,
aes(x,y, group = group), fill = NA, color = "black") +
# removing the outline of counties
scale_colour_manual(values = 'transparent', guide = "none") +
# custom fill gradient to have different shades of colors for each variable
scale_fill_gradientn(name = "test",
breaks = c(1,5,10, 50),
colors = mcolor,
guide = guide_colorbar(barwidth = 0.8, barheight = 18),
trans = "log") +
theme_void()
Update with color shade
If you want the color shade from your code you would need to reverse order (rev
). Currently the greens are going light to dark then blues light to dark again. By reversing the order of the greens, your color vector become dark green to light green/blue to dark blue.
Plus I don't think you need the colorRampPalette
as the 3 colors are already a palette. New color vector :
mcolor <- c(rev(brewer.pal(3, "Greens")), brewer.pal(3, "Blues"))
Update 2 reverse order:
This way is probably best to highlight your data. By reverting the second set so the extreme value are in the darkest colors rather than light. (keep in mind that there is a little bias for the "Qt" as the 1 on the scale is effectively 13 for Qt value but that remain the lowest one therefore light color).
ggplot() +
# polygon for Qt
geom_polygon(data = {usc_map_ex %>% filter(is.na(Apo))},
aes(x,y, group = group, fill = (Qt), color = "")) +
# polygon for Apo (multiplyong by three to have a different range of values than Qt)
geom_polygon(data = {usc_map_ex %>% filter(is.na(Qt))},
aes(x,y, group = group, fill = rev(Apo), color = "")) +
# adding the outline of each state
geom_polygon(data = us,
aes(x,y, group = group), fill = NA, color = "black") +
# removing the outline of counties
scale_colour_manual(values = 'transparent', guide = "none") +
# custom fill gradient to have different shades of colors for each variable
scale_fill_gradientn(name = "test",
breaks = c(1, 4,9, 13,25, 50, 100),
labels = c(13, 10,5, 1,25, 50, 100),
colors = mcolor,
guide = guide_colorbar(barwidth = 0.8, barheight = 18),
trans = "log") +
theme_void()
Manual scaling of color gradient with scale_color_gradientn() in R
Since you provided no value/colour pair for the upper limit of the colour scale, these got interpreted as NAs and became gray. Fixing this should be as easy as providing the upper limit too.
library(ggplot2)
library(scales)
#> Warning: package 'scales' was built under R version 3.6.3
df <- data.frame(x = seq(0, 15 , 0.001),
y = seq(0, 15, 0.001))
# Plot data
ggplot(df, aes(x=x, y=y, col = y)) +
geom_line() +
scale_color_gradientn(colours = c("green", "black", "red", "red"),
values = rescale(x = c(0, 2, 4, 15), from = c(0, 15)))
Created on 2020-04-24 by the reprex package (v0.3.0)
Related Topics
Stylecolorbar Center and Shift Left/Right Dependent on Sign
Functions Available for Tufte Boxplots in R
Problems Using Foreach Parallelization
Replace Na with Zero in Dplyr Without Using List()
Arrange Plots in a Layout Which Cannot Be Achieved by 'Par(Mfrow ='
R Table Function: How to Sum Instead of Counting
R Dpylr Select_If with Multiple Conditions
How to Subset Data.Frames Stored in a List
How to Upload a File to a Server via Ftp Using R
How to Create Design Matrix in R
Using R to Analyze Balance Sheets and Income Statements
Error in Eval(Expr, Envir, Enclos):Object Not Found
How to Use Plyr to Number Rows
R: Determine If a Script Is Running in Windows or Linux
Factor Order Within Faceted Dotplot Using Ggplot2
Using Multiple Ellipses Arguments in R
Automatic Adjustment of Margins in Horizontal Bar Chart
Matrix Expression Causes Error "Requires Numeric/Complex Matrix/Vector Arguments"