Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to control alignment in fitSelection() and "zoom in" tool #225

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
| customToolbar | - | Component | Override toolbar component |
| toolbarProps | {} | Object | Toolbar settings |
| toolbarProps.position | `right` | one of `none`, `top`, `right`, `bottom`, `left` | Toolbar position |
| toolbarProps.SVGAlignX | `left` | one of `left`, `center`, `right` | X Alignment used for "Fit to Viewer" action |
| toolbarProps.SVGAlignY | `top` | one of `top`, `center`, `bottom` | Y Alignment used for "Fit to Viewer" action |
| toolbarProps.SVGAlignX | `left` | one of `left`, `center`, `right`, `cover` | X Alignment used for "Fit to Viewer" and "Zoom in" actions |
| toolbarProps.SVGAlignY | `top` | one of `top`, `center`, `bottom`, `cover` | Y Alignment used for "Fit to Viewer" and "Zoom in" actions |
| toolbarProps.activeToolColor | `#1CA6FC` | String | Color of active and hovered tool icons |

\* handler available only with the tool `none` or `auto`
Expand All @@ -57,8 +57,8 @@
|-----|------|
| `pan(SVGDeltaX, SVGDeltaY)` | Apply a pan |
| `zoom(SVGPointX, SVGPointY, scaleFactor)` | Zoom in or out the SVG |
| `fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight)`| Fit an SVG area to viewer |
| `fitToViewer(SVGAlignX = "left", SVGAlignY = "top")` | Fit all SVG to Viewer (`SVGAlignX`: one of `left`, `center`, `right`, `SVGAlignY`: one of `top`, `center`, `bottom`) |
| `fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX = "left", SVGAlignY = "top")`| Fit an SVG area to viewer (`SVGAlignX`: one of `left`, `center`, `right`, `cover`, `SVGAlignY`: one of `top`, `center`, `bottom`, `cover`) |
| `fitToViewer(SVGAlignX = "left", SVGAlignY = "top")` | Fit all SVG to Viewer (`SVGAlignX`: one of `left`, `center`, `right`, `cover`, `SVGAlignY`: one of `top`, `center`, `bottom`, `cover`) |
| `setPointOnViewerCenter(SVGPointX, SVGPointY, zoomLevel)`| Set a point on Viewer center |
| `reset()` | Reset Viewer view to default |
| `zoomOnViewerCenter(scaleFactor)` | Zoom SVG on center |
Expand Down
6 changes: 3 additions & 3 deletions src/features/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function onMouseMove(event, ViewerDOM, tool, value, props, coords = null)
switch (tool) {
case TOOL_ZOOM_IN:
if (value.mode === MODE_ZOOMING)
nextValue = forceExit ? stopZooming(value, x, y, props.scaleFactor, props) : updateZooming(value, x, y);
nextValue = forceExit ? stopZooming(value, x, y, props.scaleFactor, props.toolbarProps?.SVGAlignX, props.toolbarProps?.SVGAlignY) : updateZooming(value, x, y);
break;

case TOOL_AUTO:
Expand All @@ -73,12 +73,12 @@ export function onMouseUp(event, ViewerDOM, tool, value, props, coords = null) {
switch (tool) {
case TOOL_ZOOM_OUT:
if (value.mode === MODE_ZOOMING)
nextValue = stopZooming(value, x, y, 1 / props.scaleFactor, props);
nextValue = stopZooming(value, x, y, 1 / props.scaleFactor, props.toolbarProps?.SVGAlignX, props.toolbarProps?.SVGAlignY);
break;

case TOOL_ZOOM_IN:
if (value.mode === MODE_ZOOMING)
nextValue = stopZooming(value, x, y, props.scaleFactor, props);
nextValue = stopZooming(value, x, y, props.scaleFactor, props.toolbarProps?.SVGAlignX, props.toolbarProps?.SVGAlignY);
break;

case TOOL_AUTO:
Expand Down
79 changes: 24 additions & 55 deletions src/features/zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,105 +57,69 @@ export function zoom(value, SVGPointX, SVGPointY, scaleFactor) {
}, ACTION_ZOOM);
}

//ENHANCEMENT: add ability to control alignment
//ENHANCEMENT: add ability to selectively fit image inside viewer
//ENHANCEMENT: refactor some logic in order to merge with fitToViewer function
export function fitSelection(value, selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight) {
export function fitSelection(value, selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) {
let {viewerWidth, viewerHeight} = value;

let scaleX = viewerWidth / selectionWidth;
let scaleY = viewerHeight / selectionHeight;

let scaleLevel = Math.min(scaleX, scaleY);

const matrix = transform(
scale(scaleLevel, scaleLevel), //2
translate(-selectionSVGPointX, -selectionSVGPointY) //1
);

if(isZoomLevelGoingOutOfBounds(value, scaleLevel / value.d)) {
// Do not allow scale and translation
return set(value, {
mode: MODE_IDLE,
startX: null,
startY: null,
endX: null,
endY: null
});
}

return set(value, {
mode: MODE_IDLE,
...limitZoomLevel(value, matrix),
startX: null,
startY: null,
endX: null,
endY: null
}, ACTION_ZOOM);
}

export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) {
let {viewerWidth, viewerHeight, SVGMinX, SVGMinY, SVGWidth, SVGHeight} = value;

let scaleX = viewerWidth / SVGWidth;
let scaleY = viewerHeight / SVGHeight;
let scaleLevel = Math.min(scaleX, scaleY);

let scaleMatrix = scale(scaleLevel, scaleLevel);

let translateX = -SVGMinX * scaleX;
let translateY = -SVGMinY * scaleY;
let translateX = -selectionSVGPointX * scaleX;
let translateY = -selectionSVGPointY * scaleY;

// after fitting, SVG and the viewer will match in width (1) or in height (2) or SVG will cover the container with preserving aspect ratio (0)
if (scaleX < scaleY) {
let remainderY = viewerHeight - scaleX * SVGHeight;
// after fitting, SVG and the viewer will match in width (1) or in height (2) or SVG will cover the container with preserving aspect ratio (0)
if (scaleX < scaleY) {
let remainderY = viewerHeight - scaleX * selectionHeight;

//(1) match in width, meaning scaled SVGHeight <= viewerHeight
switch(SVGAlignY) {
case ALIGN_TOP:
translateY = -SVGMinY * scaleLevel;
translateY = -selectionSVGPointY * scaleLevel;
break;

case ALIGN_CENTER:
translateY = Math.round(remainderY / 2) - SVGMinY * scaleLevel;
translateY = Math.round(remainderY / 2) - selectionSVGPointY * scaleLevel;
break;

case ALIGN_BOTTOM:
translateY = remainderY - SVGMinY * scaleLevel;
translateY = remainderY - selectionSVGPointY * scaleLevel;
break;

case ALIGN_COVER:
scaleMatrix = scale(scaleY, scaleY); // (0) we must now match to short edge, in this case - height
let remainderX = viewerWidth - scaleY * SVGWidth; // calculate remainder in the other scale
let remainderX = viewerWidth - scaleY * selectionWidth; // calculate remainder in the other scale

translateX = SVGMinX + Math.round(remainderX / 2); // center by the long edge
translateX = selectionSVGPointX + Math.round(remainderX / 2); // center by the long edge
break;

default:
//no op
}
} else {
let remainderX = viewerWidth - scaleY * SVGWidth;
let remainderX = viewerWidth - scaleY * selectionWidth;

//(2) match in height, meaning scaled SVGWidth <= viewerWidth
switch(SVGAlignX) {
case ALIGN_LEFT:
translateX = -SVGMinX * scaleLevel;
translateX = -selectionSVGPointX * scaleLevel;
break;

case ALIGN_CENTER:
translateX = Math.round(remainderX / 2) - SVGMinX * scaleLevel;
translateX = Math.round(remainderX / 2) - selectionSVGPointX * scaleLevel;
break;

case ALIGN_RIGHT:
translateX = remainderX - SVGMinX * scaleLevel;
translateX = remainderX - selectionSVGPointX * scaleLevel;
break;

case ALIGN_COVER:
scaleMatrix = scale(scaleX, scaleX); // (0) we must now match to short edge, in this case - width
let remainderY = viewerHeight - scaleX * SVGHeight; // calculate remainder in the other scale
let remainderY = viewerHeight - scaleX * selectionHeight; // calculate remainder in the other scale

translateY = SVGMinY + Math.round(remainderY / 2); // center by the long edge
translateY = selectionSVGPointY + Math.round(remainderY / 2); // center by the long edge
break;

default:
Expand Down Expand Up @@ -191,6 +155,11 @@ export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) {
}, ACTION_ZOOM);
}

export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) {
let {SVGMinX, SVGMinY, SVGWidth, SVGHeight} = value;
return fitSelection(value, SVGMinX, SVGMinY, SVGWidth, SVGHeight, SVGAlignX, SVGAlignY);
}

export function zoomOnViewerCenter(value, scaleFactor) {
let {viewerWidth, viewerHeight} = value;
let SVGPoint = getSVGPoint(value, viewerWidth / 2, viewerHeight / 2);
Expand All @@ -216,7 +185,7 @@ export function updateZooming(value, viewerX, viewerY) {
});
}

export function stopZooming(value, viewerX, viewerY, scaleFactor) {
export function stopZooming(value, viewerX, viewerY, scaleFactor, SVGAlignX, SVGAlignY) {
const TOLERATED_DISTANCE = 7 //minimum distance to choose if area selection or drill down on point
let {startX, startY} = value;

Expand All @@ -225,7 +194,7 @@ export function stopZooming(value, viewerX, viewerY, scaleFactor) {

if (Math.abs(startX - viewerX) > TOLERATED_DISTANCE && Math.abs(startY - viewerY) > TOLERATED_DISTANCE) {
let box = calculateBox(start, end);
return fitSelection(value, box.x, box.y, box.width, box.height);
return fitSelection(value, box.x, box.y, box.width, box.height, SVGAlignX, SVGAlignY);
} else {
let SVGPoint = getSVGPoint(value, viewerX, viewerY);
return zoom(value, SVGPoint.x, SVGPoint.y, scaleFactor);
Expand Down
6 changes: 3 additions & 3 deletions src/uncontrolled-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export default class UncontrolledReactSVGPanZoom extends React.Component {
this.Viewer.zoom(SVGPointX, SVGPointY, scaleFactor)
}

fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight) {
this.Viewer.fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight)
fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX = ALIGN_LEFT, SVGAlignY = ALIGN_TOP) {
this.Viewer.fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX, SVGAlignY)
}

fitToViewer(SVGAlignX, SVGAlignY) {
fitToViewer(SVGAlignX = ALIGN_LEFT, SVGAlignY = ALIGN_TOP) {
this.Viewer.fitToViewer(SVGAlignX, SVGAlignY)
}

Expand Down
4 changes: 2 additions & 2 deletions src/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ export default class ReactSVGPanZoom extends React.Component {
this.setValue(nextValue);
}

fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight) {
let nextValue = fitSelection(this.getValue(), selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight);
fitSelection(selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX = ALIGN_LEFT, SVGAlignY = ALIGN_TOP) {
let nextValue = fitSelection(this.getValue(), selectionSVGPointX, selectionSVGPointY, selectionWidth, selectionHeight, SVGAlignX, SVGAlignY);
this.setValue(nextValue);
}

Expand Down