Move zoom methods into a hook.

This commit is contained in:
Kia Lam 2022-02-22 09:36:43 -08:00
parent ef5cd66494
commit 039c038cd7
3 changed files with 82 additions and 46 deletions

View File

@ -1,5 +1,4 @@
import React, { useEffect, useCallback, useState } from 'react';
import * as d3 from 'd3';
import { t } from '@lingui/macro';
import { PageSection, Card, CardBody } from '@patternfly/react-core';
import ContentError from 'components/ContentError';
@ -7,6 +6,8 @@ import useRequest from 'hooks/useRequest';
import { MeshAPI } from 'api';
import Header from './Header';
import MeshGraph from './MeshGraph';
import useZoom from './utils/useZoom';
import { CHILDSELECTOR, PARENTSELECTOR } from './constants';
function TopologyView() {
const [showLegend, setShowLegend] = useState(true);
@ -27,52 +28,11 @@ function TopologyView() {
useEffect(() => {
fetchMeshVisualizer();
}, [fetchMeshVisualizer]);
const { zoom, zoomFit, zoomIn, zoomOut, resetZoom } = useZoom(
PARENTSELECTOR,
CHILDSELECTOR
);
const zoom = d3.zoom().on('zoom', ({ transform }) => {
d3.select('.mesh').attr('transform', transform);
});
const zoomIn = () => {
d3.select('.mesh-svg').transition().call(zoom.scaleBy, 2);
};
const zoomOut = () => {
d3.select('.mesh-svg').transition().call(zoom.scaleBy, 0.5);
};
const resetZoom = () => {
const parent = d3.select('.mesh').node().parentElement;
const width = parent.clientWidth;
const height = parent.clientHeight;
d3.select('.mesh-svg')
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity,
d3
.zoomTransform(d3.select('.mesh-svg').node())
.invert([width / 2, height / 2])
);
};
const zoomFit = () => {
const bounds = d3.select('.mesh').node().getBBox();
const parent = d3.select('.mesh').node().parentElement;
const fullWidth = parent.clientWidth;
const fullHeight = parent.clientHeight;
const { width, height } = bounds;
const midX = bounds.x + width / 2;
const midY = bounds.y + height / 2;
if (width === 0 || height === 0) return; // nothing to fit
const scale = 0.8 / Math.max(width / fullWidth, height / fullHeight);
const translate = [
fullWidth / 2 - scale * midX,
fullHeight / 2 - scale * midY,
];
const [x, y] = translate;
d3.select('.mesh-svg')
.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale));
};
return (
<>
<Header

View File

@ -1,4 +1,6 @@
export const SELECTOR = '#chart';
export const PARENTSELECTOR = '.mesh-svg';
export const CHILDSELECTOR = '.mesh';
export const MESH_FORCE_LAYOUT = {
defaultCollisionFactor: 80,
defaultForceStrength: -100,

View File

@ -0,0 +1,74 @@
import * as d3 from 'd3';
import { getWidth, getHeight } from './helpers';
/**
* useZoom provides a collection of zoom behaviors/functions for D3 graphs
* Params: string value of parent and child classnames
* The following hierarchy should be followed:
* <div id="chart">
* <svg><-- parent -->
* <g><-- child -->
* </svg>
* </div>
* Returns: {
* zoom: d3 zoom behavior/object/function to apply on selected elements
* zoomIn: function that zooms in
* zoomOut: function that zooms out
* zoomFit: function that scales child element to fit within parent element
* resetZoom: function resets the zoom level to its initial value
* }
*/
export default function useZoom(parentSelector, childSelector) {
const zoom = d3.zoom().on('zoom', ({ transform }) => {
d3.select(childSelector).attr('transform', transform);
});
const zoomIn = () => {
d3.select(parentSelector).transition().call(zoom.scaleBy, 2);
};
const zoomOut = () => {
d3.select(parentSelector).transition().call(zoom.scaleBy, 0.5);
};
const resetZoom = () => {
const parent = d3.select(parentSelector).node();
const width = parent.clientWidth;
const height = parent.clientHeight;
d3.select(parentSelector)
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity,
d3
.zoomTransform(d3.select(parentSelector).node())
.invert([width / 2, height / 2])
);
};
const zoomFit = () => {
const bounds = d3.select(childSelector).node().getBBox();
const fullWidth = getWidth(parentSelector);
const fullHeight = getHeight(parentSelector);
const { width, height } = bounds;
const midX = bounds.x + width / 2;
const midY = bounds.y + height / 2;
if (width === 0 || height === 0) return; // nothing to fit
const scale = 0.8 / Math.max(width / fullWidth, height / fullHeight);
const translate = [
fullWidth / 2 - scale * midX,
fullHeight / 2 - scale * midY,
];
const [x, y] = translate;
d3.select(parentSelector)
.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale));
};
return {
zoom,
zoomIn,
zoomOut,
zoomFit,
resetZoom,
};
}