edzer / hexbin

Hexagonal binning routines and plotting methods
37 stars 11 forks source link

hexViewport does not respect hexbin bounds #20

Open davidorme opened 3 years ago

davidorme commented 3 years ago

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?

TomKellyGenetics commented 3 years ago

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.