Reputation: 601
I have a code that makes a ggplot and scales the y-axis limits based on the range of the data. The Y-axis labels have a different number of digits, causing the plots to shift slightly to the right (the difference is slight, but present). This creates issues when viewing the plots together, because the x-axis across the plots are offset. I cannot facet these plots because the data for each plot originates from large, independent datasets that are analyzed sequentially.
Is there a way to create "white space" in the Y-axis label area, so that regardless of the number of digits displayed the x-axis will always occupy the same area within the figure?
How do I control the number of decimal places in my plot? I only want whole numbers as axis labels.
I saw a post that addresses #2, but the responses rely on using scale_y_continuous()
. I adjusted my code to use scale_y_continuous()
instead of ylim()
, but the decimal places persist (see Plotv2.1).
I have provided a simplified example, but I think it captures the issue. Please keep in mind that the plots must be exported independently so faceting or using ggarrange to align axes is not possible.
Thank you!
library(ggplot2)
library(grid)
df <- data.frame(
Position=(c(1:5000)),
Signal=(c((rep(c(-1),times=2000)), (rep(c(100),times=1000)), (rep(c(-1),times=2000))))
)
Plotv1 <- ggplot() +
geom_line(data = df, aes(x=Position, y=Signal, col = "#000000")) +
ylim(-ceiling(max(abs(df$Signal))),ceiling(max(abs(df$Signal)))) +
coord_cartesian(clip="off") +
guides(fill = "none") +
guides(color=guide_legend(title="MyLegend")) +
theme_bw() +
theme(axis.text.x = element_blank()) +
theme(
axis.title.x = element_text(margin=margin(t=30)),
legend.title = element_text(colour = "#000000", size=12),
legend.text = element_text(colour = "#000000", size=12)
) +
annotation_custom(
grid::segmentsGrob(
y0 = unit(-25, "pt"),
y1 = unit(-25, "pt"),
arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
),
xmin = 2000,
xmax = 3000
)
ggsave(paste("~/Desktop/Plotv1.png", sep = ""), Plotv1, width = 8, height = 1.7)
df2 <- data.frame(
Position=(c(1:5000)),
Signal=(c((rep(c(-1),times=2000)), (rep(c(4.9),times=1000)), (rep(c(-1),times=2000))))
)
Plotv2 <- ggplot() +
geom_line(data = df2, aes(x=Position, y=Signal, col = "#000000")) +
ylim(-ceiling(max(abs(df2$Signal))),ceiling(max(abs(df2$Signal)))) +
coord_cartesian(clip="off") +
guides(fill = "none") +
guides(color=guide_legend(title="MyLegend")) +
theme_bw() +
theme(axis.text.x = element_blank()) +
theme(
axis.title.x = element_text(margin=margin(t=30)),
legend.title = element_text(colour = "#000000", size=12),
legend.text = element_text(colour = "#000000", size=12)
) +
annotation_custom(
grid::segmentsGrob(
y0 = unit(-25, "pt"),
y1 = unit(-25, "pt"),
arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
),
xmin = 2000,
xmax = 3000
)
ggsave(paste("~/Desktop/Plotv2.png", sep = ""), Plotv2, width = 8, height = 1.7)
Plotv2.1 <- ggplot() +
geom_line(data = df2, aes(x=Position, y=Signal, col = "#000000")) +
scale_y_continuous(labels = function(x) format(x, nsmall = 0),
limits = c(-ceiling(max(abs(df2$Signal))),ceiling(max(abs(df2$Signal))))) +
coord_cartesian(clip="off") +
guides(fill = "none") +
guides(color=guide_legend(title="MyLegend")) +
theme_bw() +
theme(axis.text.x = element_blank()) +
theme(
axis.title.x = element_text(margin=margin(t=30)),
legend.title = element_text(colour = "#000000", size=12),
legend.text = element_text(colour = "#000000", size=12)
) +
annotation_custom(
grid::segmentsGrob(
y0 = unit(-25, "pt"),
y1 = unit(-25, "pt"),
arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
),
xmin = 2000,
xmax = 3000
)
ggsave(paste("~/Desktop/Plotv2.1.png", sep = ""), Plotv2.1, width = 8, height = 1.7)
Upvotes: 1
Views: 193
Reputation: 756
Can you use the approach in https://stackoverflow.com/a/69843619/4413615 to get this working in ggplot2? Use a formatting function on the axis labels through scale_y_continuous(labels = \(x) formatC(x, width = 6))
and change the font to fixed-width using theme(axis.text = element_text(family = "mono"))
. The width of 6 can be changed to the maximum width observed across your plots.
Full example on Plotv1 below
library(ggplot2)
library(grid)
df <- data.frame(
Position=(c(1:5000)),
Signal=(c((rep(c(-1),times=2000)), (rep(c(100),times=1000)), (rep(c(-1),times=2000))))
)
Plotv1 <- ggplot() +
geom_line(data = df, aes(x=Position, y=Signal, col = "#000000")) +
scale_y_continuous(labels = \(x) formatC(x, width = 6),
limits = c(-ceiling(max(abs(df$Signal))),ceiling(max(abs(df$Signal))))) +
coord_cartesian(clip="off") +
guides(fill = "none") +
guides(color=guide_legend(title="MyLegend")) +
theme_bw() +
theme(axis.text.x = element_blank()) +
theme(
axis.title.x = element_text(margin=margin(t=30)),
axis.text = element_text(family = "mono"),
legend.title = element_text(colour = "#000000", size=12),
legend.text = element_text(colour = "#000000", size=12)
) +
annotation_custom(
grid::segmentsGrob(
y0 = unit(-25, "pt"),
y1 = unit(-25, "pt"),
arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
),
xmin = 2000,
xmax = 3000
)
Upvotes: 1
Reputation: 73712
If you want to consider using base graphics, their labels and axis positions might be more consistent, even with decimal places. We will use normalized plot coordinates (npc) in grconvertY
to get consistent arrow and legend positions. Assuming your data is in a list "dfl" you could loop over, and the legend texts are in another list "leg", you could proceed as follows.
for (i in seq_along(dfl)) {
png(sprintf('plot%s.png', i), 800, 200) ## open png device
par(mar=c(4, 4, 1, 9) + .1) ## set margins
plot(dfl[[i]], type='l', col='red', xaxt='n', xlab='Position', ylab='Signal',
panel.first=grid(), las=1, ylim=max(dfl[[i]]$Signal)*c(-1, 1))
axis(1, axTicks(1), labels=FALSE)
y <- grconvertY(c(-.25, .6), from='npc', to='user') ## get y for arrow and leg
arrows(x0=2000, y0=y[1], x1=3000, y1=y[1], lwd=2, xpd=TRUE, length=.05)
legend(par()$usr[2]*1.03, y[2], title=leg[[i]][1], leg=leg[[i]][2], lty=1,
col='red', xpd=TRUE, bty='n')
dev.off() ## close png, which saves plot
}
You could also consider using pdf()
instead of png()
to get vector graphics.
Data:
n <- 5e3
dfl <- lapply(c(100, 5, 1), \(s) data.frame(
Position=seq_len(n),
Signal=replace(rep_len(-1, n), 2001:3e3, s)
))
leg <- list(c("MyLegend 1", "#000000 1"), c("MyLegend 2", "#000000 2"),
c("MyLegend 3", "#000000 3"))
Upvotes: 1
Reputation: 79266
theme(axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)))
we can tweak the position on the right side of the plot by tweaking r = 10 etc..scale_y_continuous(labels = label_number(accuracy = 1))
from scales
package we can control thy y axis numbers.I use ggarrange
to demonstrate how the plots look when plotted in one column. If you change r
in element_text
the alignment will change:
library(ggpubr)
library(scales)
Plotv1 <- Plotv1 +
theme(axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)))+
#scale_y_continuous(labels = label_number(accuracy = 0.01))
scale_y_continuous(labels = label_number(accuracy = 1))
Plotv2 <- Plotv2 +
theme(axis.title.y = element_text(margin = margin(t = 0, r = 16, b = 0, l = 0)))+
#scale_y_continuous(labels = label_number(accuracy = 0.01))
scale_y_continuous(labels = label_number(accuracy = 1))
Plotv2.1 <- Plotv2.1 +
theme(axis.title.y = element_text(margin = margin(t = 0, r = 16, b = 0, l = 0)))+
#scale_y_continuous(labels = label_number(accuracy = 0.01))
scale_y_continuous(labels = label_number(accuracy = 1))
ggarrange(Plotv1, Plotv2, Plotv2.1, ncol = 1)
Upvotes: 1