Vraag teken het segment uit de hoek, ondanks de veranderende beeldverhouding


Ik probeer een tabel in R te plotten, met kolomnamen die een hoek ten opzichte van de tabel hebben. Ik wil regels toevoegen om deze kolomnamen te scheiden, onder dezelfde hoek als de tekst. Het lijkt er echter op dat de hoek gespecificeerd in de text() functie is onafhankelijk van de beeldverhouding van de plot, terwijl de hoek die ik gebruik in de segments() functie is afhankelijk van de beeldverhouding van de plot.

Hier is een voorbeeld van wat ik bedoel:

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

Als ik echter het formaat van dit plotvenster naar wens wil wijzigen zonder op te geven asp, de hoeken komen niet meer overeen:

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

Is er een manier om een ​​ingestelde hoek op te geven, zodat de figuur er goed uitziet als ik het formaat van het venster wijzig?


20
2018-01-18 19:24


oorsprong


antwoorden:


De theta waarde van 30 booggraden is een hoek van de gegevensruimte. Het is alleen geschikt voor gebruik in gegevensruimteberekeningen zoals in uw oproep naar segments() die de diagonale lijnen tekent.

De srt grafische parameter specificeert tekstrotatie in apparaatruimte, wat betekent dat de tekst wordt weergegeven om de opgegeven hoek op het fysieke apparaat te volgen, ongeacht de beeldverhouding van het onderliggende plotgebied.

De relatie tussen de gegevens- en apparaatruimten wordt dynamisch bepaald en wordt beïnvloed door een aantal factoren:

  • De apparaatafmetingen (GUI-venster clientoppervlakgrootte of doelbestandsgrootte).
  • Figuurveelvoud (als u een plot met meerdere cijfers gebruikt, zie de mfrow en mfcol grafische parameters).
  • Alle binnen- en buitenmarges (de meeste plots hebben binnenmarges, outer is zeldzaam).
  • Elke interne afstand (zie de xaxs en yaxs grafische parameters).
  • Het plotbereik (xlim en ylim).

De juiste manier om te doen wat u wilt is om (1) dynamisch te zoeken naar de verhouding tussen de gegevensruimte en de ruimte-afstand, zoals gemeten in apparaat-afstandsafstandseenheden en (2) om te transformeren theta van een data-hoek tot een ruimte-hoek.

1: query voor beeldverhouding

We kunnen de beeldverhouding berekenen door het apparaatruimtelequivalent van 1 gegevensruimteapparaat langs de x-as te vinden, hetzelfde te doen voor de y-as en dan de verhouding te nemen y/x. De functies grconvertX() en grconvertY() zijn gemaakt voor dit doel.

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

De conversiefuncties werken op individuele coördinaten, niet op afstanden. Maar ze zijn gevectoriseerd, dus we kunnen slagen 0:1 om twee coördinaten die 1 eenheid van elkaar zijn in het invoercoördinatensysteem om te zetten en neem vervolgens een diff() om de equivalente eenheidafstand in het uitvoercoördinatensysteem te krijgen.

U vraagt ​​zich misschien af ​​waarom het abs() bellen was noodzakelijk. Voor veel grafische apparaten stijgt de y-as naar beneden in plaats van naar boven, dus kleinere coördinaten van de gegevensruimte worden geconverteerd naar grotere apparaat-spatiecoördinaten. Dus het resultaat van de eerste diff() oproep in deze gevallen zal negatief zijn. In theorie zou dit nooit met de x-as moeten gebeuren, maar we kunnen net zo goed het hele quotiënt in de abs() bel gewoon voor het geval dat.

2: Theta transformeren van dataruimte naar device-space

Er zijn verschillende wiskundige benaderingen die hier kunnen worden genomen, maar ik denk dat de eenvoudigste is om de tan()van de hoek om de trigonometrische te krijgen y/x verhouding, vermenigvuldig dit met de beeldverhouding en converteer vervolgens terug naar een hoek met 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()

Even terzijde vind ik dit een zeer interessante wiskundige transformatie. De hoeken 0, 90, 180 en 270 worden niet beïnvloed, wat logisch is; een verandering in de beeldverhouding mag die hoeken niet beïnvloeden. Een verticale verlenging trekt hoeken naar de y-as toe, en een horizontale verlenging trekt hoeken naar de x-as. Dat is tenminste hoe ik het visualiseer.


Dus, dit alles bij elkaar brengend, hebben we de onderstaande oplossing. Merk op dat ik je code herschreef voor meer concessies en een paar kleine wijzigingen aanbracht, maar meestal is het hetzelfde. De belangrijkste verandering is natuurlijk dat ik er een oproep aan heb toegevoegd dataAngleToDevice() in de omgeving van theta, met het tweede argument voorbij calcAspectRatio(). Daarnaast heb ik kleinere (lettertype) maar langere (stringwise) kolomnamen gebruikt om de hoek van de tekst duidelijker te tonen, ik heb de tekst dichter bij de diagonale lijnen geplaatst, ik heb deze opgeslagen theta in radialen vanaf het begin, en ik herschikte de dingen een beetje.

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

Hier is een demo met een ongeveer vierkante beeldverhouding:

roughly-square

Breed:

wide

En lang:

tall


Ik maakte een plot van de transformatie:

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
2018-03-22 14:21