BoxPlot

Data Visualization

visx 기반 상자 수염 그림.

Usage

여러 집단의 중앙값·사분위·이상치 분포를 함께 비교할 때 사용한다.

import

import

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

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

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

API Reference

BoxPlot Props

Prop

Type

Default

data
필수

TDatum[]

지정 안 함

valueKey
필수

string

지정 안 함

categoryKey

string

지정 안 함

className

string

지정 안 함

colors

string[]

지정 안 함

labels

Record<string, string>

지정 안 함

layout

"horizontal" | "vertical"

vertical

legendPosition

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

bottom

margin

Partial<ChartMargin>

지정 안 함

paints

ChartPaint[]

지정 안 함

showGrid

boolean

true

showLegend

boolean

false

showOutliers

boolean

true

showTooltip

boolean

false

showXAxis

boolean

true

showYAxis

boolean

true

statLabels

Partial<BoxPlotStatLabels>

지정 안 함

예제

세로 배치 vs 가로 배치

layout 으로 상자를 세로로 나열할지 가로로 나열할지 고른다 — 카테고리 라벨이 길면 가로가 읽기 편하다.

코드

'use client';

import { BoxPlot } 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';

/**
 * `layout` 으로 상자를 세로(카테고리가 x축)로 나열할지 가로(카테고리가 y축)로 나열할지
 * 고른다. 카테고리 라벨이 길면 가로 배치가 겹침 없이 읽기 편하다.
 */
const data = [
  { class: '1반', score: 62 },
  { class: '1반', score: 78 },
  { class: '1반', score: 85 },
  { class: '1반', score: 91 },
  { class: '2반', score: 55 },
  { class: '2반', score: 70 },
  { class: '2반', score: 88 },
  { class: '2반', score: 95 },
];

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

export default function BoxPlotLayoutExample() {
  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">
          vertical(기본) — 카테고리가 x축
        </Text>
        <div className={frame}>
          <BoxPlot data={data} valueKey="score" categoryKey="class" layout="vertical" />
        </div>
      </VStack>
      <VStack gap="2" alignItems="stretch" flex="1" minWidth="[220px]">
        <Text variant="caption-md" color="neutral.text.low">
          horizontal — 카테고리가 y축
        </Text>
        <div className={frame}>
          <BoxPlot data={data} valueKey="score" categoryKey="class" layout="horizontal" />
        </div>
      </VStack>
    </HStack>
  );
}

기본

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotDefaultExample = () => <BoxPlot data={scoreData} valueKey="score" categoryKey="className" />;

export default BoxPlotDefaultExample;

Hover Tooltip

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotHoverTooltipExample = () => <BoxPlot data={scoreData} valueKey="score" categoryKey="className" showTooltip />;

export default BoxPlotHoverTooltipExample;

가로 레이아웃

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotHorizontalExample = () => <BoxPlot data={scoreData} valueKey="score" categoryKey="className" layout="horizontal" showTooltip />;

export default BoxPlotHorizontalExample;

단일 박스 (카테고리 없음)

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotSingleBoxExample = () => <BoxPlot data={scoreData} valueKey="score" labels={{ score: '전체 점수' }} showTooltip />;

export default BoxPlotSingleBoxExample;

이상치 숨김

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotWithoutOutliersExample = () => <BoxPlot data={scoreData} valueKey="score" categoryKey="className" showOutliers={false} />;

export default BoxPlotWithoutOutliersExample;

범례 표시

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotWithLegendExample = () => <BoxPlot data={scoreData} valueKey="score" categoryKey="className" showLegend />;

export default BoxPlotWithLegendExample;

빈 데이터

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

const BoxPlotEmptyDataExample = () => <BoxPlot data={[]} valueKey="score" categoryKey="className" />;

export default BoxPlotEmptyDataExample;

데이터 1개

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

const BoxPlotSingleDatumExample = () => <BoxPlot data={[{ className: '1반', score: 85 }]} valueKey="score" categoryKey="className" showTooltip />;

export default BoxPlotSingleDatumExample;

반응형 (너비 자동 조정)

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

// 반별 수학 시험 점수 — 반마다 13명 (사분위가 정수로 떨어지는 크기)
// 2반의 32점은 Tukey fence(1.5×IQR) 밖 이상치
const classScores: Record<string, number[]> = {
  '1반': [81, 62, 74, 96, 68, 84, 76, 88, 71, 90, 79, 93, 86],
  '2반': [80, 32, 75, 91, 70, 83, 77, 87, 72, 89, 78, 85, 82],
  '3반': [65, 58, 72, 78, 61, 70, 55, 74, 68, 80, 63, 76, 59],
  '4반': [88, 79, 92, 85, 95, 82, 90, 76, 97, 84, 93, 87, 81],
};

const scoreData = Object.entries(classScores).flatMap(([className, scores]) =>
  scores.map((score) => ({ className, score })),
);

const BoxPlotResponsiveExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 32, width: '100%' }}>
      {([280, 480, 720] as const).map((w) => (
        <div key={w} style={{ width: w, height: 240 }}>
          <BoxPlot data={scoreData} valueKey="score" categoryKey="className" />
        </div>
      ))}
    </div>
  );

export default BoxPlotResponsiveExample;