Горизонтальное выравнивание больших меток смещается при использовании аргумента adj в тексте.

В text аргумент adj позволяет настроить labels по отношению к x и y. Например, adj значений (0, 1) означают выравнивание по левому верхнему краю, т. е. левый верхний угол метки помещается в заданную координату x, y.

Это прекрасно работает с расширением символов по умолчанию cex = 1. Но когда мне нужна метка большего размера, созданная путем увеличения cex, позиция метки смещается по горизонтали от заданной координаты и adjustment.

Вот небольшой пример, демонстрирующий это:

# a basic plot
plot(1:10, asp = 1, cex = 1)

# a red reference mark at 5, 5
points(x = 5, y = 5, pch = 3, cex = 3, col = 'red')

# a label of default size (cex = 1), left top adjusted
text(x = 5, y = 5, labels = 'H', cex = 1, adj = c(0, 1))

# a large label (cex = 8) with same position and adjustment as above becomes offset horizontally
text(x = 5, y = 5, labels = 'H', cex = 8, adj = c(0, 1), col = rgb(0.1, 0.9, 0.1, 0.5))

введите здесь описание изображения

Горизонтальное смещение происходит для всех комбинаций выравнивания по левому/правому нижнему/верхнему краю:

plot(1:10, cex = 1)
points(x = 5, y = 5, pch = 3, lwd = 4, cex = 4, col = 'red')

text(x = 5, y = 5, labels = "H", cex = 1, adj = c(0, 0))
text(x = 5, y = 5, labels = "H", cex = 1, adj = c(0, 1))
text(x = 5, y = 5, labels = "H", cex = 1, adj = c(1, 0))
text(x = 5, y = 5, labels = "H", cex = 1, adj = c(1, 1))

text(x = 5, y = 5, labels = "H", cex = 8, adj = c(0, 0), col = "green")
text(x = 5, y = 5, labels = "H", cex = 8, adj = c(0, 1), col = "green")
text(x = 5, y = 5, labels = "H", cex = 8, adj = c(1, 0), col = "green")
text(x = 5, y = 5, labels = "H", cex = 8, adj = c(1, 1), col = "green")

введите здесь описание изображения

Как избежать горизонтального смещения меток, когда cex > 1?


person yalei du    schedule 10.05.2015    source источник
comment
Вы тестировали это с другими шрифтами/буквами? Возможно, что дополнительный пробел также является частью символа H в этом шрифте.   -  person Max Candocia    schedule 10.05.2015
comment
Если дополнительный пробел также является частью H, пробел также должен существовать, когда cex = 1.   -  person yalei du    schedule 11.05.2015
comment
Между буквами H есть крошечный промежуток, когда cex=1. С моего экрана это выглядит как два пикселя. Также похоже, что их примерно в 8 раз больше для больших (когда cex = 8). Вы хотели удалить это смещение только для символов H или вообще?   -  person Max Candocia    schedule 11.05.2015
comment
Конечно в общем. Существование пространства — хорошее объяснение, но я едва вижу его, когда cex=1. У вас есть идеи, как убрать эти пробелы?   -  person yalei du    schedule 11.05.2015
comment
Спасибо, Хенрик. Спасибо за эти блоги. Кажется, я должен остаться с пробелами.   -  person yalei du    schedule 12.05.2015


Ответы (2)


Проблема может быть немного сложной для решения. Сначала попытка объяснить, почему, а затем потенциальное решение.

Как написано членом правления R Брайаном Рипли в списке рассылки помощи R здесь:

«Текстовые строки в графике R отображаются непосредственно указанным шрифтом, а не отдельными буквами».

Буквы (или цифры, знаки препинания и фигуры) в любом шрифте представлены глифами. У каждого глифа есть горизонтальные пробелы с обеих сторон, так называемые левый и правый подшипники. См., например. здесь, здесь ("показатели глифов")< /a> и здесь.

введите здесь описание изображения

Именно боковые подшипники вызывают смещение на вашем графике, хотя и очень маленькое при использовании cex = 1. Когда вы увеличиваете размер «глифа» на своем графике (используя cex), увеличивается не только сам символ, но и абсолютная ширина подшипников размера.

И Рипли заключает:
"поэтому вы ничего не можете сделать с межбуквенным интервалом в R."

Эти вопросы и ответы показывают хак для уменьшения пробела между буквами. Однако снять передний левый подшипник может быть сложнее.


Потенциальное решение может состоять в том, чтобы использовать systemfonts::shape_string для захвата левого подшипника, а затем соответствующим образом отрегулировать позиции x.

Вот пример некоторых строк с координатами. Строки отображаются (с «большим» cex) с использованием исходных значений x (светло-серый) и значений x за вычетом пеленга (темно-серый).

d <- data.frame(x = 1:3, y = 1:3, labs = c("Where", "is", "Here"))

# set pointsize, cex and resolution
ps <- 12
cex <- 8
res <- 72

# calculate left bearing in pixels
left_bear_px <- shape_string(d$labs, size = ps * cex)$metrics$left_bearing

# open device
png("pp.png", width = 10, height = 5, units = "in", res = res)

# plot with "cross hair"
plot(x = d$x, y = d$y, pch = 3, cex = 3, col = "red", xlim = c(0, 5), ylim = c(0, 3))

# convert unit of bearing from pixel to xy: multiply by xy / pixel ratio
left_bear_xy <- left_bear_px * ((par("cxy") / par("cra"))[1])

# add text at original positions (light grey)
text(x = d$x, y = d$y, labels = d$labs,
     cex = cex, adj = c(0, 1), col = grey(0.6, alpha = 0.5))

# x values with left bearing removed (dark grey)
text(x = d$x - left_bear_xy, y = d$y, labels = d$labs,
     cex = cex, adj = c(0, 1), col = grey(0.1, alpha = 0.5))    

dev.off()

введите здесь описание изображения

person Henrik    schedule 13.05.2015
comment
Приятно видеть другое решение, предлагаемое здесь @Henrik. Это выглядит проще, чем у меня, и с аналогичными результатами. Я лучше поменяю интро прямо сейчас! - person Allan Cameron; 12.05.2020

Существующему ответу на этот вопрос пять лет, и он полезен и информативен. Недавнее дополнение к нему указывает на хорошее решение, и я думаю, что это лучший ответ.

Тем не менее, мой ответ здесь состоит в том, чтобы показать, что все это время существовало и базовое решение проблемы R. Мы можем видеть разрыв в графическом выводе, поэтому все, что нам нужно сделать, это придумать способ, чтобы R тоже его увидел.

Допустим, мы делаем график без полей и с черным фоном и помещаем на него красивую большую букву H в точке x = 5 белого цвета. Затем мы проводим тонкую вертикальную линию в точке x = 5.

Мы создадим его в формате png, указав ширину 10 дюймов и установив разрешение 100 пикселей на дюйм. После его создания мы можем получить все его пиксели в виде массива:

mar <- par("mar")
par(mar = c(0, 0, 0, 0))

ppi <- 100

tmpfile <- tempfile(fileext = ".png")
png(tmpfile, width = 10, height = 5, units = "in", res = ppi, bg = "black")
plot(1:10, asp = 1, cex = 1)
text(x = 5, y = 10, labels = "H", adj = c(0, 1), cex = 30, col = "white")
lines(c(5,5), c(-1,20), col = "white", cex = 1)
dev.off()
mat <- png::readPNG(tmpfile)
plot(raster::as.raster(mat))

Теперь массив у нас одноцветный, то есть фактически представляет собой три одинаковые сложенные матрицы по 500 строк на 1000 столбцов. Мы можем взять один его слой, который будет представлять белые (1) или черные (0) пиксели, каждый шириной 1/100 дюйма.

Поэтому, если мы используем apply для получения максимального значения этих столбцов, мы получаем вектор из 1000 элементов из 0 и 1, который фактически является одномерной проекцией этого изображения. Первые 499 или 500 значений будут равны 0 для черной половины изображения, затем будет одно значение 1, представляющее белую линию, затем нули там, где левый пробел находится перед нашей буквой, а затем ряд 1 с для само письмо. Если мы посчитаем черные пиксели между линией и буквой, то получим размер зазора.

space_in_pixels <- diff(which(apply(mat, 2, max) > 0.9))[1]
space_in_pixels
#> [1] 40

Поскольку мы можем использовать strwidth для получения общей ширины глифа, мы можем определить, какая часть глифа занимает пробел слева:

space_in_inches <- space_in_pixels / ppi
leading_proportion <- space_in_inches/strwidth("H", cex = 30, units = "inches")
leading_proportion
#> [1] 0.1106628

Если подумать, это именно то число, которое вам нужно передать adj, чтобы сдвинуть букву так, чтобы ее крайний левый пиксель совпал с заданным значением x в вызове text.

Мы можем повторить это для всех прописных и строчных букв (это займет всего несколько секунд) и сохранить все значения в именованном списке.

sizes <- numeric(52)
l <- c(LETTERS, letters)

for(i in 1:52)
{
  tmpfile <- tempfile(fileext = ".png")
  png(tmpfile, width = 10, height = 5, units = "in", res = ppi, bg = "black")
  plot(1:10, asp = 1, cex = 1)
  text(x = 5, y = 10, labels = l[i], adj = c(0, 1), cex = 30, col = "white")
  lines(c(5,5), c(-1,20), col = "white", cex = 1)
  dev.off()
  mat <- png::readPNG(tmpfile)
  space_size <- diff(which(apply(mat, 2, max) > 0.9))[1]/ppi
  sizes[i] <- space_size/strwidth(l[i], cex = 30, units = "inches")
}

leading <- setNames(as.list(sizes), c(LETTERS, letters))

Чтобы упростить использование, мы определяем функцию, которую мы можем передать adj любой заданной строке, которая сдвинет строку влево на правильную величину:

Adj <- function(x, y = 0) 
{
  first_char <- substr(x, 1, 1)
  c(leading[[first_char]] * strwidth(first_char)/strwidth(x), 1)
}

Итак, теперь давайте попробуем:

par(mar = mar)
string <- "H"

plot(1:10, asp = 1, cex = 1)
points(x = 5, y = 5, pch = 3, cex = 3, col = 'red')
text(5, 5, labels = string, cex = 8, adj = Adj(string), col = rgb(0.1, 0.9, 0.1, 0.5))

и с многосимвольной строкой:

string <- "Xerxes"

plot(1:10, asp = 1, cex = 1)
points(x = 5, y = 5, pch = 3, cex = 3, col = 'red')
text(5, 5, labels = string, cex = 8, adj = Adj(string), col = rgb(0.1, 0.9, 0.1, 0.5))

...и строка в нижнем регистре:

string <- "lol"

plot(1:10, asp = 1, cex = 1)
points(x = 5, y = 5, pch = 3, cex = 3, col = 'red')
text(5, 5, labels = string, cex = 8, adj = Adj(string), col = rgb(0.1, 0.9, 0.1, 0.5))

Обратите внимание, что этот результат не совсем эквивалентен типографскому левому азимуту. В своей текущей форме он применяется только к шрифту без шрифта по умолчанию, но может быть рассчитан для любого шрифта, который вы хотите использовать.

Вы также можете использовать эту технику, чтобы получить пространство справа от глифа. Теоретически вы можете использовать эти методы для управления расстоянием между символами в сюжете. Это может сделать полезный небольшой пакет.

Создано 08 мая 2020 г. с помощью пакета reprex (v0.3.0)

person Allan Cameron    schedule 08.05.2020
comment
Еще один отличный и подробный ответ, спасибо. Я рассматриваю возможность открытия еще одного вознаграждения за это. - person MichaelChirico; 13.05.2020