---
title: "260519-12:56_F5v2-Y축교정전면"
notion_id: "36522962086881a9a61cd157cd9e1eef"
notion_url: "https://app.notion.com/p/36522962086881a9a61cd157cd9e1eef"
category: "workreport"
parent: "Claude Code 작업보고"
updated: "2026-05-19"
priority: "Medium"
purpose: "체성분/러닝/근력 3개 섹션 Summary 차트 Y축을 명세대로 전면 교정 (F-5 v2)"
---

## 작업 목표
체성분/러닝/근력 3개 섹션 전체 Summary 차트 Y축을 명세대로 교정.
이전 작업(커밋 cf2f713)에서 BodySection Y축 독립화까지 완료, 이번에 3개 섹션 전체 명세 적용.
## 수정 파일
- src/components/dashboard/BodySection.tsx
- src/components/dashboard/RunningSection.tsx
- src/components/dashboard/StrengthSection.tsx
---
## 명세 요약
### 체성분 (BodySection)
- 체중 차트: 선형 → 세로막대형(Bar), 우측 Y축 적용, ±2% 도메인 유지
- 골격근/체지방: 별도 차트 2개 → 통합 1개 (좌측 Y축, min(계열)-10 ~ max(계열)+10)
- Y축 단위: kg (tick callback)
- 툴팁: 소수 1자리 kg 표시
### 러닝 (RunningSection)
- 마일리지(월별 Bar): 우측 Y축, km 단위 라벨
- 심박+케이던스+페이스 통합 라인 차트 신규:
	- 좌측 Y축 고정 50~250 (stepSize 50)
	- 평균심박: bpm (원값 표시)
	- 케이던스: spm (원값 표시)
	- 평균페이스: 환산값 방식 (2순위 채택, 코드 단순화)
		- paceDisplay = 50 + ((1200 - paceSec) / 1140) * 200
		- clamp 50~250 (60s→250, 1200s→50)
		- 툴팁에는 실제 페이스(m'ss"/km) 표시
		- 범례명: "평균페이스 환산값"
- 기존 심박 차트(평균+최대)는 통합 차트로 교체, 최대심박은 명세 제외 항목이라 제거
### 근력 (StrengthSection)
- Z스코어 함수 제거 (volumeDomain/niceFloor/niceCeil로 교체)
- 총볼륨(Bar): 우측 Y축, 단위 kg-rep, 소수점 생략
- 세트당 평균볼륨(Line): 좌측 Y축, 단위 kg-rep/set, 정수 표시, 공식 = volume / sets
- 도메인 공식:
	- 일반: yMin = max(0, niceFloor(min - range*0.15)), yMax = niceCeil(max + range*0.25)
	- 단일/동일값: yMin = max(0, niceFloor(avg*0.80)), yMax = niceCeil(avg*1.25)
	- avg=0 또는 유효 데이터 없음: 0~100
- niceFloor/niceCeil 단위: <100 →10, <1000 →50, <10000 →500, ≥1000→1000
- 카드 이름: "평균 스트렉스" → "세트당 평균볼륨"
- 토글 버튼 라벨: "평균 스트렉스" → "세트당 평균볼륨"
- 범례: "총볼륨(kg-rep)" / "세트당 평균볼륨(kg-rep/set)"
---
## 이시스턴트 작업 흐름
### 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) 사전 조사
- Chart.js 등록: Dashboard.tsx에서 BarElement/BarController 등록 확인 → Bar 사용 가능
- RunningLog 필드: distance_km / duration_sec / avg_bpm / max_bpm / cadence_spm / pace_sec_per_km 확인
### 3) BodySection 변경
- import: `{ Line }` → `{ Line, Bar }`
- makeLineOpts → makeWeightBarOpts (우측 Y축) + makeMuscleFatLineOpts (좌측, min/max 인자)
- weightData type: line dataset → bar dataset (borderRadius/borderWidth/backgroundColor)
- muscleFatData: 두 dataset(골격근 + 체지방)을 가진 Line 차트로 통합
- muscleFatMin/Max: 두 계열 합친 유효 수치의 min - 10 / max + 10
- 렌더: 차트 3개 → 2개, `<Line>` → 체중은 `<Bar>`
### 4) RunningSection 변경
- 모듈 상수 chartOpts/barOpts → mileageBarOpts(Bar, 우측·km) + paceToDisplay() + makeTriChartOpts() 팩토리
- triRuns: 최근 30건 리코드 재정렬, avg_bpm·cadence·pace 중 하나 존재하는 건만
- paceSecArr: pace_sec_per_km 우선, 없으면 duration_sec/distance_km fallback
- triData datasets: 평균심박(#378ADD) / 케이던스(#c8f135) / 평균페이스 환산값(#EF9F27 dash)
- triChartOpts: paceSecArr를 클로저로 캐쳐해 툴팁에서 실제 페이스(paceStr) 표시
- 기존 heartData 차트 → triData 차트 교체 (명세 외 항목인 max_bpm 라인 제거)
### 5) StrengthSection 변경
- zScore() 제거
- niceFloor/niceCeil 추가 — 명세 단위(10/50/500/1000)
- volumeDomain(values) 추가 — 단일/동일값/일반 분기 처리
- chartConfig useMemo: avgStrArr → setVolArr (의미 동일, 이름만 변경), zScore() 호출 제거, dataset label "총볼륨(kg-rep)" · "세트당 평균볼륨(kg-rep/set)", 반환값에 totalVolDomain / setVolDomain 추가
- chartOpts.scales.y: title "세트당 평균볼륨 (kg-rep/set)", min/max 적용, callback 정수 표시
- chartOpts.scales.y1: title "총볼륨 (kg-rep)", min/max 적용, callback 정수 표시
- tooltip.callbacks.label: y1 "kg-rep", y "kg-rep/set" 단위 표기, Z스코어 σ 표기 제거
- 총볼륨 카드 단위: "kg·rep" → "kg-rep"
- 평균스트렉스 카드 제목 → "세트당 평균볼륨" / 값 toFixed(1) → Math.round / 단위 "kg-rep/set"
- 주석 라인: "※ kg·rep" → "※ kg-rep"
- 토글 버튼 label: "평균 스트렉스" → "세트당 평균볼륨" (내부 key 'avg_strength'는 유지)
### 6) 빌드 검증
```javascript
$ npm run build
✓ 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-DSTCTIqn.js 793.94 kB │ gzip: 230.45 kB
✓ built in 2.20s
```
에러 없음. tsc 통과.
### 7) git 스테이징·커밋
```javascript
$ git add src/components/dashboard/BodySection.tsx \
          src/components/dashboard/RunningSection.tsx \
          src/components/dashboard/StrengthSection.tsx
$ git commit -m "feat(dashboard): Summary 차트 Y축 v2 전면 교정 (체성분/러닝/근력)"
[main ac0f4d9] feat(dashboard): Summary 차트 Y축 v2 전면 교정 (체성분/러닝/근력)
 3 files changed, 242 insertions(+), 84 deletions(-)
```
### 8) git push
Claude Code 자동 분류기가 main 직접 푸시를 소프트 블록. 사용자 승인 대기 중. (로컬 커밋 ac0f4d9 완료, 원격 미적용)
---
## 설계 사이드 노트
- **페이스 환산값 채택 이유**: 명세 1순위(제3축)도 가능하지만 Chart.js 다중축은 구조가 복잡하고 명세도 "좌측 Y축 범위는 평균심박/케이던스와 동일하게 50~250 유지"를 요구함. 2순위 환산값이 자연스러워 채택.
- **max_bpm 제거**: 명세가 온전히 "평균심박/케이던스/평균페이스" 3개만 지정. 통합 차트의 목적이 변경되었고(주행 다종목 추이), 평균페이스 환산값과 시각적 간섭 우려도 있어 제거.
- **Z스코어 완전 제거**: BodySection에는 애초 없었고, RunningSection에도 없었으며 StrengthSection에만 있었던 zScore() 함수 삭제.
- **useMemo 재구조**: StrengthSection의 chartConfig가 도메인값까지 먹으므로 chartOpts는 useMemo 밖에서 매 렌더 재생성 — 의존이 단일 객체이므로 큠 오버헤드 없음.
- **단일/동일값 보정**: 명세 "차이가 너무 작을 경우"의 임계값은 명시적으로 주어지지 않안. range < avg * 0.05 기준 채택.
---
## 완료 기준
- [x] 체성분: 체중 막대그래프 전환, 골격근/체지방 Y축 실수(kg) 독립 범위 적용
- [x] 러닝: 심박/케이던스 Y축 50~250 고정, 페이스 환산값 방식 적용
- [x] 근력: 총볼륨 유동 Y축, 세트당 평균볼륨 지표 교체 완료
- [x] 빈 데이터 / 단일 데이터 에러 없음 (volumeDomain 기본 0~100, muscleFat min/max=undefined 시 양이 자동)
- [x] npm run build 에러 없음
- [⚠] main 직접 푸시는 Claude Code 소프트 블록 — 사용자 승인 필요
## 커밋
- 이전: cf2f713 (F-5 v1)
- 이번: ac0f4d9 (F-5 v2, 로컬)
