The fontsize is used to determine the size of text or points in R graphics. But what does it actually represent? For example,
grid::grid.newpage()
grid::grid.points(default.units = "npc",
gp = gpar(fontsize = 100))
Here, the point fontsize is 100. So, I have two questions:
What does the value mean, the radius, the diameter or the area of a point? Can I find any reference?
What is the unit of 100? In specific, does it mean 100 pointsize (1 fontsize = 1pt)? Otherwise, is it determined by device-specification? If so, 1 fontsize = ?pt.
Also, I have another question that, in language R, is there any handy functions to convert pt to pixel (px)? For example, we can convert a pt unit to a mm unit as in
convertUnit(unit(1, "pt"), "mm", valueOnly = TRUE)
So, is there any function to query the dimension of the screen and then do the transformation from pt to px?
Thanks!
The size of the symbols drawn by grid.points() depend on several factors, primarily pch, which determines the shape of the symbol, and size. By default, the latter is unit(1, "char"), which means the size further depends on the current font size (fontsize and cex).
Deep in the graphics engine source code (https://github.com/r-devel/r-svn/blob/master/src/main/engine.c#L2110) are some constants that further modify the nominal symbol size based on the symbol shape. For example, pch=1 multiplies size by .375 to get the circle radius.
Yes, fontsize=100 means 100pt.
The following code demonstrates the calculations by drawing a symbol and a rectangle with the same width:
library(grid)
grid.newpage()
pushViewport(viewport())
grid.points(.5, .5)
grid.rect(width=2*.375*unit(1, "char"))
grid.points(.5, .5, gp=gpar(fontsize=100))
grid.rect(width=2*.375*unit(100, "pt"))
Related
I am plotting some geometry using bokeh and came across this. I am plotting a rectangle with equal sides (i.e. a square), and in that square, plotting a circle with diameter = width of the square. The circle should tangent to the square at edges, but it is not.
here is the code:
from bokeh.plotting import output_notebook, figure, show
output_notebook()
p = figure(width=500, height=500)
p.rect(0, 0, 300, 300, line_color='black')
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
p.axis.minor_tick_out = 0
show(p)
Which results in this:
Is there anything I am doing wrong or could change to make the circle fit exactly in the square?
Thanks in advance,
Randall
Here's another case - just drawing a circle:
p = figure(width=500, height=500, x_range=(-150, 150), y_range=(-150, 150))
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
show(p)
radius of the circle is 150 in the x direction, but not the y-direction.
I would like to report that as of Bokeh 0.12.7, this issue can now be fixed in a simpler manner.
As described in other posts, the main issue is not that the circle is not a circle, but that the square is not a square. This is due to the fact that actual area on which Bokeh draws the figure (the canvas) is usually not a square by default or even when the width and height are set to the same value. Bokeh by default will attempt to draw a figure by using up all the space on the canvas. This creates a mismatch between the data distance and the pixel distance of the plot.
As of 0.12.7, figures can now accept a match_aspect property which when set to True will will match the aspect of the data space to the pixel space of the plot.
In your example, simply adding the match_aspect = True in your figure
p = figure(width=500, height=500, match_aspect=True,
title="Circle touches all 4 sides of square")
p.rect(0, 0, 300, 300, line_color='black')
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
will now produce
UPDATE: Please note new answer by #DuCorey below. As of Bokeh 0.12.7, aspect control is now available, for situations like this.
The issue is actually that the square is not square, and that is because the pixel aspect ratio and the "data" aspect ratio do not match. i.e., the distance per pixel is different in the x direction than it is in the y direction.
There are a few options:
You can use various properties to control the dimensions of the central plot area (e.g. plot border width and axis tick label orientation) You can also control you data ranges explicitly. In other words, you can make the aspect ratios match, and then the circle and rect will match
You can use absolute pixel units (e.g. size for a circle, and use a large square marker instead of rect) instead of "data" units.
Alternatively, if you want a circle that "deforms" when the aspects do not match, then your best bet is to use an ellipse with an identical width and height, which will work because in this case bokeh has two dimensions to use to measure (instead of the single radius) and can match each to the scale along each dimension independently.
(This is actually the fundamental difference that explains the behaviour: rect has two dimensions to measure independently. circle does not, it only has one, and has to arbitrarily use the x or y dimension to measure distance per pixel)
ok, based on the suggestions, I tried a few things.
Changed the orientation of the y-axis tick labels - still
had issue.
Changed various stand-offs, even moving in the tick
labels inside the plot (with a negative offset). Did not work either.
Changed the x_range and r_range in figure() to be equal tuples. Did not work either
Changes the plot_height (decreased it), and I could eventually, through rial and error, get the circle to fit in the square with a plot_height that was < plot width.
Lots of great practice controlling attributes of the plot. Time will invested.
However, the last change I tried worked the best. It was one of the first suggestions - change the plot border.
Weirdly, setting p.min_border=40, which on 0.12.6 is the default value, and voila, it appears the chart aspect ratio for a chart where plot_width=plot_height is truly 1 on the screen as well.
p = figure(plot_width=500, plot_height=500)
p.rect(0, 0, 300, 300, line_color=None)
p.circle(x=0, y=0, radius=150, line_color=None,
fill_color='lightgrey', radius_units='data')
p.min_border=40
show(p)
Before and after images showing the effect of adding p.min_border=40. Any value over ~33 appeared to be enough force the plot area to have the same screen x and y dimension - so the square was really a square (and the circle fit inside).
The reason for this is that you're creating a circular marker (or circle glyphs) and placing it at position (0, 0), while it seems like you want to create a circle centered at 0.
I think the rect here "happens" to work because it can scale correctly in both dimensions and remain a "rectangle".
Keyword Args:
radius (UnitsSpecPropertyDescriptor) : The radius values for circle markers (in "data space" units, by default). (default None)
radius_dimension (BasicPropertyDescriptor) : What dimension to measure circle radii along. (default 'x')
radius_units (Enum('screen', 'data')) : (default 'data')
I guess my point is here you've taken a shortcut by trying to use a "glyph" as your plot and specifying the units to be the data units.
If you want to create an actual circle you could do the following:
th = np.linspace(0, 2*np.pi)
r = 150
p = figure(width=500, height=500)
p.rect(0, 0, 300, 300, line_color='black')
p.line(r * np.cos(th), r * np.sin(th), line_color='black')
# p.circle(x=0, y=0, radius=150, line_color='black',
# fill_color='grey', radius_units='data')
p.axis.minor_tick_out = 0
show(p)
Notice the above is harder to fill (I didn't bother) because I'm guessing you need to define some closed polygon function while I only defined a line that happens to be a closed polygon, in this case a circle.
Not sure, but the bleu rectangle is not your rectangle.
Replace:
p.rect(0, 0, 300, 300, line_color='black')
By:
p.rect(-150, -150, 150, 150, line_color='black')
What units are cex and size in?
For example, if I specify that size = 3, what does this mean? Pixels? mm? cm?
If I specify that cex = 2, is this measured in pixels, mm, cm? I know that cex = 2 means that I am making the size of a point 2 times the original size, but if the original size is not specified what is the default size and its units?
I've tried to search for this, but cannot seem to find anything.
Somewhat inexplicably, the length parameter in arrows is specified in inches (from ?arrows):
length length of the edges of the arrow head (in inches).
R source even goes so far as to explicitly make note that this measurement is in inches in a comment, highlighting how peculiar this design is.
That means the relative size of the arrows depends on dev.size(). What's not clear is how to translate inches into axis units (which are infinitely more useful in the first place). Here's a simplified version:
h = c(1, 2, 3)
xs = barplot(h, space = 0, ylim = c(0, 4))
arrows(xs, h - .5, xs, h + .5,
length = .5*mean(diff(xs)))
How this displays will depend on the device. E.g. here is the output on this device:
png('test.png', width = 5, height = 5)
And here it is on another:
png('test.png', width = 8, height = 8)
It's a bit of an optical illusion to tell on sight, but the arrows are indeed the same width in the two plots. How can I control this so that both plots (which convey the same data) display identically? More specifically, how can I make sure that the arrows are exactly .5 plot units in width?
I spent far too much time in the rabbit hole on this, but here goes. I'll document a bit of my journey first to aid others who happen upon this in the types of nooks and crannies to search when trying to pull yourself up by your bootstraps.
I started looking in the source of arrows, but to no avail, since it quickly dives into internal code. So I searched the R source for "C_arrows" to find what's happening; luckily, it's not too esoteric, as far as R internal code goes. Poking around it seems the workhorse is actually GArrow, but this was a dead end, as it seems the length parameter isn't really transformed there (IIUC this means the conversion to inches is done for the other coordinates and length is untouched). But I happened to notice some GConvert calls that looked closer to what I want and hoped to find some user-facing function that appeals to these directly.
This led me to go back to R and to simply run through the gamut of functions in the same package as arrows looking for anything that could be useful:
ls(envir = as.environment('package:grDevices'))
ls(envir = as.environment('package:graphics'))
Finally I found three functions in graphics: xinch, yinch, and xyinch (all found on ?xinch) are used for the opposite of my goal here -- namely, they take inches and convert them into device units (in the x, y, and x&y directions, respectively). Luckily enough, these functions are all very simple, e.g. the work horse of xinch is the conversion factor:
diff(par("usr")[1:2])/par("pin")[1L]
Examining ?par (for the 1,000,000th time), indeed pin and usr are exactly the graphical parameter we need (pin is new to me, usr comes up here and there):
pin The current plot dimensions, (width, height), in inches.
usr A vector of the form c(x1, x2, y1, y2) giving the extremes of the user coordinates of the plotting region.
Hence, we can convert from plot units to inches by inverting this function:
xinch_inv = function(dev_unit) {
dev_unit * par("pin")[1L]/diff(par("usr")[1:2])
}
h = c(1, 2, 3)
xs = barplot(h, space = 0, ylim = c(0, 4))
arrows(xs, h - .5, xs, h + .5,
# just convert plot units to inches
length = xinch_inv(.5*mean(diff(xs))))
Resulting in (5x5):
And (8x8):
One further note, it appears length is the length of each side of the arrow head -- using length = xinch_inv(.5), code = 3, angle = 90 results in segments as wide as the bars (i.e., 1).
On the off chance you're interested, I've packaged these in my package as xdev2in, etc.; GitHub only for now.
A quick question that I can't find answer on the web (or Wickham's book):
What is the unit of the size argument in ggplot2? For example, geom_text(size = 10) -- 10 in what units?
The same question applies to default unit in ggsave(height = 10, width = 10).
The answer is : The unit is the points. It is the unit of fontsize in the grid package. In ?unit, we find the following definition
"points" Points. There are 72.27 points per inch.
(but note the closely related "bigpts" Big Points. 72 bp = 1 in.)
Internally ggplot2 will multiply the font size by a magic number ggplot2:::.pt, defined as 1/0.352777778.
Here a demonstration, I create a letter using grid and ggplot2 with same size:
library(grid)
library(ggplot2)
ggplot(data=data.frame(x=1,y=1,label=c('A'))) +
geom_text(aes(x,y,label=label),size=100)
## I divide by the magic number to get the same size.
grid.text('A',gp=gpar(fontsize=100/0.352777778,col='red'))
Addendum Thanks to #baptiste
The "magic number"(defined in aaa-constants.r as .pt <- 1 / 0.352777778) is really just the conversion factor between "points" and "mm", that is 1/72 * 25.4 = 0.352777778. Unfortunately, grid makes the subtle distinction between "pts" and "bigpts", which explains why convertUnit(unit(1, "pt"), "mm", valueOnly=TRUE) gives the slightly different value of 0.3514598.
The 'ggplot2' package, like 'lattice' before it, is built on the grid package. You can get the available units at:
?grid::unit
?grid::convertX
?grid::convertY
grid::convertX(grid::unit(72.27, "points"), "inches")
(I use the formalism pkg::func because in most cases grid is loaded a a NAMESPACE but not attached when either lattice or `ggplot2 are loaded.)
I earlier posted a comment that I later deleted saying that size was in points. I did so after seeing that the size of the text with size=10 was roughly 10 mm. The "magic" number mentioned by agstudy is in fact within 1% of:
as.numeric(grid::convertX(grid::unit(1, "points"), "mm"))
#[1] 0.3514598
0.352777778/.Last.value
#[1] 1.00375
From ?aes_linetype_size_shape
# Size examples
# Should be specified with a numerical value (in millimetres),
# or from a variable source
height and width in ggsave relate to par("din") from ?par
din
R.O.; the device dimensions, (width, height), in inches. See also dev.size,
which is updated immediately when an on-screen device windows is re-sized.
So I guess size in aes is in millimetres and ggsave height and width are in inches.
I would like to know the approximate dimensions of symbol in my plot area. I think that par()$ps only really refers to text size. So how is a symbol size calculated using the cex parameter? For example, below is a plot of a single point of size cex=10. Can i determine its size from the plot devices par parameters?
plot(50, 50, ylim=c(0,100), xlim=c(0,100), cex=10)
#click on outer x limits
p1 <- locator(n=1,typ="n")
p2 <- locator(n=1,typ="n")
#approx width in x units(~15)
abs(p1$x - p2$x)
Thanks for you help. -Marc
According to the documentation contained in ?par, we have that,
cin - R.O.; character size (width, height) in inches. These are the same measurements as cra, expressed in different units.
cra - R.O.; size of default character (width, height) in ‘rasters’ (pixels). Some devices have no concept of pixels and so assume an arbitrary pixel size, usually 1/72 inch. These are the same measurements as cin, expressed in different units.
On my machine, these values appear to be:
par("cin")
[1] 0.15 0.20
> par("cra")
[1] 10.8 14.4
So character magnification via cex ought to happen relative to these dimensions, presumably by scaling the horizontal and vertical dimensions separately (although I don't know that for sure).