Подтвердить что ты не робот

Отделить сегмент от угла, несмотря на изменение соотношения сторон

Я пытаюсь построить таблицу в R, с именами столбцов, которые находятся под углом относительно таблицы. Я хотел бы добавить строки для разделения этих имен столбцов под тем же углом, что и текст. Однако оказывается, что угол, указанный в функции text(), не зависит от соотношения сторон графика, тогда как угол, который я использую в функции segments(), зависит от соотношения сторон графика.

Вот пример того, что я имею в виду:

nRows <- 5
nColumns <- 3
theta <- 30

rowLabels <- paste('row', 1:5, sep='')
colLabels <- paste('col', 1:3, sep='')

plot.new()
par(mar=c(1,8,5,1), xpd=NA)
plot.window(xlim = c(0, nColumns), ylim = c(0, nRows), asp = 1)
text(labels = rowLabels, x=0, y=seq(from=0.5, to=nRows, by=1), pos=2)
text(labels = colLabels, x = seq(from = 0.4, to = nColumns, by = 1), y = nRows + 0.1, pos = 4, srt = theta, cex = 1.1)
segments(x0 = c(0:nColumns), x1 = c(0:nColumns), y0 = 0, y1 = nRows, lwd = 0.5)
segments(x0 = 0, x1 = nColumns, y0 = 0:nRows, y1 = 0:nRows, lwd = 0.5)

#column name separators, angle converted to radians
segments(x0 = 0:(nColumns - 1), x1 = 1:nColumns, y0 = nRows, y1 = nRows + tan(theta * pi/180), lwd = 0.5)

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

Однако, если я хочу изменить размер окна этого графика по своему вкусу, не указав asp, углы больше не будут соответствовать:

nRows <- 5
nColumns <- 3
theta <- 30

rowLabels <- paste('row', 1:5, sep='')
colLabels <- paste('col', 1:3, sep='')

plot.new()
par(mar=c(1,8,5,1), xpd=NA)
plot.window(xlim = c(0, nColumns), ylim = c(0, nRows))
text(labels = rowLabels, x=0, y=seq(from=0.5, to=nRows, by=1), pos=2)
text(labels = colLabels, x = seq(from = 0.4, to = nColumns, by = 1), y = nRows + 0.1, pos = 4, srt = theta, cex = 1.1)
segments(x0 = c(0:nColumns), x1 = c(0:nColumns), y0 = 0, y1 = nRows, lwd = 0.5)
segments(x0 = 0, x1 = nColumns, y0 = 0:nRows, y1 = 0:nRows, lwd = 0.5)

#column name separators, angle converted to radians
segments(x0 = 0:(nColumns - 1), x1 = 1:nColumns, y0 = nRows, y1 = nRows + tan(theta * pi/180), lwd = 0.5)

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

Есть ли способ указать заданный угол, чтобы фигура выглядела правильно, когда я изменяю размер окна?

4b9b3361

Ответ 1

Значение theta 30 градусов дуги - это угол пространства данных. Он подходит только для использования в вычислениях в пространстве данных, например, при вызове segments(), который рисует диагональные линии.

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

Связь между данными и пространствами устройств определяется динамически и зависит от ряда факторов:

  • Размеры устройства (размер клиентской области окна GUI или размер целевого файла).
  • Множество рисунков (при использовании многострочного графика, см. графические параметры mfrow и mfcol).
  • Любые внутренние и внешние поля (большинство графиков имеют внутренние поля, внешний - редко).
  • Любой внутренний интервал (см. графические параметры xaxs и yaxs).
  • Диапазон графика (xlim и ylim).

Правильный способ сделать то, что вы хотите, - (1) динамически запрашивать соотношение сторон пространства данных, измеренное в единицах расстояния между устройствами и (2) использовать его для преобразования theta из угла пространства данных к углу пространства устройства.

1: запрос для соотношения сторон

Мы можем рассчитать соотношение сторон, найдя эквивалент пространства-устройства 1 единицы пространства данных вдоль оси x, сделайте то же самое для оси y, а затем возьмите коэффициент y/x. Для этой цели выполняются функции grconvertX() и grconvertY().

calcAspectRatio <- function() abs(diff(grconvertY(0:1,'user','device'))/diff(grconvertX(0:1,'user','device')));

Функции преобразования работают на отдельных координатах, а не на расстояниях. Но они векторизованы, поэтому мы можем передать 0:1, чтобы преобразовать две координаты, разделенные на единицу во входной системе координат, и затем взять diff(), чтобы получить эквивалентное расстояние в исходной системе координат.

Вам может быть интересно, почему нужен вызов abs(). Для многих графических устройств ось y увеличивается вниз, а не вверх, поэтому меньшие координаты пространства данных преобразуются в большие координаты пространства устройства. Таким образом, результат первого вызова diff() в этих случаях будет отрицательным. Теоретически это никогда не должно происходить с осью X, но мы можем в любом случае обернуть весь фактор в вызове abs().

2: преобразование theta из пространства данных в пространство устройства

Существует несколько математических подходов, которые могут быть приняты здесь, но я считаю, что самым простым является принятие угла t219 для получения тригонометрического отношения y/x, умножьте его на соотношение сторон, а затем преобразуйте обратно в угол с помощью atan2().

dataAngleToDevice <- function(rad,asp) {
    rad <- rad%%(pi*2); ## normalize to [0,360) to make following ops easier
    y <- abs(tan(rad))*ifelse(rad<=pi,1,-1)*asp; ## derive y/x trig ratio with proper sign for y and scale by asp
    x <- ifelse(rad<=pi/2 | rad>=pi*3/2,1,-1); ## derive x component with proper sign
    atan2(y,x)%%(pi*2); ## use atan2() to derive result angle in (-180,180], and normalize to [0,360)
}; ## end dataAngleToDevice()

Вкратце, я считаю, что это очень интересная математическая трансформация. Углы 0, 90, 180 и 270 не влияют, что имеет смысл; изменение соотношения сторон не должно влиять на эти углы. Вертикальное удлинение тянет углы к оси y, а горизонтальное удлинение тянет углы к оси x. По крайней мере, как я это визуализирую.


Итак, поставив все это вместе, у нас есть решение ниже. Обратите внимание, что я переписал ваш код для более кратких и сделал несколько незначительных изменений, но в основном это то же самое. Очевидно, самое важное изменение заключается в том, что я добавил вызов dataAngleToDevice() вокруг theta, а второй аргумент прошел calcAspectRatio(). Кроме того, я использовал более мелкие (шрифтовые), но более длинные (строковые) имена столбцов, чтобы более четко продемонстрировать угол текста, я переместил текст ближе к диагональным линиям, я сохранил theta в радианах с самого начала, и я переупорядочил вещи a бит.

nRows <- 5;
nColumns <- 3;
theta <- 30*pi/180;

rowLabels <- paste0('row',1:5);
colLabels <- do.call(paste,rep(list(paste0('col',1:3)),5L));

plot.new();
par(mar=c(1,8,5,1),xpd=NA);
plot.window(xlim=c(0,nColumns),ylim=c(0,nRows));
segments(0:nColumns,0,0:nColumns,nRows,lwd=0.5);
segments(0,0:nRows,nColumns,0:nRows,lwd=0.5);
text(0,seq(0.5,nRows,1),rowLabels,pos=2);
## column name separators
segments(0:(nColumns-1),nRows,1:nColumns,nRows+tan(theta),lwd=0.5);
text(seq(0.3,nColumns,1),nRows+0.1,colLabels,pos=4,srt=dataAngleToDevice(theta,calcAspectRatio())*180/pi);

Здесь демо с примерно квадратным соотношением сторон:

грубо-квадратный

Wide:

wide

И высокий:

tall


Я сделал график преобразования:

xlim <- ylim <- c(0,360);
xticks <- yticks <- seq(0,360,30);
plot(NA,xlim=xlim,ylim=ylim,xlab='data',ylab='device',axes=F);
box();
axis(1L,xticks);
axis(2L,yticks);
abline(v=xticks,col='grey');
abline(h=yticks,col='grey');
lineParam <- data.frame(asp=c(1/1,1/2,2/1,1/4,4/1),col=c('black','darkred','darkblue','red','blue'),stringsAsFactors=F);
for (i in seq_len(nrow(lineParam))) {
    x <- 0:359;
    y <- dataAngleToDevice(x*pi/180,lineParam$asp[i])*180/pi;
    lines(x,y,col=lineParam$col[i]);
};
with(lineParam[order(lineParam$asp),],
    legend(310,70,asp,col,title=expression(bold(aspect)),title.adj=c(NA,0.5),cex=0.8)
);

data-angle-to-device