8.1 KiB
8.1 KiB
간트 차트 구현 작업 지시서 (개선판 v1.3)
##1. 목표 (Overview)
StreamingAssets/isop_chart.json로드 후 UI Toolkit 기반 간트 차트 생성.- 좌측 계층 컬럼(L1~L8) 표시, 우측 월/주/일3단 헤더 + 막대(기간) / 마커(단일일) 렌더.
- 막대/마커 클릭 시
OnRowClickedByKey호출. - 외부 선택 API(
SelectByItemKey)로 행 하이라이트. - 수평/수직 스크롤 및 향후 줌(dayWidth) 지원.
##1.1 프로젝트 개요
| 항목 | 내용 |
|---|---|
| 프로젝트명 | SN2662 블록 B11GP 생산 계획 시각화 시스템 |
| 목적 | 블록 생산 일정 실시간 모니터링 / 지연 즉시 파악 / 현장 의사결정 지원 |
| 주요 사용자 | 생산 관리자, 현장 반장, 설계/자재 담당자 |
| 사용 환경 | 태블릿(터치) + PC(마우스) |
##2. 용어 & 기본 파라미터
| 항목 | 의미 | 기본값 | 조정 범위 |
|---|---|---|---|
| dayWidth | 1일당 픽셀 폭 | 16px | 8~32px (줌) |
| rowHeight | 1행 높이 | 28px | 24~40px |
| MinDate/MaxDate | 전체 데이터 기간 경계 | 파싱 계산 | - |
| Periods | 코드별 기간/마커 딕셔너리 | 각 Row별 | - |
##3. 데이터 구조 (Model)
class GanttChartData { List<BlockScheduleRow> Rows; DateTime? MinDate, MaxDate; }
class BlockScheduleRow { string? ProjNo, BlockNo, L1,L2,L3,L4,L5,L6,L7,L8; Dictionary<string,DateRange> Periods; }
class DateRange { DateTime? Start, End; int? Duration; bool IsMarker => Start.HasValue && !End.HasValue; }
- JSON 필드 패턴:
STDTxx= 시작일(Start),FNDTxx= 종료일(End),DURxx= 기간(일수). - 날짜 문자열 형식:
yyyyMMdd.
##3.1 계획/실적 분리 확대 (미래 확장) 현재 JSON은 계획(Plan)만 제공. 실적(Actual) 데이터 추가 시 권장 구조:
class ExtendedScheduleItem {
public string Code;
public DateTime? PlannedStart, PlannedEnd;
public DateTime? ActualStart, ActualEnd;
public int? PlannedDuration, ActualDuration;
public float Progress; //0..1
public bool IsCritical; // 임계경로
public int DelayDays => (ActualEnd.HasValue && PlannedEnd.HasValue) ? (int)(ActualEnd.Value - PlannedEnd.Value).TotalDays :0; // 음수면 조기 완료
public int EarlyFinishDays => (DelayDays <0) ? -DelayDays :0;
public bool IsDelayed => DelayDays >0;
}
- DelayDays 음수일 경우 조기 완료(EarlyFinish) 표시 가능.
##4. 파싱 규칙
^STDT([0-9A-FM]{2})$ // 시작일
^FNDT([0-9A-FM]{2})$ // 종료일
^DUR([0-9A-FM]{2})$ // 일수
- 시작만 있고 종료 없음 → 마커(단일 이벤트).
- 시작/종료 모두 존재 → 기간 막대.
- Duration 없으면 캘린더 일수로 자동 계산.
##5. Duration 계산 정책 (확정)
- 기본: 캘린더 일수
(End - Start).Days +1. - 옵션: 근무일(월~금) 필요 시 별도 플래그
useBusinessDays→ 계산 시 주말 제외.
##6. 레이아웃 & 타임라인 렌더링
left(px) = (Start - MinDate).TotalDays * dayWidthwidth(px) = ((End - Start).TotalDays +1) * dayWidth- 헤더3단: Month / ISO Week / Day.
- Span cell width = 구간 일수 * dayWidth.
##7. UI 계층 구조
Root
├─ HeaderContainer (MonthRow, WeekRow, DayRow)
└─ BodyScroll
└─ RowsContainer
└─ Row
├─ HierarchyContainer (L1~L8)
└─ SegmentsLayer (절대 위치 막대/마커)
##8. 스타일 전략
- 코드별 USS 클래스:
seg-code-XX,marker-code-XX. - 선택 상태:
row-selected,segment-selected. - 외부 스타일 매핑 가능:
GanttStyleConfig.jsonor ScriptableObject.
##9. 상호작용 & 이벤트
- Segment/Marker 클릭 →
OnRowClickedByKey(BlockNo). - 세그먼트 식별 확장:
BlockNo|Code문자열 전달 고려. - 터치 vs 마우스:
private void OnSegmentPointerDown(PointerDownEvent evt, BlockScheduleRow row, string code){
if(evt.pointerType=="touch") ShowTouchPopup(row, code, evt.position);
else ShowTooltip(row, code, evt.position);
}
##10. 아키텍처 & 모듈 분리
| 모듈 | 책임 |
|---|---|
| GanttJsonParser | JSON → 모델 변환 |
| GanttBuilder | UI 생성/레이아웃 |
| GanttInteraction | 이벤트/툴팁/선택 |
| GanttVirtualizer | 대량 행 가상화 |
| GanttZoomController | 줌/재계산/캐시 무효화 |
| CriticalPathAnalyzer | 선후관계 기반 임계경로 계산 |
##11. 에러 처리 & 로깅 (정책 확정)
| 케이스 | 처리 | 로그 |
|---|---|---|
| 파일 없음 | 더미 데이터 로드 후 경고 | Error |
| 날짜 파싱 실패 | 해당 코드 Skip | Warning |
| Start > End | 무효 처리 (Skip) | Warning |
| 중복 Start/End | 마지막 값 채택 | Info |
| 파일 >5MB | 경고 + 스트리밍 파서 권고 | Info |
##12. 성능 & 가상화
- 행 >300 → 가상화 고려.
- 목표:1000행 로드 <500ms / 메모리 증가 <50MB.
CalculateBarRect캐시: Key=(Start,End,dayWidth,MinDate) → 줌(dayWidth)·범위 변경 시 캐시 Flush.
##13. 줌 & 스크롤 동기화
- 헤더/바디 스크롤 동기화: 바디 Scroll 이벤트에서 헤더 translateX 적용.
- 줌 변경 순서: dayWidth 변경 → 좌표 재계산 → 캐시 Flush → Layout pass.
##14. 확장 로드맵
- Tooltip / Today 라인 / 필터 API / PNG Export.
- 임계경로 강조 / D-3 알림 아이콘 / 조기완료(EarlyFinish) 표시.
##15. 체크리스트
Mandatory
- JSON 로드
- 파싱 & Periods 구성
- Min/Max 날짜 계산
- 헤더 렌더
- 행 + 막대/마커 렌더
- 클릭 이벤트 연결
- 선택 하이라이트
- USS 기본 스타일
Optional
- 근무일 Duration 옵션
- 가상화
- 줌 컨트롤
- Tooltip
- Today 라인
- 외부 색상 매핑
- 계획/실적 분리 모델
- 임계경로 분석
- D-3 알림 / 지연 깜빡임 / 조기완료 표시
##16. 구현 스켈레톤
public void LoadFromStreamingAssets() {
var path = Path.Combine(Application.streamingAssetsPath, "isop_chart.json");
if (!File.Exists(path)) {
Debug.LogError($"File not found: {path}");
_data = CreateDummyData(); // 더미 로드 정책
BuildUI();
return;
}
var json = File.ReadAllText(path);
var raw = JsonHelper.FromJsonDictArray(json);
var data = GanttJsonParser.Parse(raw);
LoadData(data);
BuildUI();
}
##17. 테스트 계획
| 분류 | 항목 |
|---|---|
| 단위 | 날짜 파싱 / Start>End Skip 검증 |
| 단위 | Duration 자동 계산(캘린더) & 근무일 옵션 |
| 단위 | 마커 vs 기간 구분(IsMarker) |
| 단위 | 캐시 Flush(zoom/minDate 변경) |
| 통합 | 클릭 이벤트 전달(BlockNo |
| 통합 | 선택 하이라이트 토글 |
| 성능 | 1000 /5000 행 로드 시간 & 메모리 측정 |
| UX | 터치 팝업 ≤300ms 반응 |
| 시각 | 줌 변경 후 헤더/세그먼트 정렬 유지 |
##18. 리스크 & 대응
| 리스크 | 대응 |
|---|---|
| 대용량 JSON | 스트리밍/청크 파서 |
| 잦은 줌 변경 | 최소/최대 줌 제한 + 캐시 재사용 |
| 색상 유지보수 | 외부 매핑 파일화 |
| 계획/실적 혼동 | 확장 시 모델 전환 마이그레이션 문서 |
| 태블릿 성능 저하 | 가상화 + 스타일 최소화 |
##19. Pending 결정
- 근무일 옵션 실제 사용 여부.
- 색상/코드 매핑 저장 위치.
- 임계경로 입력 포맷(선후관계) 정의.
##20. 사용 흐름
var chart = FindObjectOfType<ModelDetailChartView>();
chart.LoadFromStreamingAssets();
chart.SelectByItemKey("B11GP");
##21. 향후 API 예시
public void ApplyFilter(DateTime? from, DateTime? to, string? code = null, string? blockNo = null);
public void SetZoom(float newDayWidth); // Flush + 재계산
public void ToggleTodayLine(bool enabled);
public void SetActualDates(string blockNo, string code, DateTime? actualStart, DateTime? actualEnd);
public IReadOnlyList<string> GetCriticalPathCodes();
##22. USS 샘플
.chart-row { flex-direction: row; height:28px; position: relative; border-bottom:1px solid #3A3A3A; }
.hierarchy-container { width:240px; flex-wrap: wrap; }
.hierarchy-cell { font-size:11px; width:60px; padding-left:4px; }
.segments-layer { flex-grow:1; position: relative; }
.segment { border-radius:3px; height:22px; }
.seg-code-21 { background:#3A7BFF; }
.seg-code-23 { background:#7BD95A; }
.marker { color:#2AA3FF; font-size:12px; }
.row-selected { background:rgba(255,255,0,0.15); }
.span-cell { text-align:center; font-size:11px; border-right:1px solid #222; }
.day-cell { text-align:center; font-size:10px; border-right:1px solid #333; width:16px; }
.segment.delayed { background:#d0021b; animation: blink1s infinite; }
.today-line { position:absolute; width:1px; background:#ff6b00; top:0; bottom:0; }
@keyframes blink {0%,100% { opacity:1; }50% { opacity:.3; } }
##23. 데이터 누락 대응
- 렌더 필터:4x 계열 우선 →3x/5x 존재 시 추가 →0x/Mx 전부 null이면 제외.
- Null 날짜: 렌더 생략, Progress=0.
- 파싱 실패: Skip + Warning.
##24. 최종 검수 체크리스트
| 항목 | 기준 |
|---|---|
| 날짜 정확성 | STDT/FNDT UI 값 = JSON 원본 |
| 가독성 | 태블릿 폰트 ≥14px, 대비 확보 |
| 반응성 | 터치 팝업 ≤300ms |
| 안정성 | Null90% 상황 문제 없음 |
| 임계경로 | 지연/조기완료 시 색상/아이콘 반영 |
| 줌 | dayWidth 변경 후 좌표/헤더 싱크 정확 |
##25. 종료 위 내용으로 구현 진행. Pending 결정 완료 시 문서 재갱신. (문서 버전: v1.3)