2016-01-18 153 views
20

我正在嘗試在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) 

enter image description here

不過,如果我希望能夠調整該地塊窗口合我的胃口,而無需指定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) 

enter image description here

有沒有方法可以指定一個設定的角度,以便在調整窗口大小時圖形看起來正確?

+5

可能更容易實現電網,如果這是一個選項 – baptiste

+3

我不認爲這是可能的(我可能是錯的雖然)因爲'asp'的工作方式。從'情節。窗口幫助文件:「如果asp是一個有限的正值,那麼窗口被設置爲x方向上的一個數據單元的長度與y方向上的asp *一個數據單元相等。」現在,如果你不指定asp,'y1 = nRows + tan(theta * pi/180)'中斷,因爲弧度取決於通過窗口調整大小縮放的半徑,而與角度無關(保持不變)。所以,我沒有在R基礎上找到一個解決方案,但是,我想再次證明,否則! – Felix

+0

當您製作標題行時,選擇一個'asp',並用'x1 = 1:nColumns * asp'對它縮放'theta' – rawr

回答

2

30弧度的theta值是一個數據空間角度。它只適用於數據空間計算,例如在撥打對角線的segments()的調用中。

srt圖形參數指定設備空間中的文本旋轉,這意味着文本將呈現爲在物理設備上遵循指定的角度,而與底層繪圖區域的縱橫比無關。

的數據和設備空間之間的關係被動態地確定,並且由若干因素的影響:

  • 的器件尺寸(GUI窗口客戶區大小或目標文件大小)。
  • 圖形多重性(如果使用多圖形;請參閱mfrowmfcol圖形參數)。
  • 任何內部和外部邊緣(大多數情節有內邊距,外部很少見)。
  • 任何內部間距(請參閱xaxsyaxs圖形參數)。
  • 繪圖範圍(xlimylim)。

做你想要什麼正確的方法是:(1)動態地查詢該數據空間的縱橫比在設備空間距離單位測量,(2)使用它來從數據空間變換theta與設備空間角度成一個角度。

1:寬高比

我們可以通過沿x軸測距裝置空間等效1數據空間單元的計算的縱橫比,對於y軸做同樣的查詢,然後取比率y/x。功能grconvertX() and grconvertY()是爲此目的而設計的。

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

轉換函數對單個座標而不是距離進行操作。但是它們是矢量化的,所以我們可以通過0:1來轉換輸入座標系中相隔1個單位的兩個座標,然後用diff()來獲得輸出座標系中的等效單位距離。

您可能想知道爲什麼abs()調用是必要的。對於許多圖形設備,y軸向下而不是向上增加,所以較小的數據空間座標將轉換爲更大的設備空間座標。因此,在這些情況下,第一個diff()調用的結果將是負面的。理論上這不應該發生在x軸上,但我們也可以在abs()調用中包裝整個商以防萬一。

2:從數據空間變換THETA到設備空間

有可能在這裏採取了一些數學方法,但我認爲最簡單的就是把角的tan()得到的三角將其與縱橫比相乘,然後使用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軸的角度。至少我是這樣想象的。


所以,把這一切放在一起,我們有下面的解決方案。請注意,爲了更加簡潔,我重寫了代碼並做了一些小修改,但大部分情況都是一樣的。顯然最重要的變化是我在theta附近加了一個dataAngleToDevice()的電話,第二個參數通過了calcAspectRatio()。此外,我使用較小的(fontwise)但較長的(字符串)列名來更清晰地顯示文本的角度,我將文本移近對角線,我從頭開始以弧度存儲theta,並且我重新排列了一些東西。

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); 

這裏是一個近似正方形的長寬比演示:

roughly-square

寬:

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

+2

非常感謝這樣一個完整的解決方案! – Pascal