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) (링크)

+ Recent posts