Suggestion for improvement: thoughts about scale_size

28 views
Skip to first unread message

JiHO

unread,
May 20, 2009, 7:34:25 PM5/20/09
to ggplot2
Hello everyone,

Currently scale_size provides scales that are absolute: not relative
to the size of the viewport or the x/y scales of the plot. Therefore,
rescaling the plotting device changes the aspect of the plot.

Example:

x = y = 1:10
d = expand.grid(x=x,y=y)
set.seed(1)
d$value = runif(100)
p = qplot(x,y,size=value, data=d) + scale_size(to=c(0,15))
pdf("foo1.pdf", width=6,height=6)
print(p)
dev.off()
pdf("foo2.pdf", width=4,height=4)
print(p)
dev.off()
system("open foo1.pdf foo2.pdf")
# In the second case, the dots are much to big and overlap.


This means that once the plot looks good on screen, there is still a
lot of trial and error left to have it look OK on print, because the
size is likely to be different. And this has to be repeated for each
new size the plot has to take.
It would be more useful to have a measure of scale that would be:
- either in the same unit as the coordinates of the plot, which would
make selecting the correct size much easier
- or, if the above is not possible, at least proportional to the size
of the device, so that rescaling the device also rescales the dots,
instead of just replotting them at the same size.

What do other users think about that? Do you see an advantage to the
current behaviour? How difficult would it be to change that Hadley?

Thanks in advance for your opinion. Sincerely,

JiHO
---
http://jo.irisson.free.fr/

baptiste auguie

unread,
May 21, 2009, 4:43:43 AM5/21/09
to JiHO, ggplot2
I recently had a case where I needed to overlap a disc of accurate
diameter in units of the data, so yes, I think it is a good idea.

I thought of the possibility for the user to choose the system of
units as an option in the geoms I created recently, but I wasn't sure
where this specification would fit in the current paradigm : it's not
an aesthetic, nor a scale, and creating duplicates of each geom with
simply a different unit seems far too redundant to be good.


baptiste
--

_____________________________

Baptiste Auguié

School of Physics
University of Exeter
Stocker Road,
Exeter, Devon,
EX4 4QL, UK

Phone: +44 1392 264187

http://newton.ex.ac.uk/research/emag
______________________________

JiHO

unread,
May 21, 2009, 11:01:45 AM5/21/09
to baptiste auguie, ggplot2
On 2009-May-21 , at 04:43 , baptiste auguie wrote:

> I recently had a case where I needed to overlap a disc of accurate
> diameter in units of the data, so yes, I think it is a good idea.
>
> I thought of the possibility for the user to choose the system of
> units as an option in the geoms I created recently, but I wasn't sure
> where this specification would fit in the current paradigm : it's not
> an aesthetic, nor a scale, and creating duplicates of each geom with
> simply a different unit seems far too redundant to be good.

Currently the unit problem is really restricted to scale_size so,
assuming it is possible, a "unit" argument to scale size that would
then be passed to the unit() function of grid would seem appropriate.
Then it could default to "native", or at least "npc", so that it is
proportional to the scale of the viewport.

JiHO
---
http://jo.irisson.free.fr/


hadley wickham

unread,
May 21, 2009, 2:56:01 PM5/21/09
to JiHO, baptiste auguie, ggplot2

The main problem is that grid doesn't have any drawing primitives that
allow you to specify size in native units. The only way I can see to
get it to work would be to use polygons to reproduce (e.g.) points and
lines. This would be a lot slower than the usual drawing, and I think
would be most appropriate as new geoms. I suspect points and lines
would cover 95% of all use cases.

Hadley

--
http://had.co.nz/

baptiste auguie

unread,
May 21, 2009, 3:16:43 PM5/21/09
to ggplot2
I tested this idea today with the geom_ellipse grob that I created
last week using polygons. I got the grob to scale with the data (npc),
but the problem for me resides in the legend: the npc value makes
little sense there since it's a different viewport (output attached).

GeomEllipse <- proto(Geom, {
objname <- "ellipse"
desc <- "Ellipses"

draw <- function(., data, scales, coordinates, units.size="mm",
units.pos="npc", ...) {
with(coordinates$munch(data, scales),
ggname(.$my_name(),
ellipseGrob(x, y, size, angle, ar, col=alpha(colour, alpha), fill =
alpha(fill, alpha),
units.size=units.size, units.pos=units.pos))
)
}

library(ggplotpp)
dsmall <- diamonds[sample(nrow(diamonds), 100), ]
str(dsmall)
d <- ggplot(dsmall, aes(carat, price)) + theme_minimal()

d + geom_ellipse(aes(colour = carat, angle = cut, ar=y, fill=carat),
size=2) # default is mm for the plot
d + geom_ellipse(aes(colour = carat, angle = cut, ar=y, fill=carat),
size= 0.01, units.size="npc")

baptiste

--

testDefault.pdf
testMm.pdf

JiHO

unread,
May 21, 2009, 6:01:04 PM5/21/09
to hadley wickham, baptiste auguie, ggplot2
On 2009-May-21 , at 14:56 , hadley wickham wrote:

> The main problem is that grid doesn't have any drawing primitives that
> allow you to specify size in native units.

Oh, I see. So this is a grid problem. Do you think there is any chance
that asking Paul Murrel about that (repeatedly, every day, for a year)
would change that?

> The only way I can see to
> get it to work would be to use polygons to reproduce (e.g.) points and
> lines. This would be a lot slower than the usual drawing

Even if this would work, that does not seem desirable since it would
give jaggy edges to points (unless one devises a system to increase
the number of points in the polygon when the size increases, but that
seems like a lot of work to circumvent a bug) and would make the plots
much heavier when saved in vector formats.

:-/

Too bad.

JiHO
---
http://jo.irisson.free.fr/

JiHO

unread,
May 21, 2009, 6:29:44 PM5/21/09
to hadley wickham, baptiste auguie, ggplot2
Ooops sorry, I meant to add the following too.

On 2009-May-21 , at 14:56 , hadley wickham wrote:

> The main problem is that grid doesn't have any drawing primitives that
> allow you to specify size in native units. The only way I can see to
> get it to work would be to use polygons to reproduce (e.g.) points and
> lines.

Is there be a point along the ggplot pipe where you get access to the
real world coordinates of the viewport? It might just show my
ignorance of how grid works but if you get the range of dimensions in
px/mm on the device then, since you know from the scale range how much
that is in the coordinates of the plot, it is be possible to recompute
the radius of the dot from "native" to "px/mm" and then call the
drawing primitives in grid with those px/mm sizes.

It would still raise the issue of which coordinate to use when
coordinates are not equal and would add some complexity but the
benefit would be worth the cost I think.

Well I guess you probably thought about all that already but just in
case...

JiHO
---
http://jo.irisson.free.fr/

baptiste auguie

unread,
May 22, 2009, 4:45:38 AM5/22/09
to JiHO, ggplot2
Hi all,

JiHo's asked me about a possible modification to the segments geom.
Instead of mapping x, y, xend, yend, one could provide a set of x,y,
angle, length.

One option is to create a new geom, but since the end result is
identical to geom_segment this seems redundant. Another option I
envisaged is to provide additional parameters to geom_segment, but I'm
not sure it can work.

Here's an illustration,

fieldGrob <- function(x, y,
xend=x+length*cos(angle), yend=y+length*sin(angle),
angle=atan((yend - y)/(xend - x)), length=sqrt((x-xend)^2 + (y-yend)^2),
colour="black", fill=colour, size=1, linetype=1, arrow=NULL) {

segmentsGrob(x, y, xend, yend, default.units="native",
gp = gpar(col=colour, fill=colour,
lwd=size*ggplot2:::.pt, lty=linetype, lineend = "butt"),
arrow = arrow)
}


fieldGrob(0.5, 0.5, 0.6, 0.6)->g1
fieldGrob(0.5, 0.5, , , pi/6, 0.2, col="blue")->g2
pushViewport(vp=viewport(width=1, height=1))
grid.draw(g1)
grid.draw(g2)

This grob could replace segmentsGrob in the code for geom_segment, but
my attempt at doing so failed (something still expected xend and yend)
and I'm not completely sure it can work.

Another solution is to transform the original data, as in,

field2segment <- function(d=NULL, ...){
transform(d,
x=x - 0.5*length*cos(angle), y=y - 0.5*length*sin(angle),
# x and y shifted from the middle to the extremity of the segment
xend=x+0.5*length*cos(angle), yend=y+0.5*length*sin(angle) )
}

xy <- expand.grid(x=-10:10, y=-10:10)
d1 <- data.frame(x=xy$x, y=xy$y)
d1$colour <- sample(1:5, length(xy$x), repl=T) # check that other
aesth. are handled well

d1$angle <- 2*pi*rnorm(d1$x)
d1$length <- rnorm(d1$y)

d2 <- field2segment(d1)

p <- ggplot(map=aes(x=x, y=y, colour=colour))

p + geom_segment(data=d1, map=aes(xend=xend, yend=yend)) # fails as
xend and yend are missing
p + geom_segment(data=d2, map=aes(xend=xend, yend=yend)) # works

If I understand correctly JiHo's request (correct me if I missed the
point altogether!), what we're after is perhaps a more convenient
interface for this operation. Any suggestions?

baptiste


PS: by the by, a very similar question arose recently with the
parametrisation of ellipses: I chose x, y, angle, aspect ratio (a/b),
whilst other packages (e.g. ellipse) seem the prefer a
covariance-correlation matrix. Assuming we figure the best way to deal
with such a dual parametrisation as discussed above, does anyone know
the correspondence between the two formulations?

baptiste auguie

unread,
May 22, 2009, 5:32:36 AM5/22/09
to JiHO, ggplot2
Well, with some help from the morning coffee I realise I was being
dumb as there's a big problem with the last idea I proposed: there is
no way to map the angle and length to variables of the original
data.frame and have them appear in the legend.

I guess creating a new geom is a good idea after all. I'll have a go later.

Cheers,

baptiste

--

JiHO

unread,
May 24, 2009, 4:49:26 PM5/24/09
to baptiste auguie, ggplot2
On 2009-May-22 , at 05:32 , baptiste auguie wrote:

> Well, with some help from the morning coffee I realise I was being
> dumb as there's a big problem with the last idea I proposed: there is
> no way to map the angle and length to variables of the original
> data.frame and have them appear in the legend.

The transformation you propose in your second solution is what I am
doing currently. However, the goal is indeed to have the length and
angle mapped in the legend. Well actually, for most purposes I am more
interested in the length but I guess there is no clever way for ggplot
to guess that from the data.

The most elegant solution would be what you propose first: add
aesthetics to geom_segment. But that would raise the problem of which
are required or not. Currently ggplot requires x, y and xend, yend.
With more arguments, it should require x, y and (xend, yend OR angle,
length) and I don't know whether that is doable.

Otherwise the new geom might be the way to go. In which case it might
even be nice to have two:
- one with x,y, angle and length
- one with x,y, u and v where u and v are the horizontal and
vertical component of a vector starting in x,y. This is a very common
representation for vector fields (think magnetic, electric, current,
wind etc.). All those things are far from statistics I agree, but R is
versatile enough to accommodate for them and there is interest in it
(I am in a marine physics and biology school and was asked to teach a
class on data manipulation and plotting in R, because they are trying
to get away from the omnipotent and pricy MATLAB).

Thanks in advance.

JiHO
---
http://jo.irisson.free.fr/

baptiste auguie

unread,
May 25, 2009, 9:01:43 AM5/25/09
to JiHO, ggplot2
I've tried to make a new geom (geom_field), but I'm not sure what the
legend should show for the length. I used native parent coordinates so
that the relative size of the segments is shown. I'm not sure it's
very meaningful. Anyhow, I've just uploaded it on r-forge,

library(ggplotpp)


xy <- expand.grid(x=-10:10, y=-10:10)
d1 <- data.frame(x=xy$x, y=xy$y)
d1$colour <- sample(1:5, length(xy$x), repl=T)

d1$angle <- 2*pi*rnorm(d1$x)
d1$whatever <- rnorm(d1$x)
d1$length <- abs(rnorm(d1$y))/10


p <- ggplot(data=d1, map=aes(x=x, y=y, angle=angle, length=length,
colour=colour))

p2 <-
p + geom_field() + geom_point()
p + geom_field(aes(size=whatever))
p + geom_field(arrow=arrow(angle = 30, length = unit(2, "mm"),
ends = "both", type = "open"))

I hope I haven't missed a file in the svn commit (i'm just quickly
checking emails in a café).

--

hadley wickham

unread,
Jun 4, 2009, 1:18:17 PM6/4/09
to baptiste auguie, ggplot2
On Thu, May 21, 2009 at 2:16 PM, baptiste auguie
<bapt...@googlemail.com> wrote:
> I tested this idea today with the geom_ellipse grob that I created
> last week using polygons. I got the grob to scale with the data (npc),
> but the problem for me resides in the legend: the npc value makes
> little sense there since it's a different viewport (output attached).

Oh yes, that's another reason I never did this - I couldn't figure out
how to create sensible legends.

Hadley

--
http://had.co.nz/

Reply all
Reply to author
Forward
0 new messages