Click handler on scaled image getting clipped (#4094)

* Fix #3234: Click handler on scaled image getting clipped

There were two related problems here, both happening in the same scenario:
when an imageOutput with click handlers is showing an image at less than
its natural size (e.g. a 1000x1000 px .png file, being displayed in the
web page at 500x500 due to max-width or for whatever other reason), any
click where the image coordinate (1000x1000) exceeds the display size
(500x500).

In the example above, a user clicks at 300x300 in the 500x500 displayed
image. We call 300x300 the "CSS coordinates". This gets scaled up into
the position in the PNG's own coordinate system, "image coordinates":
in this case, 600x600. Since the 600x600 image coordinate is greater
than the 500x500 CSS coordinate limit, the following issues were
triggered.

1. When imageOutput(click=clickOpts(clip=TRUE)) (the default), these
   clicks weren't registering at all. There was code that detected
   clicks that were inside the imageOutput but outside the actual image,
   but this code didn't take scaling into account.

2. Even with clip=FALSE, the click would be triggered BUT the `x` and `y`
   values on the click event were incorrect--they would max out at the
   CSS coordinate limit. This because plot and image output divide the
   world into "panels" and clicks snap to the nearest panel. In the case
   of image outputs, the server doesn't provide any panels, so the
   client makes one big panel that covers the whole image--but that code
   was erroneously using CSS sizes, not image sizes.

* Update NEWS
This commit is contained in:
Joe Cheng
2024-07-26 09:09:39 -07:00
committed by GitHub
parent 3f4676d9a6
commit 15b5fa6c01
7 changed files with 28 additions and 17 deletions

View File

@@ -27,6 +27,7 @@ If either 1 or 2 leads to undesirable behavior in your app, you can disable them
* Output bindings that are removed, invalidated, then inserted again (while invalidated) now correctly include the `.recalculating` CSS class. (#4039) * Output bindings that are removed, invalidated, then inserted again (while invalidated) now correctly include the `.recalculating` CSS class. (#4039)
* Fixed a recent issue with `uiOutput()` and `conditionalPanel()` not properly lower opacity when recalculation (in a Bootstrap 5 context). (#4027) * Fixed a recent issue with `uiOutput()` and `conditionalPanel()` not properly lower opacity when recalculation (in a Bootstrap 5 context). (#4027)
* Image outputs that were scaled by CSS had certain regions that were unresponsive to hover/click/brush handlers. (#3234)
# shiny 1.8.1.1 # shiny 1.8.1.1

View File

@@ -14507,8 +14507,8 @@
var bounds = { var bounds = {
top: 0, top: 0,
left: 0, left: 0,
right: img.clientWidth - 1, right: img.naturalWidth - 1,
bottom: img.clientHeight - 1 bottom: img.naturalHeight - 1
}; };
coordmap_.panels[0] = { coordmap_.panels[0] = {
domain: bounds, domain: bounds,
@@ -14581,10 +14581,15 @@
}; };
var matches = []; var matches = [];
var dists = []; var dists = [];
var b3;
var i5; var i5;
for (i5 = 0; i5 < coordmap.panels.length; i5++) { for (i5 = 0; i5 < coordmap.panels.length; i5++) {
b3 = coordmap.panels[i5].range; var panelRange = coordmap.panels[i5].range;
var b3 = {
top: panelRange.top * cssToImgRatio.y,
bottom: panelRange.bottom * cssToImgRatio.y,
left: panelRange.left * cssToImgRatio.x,
right: panelRange.right * cssToImgRatio.x
};
if (x2 <= b3.right + expandImg.x && x2 >= b3.left - expandImg.x && y4 <= b3.bottom + expandImg.y && y4 >= b3.top - expandImg.y) { if (x2 <= b3.right + expandImg.x && x2 >= b3.left - expandImg.x && y4 <= b3.bottom + expandImg.y && y4 >= b3.top - expandImg.y) {
matches.push(coordmap.panels[i5]); matches.push(coordmap.panels[i5]);
var xdist = 0; var xdist = 0;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -162,8 +162,8 @@ function initCoordmap(
const bounds = { const bounds = {
top: 0, top: 0,
left: 0, left: 0,
right: img.clientWidth - 1, right: img.naturalWidth - 1,
bottom: img.clientHeight - 1, bottom: img.naturalHeight - 1,
}; };
coordmap_.panels[0] = { coordmap_.panels[0] = {
@@ -290,11 +290,16 @@ function initCoordmap(
const matches = []; // Panels that match const matches = []; // Panels that match
const dists = []; // Distance of offset to each matching panel const dists = []; // Distance of offset to each matching panel
let b;
let i; let i;
for (i = 0; i < coordmap.panels.length; i++) { for (i = 0; i < coordmap.panels.length; i++) {
b = coordmap.panels[i].range; const panelRange = coordmap.panels[i].range;
const b = {
top: panelRange.top * cssToImgRatio.y,
bottom: panelRange.bottom * cssToImgRatio.y,
left: panelRange.left * cssToImgRatio.x,
right: panelRange.right * cssToImgRatio.x,
};
if ( if (
x <= b.right + expandImg.x && x <= b.right + expandImg.x &&
@@ -413,5 +418,5 @@ function initCoordmap(
return coordmap; return coordmap;
} }
export { findOrigin, initCoordmap };
export type { Coordmap, CoordmapInit }; export type { Coordmap, CoordmapInit };
export { initCoordmap, findOrigin };

View File

@@ -48,5 +48,5 @@ type Coordmap = {
mouseCoordinateSender: (inputId: string, clip?: boolean, nullOutside?: boolean) => (e: JQuery.MouseDownEvent | JQuery.MouseMoveEvent | null) => void; mouseCoordinateSender: (inputId: string, clip?: boolean, nullOutside?: boolean) => (e: JQuery.MouseDownEvent | JQuery.MouseMoveEvent | null) => void;
}; };
declare function initCoordmap($el: JQuery<HTMLElement>, coordmap_: CoordmapInit): Coordmap; declare function initCoordmap($el: JQuery<HTMLElement>, coordmap_: CoordmapInit): Coordmap;
export { findOrigin, initCoordmap };
export type { Coordmap, CoordmapInit }; export type { Coordmap, CoordmapInit };
export { initCoordmap, findOrigin };