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

1. GeoJSON 파일 만들기

 

  이전 글에서 설명한 대로 shp 파일을 QGIS로 드래그해서 서울시 지도 파일을 열어줍니다.

 

  레이어 창의 항목을 우클릭한 후 내보내기 → 피처를 다른 이름으로 저장을 눌러주세요.

 

  파일 이름은 원하시는 대로 설정해 주시면 되고, 포맷은 GeoJSON, 좌표계는 기본좌표계 EPSG: 4326 - WGS 84로 설정한 후에 확인을 눌러주세요. 약 2.2 MB 크기의 geojson 파일이 생생될 것입니다. 


2. GeoJSON 파일 단순화 후 TopoJSON 파일로 변환하기

 

  mapshaper 홈페이지(링크)에 접속한 후 select를 눌러서 위에서 생성한 geojson 파일을 불러와주세요.

  

  Simplify를 누른 후 Apply를 누릅니다.

 

  Settings의 값을 조절해 줍니다. 값이 작아질수록 경계선의 디테일이 점점 떨어지기 때문에 값을 조절하면서 원하는 값을 찾아줍니다. 

 

  메뉴에서 Export를 누르고 TopoJSON을 선택한 후 Export 버튼을 누르면 json 파일이 생성이 됩니다. 생성된 파일의 용량을 보면 앞에서 만든 2.2 MB 크기의 geojson파일보다 용량이 많이 줄어든 것을 볼 수 있습니다. (약 16 kb)

 

  생성된 json 파일을 열어보면 좌표값들과 이전 글에서 본 SIGUNGU_NM, SIGUNGU_CD와 같은 값들이 보일 것입니다. 

 

  Prettier로 코드를 정리하면 구조를 더 쉽게 볼 수 있습니다. Objects로 검색을 해서 위의 파일에 있는 부분을 찾아주세요. 저는 파일명을 seoulmap.geojson으로 해놔서 TopoJSON 파일을 생성할 때 프로퍼티로 seoulmap이 들어가 있습니다. 이 프로퍼티 명을 잘 기억해 주세요. React에서 json 파일을 불러올 때 프로퍼티 이름이 필요합니다. 원하는 이름으로 변경하거나 이 이름을 그대로 써도 됩니다. 이 부분은 다음 글에서 설명하도록 하겠습니다.


참고한 글

  • D3.js 를 사용하여 데이터 시각화하기 #7 우리나라 지도 그리기(svg) (링크)

1. QGIS 설치하기

 

  이전 글에서 다운로드한 지도 파일을 사용하기 위해서는 QGIS를 설치해야 합니다. QGIS는 데이터 뷰, 편집, 분석을 제공하는 크로스 플랫폼 자유-오픈 소스 데스크톱 지리 정보 체계(GIS) 응용 프로그램이라고 합니다. QGIS 공식 홈페이지(링크)에 들어가서 Download를 눌러주세요. Donate 창이 뜨면 Skip it and go to download를 누릅니다.

  

  사용하고 있는 운영체제에 맞는 Long Term Version을 다운로드합니다. 파일 용량은 3.34 버전 기준 1.2 GB로 큰 편입니다. 다운로드 속도가 느린 편이라 시간이 오래 걸릴 수도 있습니다.

 

  다운로드가 완료되었으면 설치 파일을 실행해 주세요. 계속 Next를 누르고 Install을 누르면 설치가 시작됩니다. 따로 설정해 줄 항목이 없으므로 설치가 완료되면 Finish를 눌러서 종료하면 됩니다.

 

  설치가 완료되면 프로그램을 실행해 봅니다.


2. QGIS 간단한 기능 소개

  • 지도 불러오기

 

  본격적으로 지도 데이터를 추출하기 전에 QGIS의 간단한 기능들을 소개하겠습니다. 먼저 다운로드한 파일 중 shp 확장자 파일을 끌어서 QGIS 프로그램에 붙여 넣어주세요.

 

  위와 같이 서울시 지도가 표시됩니다. 여기에는 지도뿐만 아니라 동 이름, 동 코드 등의 데이터가 함께 들어가 있습니다. 이 데이터들을 이용해 서울시 열린데이터광장과 같은 오픈 API 사이트에서 받은 데이터와 연결하여 다양한 지도를 만들 수 있습니다.

  • 라벨 표시하기

 

  다음은 지도에 라벨을 표시해 보겠습니다. QGIS창의 레이어 항목에서 'bnd_sigungu_11_2023_2023_2Q' 텍스트를 더블클릭하거나 마우스 우 클릭 후 '속성'을 누르면 지도를 표시하는 창이 뜹니다. 라벨 메뉴에서 단일 라벨을 선택해 줍니다. 값 항목에 있는 'SIGUNGU_NM'은 시군구 이름, 'SIGUNGU_CD'는 시군구 코드입니다. 'SIGUNGU_NM'을 선택하고 적용을 눌러줍니다.

 

  적용을 누르면 위와 같이 지도에 구 이름이 표시됩니다. 글꼴과 스타일, 크기 등을 원하는 대로 설정할 수 있습니다.

  • 지도 색상 변경하기

 

  다음은 지도의 색상을 바꿔보겠습니다. 다시 '속성' 창에 들어가서 심볼 메뉴를 선택하고 단순 채우기를 클릭합니다. 채우기 색상을 눌러서 색상을 변경하면 지도의 색상이 변경됩니다.

 

  적용을 누르면 위와 같이 지도의 색상이 변경된 것을 확인할 수 있습니다.

  • 속성 테이블 (데이터 확인 및 변경)

 

  다음으로 레이어 창의 'bnd_sigungu_11_2023_2023_2Q' 항목을 우클릭해서 '속성 테이블 열기'를 누르면 속성 테이블이 열립니다. 여기서 연필 모양의 버튼을 누르거나 Ctrl+E를 누르면 데이터를 편집할 수 있습니다.

  • 표현식을 이용해 피처 선택 (조건에 맞는 데이터 선택)

 

  '표현식을 이용해 피처 선택' 기능을 이용해 특정 조건에 맞는 데이터를 선택할 수 있습니다. 예를 들어 length(SIGUNGU_NM)=4와 같이 조건식을 입력하고 피쳐 선택을 누르면 구 이름이 네 글자인 구를 선택할 수 있습니다. 이 기능을 사용해서 오픈 API 데이터를 가져온 후 지도 데이터와 연결하여 특정 조건을 충족하는 구만 추출할 수 있습니다.

 

  여기까지 QGIS의 간단한 기능 소개를 마치겠습니다. 오픈 API 데이터를 가져와서 연동하거나 범례를 추가하는 등 여러 가지 기능이 있지만 리액트에서 지도를 표시할 때 필요한 기능은 아니기 때문에 생략하고 다음 글에서부터 본격적으로 D3.js를 이용하여 지도를 표시하는 방법을 소개하겠습니다. 

1. 들어가기 전에

 

  2차 프로젝트를 진행할 때 서울시 지도를 그려서 구별로 다른 정보를 표시하도록 구현을 하고자 했습니다. 처음에는 서울시 지도 이미지를 넣고 좌표 값마다 링크를 다르게 주는 방식으로 구현을 하고자 했으나 현업에서 일하고 있는 친구가 D3.js를 추천해 주었고 직접 구현을 해보니 너무 만족스러웠습니다. 3D 디자인은 프론트엔드 하시는 다른 팀원분께서 지도를 여러 번 겹쳐서 만들어주셔서 멋진 지도를 구현할 수 있었습니다. 앞으로 위 이미지와 같이 QGIS와 D3.js를 이용해서 서울시 지도를 구현하는 방법을 설명드리겠습니다.


2. 통계지리정보서비스 홈페이지에서 지도 데이터 다운 받기

 

  먼저 통계지리정보서비스 홈페이지(링크)에 접속한 후에 회원가입을 합니다. 회원가입 완료 후 자료제공 → 자료 신청 메뉴로 들어가 주세요.

 

  기본 정보에 소속과 연락처, 메일주소, 요청 목적, 활용 목적, 수행과제를 적습니다. 저는 소속은 민간, 요청목적은 지도 작성을 선택했습니다. 활용 목적과 수행과제는 대충 적으면 됩니다. 어차피 자동 승인되기 때문에 대충 적어도 승인이 됩니다. 제출 동의 및 자료제공기간에 동의를 하고 자료 선택에서는 통계지역경계, 자료대상은 센서스용 행정구역경계(시군구)를 선택해 주세요. 경계년도는 가장 최신 것을 선택하고 시도/시군구는 서울, 전체를 선택한 후 추가 버튼을 눌러주세요. 추가가 끝났으면 신청 완료를 눌러주세요.

 

  10분 정도 기다린 후 신청 내역을 보면 진행상태가 승인으로 바뀝니다. 신청자료 다운로드 메뉴로 가서 다운로드를 눌러주세요. 다운로드가 완료되면 압축을 풀어줍니다. QGIS를 설치하고 다운로드한 지도 데이터를 불러오는 방법은 다음 글에서 설명드리겠습니다.

  2차 프로젝트를 진행하면서 DB에 있는 전체 데이터를 보여주는 Table을 만들었습니다. MUI Data Grid와 같은 좋은 라이브러리들이 있지만, 아직 TypeScript를 공부하지 않아서 사용을 하지 못했습니다. 그래서 임시방편으로 table 태그를 이용해 직접 표를 만들었습니다. 코드가 지저분해지고 많은 기능을 넣을 수는 없어서 아쉬웠지만, 직접 table을 만들어 보는 것도 좋은 경험이었습니다.

  표를 다 만든 후에, th 항목에 마우스를 올리면 설명이 표시되게 하고 싶었는데, 마침 Radix Tooltip이라는 좋은 라이브러리를 찾아서 아래와 같이 구현할 수 있었습니다. 사용법도 간단합니다.


1. 설치

// install
npm install @radix-ui/react-tooltip

// import
import * as Tooltip from '@radix-ui/react-tooltip';

 

  터미널에 위와 같이 입력해서 설치한 후, Radix Tooltip을 사용할 페이지에서 import 해주세요.


2. 구조

import React from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import { PlusIcon } from '@radix-ui/react-icons';
import './styles.css';

const TooltipDemo = () => {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button className="IconButton">
            <PlusIcon />
          </button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content className="TooltipContent" sideOffset={5}>
            Add to library
            <Tooltip.Arrow className="TooltipArrow" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
};

export default TooltipDemo;

 

  Radix Tooltip 페이지에서 제공하는 Demo Code입니다. Tooltip.Provier 태그가 전체를 감싸고 있고, 그 아래를 Tooltip.Root 태그가 감싸고 있습니다. Tooltip.Root 태그는 Tooltip.Trigger, Tooltip.Portal 태그를 감싸고 있는 구조입니다. 정리하면 다음과 같습니다.

 

  • Tooltip.Provider : 전체 Tooltip을 감싸는 태그
  • Tooltip.Root : Tooltip이 표시될 th 하나를 감싸는 태그
    • Tooltip.Trigger : Tooltip이 작동될 th 요소가 있는 태그
    • Tooltip.Portal : Tooltip event가 작동되면 표시될 메시지가 있는 Tooltip.Content 태그를 감싸는 태그

  전체적인 구조는 위와 같고, 사용하실 때는 Tooltip.Provider로 Tooltip이 들어갈 태그 전체를 감싸신 후에, th 하나마다 Tooltip.Root로 감싸주고, 그 안에 Tooltip.Trigger와 Tooltip.Portal로 나눠주시면 됩니다.


3. 사용 예시

<Tooltip.Provider delayDuration={0}>                              // Provider
  <Tooltip.Root>                                                    // Root 1
    <Tooltip.Trigger asChild>                                         // Trigger
      <th
        className='academyCount'
        onClick={(e) => handleThClick(e.target.className)}
      >
        *학원 수{getSortIcon('academyCount', sortColumn, sort)}
      </th>
    </Tooltip.Trigger>
    <Tooltip.Portal>                                                   // Portal
      <Tooltip.Content className='TooltipContent' sideOffset={10}>
        학원 수 (평생직업 교육학원), 자치구 기준
        <Tooltip.Arrow className='TooltipArrow' />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
  <Tooltip.Root>                                                    // Root 2
    <Tooltip.Trigger asChild>                                          // Trigger
      <th
        className='libraryCount'
        onClick={(e) => handleThClick(e.target.className)}
      >
        *도서관 수{getSortIcon('libraryCount', sortColumn, sort)}
      </th>
    </Tooltip.Trigger>                                                  // Portal
    <Tooltip.Portal>
      <Tooltip.Content className='TooltipContent' sideOffset={10}>
        도서관 (공공도서관, 작은도서관, 장애인도서관)
        <Tooltip.Arrow className='TooltipArrow' />
      </Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>;

 

  예시 코드를 보시면, 전체 Tooltip 태그를 Tooltip.Provider가 감싸고 있고, Tooltip.Root 태그 두 개를 사용하여 두 개의 th 태그에 tooltip을 표시하고 있습니다. Tooltip.Trigger 안에는 th 태그 내용이 들어가 있고, Tooltip.Portal 안에는 tooltip에 표시될 Tooltip.Content가 있습니다. 코드가 길어질 뿐, 코드 자체는 간단합니다.


4. CSS 적용

 

  CSS를 적용하려면 CSS 파일을 생성한 후에, 공식 홈페이지에 있는 CSS 내용을 복사 붙여넣기 해주시고, css 파일을 import 해주시면 됩니다.


5. 주요 옵션

<Tooltip.Provider delayDuration={0} disableHoverableContent={false}> // 사용 예시

1. Provider

1) delayDuration (기본값: 700)

  요소 위에 마우스를 올려놨을 때, 몇 초 후에 tooltip이 표시될지 선택하는 옵션입니다. 저는 바로 뜨기를 원해서 0으로 설정했습니다.

 

2) skipDelayDuration (기본값: 300)

  tooltip 요소가 표시된 후 다른 tooltip으로 이동했을 때 설정한 delayDuration을 Reset 하는 시간을 설정합니다. 예를 들어, 학원 수에 마우스를 올려서 tooltip이 표시되었으면, skipDelayDuration에서 설정한 시간 내(300)에 다른 요소로 이동하면 바로 tooltip이 뜹니다. 하지만 skipDelayDuration에서 설정한 시간 이후에 다시 tooltip에 마우스를 올린다면, delayDuration에서 설정한 시간 후에 tooltip이 표시됩니다. skipDelayDuration을 0으로 설정한다면, 마우스를 이동할 때마다 항상 delayDuration에서 설정한 시간 후에 tooltip이 표시될 것입니다.

 

3) disableHoverableContent (Boolean 값)

  이 옵션을 false로 설정하면 tooltip 메시지 위에 마우스를 올려놓아도, tooltip이 사라지지 않지만, true로 설정하면 tooltip 메시지에 마우스를 올려놓으면 tooltip이 사라집니다. true로 설정하면 접근성에 영향을 미칠 수도 있다고 하니 기본값인 false로 설정하는 게 좋습니다.

<Tooltip.Provider delayDuration={0} disableHoverableContent={true}> // 사용 예시

2. Root

1) defaultOpen (Boolean 값)

  true로 설정하면 처음 랜더링 될 때 tooltip이 표시됩니다.

<Tooltip.Root defaultOpen={true}> // 사용 예시

3. Content

1) sideOffset (기본값: 0)

  Trigger 요소로부터 위로 얼마만큼 떨어져서 표시할 것 인지 설정합니다. 설정한 숫자가 표시할 수 있는 높이를 초과하면 아래로 표시가 됩니다.

 

2) align (기본값: 'center, ENUM = ['start', 'center', 'end']), alignOffset (기본값: 0)

  Trigger 요소로부터 왼쪽이나 오른쪽으로 얼마만큼 떨어져서 표시할 것인지 설정합니다. 기본값은 center이므로, align 값을 생략하고 alignOffset 값을 입력하면 적용이 되지 않지만, align을 'start'로 설정한 후에 alignOffset 값을 입력하면, 입력한 값만큼 오른쪽으로 표시가 됩니다. 반대로 align 값을 'end'로 설정하면 alignOffset에서 입력한 값만큼 왼쪽에서 표시가 됩니다.

<Tooltip.Content sideOffset={10} align='start' alignOffset={50}>

 

  그 외에 여러 가지 옵션들이 있지만, 기본 옵션만으로도 멋진 tooltip을 구현할 수 있기 때문에 생략하겠습니다. 필요한 옵션이 있다면 공식 문서를 참고해 주세요.


참고 문헌

1. Tooltip - Radix Primitives

2. github 예제 코드

WARNING in [eslint]
src\App.js
  Line 22:48:  Array.prototype.map() expects a return value from arrow function  array-callback-return

 

  React에서 map 함수를 사용했을 때 결과가 제대로 나오지 않으면서 "Array.prototype.map() expects a return value from arrow function"이라는 에러가 발생할 때가 있습니다. React는 JSX문법을 사용하는데, HTML 태그를 리턴해주는 경우 JavaScript에서 사용했을 때 처럼 중괄호를 이용해서 map 함수를 작성하면 위의 에러가 발생합니다.

// JavaScript
const A = [1, 2, 3, 4, 5];

A.map((data) => {
  console.log(`${data}번 째 console.log`);
  console.log(data);
});

// JSX
const Breeds = (props) => {
  return (
    <dl>
      {props.list.map(({ breed, description }) => {      // 중괄호({ })가 아닌 괄호(( ))를 써야 함
        <Fragment key={breed}>
          <dt>{breed}</dt>
          <dd>{description}</dd>
        </Fragment>;
      })}
    </dl>
  );
};

 

  JavaScript에서 map 함수를 이용해서 여러 줄을 반환할 때 중괄호를 써야 하지만, JSX에서는 중괄호를 괄호로 바꿔주어야 에러가 발생하지 않고 함수가 제대로 실행됩니다.

import { Fragment } from "react";

const Breeds = (props) => {
  return (
    <dl>
      {props.list.map(({ breed, description }) => (    // 코드 수정한 부분
        <Fragment key={breed}>
          <dt>{breed}</dt>
          <dd>{description}</dd>
        </Fragment>;
      ))}    // 코드 수정한 부분
    </dl>
  );
};

const lists = [
  { breed: "치와와", description: "작은 품종의 개." },
  { breed: "코기", description: "귀여운 품종의 개." },
  { breed: "컴벌랜드 시프도그", description: "멸종된 품종의 개." },
];

function App() {
  return <Breeds list={lists} />;
}

export default App;

 

  이렇게 코드를 수정해주면 lists의 객체들이 제대로 화면에 표시되는 것을 볼 수 있습니다. Breeds 함수의 return 부분 처럼 여러 줄의 JSX 표현식을 반환할 때는 괄호를 사용해야 하기 때문에, map 함수를 이용해서 태그를 반환해줄 때에도 중괄호가 아닌 괄호를 사용해야 합니다.

+ Recent posts