486 lines
25 KiB
TypeScript
486 lines
25 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import {
|
|
Area,
|
|
ComposedChart,
|
|
ResponsiveContainer,
|
|
XAxis,
|
|
YAxis,
|
|
ReferenceLine,
|
|
Customized,
|
|
} from 'recharts'
|
|
|
|
// 샘플 데이터
|
|
const SAMPLE_DATA = {
|
|
cow: { name: '7805', score: 0.85 },
|
|
farm: { name: '농가', score: 0.53 },
|
|
region: { name: '보은군', score: 0.21 },
|
|
}
|
|
|
|
// 정규분포 히스토그램 데이터
|
|
const histogramData = [
|
|
{ midPoint: -2.5, percent: 2.3 },
|
|
{ midPoint: -2.0, percent: 4.4 },
|
|
{ midPoint: -1.5, percent: 9.2 },
|
|
{ midPoint: -1.0, percent: 15.0 },
|
|
{ midPoint: -0.5, percent: 19.1 },
|
|
{ midPoint: 0.0, percent: 19.1 },
|
|
{ midPoint: 0.5, percent: 15.0 },
|
|
{ midPoint: 1.0, percent: 9.2 },
|
|
{ midPoint: 1.5, percent: 4.4 },
|
|
{ midPoint: 2.0, percent: 2.3 },
|
|
]
|
|
|
|
export default function ChartOptionsDemo() {
|
|
const [selectedOption, setSelectedOption] = useState<string>('A')
|
|
|
|
const cowScore = SAMPLE_DATA.cow.score
|
|
const farmScore = SAMPLE_DATA.farm.score
|
|
const regionScore = SAMPLE_DATA.region.score
|
|
|
|
const farmDiff = cowScore - farmScore
|
|
const regionDiff = cowScore - regionScore
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-100 p-4 sm:p-8">
|
|
<div className="max-w-4xl mx-auto space-y-6">
|
|
<h1 className="text-2xl font-bold text-foreground">차트 대비 표시 옵션 데모</h1>
|
|
<p className="text-muted-foreground">
|
|
개체: +{cowScore.toFixed(2)} | 농가: +{farmScore.toFixed(2)} | 보은군: +{regionScore.toFixed(2)}
|
|
</p>
|
|
|
|
{/* 옵션 선택 탭 */}
|
|
<div className="flex flex-wrap gap-2">
|
|
{['A', 'B', 'C', 'D', 'E'].map((opt) => (
|
|
<button
|
|
key={opt}
|
|
onClick={() => setSelectedOption(opt)}
|
|
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
|
|
selectedOption === opt
|
|
? 'bg-primary text-white'
|
|
: 'bg-white text-foreground hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
옵션 {opt}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* 옵션 A: 차트 내에 대비값 항상 표시 */}
|
|
{selectedOption === 'A' && (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-2">옵션 A: 차트 내에 대비값 항상 표시</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">개체 선 옆에 농가/보은군 대비값을 직접 표시</p>
|
|
|
|
<div className="h-[400px] bg-slate-50 rounded-xl p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={histogramData} margin={{ top: 80, right: 30, left: 10, bottom: 30 }}>
|
|
<defs>
|
|
<linearGradient id="areaGradientA" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
<stop offset="100%" stopColor="#93c5fd" stopOpacity={0.1} />
|
|
</linearGradient>
|
|
</defs>
|
|
<XAxis dataKey="midPoint" type="number" domain={[-2.5, 2.5]} tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 10 }} width={30} />
|
|
<Area type="natural" dataKey="percent" stroke="#3b82f6" fill="url(#areaGradientA)" />
|
|
<ReferenceLine x={regionScore} stroke="#2563eb" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={farmScore} stroke="#f59e0b" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={cowScore} stroke="#1482B0" strokeWidth={3} />
|
|
|
|
<Customized
|
|
component={(props: any) => {
|
|
const { xAxisMap, yAxisMap } = props
|
|
if (!xAxisMap || !yAxisMap) return null
|
|
const xAxis = Object.values(xAxisMap)[0] as any
|
|
const yAxis = Object.values(yAxisMap)[0] as any
|
|
if (!xAxis || !yAxis) return null
|
|
|
|
const chartX = xAxis.x
|
|
const chartWidth = xAxis.width
|
|
const chartTop = yAxis.y
|
|
const [domainMin, domainMax] = xAxis.domain || [-2.5, 2.5]
|
|
const domainRange = domainMax - domainMin
|
|
|
|
const sigmaToX = (sigma: number) => chartX + ((sigma - domainMin) / domainRange) * chartWidth
|
|
const cowX = sigmaToX(cowScore)
|
|
|
|
return (
|
|
<g>
|
|
{/* 개체 라벨 + 대비값 */}
|
|
<rect x={cowX + 10} y={chartTop + 20} width={120} height={50} rx={6} fill="#1482B0" />
|
|
<text x={cowX + 70} y={chartTop + 38} textAnchor="middle" fill="white" fontSize={12} fontWeight={600}>
|
|
개체 +{cowScore.toFixed(2)}
|
|
</text>
|
|
<text x={cowX + 70} y={chartTop + 55} textAnchor="middle" fill="white" fontSize={10}>
|
|
농가+{farmDiff.toFixed(2)} | 보은군+{regionDiff.toFixed(2)}
|
|
</text>
|
|
</g>
|
|
)
|
|
}}
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 옵션 B: 선 사이 영역 색으로 채우기 */}
|
|
{selectedOption === 'B' && (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-2">옵션 B: 선 사이 영역 색으로 채우기</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">개체~농가, 개체~보은군 사이를 색으로 강조</p>
|
|
|
|
<div className="h-[400px] bg-slate-50 rounded-xl p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={histogramData} margin={{ top: 80, right: 30, left: 10, bottom: 30 }}>
|
|
<defs>
|
|
<linearGradient id="areaGradientB" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
<stop offset="100%" stopColor="#93c5fd" stopOpacity={0.1} />
|
|
</linearGradient>
|
|
</defs>
|
|
<XAxis dataKey="midPoint" type="number" domain={[-2.5, 2.5]} tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 10 }} width={30} />
|
|
<Area type="natural" dataKey="percent" stroke="#3b82f6" fill="url(#areaGradientB)" />
|
|
<ReferenceLine x={regionScore} stroke="#2563eb" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={farmScore} stroke="#f59e0b" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={cowScore} stroke="#1482B0" strokeWidth={3} />
|
|
|
|
<Customized
|
|
component={(props: any) => {
|
|
const { xAxisMap, yAxisMap } = props
|
|
if (!xAxisMap || !yAxisMap) return null
|
|
const xAxis = Object.values(xAxisMap)[0] as any
|
|
const yAxis = Object.values(yAxisMap)[0] as any
|
|
if (!xAxis || !yAxis) return null
|
|
|
|
const chartX = xAxis.x
|
|
const chartWidth = xAxis.width
|
|
const chartTop = yAxis.y
|
|
const chartHeight = yAxis.height
|
|
const [domainMin, domainMax] = xAxis.domain || [-2.5, 2.5]
|
|
const domainRange = domainMax - domainMin
|
|
|
|
const sigmaToX = (sigma: number) => chartX + ((sigma - domainMin) / domainRange) * chartWidth
|
|
const cowX = sigmaToX(cowScore)
|
|
const farmX = sigmaToX(farmScore)
|
|
const regionX = sigmaToX(regionScore)
|
|
|
|
return (
|
|
<g>
|
|
{/* 개체~농가 영역 (주황색) */}
|
|
<rect
|
|
x={farmX}
|
|
y={chartTop}
|
|
width={cowX - farmX}
|
|
height={chartHeight}
|
|
fill="rgba(245, 158, 11, 0.25)"
|
|
/>
|
|
{/* 농가~보은군 영역 (파란색) */}
|
|
<rect
|
|
x={regionX}
|
|
y={chartTop}
|
|
width={farmX - regionX}
|
|
height={chartHeight}
|
|
fill="rgba(37, 99, 235, 0.15)"
|
|
/>
|
|
|
|
{/* 대비값 라벨 */}
|
|
<rect x={(cowX + farmX) / 2 - 35} y={chartTop + 30} width={70} height={24} rx={4} fill="#f59e0b" />
|
|
<text x={(cowX + farmX) / 2} y={chartTop + 46} textAnchor="middle" fill="white" fontSize={11} fontWeight={600}>
|
|
+{farmDiff.toFixed(2)}
|
|
</text>
|
|
|
|
<rect x={(farmX + regionX) / 2 - 35} y={chartTop + 60} width={70} height={24} rx={4} fill="#2563eb" />
|
|
<text x={(farmX + regionX) / 2} y={chartTop + 76} textAnchor="middle" fill="white" fontSize={11} fontWeight={600}>
|
|
+{(farmScore - regionScore).toFixed(2)}
|
|
</text>
|
|
</g>
|
|
)
|
|
}}
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 옵션 C: 개체 배지에 대비값 추가 */}
|
|
{selectedOption === 'C' && (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-2">옵션 C: 개체 배지에 대비값 추가</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">개체 배지를 확장해서 대비값 포함</p>
|
|
|
|
<div className="h-[400px] bg-slate-50 rounded-xl p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={histogramData} margin={{ top: 100, right: 30, left: 10, bottom: 30 }}>
|
|
<defs>
|
|
<linearGradient id="areaGradientC" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
<stop offset="100%" stopColor="#93c5fd" stopOpacity={0.1} />
|
|
</linearGradient>
|
|
</defs>
|
|
<XAxis dataKey="midPoint" type="number" domain={[-2.5, 2.5]} tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 10 }} width={30} />
|
|
<Area type="natural" dataKey="percent" stroke="#3b82f6" fill="url(#areaGradientC)" />
|
|
<ReferenceLine x={regionScore} stroke="#2563eb" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={farmScore} stroke="#f59e0b" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={cowScore} stroke="#1482B0" strokeWidth={3} />
|
|
|
|
<Customized
|
|
component={(props: any) => {
|
|
const { xAxisMap, yAxisMap } = props
|
|
if (!xAxisMap || !yAxisMap) return null
|
|
const xAxis = Object.values(xAxisMap)[0] as any
|
|
const yAxis = Object.values(yAxisMap)[0] as any
|
|
if (!xAxis || !yAxis) return null
|
|
|
|
const chartX = xAxis.x
|
|
const chartWidth = xAxis.width
|
|
const chartTop = yAxis.y
|
|
const [domainMin, domainMax] = xAxis.domain || [-2.5, 2.5]
|
|
const domainRange = domainMax - domainMin
|
|
|
|
const sigmaToX = (sigma: number) => chartX + ((sigma - domainMin) / domainRange) * chartWidth
|
|
const cowX = sigmaToX(cowScore)
|
|
const farmX = sigmaToX(farmScore)
|
|
const regionX = sigmaToX(regionScore)
|
|
|
|
return (
|
|
<g>
|
|
{/* 보은군 배지 */}
|
|
<rect x={regionX - 50} y={chartTop - 85} width={100} height={26} rx={6} fill="#dbeafe" stroke="#93c5fd" strokeWidth={2} />
|
|
<text x={regionX} y={chartTop - 68} textAnchor="middle" fill="#2563eb" fontSize={12} fontWeight={600}>
|
|
보은군 +{regionScore.toFixed(2)}
|
|
</text>
|
|
|
|
{/* 농가 배지 */}
|
|
<rect x={farmX - 50} y={chartTop - 55} width={100} height={26} rx={6} fill="#fef3c7" stroke="#fcd34d" strokeWidth={2} />
|
|
<text x={farmX} y={chartTop - 38} textAnchor="middle" fill="#d97706" fontSize={12} fontWeight={600}>
|
|
농가 +{farmScore.toFixed(2)}
|
|
</text>
|
|
|
|
{/* 개체 배지 (확장) */}
|
|
<rect x={cowX - 80} y={chartTop - 25} width={160} height={40} rx={6} fill="#1482B0" />
|
|
<text x={cowX} y={chartTop - 8} textAnchor="middle" fill="white" fontSize={13} fontWeight={700}>
|
|
개체 +{cowScore.toFixed(2)}
|
|
</text>
|
|
<text x={cowX} y={chartTop + 10} textAnchor="middle" fill="rgba(255,255,255,0.9)" fontSize={10}>
|
|
농가 대비 +{farmDiff.toFixed(2)} | 보은군 대비 +{regionDiff.toFixed(2)}
|
|
</text>
|
|
</g>
|
|
)
|
|
}}
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 옵션 D: 차트 모서리에 오버레이 박스 */}
|
|
{selectedOption === 'D' && (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-2">옵션 D: 차트 모서리에 오버레이 박스</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">차트 우측 상단에 대비값 요약 박스</p>
|
|
|
|
<div className="h-[400px] bg-slate-50 rounded-xl p-4 relative">
|
|
{/* 오버레이 박스 */}
|
|
<div className="absolute top-6 right-6 bg-white rounded-lg shadow-lg border border-slate-200 p-3 z-10">
|
|
<div className="text-xs text-muted-foreground mb-2">개체 대비</div>
|
|
<div className="space-y-1.5">
|
|
<div className="flex items-center justify-between gap-4">
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="w-2.5 h-2.5 rounded bg-amber-500"></span>
|
|
<span className="text-sm">농가</span>
|
|
</span>
|
|
<span className="text-sm font-bold text-green-600">+{farmDiff.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between gap-4">
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="w-2.5 h-2.5 rounded bg-blue-500"></span>
|
|
<span className="text-sm">보은군</span>
|
|
</span>
|
|
<span className="text-sm font-bold text-green-600">+{regionDiff.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={histogramData} margin={{ top: 80, right: 30, left: 10, bottom: 30 }}>
|
|
<defs>
|
|
<linearGradient id="areaGradientD" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
<stop offset="100%" stopColor="#93c5fd" stopOpacity={0.1} />
|
|
</linearGradient>
|
|
</defs>
|
|
<XAxis dataKey="midPoint" type="number" domain={[-2.5, 2.5]} tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 10 }} width={30} />
|
|
<Area type="natural" dataKey="percent" stroke="#3b82f6" fill="url(#areaGradientD)" />
|
|
<ReferenceLine x={regionScore} stroke="#2563eb" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={farmScore} stroke="#f59e0b" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={cowScore} stroke="#1482B0" strokeWidth={3} />
|
|
|
|
<Customized
|
|
component={(props: any) => {
|
|
const { xAxisMap, yAxisMap } = props
|
|
if (!xAxisMap || !yAxisMap) return null
|
|
const xAxis = Object.values(xAxisMap)[0] as any
|
|
const yAxis = Object.values(yAxisMap)[0] as any
|
|
if (!xAxis || !yAxis) return null
|
|
|
|
const chartX = xAxis.x
|
|
const chartWidth = xAxis.width
|
|
const chartTop = yAxis.y
|
|
const [domainMin, domainMax] = xAxis.domain || [-2.5, 2.5]
|
|
const domainRange = domainMax - domainMin
|
|
|
|
const sigmaToX = (sigma: number) => chartX + ((sigma - domainMin) / domainRange) * chartWidth
|
|
const cowX = sigmaToX(cowScore)
|
|
const farmX = sigmaToX(farmScore)
|
|
const regionX = sigmaToX(regionScore)
|
|
|
|
return (
|
|
<g>
|
|
{/* 심플 배지들 */}
|
|
<rect x={regionX - 40} y={chartTop - 60} width={80} height={22} rx={4} fill="#dbeafe" stroke="#93c5fd" />
|
|
<text x={regionX} y={chartTop - 45} textAnchor="middle" fill="#2563eb" fontSize={11} fontWeight={600}>보은군</text>
|
|
|
|
<rect x={farmX - 30} y={chartTop - 35} width={60} height={22} rx={4} fill="#fef3c7" stroke="#fcd34d" />
|
|
<text x={farmX} y={chartTop - 20} textAnchor="middle" fill="#d97706" fontSize={11} fontWeight={600}>농가</text>
|
|
|
|
<rect x={cowX - 30} y={chartTop - 10} width={60} height={22} rx={4} fill="#1482B0" />
|
|
<text x={cowX} y={chartTop + 5} textAnchor="middle" fill="white" fontSize={11} fontWeight={600}>개체</text>
|
|
</g>
|
|
)
|
|
}}
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 옵션 E: 화살표로 차이 표시 */}
|
|
{selectedOption === 'E' && (
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-2">옵션 E: 화살표로 차이 표시</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">개체에서 농가/보은군으로 화살표 + 차이값</p>
|
|
|
|
<div className="h-[400px] bg-slate-50 rounded-xl p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={histogramData} margin={{ top: 80, right: 30, left: 10, bottom: 30 }}>
|
|
<defs>
|
|
<linearGradient id="areaGradientE" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity={0.4} />
|
|
<stop offset="100%" stopColor="#93c5fd" stopOpacity={0.1} />
|
|
</linearGradient>
|
|
<marker id="arrowFarm" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
|
<path d="M0,0 L0,6 L9,3 z" fill="#f59e0b" />
|
|
</marker>
|
|
<marker id="arrowRegion" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
|
<path d="M0,0 L0,6 L9,3 z" fill="#2563eb" />
|
|
</marker>
|
|
</defs>
|
|
<XAxis dataKey="midPoint" type="number" domain={[-2.5, 2.5]} tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 10 }} width={30} />
|
|
<Area type="natural" dataKey="percent" stroke="#3b82f6" fill="url(#areaGradientE)" />
|
|
<ReferenceLine x={regionScore} stroke="#2563eb" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={farmScore} stroke="#f59e0b" strokeWidth={2} strokeDasharray="4 2" />
|
|
<ReferenceLine x={cowScore} stroke="#1482B0" strokeWidth={3} />
|
|
|
|
<Customized
|
|
component={(props: any) => {
|
|
const { xAxisMap, yAxisMap } = props
|
|
if (!xAxisMap || !yAxisMap) return null
|
|
const xAxis = Object.values(xAxisMap)[0] as any
|
|
const yAxis = Object.values(yAxisMap)[0] as any
|
|
if (!xAxis || !yAxis) return null
|
|
|
|
const chartX = xAxis.x
|
|
const chartWidth = xAxis.width
|
|
const chartTop = yAxis.y
|
|
const chartHeight = yAxis.height
|
|
const [domainMin, domainMax] = xAxis.domain || [-2.5, 2.5]
|
|
const domainRange = domainMax - domainMin
|
|
|
|
const sigmaToX = (sigma: number) => chartX + ((sigma - domainMin) / domainRange) * chartWidth
|
|
const cowX = sigmaToX(cowScore)
|
|
const farmX = sigmaToX(farmScore)
|
|
const regionX = sigmaToX(regionScore)
|
|
|
|
const arrowY1 = chartTop + chartHeight * 0.3
|
|
const arrowY2 = chartTop + chartHeight * 0.5
|
|
|
|
return (
|
|
<g>
|
|
{/* 개체 → 농가 화살표 */}
|
|
<line
|
|
x1={cowX} y1={arrowY1}
|
|
x2={farmX + 10} y2={arrowY1}
|
|
stroke="#f59e0b"
|
|
strokeWidth={3}
|
|
markerEnd="url(#arrowFarm)"
|
|
/>
|
|
<rect x={(cowX + farmX) / 2 - 30} y={arrowY1 - 22} width={60} height={20} rx={4} fill="#f59e0b" />
|
|
<text x={(cowX + farmX) / 2} y={arrowY1 - 8} textAnchor="middle" fill="white" fontSize={11} fontWeight={700}>
|
|
+{farmDiff.toFixed(2)}
|
|
</text>
|
|
|
|
{/* 개체 → 보은군 화살표 */}
|
|
<line
|
|
x1={cowX} y1={arrowY2}
|
|
x2={regionX + 10} y2={arrowY2}
|
|
stroke="#2563eb"
|
|
strokeWidth={3}
|
|
markerEnd="url(#arrowRegion)"
|
|
/>
|
|
<rect x={(cowX + regionX) / 2 - 30} y={arrowY2 - 22} width={60} height={20} rx={4} fill="#2563eb" />
|
|
<text x={(cowX + regionX) / 2} y={arrowY2 - 8} textAnchor="middle" fill="white" fontSize={11} fontWeight={700}>
|
|
+{regionDiff.toFixed(2)}
|
|
</text>
|
|
|
|
{/* 배지 */}
|
|
<rect x={cowX - 30} y={chartTop - 25} width={60} height={22} rx={4} fill="#1482B0" />
|
|
<text x={cowX} y={chartTop - 10} textAnchor="middle" fill="white" fontSize={11} fontWeight={600}>개체</text>
|
|
</g>
|
|
)
|
|
}}
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 옵션 설명 */}
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<h2 className="text-lg font-bold mb-3">옵션 비교</h2>
|
|
<div className="space-y-2 text-sm">
|
|
<div><strong>A:</strong> 개체 배지 옆에 대비값 직접 표시 - 간단하지만 정보 밀집</div>
|
|
<div><strong>B:</strong> 영역 색칠로 거리감 강조 - 시각적으로 차이가 명확</div>
|
|
<div><strong>C:</strong> 개체 배지 확장 - 배지에 모든 정보 포함</div>
|
|
<div><strong>D:</strong> 오버레이 박스 - 차트 방해 없이 정보 제공</div>
|
|
<div><strong>E:</strong> 화살표 - 방향성과 차이 동시 표현</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|