Allow plot interaction to handle scaled images

This commit is contained in:
Winston Chang
2018-07-03 22:50:04 -05:00
parent 598b48d078
commit 909bfa8c14
2 changed files with 98 additions and 31 deletions

View File

@@ -162,7 +162,7 @@ resizeSavedPlot <- function(name, session, result, width, height, pixelratio, re
coordmap <- NULL
outfile <- plotPNG(function() {
grDevices::replayPlot(result$recordedPlot)
coordmap <<- getCoordmap(result$plotResult, width, height, pixelratio, res)
coordmap <<- getCoordmap(result$plotResult, width*pixelratio, height*pixelratio, res*pixelratio)
}, width = width*pixelratio, height = height*pixelratio, res = res*pixelratio, ...)
on.exit(unlink(outfile), add = TRUE)
@@ -231,7 +231,7 @@ drawPlot <- function(name, session, func, width, height, pixelratio, res, ...) {
list(
plotResult = value,
recordedPlot = grDevices::recordPlot(),
coordmap = getCoordmap(value, width, height, pixelratio, res),
coordmap = getCoordmap(value, width*pixelratio, height*pixelratio, res*pixelratio),
pixelratio = pixelratio,
res = res
)
@@ -398,9 +398,9 @@ custom_print.ggplot <- function(x) {
# .. ..$ top : num 35.7
getCoordmap <- function(x, width, height, pixelratio, res) {
getCoordmap <- function(x, width, height, res) {
if (inherits(x, "ggplot_build_gtable")) {
getGgplotCoordmap(x, pixelratio, res)
getGgplotCoordmap(x, res)
} else {
getPrevPlotCoordmap(width, height)
}
@@ -447,7 +447,7 @@ getPrevPlotCoordmap <- function(width, height) {
}
# Given a ggplot_build_gtable object, return a coordmap for it.
getGgplotCoordmap <- function(p, pixelratio, res) {
getGgplotCoordmap <- function(p, res) {
if (!inherits(p, "ggplot_build_gtable"))
return(NULL)
@@ -458,7 +458,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
# Get ranges from gtable - it's possible for this to return more elements than
# info, because it calculates positions even for panels that aren't present.
# This can happen with facet_wrap.
ranges <- find_panel_ranges(p$gtable, pixelratio, res)
ranges <- find_panel_ranges(p$gtable, res)
for (i in seq_along(info)) {
info[[i]]$range <- ranges[[i]]
@@ -827,7 +827,7 @@ find_panel_info_non_api <- function(b, ggplot_format) {
# Given a gtable object, return the x and y ranges (in pixel dimensions)
find_panel_ranges <- function(g, pixelratio, res) {
find_panel_ranges <- function(g, res) {
# Given a vector of unit objects, return logical vector indicating which ones
# are "null" units. These units use the remaining available width/height --
# that is, the space not occupied by elements that have an absolute size.
@@ -957,26 +957,15 @@ find_panel_ranges <- function(g, pixelratio, res) {
layout <- layout[order(layout$t, layout$l), ]
layout$panel <- seq_len(nrow(layout))
# When using a HiDPI client on a Linux server, the pixel
# dimensions are doubled, so we have to divide the dimensions by
# `pixelratio`. When a HiDPI client is used on a Mac server (with
# the quartz device), the pixel dimensions _aren't_ doubled, even though
# the image has double size. In the latter case we don't have to scale the
# numbers down.
pix_ratio <- 1
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
pix_ratio <- pixelratio
}
# Return list of lists, where each inner list has left, right, top, bottom
# values for a panel
lapply(seq_len(nrow(layout)), function(i) {
p <- layout[i, , drop = FALSE]
list(
left = x_pos[p$l - 1] / pix_ratio,
right = x_pos[p$r] / pix_ratio,
bottom = y_pos[p$b] / pix_ratio,
top = y_pos[p$t - 1] / pix_ratio
left = x_pos[p$l - 1],
right = x_pos[p$r],
bottom = y_pos[p$b],
top = y_pos[p$t - 1]
)
})
}

View File

@@ -312,16 +312,94 @@ imageutils.initCoordmap = function($el, coordmap) {
// Add scaling functions to each panel
imageutils.initPanelScales(coordmap);
// Firefox doesn't have offsetX/Y, so we need to use an alternate
// method of calculation for it. Even though other browsers do have
// offsetX/Y, we need to calculate relative to $el, because sometimes the
// mouse event can come with offset relative to other elements on the
// page. This happens when the event listener is bound to, say, window.
coordmap.mouseOffset = function(mouseEvent) {
var offset = $el.offset();
// Returns the ratio that an element has been scaled (for example, by CSS
// transforms) in the x and y directions.
function findScalingRatio($el) {
const boundingRect = $el[0].getBoundingClientRect();
return {
x: mouseEvent.pageX - offset.left,
y: mouseEvent.pageY - offset.top
x: boundingRect.width / $el.outerWidth(),
y: boundingRect.height / $el.outerHeight()
};
}
function findOrigin($el) {
const offset = $el.offset();
const scaling_ratio = findScalingRatio($el);
// Find the size of the padding and border, for the top and left. This is
// before any transforms.
const paddingBorder = {
left: parseInt($el.css("border-left")) + parseInt($el.css("padding-left")),
top: parseInt($el.css("border-top")) + parseInt($el.css("padding-top"))
};
// offset() returns the upper left corner of the element relative to the
// page, but it includes padding and border. Here we find the upper left
// of the element, not including padding and border.
return {
x: offset.left + scaling_ratio.x * paddingBorder.left,
y: offset.top + scaling_ratio.y * paddingBorder.top
};
}
// Find the dimensions of a tag, after transforms, and without padding and
// border.
function findDims($el) {
// If there's any padding/border, we need to find the ratio of the actual
// element content compared to the element plus padding and border.
const content_ratio = {
x: $el.width() / $el.outerWidth(),
y: $el.height() / $el.outerHeight()
};
// Get the dimensions of the element _after_ any CSS transforms. This
// includes the padding and border.
const bounding_rect = $el[0].getBoundingClientRect();
// Dimensions of the element after any CSS transforms, and without
// padding/border.
return {
x: content_ratio.x * bounding_rect.width,
y: content_ratio.y * bounding_rect.height
};
}
// Returns the x and y ratio that image content (like a PNG) is scaled to on
// screen. If the image data is 1000 pixels wide and is scaled to 300 pixels
// on screen, then this returns 0.3. (Note the 300 pixels refers to CSS
// pixels.)
function findImgPixelScalingRatio($img) {
const img_dims = findDims($img);
return {
x: img_dims.x / $img[0].naturalWidth,
y: img_dims.y / $img[0].naturalHeight
};
}
// This returns the offset of the mouse, relative to the img, but with some
// extra sauce. First, it returns the offset in the pixel dimensions of the
// image as if it were scaled to 100%. If the img content is 1000 pixels
// wide, but is scaled to 400 pixels on screen, and the mouse is on the far
// right side, then this will return x:1000. Second, if there is any padding
// or border around the img, it handles that. Third, if there are any
// scaling transforms on the image, it handles that as well.
coordmap.mouseOffset = function(mouseEvent) {
const $img = $el.find("img");
const img_origin = findOrigin($img);
// The offset of the mouse from the upper-left corner of the img, in
// pixels.
const offset_raw = {
x: mouseEvent.pageX - img_origin.x,
y: mouseEvent.pageY - img_origin.y
};
const pixel_scaling = findImgPixelScalingRatio($img);
return {
x: offset_raw.x / pixel_scaling.x,
y: offset_raw.y / pixel_scaling.y
};
};