타임라인

배율을 고르고, 날짜 범위를 정하고, 주말과 휴일에 음영을 넣어요.

타임라인은 화면에 얼마만큼의 기간을 담을지 정해요. 차트가 시작할 배율을 고르고, 날짜 범위를 데이터에 맞추거나 고정하고, 아무도 일하지 않는 날에 음영을 넣어요.

시작 배율 고르기

배율은 촘촘한 순서로 다섯 가지가 준비돼 있어요. defaultScale은 차트가 시작할 배율을 정하고 기본값은 month예요.

배율칸이 담는 기간드래그 단계하루당 px여유
day6시간1시간28830시간
week1일6시간725일
month7일1일1835일
quarter1개월7일45개월
year1분기28일115개월

한 단계 축소할 때마다 약 4배씩 성겨지고, 헤더 칸은 60px에서 130px 사이를 유지해요. quarter와 year의 칸은 달력을 따라요. 드래그 단계는 막대가 걸리는 스냅 간격이기도 해요. 작업 편집에서 다뤄요.

ProjectChart.tsx
import { useState } from 'react';
import { ReactGanttChart, type Task } from '@jaeungkim/gantt-chart';
import '@jaeungkim/gantt-chart/style.css';

const initialTasks: Task[] = [
  { id: 'spec', name: 'Spec', parentId: null, sequence: '1',
    startDate: '2026-06-10', endDate: '2026-06-13' },
];

export function ProjectChart() {
  const [tasks, setTasks] = useState(initialTasks);

  return (
    <ReactGanttChart
      tasks={tasks}
      onTasksChange={setTasks}
      defaultScale="week"
      height={420}
    />
  );
}

defaultScale은 마운트할 때 한 번만 읽고 이후 변경은 무시해요. onScaleChange는 ref, 휠 줌, 키보드, zoomToFit() 중 무엇이 바꿨든 확정된 배율 변경을 알려주고 마운트할 때는 발생하지 않아요.

차트는 배율 선택기를 그리지 않아요. 직접 만든 선택기를 ref의 setScale에 연결하고 값은 onScaleChange에서 읽어요. 명령형 API를 참고해요.

데이터에 타임라인 맞추기

범위 prop이 없으면 롤업한 작업 날짜에 타임라인을 맞춰요. 가장 이른 startDate부터 가장 늦은 endDate까지에 양쪽으로 눈금 5개만큼 여유를 더해요. 칸은 그 지점을 눈금 경계로 내려 맞춘 곳에서 시작하므로 month 배율의 주 단위 칸은 일요일에 열려요.

마지막 칸은 여유 구간보다 뒤에서 끝날 수 있어요. 작업 목록과 계층 구조를 참고해요.

onRangeChange는 렌더링된 범위를 Dayjs 값 두 개로 알려줘요. end는 마지막 칸의 시작에 눈금 하나를 더한 값이라 범위에서 빠지고, 양끝을 포함해 조회하면 마지막 눈금을 두 번 세게 돼요.

칸이 처음 만들어진 렌더링에서 한 번, 그 뒤로는 두 날짜가 바뀔 때마다 발생하고, 배율을 바꾸면 거의 항상 두 날짜가 함께 바뀌어요. 핸들러는 ref로 읽으므로 다른 함수로 바꿔도 다시 발생하지 않고, 나중에 넘긴 핸들러는 그다음 변경부터 받아요.

오늘 날짜에는 세로선 하나를 그리고 헤더의 같은 자리에 점을 함께 표시해요. 다른 표식을 더하거나 이 선을 옮기고 라벨을 달거나 숨기는 prop은 없어서, 모양을 바꾸려면 .gantt-today-marker와 .gantt-today-dot에 CSS를 적용해요. 날짜는 타임라인을 다시 계산할 때 읽으므로 자정에 저절로 움직이지 않고, 오늘이 범위를 벗어나면 사라져요.

범위 고정하거나 넓히기

visibleStart와 visibleEnd는 ISO 문자열을 받아서 타임라인을 데이터에 맞추는 대신 고정해요. 고정한 시작은 눈금 경계로 내려 맞추고, 고정한 끝은 넘긴 값을 그대로 써요.

양쪽을 모두 고정하면 작업 날짜는 배치에 영향을 주지 않아요. 범위 밖 작업도 행은 그대로 두고, 막대는 가까운 쪽 가장자리에 최소 너비로 붙여요.

한쪽만 고정하면 나머지 한쪽은 여유를 두고 자동으로 맞춰요. 작업이 없는데 한쪽만 고정하면 차트는 빈 요소를 그리고 오류를 던지지 않아요.

infiniteScroll은 스크롤을 따라 범위를 넓히고 기본값은 꺼짐이에요. 어느 쪽이든 끝에서 뷰포트 절반 안쪽까지 오면 그쪽에 뷰포트 하나 정도의 눈금을 더하고, 한쪽당 2000눈금까지 늘어나요.

앞쪽을 먼저 확인하므로 맨 왼쪽에서 마운트한 차트는 바로 넓어져요. 앞에 칸을 더할 때는 스크롤도 함께 옮겨서 보이는 날짜를 유지해요. 고정한 끝은 넓어지지 않고, 배율을 바꾸면 늘려 둔 만큼은 사라져요.

줌 허용하기

zoomOnWheel의 기본값은 꺼짐이고, 이때 Ctrl이나 Cmd와 휠은 페이지 확대에 그대로 쓰여요. 켜면 그 제스처가 배율을 한 단계 옮기고, 휠을 내리면 축소해요.

커서 아래 날짜는 x 위치를 유지해요. 커서가 작업 목록 위에 있으면 타임라인 왼쪽 가장자리를 기준으로 삼고, 마지막 칸을 지난 자리에서는 줌이 취소돼요.

Ctrl이나 Cmd와 ArrowUp은 한 단계 촘촘하게, ArrowDown은 한 단계 성기게 바꾸고 따로 켤 prop이 없어요. 기준은 타임라인 가운데이고 바뀐 배율을 알려줘요. 키보드와 스크린 리더를 참고해요.

둘 다 가장 촘촘한 배율과 가장 성긴 배율에서 멈춰요. 휠만 굴리면 세로로, Shift와 함께 굴리면 가로로 스크롤하고, 이는 zoomOnWheel을 켜든 끄든 같아요. ref의 zoomToFit()은 렌더링된 막대 전체를 한 번에 화면에 맞춰요. 명령형 API에서 다뤄요.

주말과 휴일 음영 넣기

showNonWorkingDays의 기본값은 켜짐이고, 비근무일을 막대 뒤에 음영으로 칠하고 휴일 이름을 눈금 줄에 써요. workingWeekdays는 주간 규칙을 UTC 요일 번호로 정하고 0이 일요일이며 기본값은 월요일부터 금요일까지예요. holidays는 그 위에 얹는 예외 목록이고 각 눈금의 UTC 날짜와 맞춰요.

const WORKING_WEEKDAYS = [1, 2, 3, 4, 5, 6];
const HOLIDAYS = [
  '2026-06-06',
  { date: '2026-09-24', endDate: '2026-09-26', label: 'Chuseok', color: '#7c3aed' },
];

<ReactGanttChart
  tasks={tasks}
  onTasksChange={setTasks}
  workingWeekdays={WORKING_WEEKDAYS}
  holidays={HOLIDAYS}
/>

두 배열은 참조로 비교하므로 렌더링마다 새 리터럴을 넘기면 달력을 매번 다시 만들어요. 모듈 상수나 useMemo에 담아 둬요.

YYYY-MM-DD 문자열만 넘기면 이름 없는 휴일이에요. 객체 형태는 마지막 날을 포함하는 endDate와 label, color를 더해요. 같은 날을 두 항목이 덮으면 배열에서 앞선 항목이 이겨요.

이름은 구간이 44px 이상일 때만 눈금 줄에 보여요. 그래서 위 데모의 셧다운은 이름이 보이고 하루짜리 오프사이트는 보이지 않아요. 더 좁은 구간은 헤더에서 포인터를 올려 읽어요.

두 이름이 같은 칸에서 시작하면 앞선 이름만 보여요. 이름은 display가 아니라 opacity로 숨기므로 스크린 리더는 어느 쪽이든 읽어요.

color는 주말 음영과 같은 농도로 입혀지고, 휴일 구간은 옆 주말과 나눠서 그려요. 이미 쉬는 날에 걸린 휴일도 이름과 색을 유지해요.

이름 모양은 .gantt-holiday-cell에 CSS를 적용해서 바꿔요. 휴일 하나에만 줄 수 있는 값은 color뿐이고, 클래스나 아이콘은 붙일 수 없어요.

음영은 배율 이름이 아니라 눈금을 따라요. day, week, month는 음영을 넣고 quarter와 year는 넣지 않아요. 한 달짜리 눈금은 절반만 비근무일일 수 없기 때문이에요. 여러 날을 담은 칸에서도 하루씩 따로 확인하고, 이어진 비근무일은 하나의 구간으로 합쳐요.

적용 전에 확인하기

  • 줌은 다섯 단계로 고정돼 있어요. 하루당 픽셀을 정하는 prop도, 소수 단위 줌도, 직접 추가하는 배율도 없어요.
  • defaultScale은 저장되지 않아요. 새로고침하면 다시 그 값에서 시작하므로 배율은 앱에서 보관했다가 다시 넘겨요.
  • showNonWorkingDays는 음영만 담당해요. false는 음영과 휴일 이름을 감출 뿐이고, workingCalendar가 꺼져 있으면 드롭한 막대는 토요일에도 그대로 놓여요. 근무일 달력을 참고해요.
  • firstDayOfWeek는 week 배율의 위쪽 헤더만 다시 묶어요. 음영은 움직이지 않고, month 배율의 주 단위 칸도 여전히 일요일에 열려요.
  • holidays 항목은 느슨하게 읽어요. 기간은 최대 366일까지 늘어나고, date보다 앞선 endDate는 시작일 하루로 줄어들고, 해석할 수 없는 date는 항목째 버려요.

prop 정의는 GanttProps, Holiday 형태는 배율과 테마 타입에서 확인해요.