Add HMSolidPieChart and HMSolidDonutChart: a tilted, SVG-drawn disc with an outer wall and, for the donut, an inner wall seen through the hole. Same data, slice angles, order, legend and tooltip as the 2D charts; the tooltip is the same on the top face and the walls and follows the pointer. The existing 2D charts and their props are unchanged. - Side walls use darker HM tokens (--hm-chart-1-side .. --hm-chart-5-side) and a color-mix darkening for custom colors; shared chart tokens are untouched. - Add a "입체형" example to the Pie Chart and Donut Chart stories (PieSolidExample, DonutSolidExample); ExampleCard description is now optional. - Document usage and constraints in the chart package README.
214 lines
9.4 KiB
TypeScript
214 lines
9.4 KiB
TypeScript
import { useState, type ReactNode } from "react"
|
|
import { cn } from "cn"
|
|
import {
|
|
Badge,
|
|
Card,
|
|
CardAction,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@hm-design/ui"
|
|
import { HMBarChart, HMDonutChart, HMHorizontalBarChart, HMLineChart, HMSignedBarChart, HMSolidDonutChart } from "../index"
|
|
import { sampleBar, sampleDataNotice, sampleDonut, sampleDonutStates, sampleHorizontalBar, sampleLine, sampleLineCompare, sampleLinePointColors, sampleSignedBar } from "./sample-data"
|
|
|
|
/** Marks a chart as sample data. Use it next to every chart that is fed with `sample-data`. */
|
|
export function SampleDataBadge() {
|
|
return <Badge variant="outline" data-slot="chart-sample-badge" title={sampleDataNotice}>예시 데이터</Badge>
|
|
}
|
|
|
|
export function ExampleCard({ title, description, className, toolbar, children }: { title: string; description?: string; className?: string; toolbar?: ReactNode; children: ReactNode }) {
|
|
return (
|
|
// h-full lets a card fill its grid cell; the notice sits at the card bottom so cards of one row line up.
|
|
<Card className={cn("h-full", className)} data-chart-example={title}>
|
|
<CardHeader>
|
|
<CardTitle className="break-keep">{title}</CardTitle>
|
|
{description ? <CardDescription className="break-keep">{description}</CardDescription> : null}
|
|
<CardAction><SampleDataBadge /></CardAction>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-1 flex-col">
|
|
{toolbar !== undefined ? <div data-slot="chart-example-toolbar" className="mb-3 flex min-h-[34px] items-center text-sm text-muted-foreground">{toolbar}</div> : null}
|
|
{children}
|
|
<p data-slot="chart-sample-notice" className="mt-auto pt-3">{sampleDataNotice}</p>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
export function BarChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="월별 접수·완료 현황" description="막대 차트 · 상반기 월별 건수" className={className}>
|
|
<HMBarChart {...sampleBar} label="월별 접수·완료 건수 막대 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function StackedBarChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="월별 접수·완료 현황(누적)" description="막대 차트 · stacked" className={className}>
|
|
<HMBarChart {...sampleBar} stacked label="월별 접수·완료 건수 누적 막대 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function HorizontalBarChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="항목별 처리 건수" description="가로 막대 차트 · 항목별 건수" className={className}>
|
|
<HMHorizontalBarChart {...sampleHorizontalBar} label="항목별 처리 건수 가로 막대 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function SignedBarChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="월별 순증감" description="막대 차트 · 0 기준 양수·음수" className={className}>
|
|
<HMSignedBarChart {...sampleSignedBar} label="월별 순증감 양수·음수 막대 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="월별 처리 시간 추이" description="선 차트 · 단위: 시간" className={className}>
|
|
<HMLineChart {...sampleLine} label="월별 평균 처리 시간 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineCurveExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="곡선형" description="monotone · Dot 없음" className={className}>
|
|
<HMLineChart {...sampleLineCompare} curve="monotone" dots={false} label="월별 접수·완료 곡선형 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineCurveDotLabelExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="곡선형 · Dot + Label" description="monotone · Dot과 값 Label" className={className}>
|
|
<HMLineChart {...sampleLineCompare} curve="monotone" pointLabels label="월별 접수·완료 곡선형 Dot·값 라벨 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineStraightExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="직선형" description="linear · Dot 없음" className={className}>
|
|
<HMLineChart {...sampleLineCompare} curve="linear" dots={false} label="월별 접수·완료 직선형 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineStraightColorLabelExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="직선형 · Color Dot" description="linear · Dot 개별 색상 + 값 Label" className={className}>
|
|
<HMLineChart {...sampleLinePointColors} curve="linear" pointLabels label="월별 접수·완료 직선형 포인트별 색상 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineStepExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="계단형" description="step · Dot 없음" className={className}>
|
|
<HMLineChart {...sampleLineCompare} curve="step" dots={false} label="월별 접수·완료 계단형 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function LineStepDotLabelExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="계단형 · Dot + Label" description="step · Dot과 값 Label" className={className}>
|
|
<HMLineChart {...sampleLineCompare} curve="step" pointLabels label="월별 접수·완료 계단형 Dot·값 라벨 선 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function DonutChartExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="업무 상태 비중" description="도넛 차트 · 상태별 건수" className={className}>
|
|
<HMDonutChart {...sampleDonut} label="업무 상태별 비중 도넛 차트(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
/** Bar, line and donut examples: two per row from 1024px (HM desktop boundary), one per row below. With an odd count the last card keeps one column's width. */
|
|
export function ChartExamples() {
|
|
return (
|
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2" data-chart-examples="">
|
|
<BarChartExample className="min-w-0" />
|
|
<LineChartExample className="min-w-0" />
|
|
<DonutChartExample className="min-w-0" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Four donut states on one data set: plain, center total, active (fixed selection) and interactive (Select + slice click).
|
|
// The toolbar row has the Select height in every card, so the donuts of a row start at the same height.
|
|
export function DonutPlainExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="기본형" description="도넛 차트 · 기본" className={className} toolbar="중앙 표시 없음">
|
|
<HMDonutChart data={sampleDonutStates.data} label="업무 상태 도넛 차트, 가운데 값 없음(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function DonutCenterExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="기본형 + 텍스트" description="도넛 차트 · 전체 합계" className={className} toolbar="중앙 표시: 전체 합계">
|
|
<HMDonutChart data={sampleDonutStates.data} totalLabel={sampleDonutStates.totalLabel} label="업무 상태 도넛 차트, 가운데 전체 합계(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function DonutSolidExample({ className }: { className?: string }) {
|
|
return (
|
|
<ExampleCard title="입체형" className={className}>
|
|
<HMSolidDonutChart data={sampleDonutStates.data} totalLabel={sampleDonutStates.totalLabel} label="업무 상태 입체형 도넛 차트, 가운데 전체 합계(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function DonutActiveExample({ className }: { className?: string }) {
|
|
const selected = sampleDonutStates.data.find(item => item.id === sampleDonutStates.activeId)
|
|
return (
|
|
<ExampleCard title="Active 상태" description="도넛 차트 · 선택된 항목" className={className} toolbar={`선택 항목: ${selected?.label ?? ""}`}>
|
|
<HMDonutChart data={sampleDonutStates.data} activeId={sampleDonutStates.activeId} label="업무 상태 도넛 차트, 선택된 항목 강조(예시 데이터)" />
|
|
</ExampleCard>
|
|
)
|
|
}
|
|
|
|
export function DonutInteractiveExample({ className }: { className?: string }) {
|
|
const [activeId, setActiveId] = useState(sampleDonutStates.activeId)
|
|
return (
|
|
<ExampleCard
|
|
title="Interactive"
|
|
description="도넛 차트 · 선택 또는 클릭"
|
|
className={className}
|
|
toolbar={
|
|
<Select value={activeId} onValueChange={setActiveId}>
|
|
<SelectTrigger size="medium" aria-label="강조할 업무 상태 선택" className="w-full max-w-64">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{sampleDonutStates.data.map(item => (
|
|
<SelectItem key={item.id} value={item.id}>{item.label}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
}
|
|
>
|
|
<HMDonutChart
|
|
data={sampleDonutStates.data}
|
|
activeId={activeId}
|
|
onActiveChange={setActiveId}
|
|
label="업무 상태 도넛 차트, 항목을 선택하거나 조각을 클릭해 강조(예시 데이터)"
|
|
/>
|
|
</ExampleCard>
|
|
)
|
|
}
|