1. 전체 코드
import * as d3 from 'd3';
import { useRef, useEffect } from 'react';
import { feature } from 'topojson-client';
import seoul from './data/seoulmap.json'; // 파일 경로, 이름에 맞게 수정
const featureData = feature(seoul, seoul.objects['seoulmap']); // objects 이름 맞게 수정
function App() {
const chart = useRef(null);
const printD3 = () => {
const width = 700; // 지도 넓이
const height = 700; // 지도 높이
// 메르카토르 투영법 설정
const projection = d3.geoMercator().scale(1).translate([0, 0]);
const path = d3.geoPath().projection(projection);
const bounds = path.bounds(featureData);
const dx = bounds[1][0] - bounds[0][0];
const dy = bounds[1][1] - bounds[0][1];
const x = (bounds[0][0] + bounds[1][0]) / 2;
const y = (bounds[0][1] + bounds[1][1]) / 2;
const scale = 0.9 / Math.max(dx / width, dy / height);
const translate = [width / 2 - scale * x, height / 2 - scale * y];
projection.scale(scale).translate(translate);
const svg = d3
.select(chart.current)
.append('svg')
.attr('width', width)
.attr('height', height);
const mapLayer = svg.append('g');
mapLayer
.selectAll('path')
.data(featureData.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', '#5b5ba0') // 배경 색상
.style('stroke', '#ffffff') // 경계선 색상
.style('stroke-width', 0.2) // 경계선 굵기
.on('mouseover', function () {
d3.select(this).style('fill', '#212168'); // 마우스 올라갔을 때 색상
})
.on('mouseout', function () {
d3.select(this).style('fill', '#5b5ba0'); // 마우스 뗐을 때 색상
})
.on('click', function (event, d) {
alert('클릭된 요소: ' + d.properties.SIGUNGU_NM); // 클릭했을 때 이벤트
});
mapLayer
.selectAll('.gu-label')
.data(featureData.features)
.enter()
.append('text')
.attr('class', 'gu-label') // 클래스 이름
.attr('x', (d) => path.centroid(d)[0])
.attr('y', (d) => path.centroid(d)[1])
.attr('dx', '-0.9em') // x 좌표 위치 조정
.attr('dy', '0.4em') // y 좌표 위치 조정
.text((d) => d.properties.SIGUNGU_NM) // 표시할 라벨 텍스트 항목 설정
.style('fill', '#ffffff') // 텍스트 색상 설정
.style('font-size', '13px') // 텍스트 크기 설정
.on('click', function (event, d) {
alert('클릭된 요소: ' + d.properties.SIGUNGU_NM); // 클릭했을 때 이벤트
});
};
useEffect(() => {
printD3();
// 정리 함수
return () => {
d3.select(chart.current).select('svg').remove();
};
}, []);
return <div ref={chart}></div>;
}
export default App;
2. 코드 설명
npm install d3
npm install topojson-client
먼저 d3, topojson-client 라이브러리를 설치해 줍니다.
import * as d3 from 'd3';
import { useRef, useEffect } from 'react';
import { feature } from 'topojson-client';
import seoul from './data/seoulmap.json'; // 파일 경로, 이름에 맞게 수정
위의 요소들을 import 해주세요. 저는 소스 파일이 있는 폴더 안에 data 폴더를 만들고 그 안에 seoulmap.json 파일을 넣어서 경로를 이렇게 설정했습니다.
const featureData = feature(seoul, seoul.objects['seoulmap']); // objects 이름 맞게 수정
seoul은 위에서 json 파일을 import한 이름, objects의 프로퍼티 이름은 이전 글을 참고해서 적어줍니다. 저는 프로퍼티 이름이 seoulmap으로 되어 있어서 이렇게 입력했습니다.
function App() {
const chart = useRef(null);
const printD3 = () => {
const width = 700; // 지도 넓이
const height = 700; // 지도 높이
// 메르카토르 투영법 설정
const projection = d3.geoMercator().scale(1).translate([0, 0]);
const path = d3.geoPath().projection(projection);
const bounds = path.bounds(featureData);
지도의 기본 넓이와 높이를 설정하는 부분입니다. 원하시는 값을 설정하시면 됩니다. 그 아래 부분은 메르카토르 투영법을 사용해서 구형 지도를 평면에 표시하는 코드입니다. 따로 값을 수정하지 않아도 됩니다.
const dx = bounds[1][0] - bounds[0][0];
const dy = bounds[1][1] - bounds[0][1];
const x = (bounds[0][0] + bounds[1][0]) / 2;
const y = (bounds[0][1] + bounds[1][1]) / 2;
const scale = 0.9 / Math.max(dx / width, dy / height);
const translate = [width / 2 - scale * x, height / 2 - scale * y];
projection.scale(scale).translate(translate);
이 부분은 지도 데이터를 SVG 크기에 맞춰서 크기를 조정하고 지도 중앙에 배치하는 코드입니다. 이 부분도 따로 수정할 부분은 없습니다.
const svg = d3
.select(chart.current)
.append('svg')
.attr('width', width)
.attr('height', height);
지도가 그려질 캔버스를 그려주는 코드입니다. <svg width="700" height="700"> 이런 코드가 만들어집니다.
const mapLayer = svg.append('g');
mapLayer
.selectAll('path')
.data(featureData.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', '#5b5ba0') // 배경 색상
.style('stroke', '#ffffff') // 경계선 색상
.style('stroke-width', 0.2) // 경계선 굵기
.on('mouseover', function () {
d3.select(this).style('fill', '#212168'); // 마우스 올라갔을 때 색상
})
.on('mouseout', function () {
d3.select(this).style('fill', '#5b5ba0'); // 마우스 뗐을 때 색상
})
.on('click', function (event, d) {
alert('클릭된 요소: ' + d.properties.SIGUNGU_NM); // 클릭했을 때 이벤트
});
지도가 그려지는 코드입니다. mouseover와 mouseout 이벤트를 이용해서 마우스 호버 시 동작을 설정할 수 있고, click 이벤트를 사용해서 클릭했을 때 이벤트를 설정할 수 있습니다. 위 코드는 지도를 클릭했을 때 구 이름이 경고창으로 뜨도록 설정을 했습니다. 구마다 경계가 나누어져 있기 때문에 구마다 다른 이벤트가 발생합니다.
mapLayer
.selectAll('.gu-label')
.data(featureData.features)
.enter()
.append('text')
.attr('class', 'gu-label') // 클래스 이름
.attr('x', (d) => path.centroid(d)[0])
.attr('y', (d) => path.centroid(d)[1])
.attr('dx', '-0.9em') // x 좌표 위치 조정
.attr('dy', '0.4em') // y 좌표 위치 조정
.text((d) => d.properties.SIGUNGU_NM) // 표시할 라벨 텍스트 항목 설정
.style('fill', '#ffffff') // 텍스트 색상 설정
.style('font-size', '13px') // 텍스트 크기 설정
.on('click', function (event, d) {
alert('클릭된 요소: ' + d.properties.SIGUNGU_NM); // 클릭했을 때 이벤트
});
};
다음은 지도의 라벨이 그려지는 코드입니다.
위와 같이 text 태그가 생성되는 것을 볼 수 있습니다. 라벨의 위치를 조정할 수 있고, 라벨 텍스트 색상과 크기를 설정할 수 있습니다.
useEffect(() => {
printD3();
// 정리 함수
return () => {
d3.select(chart.current).select('svg').remove();
};
}, []);
return <div ref={chart}></div>;
}
useEffect를 이용해 랜더링 될 때 printD3 함수를 그려줍니다. 지도가 중복 생성되는 것을 방지하기 위해서 정리함수를 이용해 이전의 svg 요소를 삭제해 줍니다.
여기까지 코드를 입력했다면 위와 같이 기본적인 서울시 지도가 만들어진 것을 볼 수 있습니다.
CSS나 React 기능들을 이용해서 지도를 여러 가지 방식으로 커스텀할 수 있습니다. 이 글을 바탕으로 자신만의 멋진 지도를 만들어보시길 바랍니다. 읽어주셔서 감사합니다.
참고한 글
- D3.js 를 사용하여 데이터 시각화하기 #7 우리나라 지도 그리기(svg) (링크)
'코딩 공부 > React' 카테고리의 다른 글
[React] QGIS, D3.js를 이용해서 서울시 지도 그리기 - 3. GeoJSON 파일 만들고 단순화하기 (0) | 2024.12.01 |
---|---|
[React] QGIS, D3.js를 이용해서 서울시 지도 그리기 - 2. QGIS 설치 및 간단한 기능 소개 (0) | 2024.11.20 |
[React] QGIS, D3.js를 이용해서 서울시 지도 그리기 - 1. 지도 데이터 받기 (0) | 2024.11.19 |
[React] Radix Tooltip을 이용한 Table Tooltip 만들기 (0) | 2024.06.26 |
[React] JSX - Array.prototype.map() expects a return value from arrow function 에러에 대해서 (0) | 2024.05.31 |