Reputation: 356
I have some code that creates a map with numerous points, annotated with some stats, on a monthly basis. This worked fine until I updated ggplot2
to 3.3.6
, after which the plots have broken and I've not been able to figure out the solution. As far as I can tell, the issue lies in the ggsave()
call, but I don't know what has changed and how to fix it.
There are two main issues that have come up. But to illustrate, I will attach below comparison images of the "good" and "bad" versions (click links to see full-size).
The good version has uniformly coloured points/squares while the latter has strange points coloured irregularly.
The non-breaking space is formatted properly in the good version, while it appears as a box in the bad version.
One potential cause I noted for the irregular points was some updates in the works for the "size" parameter (see this blog post). Such things have happened in the past as well (see this for example). However, this update is claimed to be for the next release, and moreover like I said, I have a hunch the issue I'm facing has something to do with ggsave()
. And regardless, I already tried tweaking the size and stroke of the geom_point()
but haven't been able to recover the old version properly.
The RStudio plot device doesn't indicate any issue with the points, and using the png()
method instead of ggsave()
to write produces the correct/"good" version.
png(filename = "map_cov_plain.png",
units = "in", width = 8, height = 11, bg = "transparent", res = 300)
print(map_cov_plain)
dev.off()
I tried reverting to ggplot 3.3.5
but this did not fix the issue. Moreover, two others tried the same code on their separate systems, both with ggplot 3.3.6
, but only one replicated my issue while the other produced the good version. Nevertheless, the code was certainly working fine until July, only after which I updated several packages and the code broke.
For the record, I have ensured that the issue is not with the data. So, although I have used data until June to illustrate the good version, that same dataset generates the bad maps when the code is run now (i.e., after the updates).
I am hoping someone with a better understanding of the package and the update will be able to figure out what exactly the breaking change was!
ggsave()
doesn't render custom fonts when saving (+workaround)
I also posted this as an issue in the ggplot2
repo; apologies for cross-posting, but wasn't sure which one was more appropriate, and felt a SO post might still be useful for future users with the same problem.
There are two files required for the reprex below to work:
library(lubridate)
library(tidyverse)
library(glue)
library(magick)
library(scales) # for comma format of numbers
library(grid)
# loading objects
load("reprex.RData")
map_cov_logo <- image_convert(image_read("bcilogo-framed.png"), matte = T)
map_cov_text <- glue::glue("{label_comma()(data_cov$LOCATIONS)} locations
{label_comma()(data_cov$LISTS)} lists
{label_comma()(data_cov$HOURS)} hours
{label_comma()(data_cov$PEOPLE)} people
{label_comma()(data_cov$STATES)} states/UTs
{label_comma()(data_cov$DISTRICTS)} districts
{label_comma()(data_cov$SPECIES)} species
{round(data_cov$OBSERVATIONS, 1)} million observations")
map_cov_footer <- glue::glue("Data until September 2022")
### map with annotations of stats and BCI logo ###
map_cov_annot <- ggplot() +
geom_polygon(data = indiamap, aes(x = long, y = lat, group = group),
colour = NA, fill = "black")+
geom_point(data = data_loc, aes(x = LONGITUDE, y = LATITUDE),
colour = "#fcfa53", size = 0.05, stroke = 0) +
# scale_x_continuous(expand = c(0,0)) +
# scale_y_continuous(expand = c(0,0)) +
theme_bw() +
theme(axis.line = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
# panel.border = element_blank(),
plot.background = element_rect(fill = "black", colour = NA),
panel.background = element_rect(fill = "black", colour = NA),
plot.title = element_text(hjust = 0.5)) +
coord_cartesian(clip = "off") +
theme(plot.margin = unit(c(2,2,0,23), "lines")) +
annotation_raster(map_cov_logo,
ymin = 4.5, ymax = 6.5,
xmin = 46.5, xmax = 53.1) +
annotation_custom(textGrob(label = map_cov_text,
hjust = 0,
gp = gpar(col = "#FCFA53", cex = 1.5)),
ymin = 19, ymax = 31,
xmin = 40, xmax = 53) +
annotation_custom(textGrob(label = map_cov_footer,
hjust = 0,
gp = gpar(col = "#D2D5DA", cex = 1.0)),
ymin = 15, ymax = 16,
xmin = 40, xmax = 53)
ggsave(map_cov_annot, file = "map_cov_annot.png", device = "png",
units = "in", width = 13, height = 9, bg = "transparent", dpi = 300)
### plain map without annotations ###
map_cov_plain <- ggplot() +
geom_polygon(data = indiamap, aes(x = long, y = lat, group = group),
colour = NA, fill = "black")+
geom_point(data = data_loc, aes(x = LONGITUDE, y = LATITUDE),
colour = "#fcfa53", size = 0.05, stroke = 0.1) +
# scale_x_continuous(expand = c(0,0)) +
# scale_y_continuous(expand = c(0,0)) +
theme_bw() +
theme(axis.line = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
plot.margin = unit(c(0, 0, 0, 0), "cm"),
# panel.border = element_blank(),
plot.background = element_rect(fill = "black", colour = NA),
panel.background = element_rect(fill = "black", colour = NA),
plot.title = element_text(hjust = 0.5)) +
coord_map()
ggsave(map_cov_plain, file = "map_cov_plain.png", device = "png",
units = "in", width = 8, height = 11, bg = "transparent", dpi = 300)
Upvotes: 3
Views: 350
Reputation: 66870
I think this comes down to taste, and you prefer the result of the rendering without anti-aliasing. It looks bolder and sharper because the cells that are partially activated are shown at full brightness, in contrast with fully black adjacent pixels. Mssr. Pederson argues that the anti-aliased version is in some respects a truer depiction of the underlying data, since it will perceptually give the dots more consistent weight and spacing that corresponds to their actual size and placement.
Two examples:
set.seed(42)
library(ggplot2)
df_rand <- data.frame(x = runif(1000), y = runif(1000))
ggplot(df_rand, aes(x,y)) +
geom_point(colour = "#fcfa53", size = 0.01, stroke = 0) +
theme_void() +
theme(plot.background = element_rect(fill = "black", colour = NA),
panel.background = element_rect(fill = "black", colour = NA))
ggsave("rand_no_anti-alias.png", device = png,
units = "px", width = 100, height = 100)
ggsave("rand_ragg_anti-alias.png", device = ragg::agg_png(),
units = "px", width = 100, height = 100)
Of these two, you prefer the former for your use case and particular settings.
df_grid <- data.frame(expand.grid(x = 1:30, y = 1:30))
ggplot(df_grid, aes(x,y)) +
geom_point(colour = "#fcfa53", size = 0.01, stroke = 0) +
theme_void() +
theme(plot.background = element_rect(fill = "black", colour = NA),
panel.background = element_rect(fill = "black", colour = NA))
ggsave("grid_no_anti-alias.png", device = png,
units = "px", width = 100, height = 100)
ggsave("grid_ragg_anti-alias.png", device = ragg::agg_png(),
units = "px", width = 100, height = 100)
Note here that the version without anti-aliasing creates phantom groupings out of a uniform grid. Its algorithm shows full intensity even when the point covers a tiny part of the pixel. The anti-aliased version reflects that the points cover a very small part of each pixel, and it attempts to even out the error by sometimes depicting a point in one pixel and sometimes using two adjacent pixels, but dimmer. While the uniform data creates some moire effect in this contrived example, in my view the perceptual distortion is smaller.
If I make the point size much larger (e.g. 0.3), it makes for a closer match in brightness with the non-anti-aliased version. This version avoids the phantom groupings that the non-anti-aliased version has, but at the cost of the pixels looking smudged at the pixel level. That's anti-aliasing for ya.
Technical arguments aside, use the rendering method that gives you the output you want.
Upvotes: 2