mirror of
https://github.com/rstudio/shiny.git
synced 2026-04-07 03:00:20 -04:00
Allow plot interaction to handle scaled images
This commit is contained in:
@@ -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]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user