PieChart

Data Visualization

visx 기반 파이/도넛 차트.

Usage

적은 수의 범주가 전체에서 차지하는 비율을 파이 또는 도넛으로 비교할 때 사용한다.

import

import

import { PieChart } from '@mildang/design-system/Chart';

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

PieChart Props

Prop

Type

Default

data
필수

TDatum[]

지정 안 함

valueKey
필수

string

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

colors

string[]

지정 안 함

cornerRadius

number

지정 안 함

innerRadius

number | "donut"

지정 안 함

labelKey

string

지정 안 함

legendPosition

"bottom" | "left" | "right" | "top"

지정 안 함

margin

Partial<ChartMargin>

지정 안 함

padAngle

number

지정 안 함

showLabels

boolean

지정 안 함

showLegend

boolean

지정 안 함

showTooltip

boolean

지정 안 함

strokeColor

string

지정 안 함

strokeColors

string[]

지정 안 함

textColors

string[]

지정 안 함

예제

파이 vs 도넛

innerRadius="donut" 을 주면 반지름의 절반이 자동으로 비워져 가운데가 뚫린 도넛 차트가 된다.

코드

'use client';

import { PieChart } from '@mildang/design-system/Chart';
import { Text } from '@mildang/design-system/Text';
import { HStack, VStack } from '@mildang/styled-system/jsx';
import { css } from '@mildang/styled-system/css';

/**
 * `innerRadius`를 주면 가운데가 뚫린 도넛 차트가 된다. `'donut'` 은 반지름의 50%를 자동으로
 * 비워, 숫자를 직접 계산하지 않아도 된다.
 */
const data = [
  { category: '국어', value: 32 },
  { category: '수학', value: 28 },
  { category: '영어', value: 24 },
  { category: '탐구', value: 16 },
];

const frame = css({ width: '100%', height: '[220px]' });

export default function PieChartDonutExample() {
  return (
    <HStack gap="6" alignItems="flex-start" flexWrap="wrap">
      <VStack gap="2" alignItems="stretch" flex="1" minWidth="[220px]">
        <Text variant="caption-md" color="neutral.text.low">
          기본 파이
        </Text>
        <div className={frame}>
          <PieChart data={data} valueKey="value" labelKey="category" showLabels />
        </div>
      </VStack>
      <VStack gap="2" alignItems="stretch" flex="1" minWidth="[220px]">
        <Text variant="caption-md" color="neutral.text.low">
          innerRadius=&quot;donut&quot;
        </Text>
        <div className={frame}>
          <PieChart data={data} valueKey="value" labelKey="category" innerRadius="donut" showLabels />
        </div>
      </VStack>
    </HStack>
  );
}

파이 차트

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartPieExample = () => <PieChart data={subjectData} valueKey="score" labelKey="subject" showLabels />;

export default PieChartPieExample;

도넛 차트

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartDonutExample = () => (
    <PieChart
      data={subjectData}
      valueKey="score"
      labelKey="subject"
      innerRadius="donut"
      showLabels
      showLegend
    />
  );

export default PieChartDonutExample;

border 없음 (파이)

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const noStroke = subjectData.map(() => 'transparent');

const PieChartNoBorderPieExample = () => (
    <PieChart data={subjectData} valueKey="score" labelKey="subject" showLabels strokeColors={noStroke} />
  );

export default PieChartNoBorderPieExample;

border 없음 (도넛)

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const noStroke = subjectData.map(() => 'transparent');

const PieChartNoBorderDonutExample = () => (
    <PieChart
      data={subjectData}
      valueKey="score"
      labelKey="subject"
      innerRadius="donut"
      showLabels
      showLegend
      strokeColors={noStroke}
    />
  );

export default PieChartNoBorderDonutExample;

간격으로 구분 (border 없음)

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const noStroke = subjectData.map(() => 'transparent');

const PieChartWithGapExample = () => (
    <PieChart
      data={subjectData}
      valueKey="score"
      labelKey="subject"
      padAngle={0.04}
      cornerRadius={4}
      showLabels
      strokeColors={noStroke}
    />
  );

export default PieChartWithGapExample;

Hover Tooltip

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartHoverTooltipExample = () => (
    <PieChart data={subjectData} valueKey="score" labelKey="subject" innerRadius="donut" showTooltip />
  );

export default PieChartHoverTooltipExample;

범례 포함

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartWithLegendExample = () => (
    <PieChart data={subjectData} valueKey="score" labelKey="subject" innerRadius="donut" showLegend />
  );

export default PieChartWithLegendExample;

범례 오른쪽

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartWithLegendRightExample = () => (
    <PieChart
      data={subjectData}
      valueKey="score"
      labelKey="subject"
      innerRadius="donut"
      showLegend
      legendPosition="right"
    />
  );

export default PieChartWithLegendRightExample;

도넛 두께 커스텀

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartDonutCustomThicknessExample = () => (
    <PieChart
      data={subjectData}
      valueKey="score"
      labelKey="subject"
      innerRadius={120}
      cornerRadius={8}
      padAngle={0.04}
    />
  );

export default PieChartDonutCustomThicknessExample;

반응형 (너비 자동 조정)

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartResponsiveExample = () => (
    <div style={{ display: 'flex', gap: 24, alignItems: 'flex-start', flexWrap: 'wrap', width: '100%' }}>
      {([160, 280, 400] as const).map((size) => (
        <div key={size}>
          <div style={{ marginBottom: 4, fontSize: 11, color: '#888' }}>{size}px</div>
          <div style={{ width: size, height: size }}>
            <PieChart data={subjectData} valueKey="score" labelKey="subject" innerRadius="donut" showLegend />
          </div>
        </div>
      ))}
    </div>
  );

export default PieChartResponsiveExample;

Compound 패턴 (라벨 없이)

import { PieChart } from '@mildang/design-system/Chart';

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartCompoundUsageExample = () => (
    <PieChart data={subjectData} valueKey="score" innerRadius="donut">
      <PieChart.Arcs />
    </PieChart>
  );

export default PieChartCompoundUsageExample;

반응형 범례 (Compound — md 이상 오른쪽)

import { getChartColor, PieChart } from '@mildang/design-system/Chart';
import { css } from '@mildang/styled-system/css';
import { token } from '@mildang/styled-system/tokens';
import { ChartLegend } from '@mildang/design-system/Chart';

const TREMOR_9: string[] = ['#3b82f6', '#10b981', '#8b5cf6', '#f59e0b', '#6b7280', '#06b6d4', '#ec4899', '#84cc16', '#d946ef'];

const CHART_STROKE_PRESET: string[] = ['#2563eb', '#059669', '#7c3aed', '#d97706', '#4b5563', '#0891b2', '#db2777', '#65a30d', '#c026d3'];

const CHART_COLOR_PRESET: string[] = TREMOR_9;
const SEMANTIC_CHART_PAINT = [
  { fill: token('colors.positive.surface.high'), stroke: token('colors.positive.border.highest') },
  { fill: token('colors.critical.surface.high'), stroke: token('colors.critical.border.highest') },
  { fill: token('colors.info.surface.high'), stroke: token('colors.info.border.highest') },
  { fill: token('colors.warning.surface.high'), stroke: token('colors.warning.border.highest') },
  { fill: token('colors.brand.surface.highest'), stroke: token('colors.brand.border.highest') },
];
function getChartStroke(fill: string): string {
  const index = CHART_COLOR_PRESET.indexOf(fill);
  if (index >= 0) return CHART_STROKE_PRESET[index];
  return SEMANTIC_CHART_PAINT.find((paint) => paint.fill === fill)?.stroke ?? fill;
}

const subjectData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const PieChartResponsiveLegendExample = () => {
    const responsiveWrapperClass = css({
      display: 'flex',
      flexDirection: { base: 'column', md: 'row' },
      width: 'full',
      height: 'full',
      gap: '22',
    });
    const responsiveChartAreaClass = css({
      w: 'full',
      h: { base: '[240px]', md: 'full' },
      flexGrow: { base: 0, md: 1 },
      flexShrink: { base: 0, md: 1 },
      flexBasis: { base: '[240px]', md: '[0px]' },
      minW: 0,
      minH: 0,
    });
    const total = subjectData.reduce((s, d) => s + d.score, 0);
    const legendItems = subjectData.map((d, i) => ({
      key: String(i),
      label: d.subject,
      value: `${((d.score / total) * 100).toFixed(1)}%`,
      color: getChartColor(i),
      strokeColor: getChartStroke(getChartColor(i)),
    }));
    return (
      <div className={responsiveWrapperClass}>
        <div className={responsiveChartAreaClass}>
          <PieChart data={subjectData} valueKey="score" labelKey="subject" innerRadius="donut">
            <PieChart.Arcs />
          </PieChart>
        </div>
        <ChartLegend items={legendItems} direction="column" />
      </div>
    );
  };

export default PieChartResponsiveLegendExample;