개발자

d3.js 사용 시 레이블 텍스트 표시

2023년 12월 14일조회 315

리액트에서 d3.js 도넛 차트를 구현하고자 합니다. 하단 "g.append('text')" 이하 부분에서 레이블과 퍼센티지(%)를 차트 중심부에 표시되도록 설정했는데, arc 면적이 작을 경우, 레이블과 퍼센티지 전부 표시되지 않습니다. 어떻게 수정해야 할까요? (현재 해결되었습니다. 수정 후 정상적으로 표시되는 이미지도 같이 보여드립니다.)

이 질문이 도움이 되었나요?
'추천해요' 버튼을 누르면 좋은 질문이 더 많은 사람에게 노출될 수 있어요. '보충이 필요해요' 버튼을 누르면 질문자에게 질문 내용 보충을 요청하는 알림이 가요.

답변 2

삭제된 사용자님의 프로필 사진

삭제된 사용자

2023년 12월 14일

안녕하세요. d3.js를 써보진 않아서 좋은 해결책일지는 모르겠으나 레이블과 퍼센티지의 z-index를 높여서 차트 위에 덮어지게끔 그려보시는건 어떨까요?

그린티라떼님의 프로필 사진

그린티라떼

작성자

프론트엔드 개발자2023년 12월 14일

안녕하세요. 저도 z-index 속성을 지원하는지 확인해 보았는데 제공하지 않는것 같습니다. Arc의 영역이 일정 범위 이하일때 레이블과 퍼센티지를 바깥에 표시하고 꺾은선으로 영역을 연결하는 방법으로 진행해야 할것 같습니다.

그린티라떼님의 프로필 사진

그린티라떼

작성자

프론트엔드 개발자2023년 12월 15일

안녕하세요. GPT로 코드 수정을 해보았는데, 일부를 변경하여 적용하니 정상적으로 표시되었습니다. 다만, 레이블과 퍼센티지에 배치에 관한 것이 아니라 데이터 계산 방법만 변경된 것 같아서 유의미한 차이는 못느끼겠습니다.

그린티라떼님의 프로필 사진

chat GPT를 활용해서 코드를 다음과 같이 수정하였습니다. const percentage = ((d.data.count / Object.values(data).reduce((acc, val) => acc + val, 0)) * 100).toFixed(0).toString(); 부분이 const percentage = Math.round((d.data.count / Object.values(data).reduce((acc, val) => acc + val, 0)) * 100).toString(); 로 바뀌었습니다. 수정코드로 적용하니 정상적으로 표시가 되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';

interface DeviceInfo {
  device_manuf: string;
  device_memory: number;
  device_os: number;
}

interface DonutChartProps {
  data: DeviceInfo[];
  type: keyof DeviceInfo;
}

const DonutChart: React.FC<DonutChartProps> = ({ data, type }) => {
  const chartContainerRef = useRef<HTMLDivElement>(null);

  
  useEffect(() => {

      if (!data || data.length === 0) {
      return;
  }

    d3.select(chartContainerRef.current).selectAll('*').remove();

    const categoryCounts: Record<string, number> = data.reduce((acc: any, device) => {
      acc[device[type]] = (acc[device[type]] || 0) + 1;
      return acc;
    }, {});

    const sortedCategories = Object.keys(categoryCounts).sort((a, b) => categoryCounts[b] - categoryCounts[a]);
    const topCategories = sortedCategories.slice(0, 4);

    const totalCount = sortedCategories.reduce((sum, category) => sum + categoryCounts[category], 0);

    const dataForChart = topCategories.map(category => ({ category, count: categoryCounts[category] }));
    const etcCount = totalCount - dataForChart.reduce((sum, entry) => sum + entry.count, 0);


    if (sortedCategories.length > 4) {
      dataForChart.push({ category: 'ETC', count: etcCount });
    }

    const width = 280;
    const height = 400;
    const radius = Math.min(width, height) / 2;

    const colorScheme = ['#006FFD', '#ED3241', '#E86339', '#298267', '#C5C6CC'];

    const color = d3.scaleOrdinal<string>().range(colorScheme);

    const chartTitle =
      type === 'device_manuf' ? '디바이스 제조사' : type === 'device_memory' ? '디바이스 램용량' : '디바이스 OS 버전';

    const arc = d3.arc<d3.PieArcDatum<{ category: string; count: number }>>()
      .innerRadius(radius - 100)
      .outerRadius(radius - 20);

    const pie = d3.pie<{ category: string; count: number }>().value(d => d.count);

    const svg = d3.select(chartContainerRef.current)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g')
      .attr('transform', `translate(${width / 2},${height / 2})`);

    svg.append('text')
      .attr('x', 0)
      .attr('y', -height / 2 + 40)
      .text(chartTitle)
      .style('font-size', '16px')
      .style('font-weight', '900')
      .style('text-anchor', 'middle')
      .style('fill', 'black');

    const g = svg.selectAll('.arc')
      .data(pie(dataForChart))
      .enter()
      .append('g')
      .attr('class', 'arc');

    g.append('path')
      .attr('d', arc)
      .style('fill', d => color(d.data.category));

    g.append('text')
      .attr('transform', d => `translate(${arc.centroid(d)})`)
      .attr('dy', '-0.5em')
      .style('text-anchor', 'middle')
      .style('fill', 'white')
      .html(d => {
        const lines = `${d.data.category}\n${Math.round((d.data.count / totalCount) * 100)}%`.split('\n');
        return lines
          .map((line, index) => `<tspan x="0" dy="${index === 0 ? '0' : '1.2em'}">${line}</tspan>`)
          .join('');
      });
  }, [data, type]);

  return <div ref={chartContainerRef}></div>;
};

export default DonutChart;

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

목록으로
키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!