CalendarHeatmap
Data Visualization
깃허브 잔디 스타일 달력 히트맵.
Usage
import
API Reference
예제
Usage
날짜별 활동량이나 빈도를 달력 격자의 색 농도로 살펴볼 때 사용한다.
import
import
import { CalendarHeatmap } from '@mildang/design-system/Chart';예제를 복사해 쓸 때 필요한 준비
• @mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.
API Reference
CalendarHeatmap Props
Prop
Type
Default
data
필수
TDatum[]
지정 안 함
dateKey
필수
string
지정 안 함
valueKey
필수
string
지정 안 함
className
string
지정 안 함
colorRange
string[]
지정 안 함
emptyColor
string
token('colors.neutral.surface.high')
legendPosition
"bottom" | "left" | "right" | "top"
bottom
margin
Partial<ChartMargin>
지정 안 함
monthLabelFormat
(date: Date) => string
(date) => `${date.getMonth() + 1}월`
showLegend
boolean
false
showTooltip
boolean
false
valueLabel
string
지정 안 함
weekdayLabels
string[]
['일', '월', '화', '수', '목', '금', '토']
예제
데이터 1개
import { CalendarHeatmap } from '@mildang/design-system/Chart';
const CalendarHeatmapSingleDatumExample = () => (
<CalendarHeatmap
data={[{ date: '2026-03-02', solvedCount: 5 }]}
dateKey="date"
valueKey="solvedCount"
valueLabel="푼 문제 수"
showTooltip
/>
);
export default CalendarHeatmapSingleDatumExample;
빈 데이터
import { CalendarHeatmap } from '@mildang/design-system/Chart';
const CalendarHeatmapEmptyExample = () => (
<CalendarHeatmap data={[]} dateKey="date" valueKey="solvedCount" showLegend />
);
export default CalendarHeatmapEmptyExample;
기본
import { CalendarHeatmap } from '@mildang/design-system/Chart';
function deterministicJitter(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i += 1) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h >>> 0) / 4294967296 - 0.5;
}
const pad = (n: number) => String(n).padStart(2, '0');
/** 시작일부터 days일간의 학습 기록 — deterministicJitter 기반 결정적 샘플 (Math.random 금지) */
function buildStudyLog(start: { year: number; month: number; day: number }, days: number) {
const rows: { date: string; solvedCount: number }[] = [];
for (let i = 0; i < days; i += 1) {
const d = new Date(start.year, start.month - 1, start.day + i);
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const level = Math.round((deterministicJitter(date) + 0.5) * 10);
// 일부 날짜는 0 (학습하지 않은 날)
rows.push({ date, solvedCount: Math.max(0, level - 2) * 4 });
}
return rows;
}
// 2026년 1~6월 학생 학습 기록 (푼 문제 수)
const studyLog = buildStudyLog({ year: 2026, month: 1, day: 5 }, 180);
const CalendarHeatmapDefaultExample = () => (
<CalendarHeatmap
data={studyLog}
dateKey="date"
valueKey="solvedCount"
valueLabel="푼 문제 수"
showLegend
/>
);
export default CalendarHeatmapDefaultExample;
Hover Tooltip
import { CalendarHeatmap } from '@mildang/design-system/Chart';
function deterministicJitter(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i += 1) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h >>> 0) / 4294967296 - 0.5;
}
const pad = (n: number) => String(n).padStart(2, '0');
/** 시작일부터 days일간의 학습 기록 — deterministicJitter 기반 결정적 샘플 (Math.random 금지) */
function buildStudyLog(start: { year: number; month: number; day: number }, days: number) {
const rows: { date: string; solvedCount: number }[] = [];
for (let i = 0; i < days; i += 1) {
const d = new Date(start.year, start.month - 1, start.day + i);
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const level = Math.round((deterministicJitter(date) + 0.5) * 10);
// 일부 날짜는 0 (학습하지 않은 날)
rows.push({ date, solvedCount: Math.max(0, level - 2) * 4 });
}
return rows;
}
// 2026년 1~6월 학생 학습 기록 (푼 문제 수)
const studyLog = buildStudyLog({ year: 2026, month: 1, day: 5 }, 180);
const tooltipLog = studyLog.map((row) =>
row.date === '2026-03-02' ? { ...row, solvedCount: 1234 } : row,
);
const CalendarHeatmapHoverTooltipExample = () => (
<CalendarHeatmap
data={tooltipLog}
dateKey="date"
valueKey="solvedCount"
valueLabel="푼 문제 수"
showTooltip
/>
);
export default CalendarHeatmapHoverTooltipExample;
커스텀 색상·라벨
import { CalendarHeatmap } from '@mildang/design-system/Chart';
import { token } from '@mildang/styled-system/tokens';
function deterministicJitter(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i += 1) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h >>> 0) / 4294967296 - 0.5;
}
const pad = (n: number) => String(n).padStart(2, '0');
/** 시작일부터 days일간의 학습 기록 — deterministicJitter 기반 결정적 샘플 (Math.random 금지) */
function buildStudyLog(start: { year: number; month: number; day: number }, days: number) {
const rows: { date: string; solvedCount: number }[] = [];
for (let i = 0; i < days; i += 1) {
const d = new Date(start.year, start.month - 1, start.day + i);
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const level = Math.round((deterministicJitter(date) + 0.5) * 10);
// 일부 날짜는 0 (학습하지 않은 날)
rows.push({ date, solvedCount: Math.max(0, level - 2) * 4 });
}
return rows;
}
// 2026년 1~6월 학생 학습 기록 (푼 문제 수)
const studyLog = buildStudyLog({ year: 2026, month: 1, day: 5 }, 180);
const CalendarHeatmapCustomStyleExample = () => (
<CalendarHeatmap
data={studyLog}
dateKey="date"
valueKey="solvedCount"
valueLabel="학습 시간(분)"
colorRange={[
token('colors.blue.light.100'),
token('colors.blue.light.400'),
]}
weekdayLabels={['', '월', '', '수', '', '금', '']}
monthLabelFormat={(d) => `${d.getFullYear()}.${pad(d.getMonth() + 1)}`}
showLegend
showTooltip
/>
);
export default CalendarHeatmapCustomStyleExample;
전체 0 (학습 기록 없음)
import { CalendarHeatmap } from '@mildang/design-system/Chart';
function deterministicJitter(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i += 1) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h >>> 0) / 4294967296 - 0.5;
}
const pad = (n: number) => String(n).padStart(2, '0');
/** 시작일부터 days일간의 학습 기록 — deterministicJitter 기반 결정적 샘플 (Math.random 금지) */
function buildStudyLog(start: { year: number; month: number; day: number }, days: number) {
const rows: { date: string; solvedCount: number }[] = [];
for (let i = 0; i < days; i += 1) {
const d = new Date(start.year, start.month - 1, start.day + i);
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const level = Math.round((deterministicJitter(date) + 0.5) * 10);
// 일부 날짜는 0 (학습하지 않은 날)
rows.push({ date, solvedCount: Math.max(0, level - 2) * 4 });
}
return rows;
}
// 2026년 1~6월 학생 학습 기록 (푼 문제 수)
const studyLog = buildStudyLog({ year: 2026, month: 1, day: 5 }, 180);
const CalendarHeatmapAllZeroExample = () => (
<CalendarHeatmap
data={studyLog.map((row) => ({ ...row, solvedCount: 0 }))}
dateKey="date"
valueKey="solvedCount"
valueLabel="푼 문제 수"
showLegend
/>
);
export default CalendarHeatmapAllZeroExample;
반응형 (너비 자동 조정)
import { CalendarHeatmap } from '@mildang/design-system/Chart';
function deterministicJitter(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i += 1) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h >>> 0) / 4294967296 - 0.5;
}
const pad = (n: number) => String(n).padStart(2, '0');
/** 시작일부터 days일간의 학습 기록 — deterministicJitter 기반 결정적 샘플 (Math.random 금지) */
function buildStudyLog(start: { year: number; month: number; day: number }, days: number) {
const rows: { date: string; solvedCount: number }[] = [];
for (let i = 0; i < days; i += 1) {
const d = new Date(start.year, start.month - 1, start.day + i);
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const level = Math.round((deterministicJitter(date) + 0.5) * 10);
// 일부 날짜는 0 (학습하지 않은 날)
rows.push({ date, solvedCount: Math.max(0, level - 2) * 4 });
}
return rows;
}
// 2026년 1~6월 학생 학습 기록 (푼 문제 수)
const studyLog = buildStudyLog({ year: 2026, month: 1, day: 5 }, 180);
const CalendarHeatmapResponsiveExample = () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 32, width: '100%' }}>
{([320, 560, 760] as const).map((w) => (
<div key={w} style={{ width: w, height: 160 }}>
<CalendarHeatmap
data={studyLog}
dateKey="date"
valueKey="solvedCount"
valueLabel="푼 문제 수"
/>
</div>
))}
</div>
);
export default CalendarHeatmapResponsiveExample;