Skip to content

Commit

Permalink
zoomin as tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
ovysotska committed Aug 6, 2023
1 parent ca22c85 commit f35d633
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 108 deletions.
1 change: 1 addition & 0 deletions src/viewer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function App() {
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
}}
>
<div>
Expand Down
218 changes: 119 additions & 99 deletions src/viewer/src/ImageCostMatrix.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import React, { forwardRef } from "react";
import { useState, useRef, useEffect } from "react";

const kZoomWindowPx = 25;
const kZoomWindowPx = 30;

type ZoomBlockParams = {
topLeftX: number; // column
Expand All @@ -10,6 +10,68 @@ type ZoomBlockParams = {
windowHeightPx: number;
};

type ZoomTooltipProps = {
coordX?: string;
coordY?: string;
};

const ZoomTooltip = forwardRef((props: ZoomTooltipProps, pixelZoomRef: any) => {
useEffect(() => {
if (pixelZoomRef.current != null) {
pixelZoomRef.current.width = 300;
pixelZoomRef.current.height = 300;

const zoomContext = pixelZoomRef.current.getContext("2d");
if (zoomContext != null) {
zoomContext.imageSmoothingEnabled = false;
}
}
}, [pixelZoomRef]);

return (
<div
className="ZoomView"
style={{
margin: "10px",
position: "absolute",
top: props.coordY + "px",
left: props.coordX + "px",
border: "3px solid pink",
visibility: props.coordX && props.coordY ? "visible" : "hidden",
}}
>
<canvas ref={pixelZoomRef}></canvas>
</div>
);
});

function drawZoomBoxInContext(
imageCanvas: HTMLCanvasElement,
pixelZoomCanvas: HTMLCanvasElement,
zoomBbox: ZoomBlockParams
) {
if (imageCanvas == null || pixelZoomCanvas == null) {
return;
}

if (pixelZoomCanvas != null) {
const zoomContext = pixelZoomCanvas.getContext("2d");
if (zoomContext != null) {
zoomContext.drawImage(
imageCanvas,
zoomBbox.topLeftX,
zoomBbox.topLeftY,
zoomBbox.windowWidthPx,
zoomBbox.windowHeightPx,
0,
0,
pixelZoomCanvas.width,
pixelZoomCanvas.height
);
}
}
}

type ImageCostMatrixProps = {
image: ImageBitmap;
setZoomParams: (zoomParams: ZoomBlockParams) => void;
Expand All @@ -19,6 +81,10 @@ type ImageSize = {
width: number;
height: number;
};
type HoverCoords = {
screenX: number;
screenY: number;
};

function ImageCostMatrix(props: ImageCostMatrixProps): React.ReactElement {
const imageCanvasRef = useRef<HTMLCanvasElement>(null);
Expand All @@ -27,46 +93,14 @@ function ImageCostMatrix(props: ImageCostMatrixProps): React.ReactElement {
width: 0,
height: 0,
});

function onHoverOverImage(event: any) {
let x, y;
x = event.nativeEvent.layerX;
y = event.nativeEvent.layerY;

if (pixelZoomRef.current != null) {
const canvas = pixelZoomRef.current;
const context = canvas.getContext("2d");
if (context != null) {
drawZoomBoxInContext(context, x, y);
}
}
}

function onClick(event: any) {
const x = event.nativeEvent.layerX;
const y = event.nativeEvent.layerY;
const topLeftX = Math.floor(Math.max(0, x - kZoomWindowPx / 2));
const topLeftY = Math.floor(Math.max(0, y - kZoomWindowPx / 2));
props.setZoomParams({
topLeftX: topLeftX,
topLeftY: topLeftY,
windowWidthPx: kZoomWindowPx,
windowHeightPx: kZoomWindowPx,
});
}
const [hoverCoords, setHoverCoords] = useState<HoverCoords>();

// Assumptions: Canvas size is adapted to image size.
// Zoom image is always rescaled to 300px x 300px
useEffect(() => {
// Default for Firefox area:
// canvasRef.current.width = 300;
// canvasRef.current.height = 150;
if (pixelZoomRef.current == null) {
return;
}

pixelZoomRef.current.width = 300;
pixelZoomRef.current.height = 300;

if (props.image == null) {
console.error("Image is empty.");
Expand All @@ -92,65 +126,55 @@ function ImageCostMatrix(props: ImageCostMatrixProps): React.ReactElement {

// Draw image of exact size.
imageContext.drawImage(props.image, 0, 0);

const zoomContext = pixelZoomRef.current.getContext("2d");
if (zoomContext != null) {
zoomContext.imageSmoothingEnabled = false;
}
}, [props.image]);

const drawZoomBoxInContext = (
context: CanvasRenderingContext2D,
xCenterPx: number,
yCenterPx: number
) => {
if (imageSize == null) {
return;
}
const zoomBbox = {
xStart: Math.min(
Math.max(0, xCenterPx - kZoomWindowPx / 2),
imageSize.width - kZoomWindowPx
),
yStart: Math.min(
Math.max(0, yCenterPx - kZoomWindowPx / 2),
imageSize.height - kZoomWindowPx
),
width: kZoomWindowPx,
heigt: kZoomWindowPx,
};

if (imageCanvasRef.current == null || pixelZoomRef.current == null) {
return;
function onHoverOverImage(event: any) {
const xCenterPx = event.nativeEvent.layerX;
const yCenterPx = event.nativeEvent.layerY;

setHoverCoords({ screenX: event.clientX, screenY: event.clientY });

if (pixelZoomRef.current != null && imageCanvasRef.current != null) {
// const canvas = pixelZoomRef.current;
const zoomBbox: ZoomBlockParams = {
topLeftX: Math.min(
Math.max(0, xCenterPx - kZoomWindowPx / 2),
imageSize.width - kZoomWindowPx
),
topLeftY: Math.min(
Math.max(0, yCenterPx - kZoomWindowPx / 2),
imageSize.height - kZoomWindowPx
),
windowWidthPx: kZoomWindowPx,
windowHeightPx: kZoomWindowPx,
};

drawZoomBoxInContext(
imageCanvasRef.current,
pixelZoomRef.current,
zoomBbox
);
}
}

context.drawImage(
imageCanvasRef.current,
zoomBbox.xStart,
zoomBbox.yStart,
zoomBbox.width,
zoomBbox.heigt,
0,
0,
pixelZoomRef.current.width,
pixelZoomRef.current.height
);
};
function onClick(event: any) {
const x = event.nativeEvent.layerX;
const y = event.nativeEvent.layerY;
const topLeftX = Math.floor(Math.max(0, x - kZoomWindowPx / 2));
const topLeftY = Math.floor(Math.max(0, y - kZoomWindowPx / 2));
props.setZoomParams({
topLeftX: topLeftX,
topLeftY: topLeftY,
windowWidthPx: kZoomWindowPx,
windowHeightPx: kZoomWindowPx,
});
}

return (
<div
style={{
display: "flex",
flexDirection: "row",
gap: "10px",
justifyContent: "center",
backgroundColor: "ghostwhite",
}}
>
<div>
<div
className="costMatrix"
style={{
maxWidth: "50%",
maxHeight: "500px",
overflow: "auto",
margin: "10px",
Expand All @@ -159,23 +183,19 @@ function ImageCostMatrix(props: ImageCostMatrixProps): React.ReactElement {
<canvas
ref={imageCanvasRef}
onMouseMove={onHoverOverImage}
onMouseLeave={() => {
setHoverCoords(undefined);
}}
onClick={onClick}
/>
</div>

<div
className="ZoomView"
style={{
display: "flex",
flexDirection: "column",
width: "40%",
margin: "10px",
maxWidth: "400px",
}}
>
<h3 style={{ textAlign: "center" }}> Zoom In View</h3>
<canvas ref={pixelZoomRef}></canvas>
</div>
{hoverCoords && (
<ZoomTooltip
coordX={hoverCoords.screenX.toFixed(0)}
coordY={hoverCoords.screenY.toFixed(0)}
ref={pixelZoomRef}
></ZoomTooltip>
)}
</div>
);
}
Expand Down
30 changes: 21 additions & 9 deletions src/viewer/src/InteractiveCostMatrix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,27 @@ function InteractiveCostMatrix(props: InteractiveCostMatrixProps) {
}, [props, cellSize, onCellHover, onCellUnHover]);

return (
<div>
<svg
ref={svgRef}
style={{
backgroundColor: "lightpink",
width: "400px",
height: "400px",
}}
></svg>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<div>
<h3> Interactive Zoom in</h3>
</div>
<div>
<svg
ref={svgRef}
style={{
backgroundColor: "salmon",
width: "400px",
height: "400px",
margin: "10px",
}}
></svg>
</div>
{tooltipVisible && tooltipProps && <Tooltip {...tooltipProps} />}
</div>
);
Expand Down

0 comments on commit f35d633

Please sign in to comment.