I will be building upon this example which I found too bare bones. It was also in a script tag for you to glue into a HTML document, who even does that.
Here is how I did it with React and it is zoomable and toggles opacity upon a mouse event. A cheeky bonus; a tooltip.
import * as React from 'react';
import * as d3 from 'd3';
interface Props { }
const ChoroplethMap: React.FC<Props> = props => {
const svgRef = React.useRef(null);
const [width, height] = [1600, 1000];
const countries = ['canada', 'russia', 'usa']
const availableCountry = (d: any): boolean => {
return countries.indexOf(d?.properties?.name?.toLowerCase()) > -1;
}
const countryColor = (d: any): string => {
return availableCountry(d) ? "#B5D3E7" : "grey";
}
React.useEffect(() => {
const svg = d3.select(svgRef.current)
.attr("viewBox", `0 0 ${width} ${height}`)
const everything = svg.selectAll('*');
everything.remove();
const projection = d3.geoNaturalEarth1()
.scale(320)
.translate([width / 2, height / 2])
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson").then((data: any) => {
svg.append("g")
.selectAll("path")
.data(data.features)
.join("path")
.attr("fill", (d) => countryColor(d))
.attr("d", d3.geoPath().projection(projection))
.style("stroke", "#fff")
.style("opacity", .8)
.on("mouseover", (d) => d3.select(this).attr('fill', 'black').transition().duration(200))
.on("mouseleave", (d) => d3.select(this).attr('fill', countryColor(d)).transition().duration(200))
const zoom = d3.zoom<SVGSVGElement, unknown>()
.scaleExtent([1, 4])
.translateExtent([[0, 0], [width, height]]) // Restricts panning
.on('zoom', (event) => {
svg.select("g").attr('transform', event.transform)
});
svg.call(zoom)
})
}, [])
const divStyle: React.CSSProperties = {
display: 'inline-block',
position: 'relative',
width: '100%',
verticalAlign: 'top',
overflow: 'hidden'
};
return (
<div id="world-map" className="world-map" style={divStyle}>
<svg
id="world-map-svg"
ref={svgRef}
xmlns="http://www.w3.org/2000/svg"
/>
</div>
);
};
export default ChoroplethMap;
You can largely ignore my stylings, EXCEPT FOR RELATIVE POSITIONING if you want tooltips. The most important bits would...
Experiment with the code, figure out what each attribute does. Your HTML and CSS knowledge do not carry over that much with SVGs unfortunately. You CANNOT render a text element within an svg element, do not try to embed a text box within each path element like I did.
If you want a tooltip, call this function on the mouseover event. Also add that div element as a sibling to your svg element. Text elements will not render as you expect if embedded within an SVG element.
const mapMouseOver = (event: MouseEvent, d: GeoJSON.Feature) => {
if (availableCountry(availableCountries, d)) {
d3.select("#projectmap-tooltip")
.style("opacity", 1)
.style('left', event.offsetX + 10 + 'px')
.style('top', event.offsetY - 10 + 'px')
.text(renderTooltipText(mapData, d))
// Write your own rendertooltipText function, d.properties.name will get you the country name.
}
}
// Append this div as a sibling to your svg-element, note that world-map is a div itself that contains the svg.
d3.select("#world-map")
.append("div")
.attr("id", "projectmap-tooltip")
.style("position", "absolute")
.style("opacity", 0)
I have used opacity in this case but you can also use the visibility attribute, toggle it between hidden and visible. Same effect as opacity 0 - 1 really.