ViolinPlot

Data Visualization

visx 기반 바이올린 플롯.

Usage

여러 집단의 분포 밀도와 중앙값·사분위를 한 모양에서 비교할 때 사용한다.

import

import

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

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

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

API Reference

ViolinPlot Props

Prop

Type

Default

data
필수

TDatum[]

지정 안 함

valueKey
필수

string

지정 안 함

bandwidth

number

지정 안 함

categoryKey

string

지정 안 함

children

ReactNode

지정 안 함

className

string

지정 안 함

colors

string[]

지정 안 함

legendPosition

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

bottom

margin

Partial<ChartMargin>

지정 안 함

paints

ChartPaint[]

지정 안 함

showBox

boolean

true

showGrid

boolean

true

showLegend

boolean

false

showTooltip

boolean

false

showXAxis

boolean

true

showYAxis

boolean

true

statLabels

Partial<Record<"min" | "max" | "count" | "q1" | "median" | "q3", string>>

지정 안 함

valueFormat

(value: number) => string

(value: number) => value.toLocaleString()

valueLabel

string

전체

xAxisProps

ViolinPlotAxisProps

지정 안 함

yAxisProps

ViolinPlotAxisProps

지정 안 함

예제

기본 (반별 점수 분포)

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

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

export default ViolinPlotDefaultExample;

Hover Tooltip

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

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

export default ViolinPlotHoverTooltipExample;

단일 분포 (카테고리 없음)

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

// 1:1 수업 일일 학습 시간(분) — 쌍봉 분포 (짧게 끝내는 그룹과 길게 하는 그룹)
const studyMinutes = [
  12, 15, 18, 20, 22, 24, 25, 27, 28, 30, 32, 33, 35,
  58, 61, 63, 65, 67, 68, 70, 72, 74, 75, 78, 80, 84,
].map((minutes) => ({ minutes }));

const ViolinPlotSingleDistributionExample = () => (
    <ViolinPlot
      data={studyMinutes}
      valueKey="minutes"
      valueLabel="전체 학생"
      valueFormat={(value) => `${Math.round(value)}분`}
    />
  );

export default ViolinPlotSingleDistributionExample;

박스플롯 숨김

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

const ViolinPlotWithoutBoxExample = () => (
    <ViolinPlot
      data={scoreData}
      valueKey="score"
      categoryKey="className"
      showBox={false}
      showLegend
    />
  );

export default ViolinPlotWithoutBoxExample;

대역폭 조정 (bandwidth=2)

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

const ViolinPlotNarrowBandwidthExample = () => (
    <ViolinPlot data={scoreData} valueKey="score" categoryKey="className" bandwidth={2} />
  );

export default ViolinPlotNarrowBandwidthExample;

커스텀 색상

import { ViolinPlot } from '@mildang/design-system/Chart';
import { token } from '@mildang/styled-system/tokens';

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

const ViolinPlotCustomColorsExample = () => (
    <ViolinPlot
      data={scoreData}
      valueKey="score"
      categoryKey="className"
      colors={[
        token('colors.info.surface.high'),
        token('colors.warning.surface.high'),
        token('colors.positive.surface.high'),
      ]}
      showLegend
    />
  );

export default ViolinPlotCustomColorsExample;

빈 데이터

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

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

export default ViolinPlotEmptyExample;

데이터 1개

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

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

export default ViolinPlotSingleDatumExample;

0값 포함 (오답 횟수)

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

const ViolinPlotWithZeroValuesExample = () => (
    <ViolinPlot
      data={[0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 8, 12].map((wrongCount) => ({ wrongCount }))}
      valueKey="wrongCount"
      valueLabel="오답 횟수"
      showTooltip
    />
  );

export default ViolinPlotWithZeroValuesExample;

반응형 (너비 자동 조정)

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

const ViolinPlotResponsiveExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 32, width: '100%' }}>
      {([280, 480, 720] as const).map((w) => (
        <div key={w} style={{ width: w }}>
          <div style={{ marginBottom: 4, fontSize: 11, color: '#888' }}>{w}px</div>
          <div style={{ width: w, height: 240 }}>
            <ViolinPlot data={scoreData} valueKey="score" categoryKey="className" showLegend />
          </div>
        </div>
      ))}
    </div>
  );

export default ViolinPlotResponsiveExample;

Compound 패턴 — 그리드 없이 축만

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

// 반별 시험 점수 — 반마다 15명(홀수)이라 중앙값이 실제 관측치와 일치한다
// 1반 중앙값 83 / 2반 중앙값 73 / 3반 중앙값 87
const classScores: Record<string, number[]> = {
  '1반': [62, 68, 71, 74, 76, 78, 80, 83, 85, 86, 88, 90, 92, 94, 97],
  '2반': [55, 58, 62, 64, 67, 70, 72, 73, 75, 78, 80, 82, 85, 88, 91],
  '3반': [70, 74, 77, 79, 81, 83, 85, 87, 88, 90, 91, 93, 95, 96, 98],
};

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

const ViolinPlotCompoundWithoutGridExample = () => (
    <ViolinPlot data={scoreData} valueKey="score" categoryKey="className">
      <ViolinPlot.XAxis hideTicks hideAxisLine />
      <ViolinPlot.YAxis hideTicks hideAxisLine />
      <ViolinPlot.Violins />
    </ViolinPlot>
  );

export default ViolinPlotCompoundWithoutGridExample;