hexbin icon indicating copy to clipboard operation
hexbin copied to clipboard

hexViewport does not respect hexbin bounds

Open davidorme opened this issue 4 years ago • 1 comments

I'm using hexbin to create some plots with overlays and shared axes and have ended up going back to the bare grid commands. I've run into an issue with setting bounds on viewports. The usage for hexViewport is:

hexViewport(x, offset = unit(0,"inches"), mar = NULL,
	    xbnds = NULL, ybnds = NULL, newpage = FALSE,
            clip = "off", vp.name = NULL)

The documentation for reports:

xbnds, ybnds bounds for x- and y- plotting range; these default to the corresponding slots of x.

where x is a hexbin object. The user can supply xbnds and ybnds to hexbin but these are not respected by hexViewport. The values are in x are first passed through hexbin:::smartBnds which does some smart stuff to do with shape but completely hamstrings any user attempt to control the viewport bounds.

The relevant lines are ( https://github.com/edzer/hexbin/blob/master/R/hexViewport.R#L116):

xyb <- smartBnds(x)
hvp@xscale <- xs <- if(is.null(xbnds)) xyb$xr else xbnds
hvp@yscale <- ys <- if(is.null(ybnds)) xyb$yr else ybnds

So, the user can enforce bounds directly in hexViewport but not - as the documentation suggests - via hexbin. This isn't simple - because the bounds for creating the bins and displaying them are conceptually different. I'm finding that in order to get my expected behaviour, I have to set the bounds in both locations:

library(grid)
l <- grid.layout(nrow = 3, ncol = 1,
        widths = unit(rep_len(1, 3), "null"),
        heights = unit(rep_len(1, 1), "null"),
        default.units = "null", respect = FALSE,
        just="centre")
        
pushViewport(viewport(layout=l))

x <- rnorm(100)
y <- rnorm(100)


# Processed by smartBnds and does not respect the zoomed out bounds given to hexbin
pushViewport(viewport(layout.pos.row=1, layout.pos.col=1))
hx <- hexbin(x, y, xbins=50, xbnds=c(-5,5), ybnds=c(-5,5))
pushHexport(hexViewport(hx, mar=unit(0, 'npc')))
grid.rect()
grid.hexagons(hx)
popViewport()
popViewport()

# No bounds set in hexbin - viewport is zoomed out as expected but 
# poor match to the underlying hexbin representation
pushViewport(viewport(layout.pos.row=2, layout.pos.col=1))
hx <- hexbin(x, y, xbins=50)
pushHexport(hexViewport(hx, mar=unit(0, 'npc'), xbnds=c(-5,5), ybnds=c(-5,5)))
grid.rect()
grid.hexagons(hx)
popViewport()
popViewport()

# Set bounds in both - desired behaviour for neat hexgrid and zoomed out.
pushViewport(viewport(layout.pos.row=3, layout.pos.col=1))
hx <- hexbin(x, y, xbins=50, xbnds=c(-5,5), ybnds=c(-5,5))
pushHexport(hexViewport(hx, mar=unit(0, 'npc'), xbnds=c(-5,5), ybnds=c(-5,5)))
grid.rect()
grid.hexagons(hx)
popViewport()
popViewport()

Honestly, I don't see a simple code fix - but maybe an update to the docs to highlight this?

davidorme avatar Jan 18 '21 10:01 davidorme

I've encountered a similar issue. Changing x@xbnds or x@ybndschanges the axis labels but does not move the boundaries of the plot (likexlimorylim` would be expected to) so they are mislabelled.

If it is possible to pass xlim and ylim directly from hexbinplot() or gplot.hexbin() this would be ideal. My use case is the y-axis and colour scale on a log scale.

TomKellyGenetics avatar Mar 19 '21 06:03 TomKellyGenetics