Add width/height to coordmap instead of using naturalWidth/Height

This eliminates the need to use an on load callback.
This commit is contained in:
Winston Chang
2018-08-07 12:37:40 -05:00
parent a1e1416d7a
commit 3354a47e8a
2 changed files with 111 additions and 101 deletions

View File

@@ -400,7 +400,7 @@ custom_print.ggplot <- function(x) {
getCoordmap <- function(x, width, height, res) {
if (inherits(x, "ggplot_build_gtable")) {
getGgplotCoordmap(x, res)
getGgplotCoordmap(x, width, height, res)
} else {
getPrevPlotCoordmap(width, height)
}
@@ -420,7 +420,7 @@ getPrevPlotCoordmap <- function(width, height) {
}
# Wrapped in double list because other types of plots can have multiple panels.
list(list(
panel_info <- list(list(
# Bounds of the plot area, in data space
domain = list(
left = usrCoords[1],
@@ -444,27 +444,43 @@ getPrevPlotCoordmap <- function(width, height) {
# (not an array) in JSON.
mapping = list(x = NULL)[0]
))
list(
panels = panel_info,
dims = list(
width = width,
height =height
)
)
}
# Given a ggplot_build_gtable object, return a coordmap for it.
getGgplotCoordmap <- function(p, res) {
getGgplotCoordmap <- function(p, width, height, res) {
if (!inherits(p, "ggplot_build_gtable"))
return(NULL)
tryCatch({
# Get info from built ggplot object
info <- find_panel_info(p$build)
panel_info <- find_panel_info(p$build)
# 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, res)
for (i in seq_along(info)) {
info[[i]]$range <- ranges[[i]]
for (i in seq_along(panel_info)) {
panel_info[[i]]$range <- ranges[[i]]
}
return(info)
return(
list(
panels = panel_info,
dims = list(
width = width,
height = height
)
)
)
}, error = function(e) {
# If there was an error extracting info from the ggplot object, just return

View File

@@ -90,94 +90,94 @@ $.extend(imageOutputBinding, {
}
}
if (!opts.coordmap)
opts.coordmap = [];
if (!opts.coordmap) {
opts.coordmap = {
panels: [],
dims: {
height: null,
width: null
}
};
}
// When the image loads, initialize all the interaction handlers. When the
// value of src is set, the browser may not load the image immediately,
// even if it's a data URL. If we try to initialize this stuff
// immediately, it can cause problems because we use .naturalWidth and
// .naturalHeight, but they're available only after the image loads.
$img.off("load.shiny-image-interaction");
$img.on("load.shiny-image-interaction", function() {
// Remove event handlers that were added in previous runs of this function.
$el.off('.image_output');
// Remove event handlers that were added in previous runs of this function.
$el.off('.image_output');
imageutils.initCoordmap($el, opts.coordmap);
imageutils.initCoordmap($el, opts.coordmap);
// This object listens for mousedowns, and triggers mousedown2 and dblclick2
// events as appropriate.
var clickInfo = imageutils.createClickInfo($el, opts.dblclickId, opts.dblclickDelay);
// This object listens for mousedowns, and triggers mousedown2 and dblclick2
// events as appropriate.
var clickInfo = imageutils.createClickInfo($el, opts.dblclickId, opts.dblclickDelay);
$el.on('mousedown.image_output', clickInfo.mousedown);
$el.on('mousedown.image_output', clickInfo.mousedown);
if (browser.isIE && browser.IEVersion === 8) {
$el.on('dblclick.image_output', clickInfo.dblclickIE8);
}
if (browser.isIE && browser.IEVersion === 8) {
$el.on('dblclick.image_output', clickInfo.dblclickIE8);
}
// ----------------------------------------------------------
// Register the various event handlers
// ----------------------------------------------------------
if (opts.clickId) {
var clickHandler = imageutils.createClickHandler(opts.clickId,
opts.clickClip, opts.coordmap);
$el.on('mousedown2.image_output', clickHandler.mousedown);
// ----------------------------------------------------------
// Register the various event handlers
// ----------------------------------------------------------
if (opts.clickId) {
var clickHandler = imageutils.createClickHandler(opts.clickId,
opts.clickClip, opts.coordmap);
$el.on('mousedown2.image_output', clickHandler.mousedown);
$el.on('resize.image_output', clickHandler.onResize);
$el.on('resize.image_output', clickHandler.onResize);
// When img is reset, do housekeeping: clear $el's mouse listener and
// call the handler's onResetImg callback.
$img.on('reset', clickHandler.onResetImg);
}
// When img is reset, do housekeeping: clear $el's mouse listener and
// call the handler's onResetImg callback.
$img.on('reset', clickHandler.onResetImg);
}
if (opts.dblclickId) {
// We'll use the clickHandler's mousedown function, but register it to
// our custom 'dblclick2' event.
var dblclickHandler = imageutils.createClickHandler(opts.dblclickId,
opts.clickClip, opts.coordmap);
$el.on('dblclick2.image_output', dblclickHandler.mousedown);
if (opts.dblclickId) {
// We'll use the clickHandler's mousedown function, but register it to
// our custom 'dblclick2' event.
var dblclickHandler = imageutils.createClickHandler(opts.dblclickId,
opts.clickClip, opts.coordmap);
$el.on('dblclick2.image_output', dblclickHandler.mousedown);
$el.on('resize.image_output', dblclickHandler.onResize);
$img.on('reset', dblclickHandler.onResetImg);
}
$el.on('resize.image_output', dblclickHandler.onResize);
$img.on('reset', dblclickHandler.onResetImg);
}
if (opts.hoverId) {
var hoverHandler = imageutils.createHoverHandler(opts.hoverId,
opts.hoverDelay, opts.hoverDelayType, opts.hoverClip,
opts.hoverNullOutside, opts.coordmap);
$el.on('mousemove.image_output', hoverHandler.mousemove);
$el.on('mouseout.image_output', hoverHandler.mouseout);
if (opts.hoverId) {
var hoverHandler = imageutils.createHoverHandler(opts.hoverId,
opts.hoverDelay, opts.hoverDelayType, opts.hoverClip,
opts.hoverNullOutside, opts.coordmap);
$el.on('mousemove.image_output', hoverHandler.mousemove);
$el.on('mouseout.image_output', hoverHandler.mouseout);
$el.on('resize.image_output', hoverHandler.onResize);
$img.on('reset', hoverHandler.onResetImg);
}
$el.on('resize.image_output', hoverHandler.onResize);
$img.on('reset', hoverHandler.onResetImg);
}
if (opts.brushId) {
// Make image non-draggable (Chrome, Safari)
$img.css('-webkit-user-drag', 'none');
// Firefox, IE<=10
$img.on('dragstart', function() { return false; });
if (opts.brushId) {
// Make image non-draggable (Chrome, Safari)
$img.css('-webkit-user-drag', 'none');
// Firefox, IE<=10
$img.on('dragstart', function() { return false; });
// Disable selection of image and text when dragging in IE<=10
$el.on('selectstart.image_output', function() { return false; });
// Disable selection of image and text when dragging in IE<=10
$el.on('selectstart.image_output', function() { return false; });
var brushHandler = imageutils.createBrushHandler(opts.brushId, $el, opts,
opts.coordmap, outputId);
$el.on('mousedown.image_output', brushHandler.mousedown);
$el.on('mousemove.image_output', brushHandler.mousemove);
var brushHandler = imageutils.createBrushHandler(opts.brushId, $el, opts,
opts.coordmap, outputId);
$el.on('mousedown.image_output', brushHandler.mousedown);
$el.on('mousemove.image_output', brushHandler.mousemove);
$el.on('resize.image_output', brushHandler.onResize);
$img.on('reset', brushHandler.onResetImg);
}
$el.on('resize.image_output', brushHandler.onResize);
$img.on('reset', brushHandler.onResetImg);
}
if (opts.clickId || opts.dblclickId || opts.hoverId || opts.brushId) {
$el.addClass('crosshair');
}
if (opts.clickId || opts.dblclickId || opts.hoverId || opts.brushId) {
$el.addClass('crosshair');
}
if (data.error)
console.log('Error on server extracting coordmap: ' + data.error);
});
if (data.error)
console.log('Error on server extracting coordmap: ' + data.error);
},
renderError: function(el, err) {
@@ -210,7 +210,7 @@ var imageutils = {};
// scaleDataToImg(), and clipImg() functions to each one. The panel objects
// use img and data coordinates only; they do not use css coordinates. The
// domain is in data coordinates; the range is in img coordinates.
imageutils.initPanelScales = function(coordmap) {
imageutils.initPanelScales = function(panels) {
// Map a value x from a domain to a range. If clip is true, clip it to the
// range.
function mapLinear(x, domainMin, domainMax, rangeMin, rangeMax, clip) {
@@ -307,8 +307,8 @@ imageutils.initPanelScales = function(coordmap) {
}
// Add the functions to each panel object.
for (var i=0; i<coordmap.length; i++) {
var panel = coordmap[i];
for (var i=0; i<panels.length; i++) {
var panel = panels[i];
addScaleFuns(panel);
}
};
@@ -340,7 +340,7 @@ imageutils.initCoordmap = function($el, coordmap) {
// If we didn't get any panels, create a dummy one where the domain and range
// are simply the pixel dimensions.
// that we modify.
if (coordmap.length === 0) {
if (coordmap.panels.length === 0) {
let bounds = {
top: 0,
left: 0,
@@ -348,7 +348,7 @@ imageutils.initCoordmap = function($el, coordmap) {
bottom: el.clientHeight - 1
};
coordmap[0] = {
coordmap.panels[0] = {
domain: bounds,
range: bounds,
mapping: {}
@@ -356,7 +356,7 @@ imageutils.initCoordmap = function($el, coordmap) {
}
// Add scaling functions to each panel
imageutils.initPanelScales(coordmap);
imageutils.initPanelScales(coordmap.panels);
// This returns the offset of the mouse in CSS pixels relative to the img,
@@ -415,8 +415,15 @@ imageutils.initCoordmap = function($el, coordmap) {
return result;
};
// Returns the x and y ratio the image content 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.)
coordmap.imgToCssScalingRatio = function() {
return findImgToCssScalingRatio($img);
const img_dims = findDims($img);
return {
x: img_dims.x / coordmap.dims.width,
y: img_dims.y / coordmap.dims.height
};
};
coordmap.cssToImgScalingRatio = function() {
@@ -447,15 +454,15 @@ imageutils.initCoordmap = function($el, coordmap) {
const matches = []; // Panels that match
const dists = []; // Distance of offset to each matching panel
let b;
for (var i=0; i<coordmap.length; i++) {
b = coordmap[i].range;
for (var i=0; i<coordmap.panels.length; i++) {
b = coordmap.panels[i].range;
if (x <= b.right + expand_img.x &&
x >= b.left - expand_img.x &&
y <= b.bottom + expand_img.y &&
y >= b.top - expand_img.y)
{
matches.push(coordmap[i]);
matches.push(coordmap.panels[i]);
// Find distance from edges for x and y
var xdist = 0;
@@ -1094,13 +1101,13 @@ imageutils.createBrush = function($el, opts, coordmap, expandPixels) {
// Find a panel that has matching vars; if none found, we can't restore.
// The oldPanel and new panel must match on their mapping vars, and the
// values.
for (var i=0; i<coordmap.length; i++){
var curPanel = coordmap[i];
for (var i=0; i<coordmap.panels.length; i++){
var curPanel = coordmap.panels[i];
if (equal(oldPanel.mapping, curPanel.mapping) &&
equal(oldPanel.panel_vars, curPanel.panel_vars)) {
// We've found a matching panel
state.panel = coordmap[i];
state.panel = coordmap.panels[i];
break;
}
}
@@ -1554,16 +1561,3 @@ function findDims($el) {
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.)
// TODO: memoize, don't take $img as input?
function findImgToCssScalingRatio($img) {
const img_dims = findDims($img);
return {
x: img_dims.x / $img[0].naturalWidth,
y: img_dims.y / $img[0].naturalHeight
};
}