개발자
리액트에서 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;
지금 가입하면 모든 질문의 답변을 볼 수 있어요!
현직자들의 명쾌한 답변을 얻을 수 있어요.
이미 회원이신가요?
지금 가입하면 모든 질문의 답변을 볼 수 있어요!