I am using react with d3, and it is just repliacting the map on zoom, and not zooming in place, the demo loom video can be found below,
Here is the snippet to it:
import React, { useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';
import * as topojson from 'topojson-client';
const Chart = () => {
const svgRef = useRef(null);
const [isMapLoading, setMapLoading] = useState<boolean>(false);
const [geoData, setGeoData] = useState(null);
const gRef = useRef(null);
const width = 975;
const height = 610;
const zoomed = (event) => {
const { transform } = event;
gRef.current
.attr('transform', transform)
.attr('stroke-width', 1 / transform.k);
};
const zoom = d3.zoom().scaleExtent([1, 8]).on('zoom', zoomed);
const reset = (states, svg) => {
states.transition().style('fill', null);
svg
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([width / 2, height / 2])
);
};
const clicked = (event, d, states) => {
const path = d3.geoPath();
const svg = d3.select(svgRef.current);
const [[x0, y0], [x1, y1]] = path.bounds(d);
event.stopPropagation();
states.transition().style('fill', null);
d3.select(this).transition().style('fill', 'red');
svg
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(
Math.min(8, 0.9 / Math.max((x1 - x0) / width, (y1 - y0) / height))
)
.translate(-(x0 + x1) / 2, -(y0 + y1) / 2),
d3.pointer(event, svg.node())
);
};
useEffect(() => {
setMapLoading(true);
d3.json('geojson/regions/south_america.json')
.then((data) => {
setGeoData(data);
setMapLoading(false);
})
.catch((err) => {
console.error(err);
});
}, []);
useEffect(() => {
if (geoData) {
const svg = d3
.select(svgRef.current)
.attr('viewBox', [0, 0, width, height])
.attr('width', '100%')
.attr('height', '100%')
// eslint-disable-next-line @typescript-eslint/no-use-before-define
.on('click', () => reset(states, svg));
const path = d3.geoPath();
const g = svg.append('g');
gRef.current = g;
const states = g
.append('g')
.attr('fill', '#444')
.attr('cursor', 'pointer')
.selectAll('path')
.data(topojson.feature(geoData, geoData.objects.states).features)
.join('path')
.on('click', (event, d) => clicked(event, d, states))
.attr('d', path);
states.append('title').text((d) => d.properties.name);
g.append('path')
.attr('fill', 'none')
.attr('stroke', 'white')
.attr('stroke-linejoin', 'round')
.attr(
'd',
path(
topojson.mesh(geoData, geoData.objects.states, (a, b) => a !== b)
)
);
}
}, [geoData]);
return <svg ref={svgRef} />;
};
const Page = () => {
return <Chart />;
};
export default Page;