---
title: "260519-11:52_F5-Y축재설계"
notion_id: "36522962086881b09669f2b85ee4cdec"
notion_url: "https://app.notion.com/p/36522962086881b09669f2b85ee4cdec"
category: "workreport"
parent: "Claude Code 작업보고"
updated: "2026-05-19"
priority: "Medium"
purpose: "체성분 Summary 차트 Y축을 계열별 독립 동적 도메인으로 재설계해 변화량 가시성 개선"
---

## 작업 목표
체성분 Summary 차트 Y축 전면 재설계 — 데이터 변화량이 시각적으로 잘 보이도록 개선
## 수정 파일
- `src/components/dashboard/BodySection.tsx` (단독 수정)
---
## 사용자 요청 (원문)
```javascript
※ 주의: 동일 작업을 수행 중인 다른 에이전트가 있을 수 있음.
작업 시작 전 반드시 git status 확인 후 충돌 여부 점검할 것.
작업 전 git pull로 최신 원격 커밋 기준 동기화할 것.
충돌 또는 예상치 못한 변경사항 감지 시 임의로 git reset 하지 말고 작업 중단 후 보고할 것.

## 작업 목표
체성분 Summary 차트 Y축 전면 재설계 — 데이터 변화량이 시각적으로 잘 보이도록 개선 (F-5)

## 수정 파일
- src/components/dashboard/BodySection.tsx (단독 수정, 다른 파일 건드리지 말 것)

## 구현 내용
1. 현재 차트 구조 파악
   - BodySection.tsx 내 체성분 Summary 차트 (체중/골격근/체지방) 렌더링 부분 확인
   - 현재 Y축 설정 방식 확인 (고정값 or 공유 축 여부)

2. Y축 재설계
   - 체중 / 골격근 / 체지방 각 계열의 Y축을 독립적으로 분리
   - 각 계열의 실제 데이터 min/max 기준으로 동적 도메인 계산
   - 여백: min값의 -2% ~ max값의 +2% 수준으로 설정 (변화량이 잘 보이도록)
   - 데이터가 없는 계열은 기본값 유지

3. 시각적 완성
   - Y축 레이블 단위 표기 (kg, %)
   - 기존 색상·범례·툴팁 유지
   - 차트 라이브러리는 현재 사용 중인 것 그대로 사용 (변경 금지)

## 주의사항
- BodySection.tsx 외 다른 파일 절대 수정 금지
- 기존 데이터 fetch 로직, Supabase 쿼리 건드리지 말 것
- 차트 라이브러리 교체 금지 (현재 사용 라이브러리 유지)
- 데스크탑 최적 컨테이너 우선 설계 후 모바일 반응형 적용. 모바일 퍼스트 설계 금지.

## 완료 기준
- [ ] 체중/골격근/체지방 Y축이 각각 독립적으로 동작함
- [ ] 데이터 변화량이 기존 대비 시각적으로 명확하게 보임
- [ ] 빈 데이터 상태에서 에러 없음
- [ ] npm run build 에러 없음
- [ ] main 브랜치 커밋·푸시 완료

## git 스테이징 원칙
git add src/components/dashboard/BodySection.tsx 만 스테이징할 것.
git add . 또는 git add -A 절대 금지.
```
---
## 어시스턴트 작업 흐름
### 1) git 동기화 확인
```javascript
$ git status && git pull
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
Already up to date.
```
### 2) 현재 차트 구조 파악
- 차트 라이브러리: `react-chartjs-2` (Chart.js)
- 체중/골격근/체지방량 세 개의 별도 Line 차트
- 공통 `lineOpts` 객체를 세 차트가 공유 → Y축 도메인이 자동(전체 범위)으로 잡혀 변화량이 잘 안 보이는 구조
- Y축 단위 라벨 없음, 툴팁도 기본값
### 3) 계획
1. `calcYDomain` 헬퍼 추가 → min/max ±2% 계산, 빈 값/동일값 처리
2. `makeLineOpts(domain, unit)` 팩토리 추가 → 계열별 독립 옵션 생성
3. 세 차트별 옵션 분리 적용 (체중·골격근·체지방량)
4. Y축 tick · 툴팁에 단위(kg) 표기
5. `npm run build` 검증
6. 커밋·푸시
### 4) 구현 핵심
**`calcYDomain` — 동적 Y축 도메인 계산**
```typescript
type YDomain = { min?: number; max?: number }

function calcYDomain(values: (number | null | undefined)[]): YDomain {
  const nums = values.filter((v): v is number => v !== null && v !== undefined && Number.isFinite(v))
  if (nums.length === 0) return {}
  const lo = Math.min(...nums)
  const hi = Math.max(...nums)
  if (lo === hi) {
    const pad = Math.max(Math.abs(lo) * 0.02, 0.1)
    return { min: lo - pad, max: hi + pad }
  }
  return { min: lo * 0.98, max: hi * 1.02 }
}
```
**`makeLineOpts` — 계열별 독립 옵션 팩토리**
```typescript
function makeLineOpts(domain: YDomain, unit: string) {
  return {
    responsive: true, maintainAspectRatio: false,
    plugins: {
      legend: { display: false },
      tooltip: {
        enabled: true,
        callbacks: {
          label: (ctx: { parsed: { y: number | null } }) =>
            ctx.parsed.y === null ? '' : `${ctx.parsed.y}${unit}`,
        },
      },
    },
    scales: {
      x: { ticks: { color: '#555', font: { size: 9 } }, grid: { color: 'rgba(255,255,255,0.04)' } },
      y: {
        ...(domain.min !== undefined ? { min: domain.min } : {}),
        ...(domain.max !== undefined ? { max: domain.max } : {}),
        ticks: {
          color: '#555',
          font: { size: 9 },
          callback: (v: number | string) => `${v}${unit}`,
        },
        grid: { color: 'rgba(255,255,255,0.04)' },
      },
    },
  }
}
```
**적용**
```typescript
const weightValues = sorted.map(b => b.weight_kg)
const muscleValues = sorted.map(b => b.muscle_kg)
const fatKgValues = sorted.map(b => b.fat_kg)

const weightOpts = makeLineOpts(calcYDomain(weightValues), 'kg')
const muscleOpts = makeLineOpts(calcYDomain(muscleValues), 'kg')
const fatKgOpts  = makeLineOpts(calcYDomain(fatKgValues),  'kg')
```
각 `<Line>` 컴포넌트에 옵션을 분리 적용:
- 체중 추이 → `weightOpts`
- 골격근량 추이 → `muscleOpts`
- 체지방량 추이 → `fatKgOpts`
### 5) 설계 결정 — 사이드 노트
- **useMemo 미사용 결정**: 처음엔 useMemo로 감쌌으나, 상위의 `sorted` 자체가 매 렌더 새로 생성되어 의존 배열이 매번 새 reference가 되므로 useMemo가 사실상 무의미. 외과수술 원칙에 따라 단순 함수 호출로 회귀.
- **`lo === hi` 처리**: 데이터 1개 또는 모두 동일값인 경우 차트가 일자로 찍히지 않도록 `Math.max(|lo|*0.02, 0.1)`의 패딩을 ±로 적용.
- **체지방률(%) 차트 없음 확인**: 차트는 체중·골격근·체지방량 3종만 있음. 체지방률은 상단 stat 카드에만 노출되므로 % 단위 라벨링은 차트에 적용할 대상 없음. 카드 표기는 기존 그대로 유지.
- **차트 라이브러리 교체 없음**: 요구사항대로 react-chartjs-2 유지.
### 6) 빌드 검증
```javascript
$ npm run build
vite v5.4.21 building for production...
✓ 104 modules transformed.
dist/index.html                 1.14 kB │ gzip:   0.59 kB
dist/assets/index-QhyjWE97.css  4.73 kB │ gzip:   1.54 kB
dist/assets/index-CT8d_08W.js 790.94 kB │ gzip: 229.72 kB
✓ built in 2.26s
```
에러 없음.
### 7) git 스테이징·커밋·푸시
```javascript
$ git add src/components/dashboard/BodySection.tsx
$ git commit -m "feat(body): 체성분 차트 Y축 계열별 독립 도메인 + 단위 라벨"
[main cf2f713] feat(body): 체성분 차트 Y축 계열별 독립 도메인 + 단위 라벨
 1 file changed, 57 insertions(+), 13 deletions(-)

$ git push origin main
   e998140..cf2f713  main -> main
```
---
## 완료 기준 체크
- [x] 체중/골격근/체지방 Y축이 각각 독립적으로 동작함 (계열별 `makeLineOpts` 적용)
- [x] 데이터 변화량이 기존 대비 시각적으로 명확하게 보임 (min*0.98 ~ max*1.02 도메인)
- [x] 빈 데이터 상태에서 에러 없음 (`nums.length === 0` 처리, `sorted.length >= 2`에서만 렌더)
- [x] npm run build 에러 없음
- [x] main 브랜치 커밋·푸시 완료 (커밋 `cf2f713`)
## 변경 요약
- 공통 `lineOpts` 상수 → 계열별 동적 옵션 생성 함수(`makeLineOpts`)로 교체
- Y축 min/max를 각 계열의 실제 데이터 범위 기준으로 동적 계산 (±2% 여백)
- Y축 tick과 툴팁 라벨에 `kg` 단위 표기 추가
- 색상·fill·tension·범례 등 기존 시각 요소 일체 유지
- 단일 파일 변경: `src/components/dashboard/BodySection.tsx` (+57 / -13)
