chart

문서 › 상호작용과 내보내기

실시간 차트 (스트리밍)

실시간 차트: append() 로 이어 붙이고 오래된 점은 창 밖으로, update() 는 부드럽게 전환. 14가지 차트 모두, SSE·WebSocket 예제.

실시간 차트란 데이터가 들어오는 대로 다시 그려 지금 상태를 보여 주는 차트입니다(접속자 수, 서버 자원, 체결 가격, 민원 접수 현황). 82chart 는 realtime 옵션 하나로 14가지 차트 모두 실시간으로 쓸 수 있습니다. 두 가지 방법이 있습니다.

  • 이어 붙이기 — chart.append(points): 선·영역·막대·산점도(시간 축이나 카테고리 축), 콤보, 달력 히트맵. 새 점은 오른쪽에서 미끄러져 들어오고 오래된 점은 창(window 점 개수, duration 밀리초) 밖으로 빠집니다. 값 축은 새 범위로 부드럽게 넓어지거나 좁아집니다(yAxis.min / max 를 주면 고정).
  • 바꾸기 — chart.update(data) 나 값 하나만 바꾸는 chart.setValue(시리즈, 순서, 값): 모든 종류. 파이·도넛 조각 각도, 게이지 값, 방사형 다각형, 막대·피라미드·퍼널·폭포 길이, 히트맵·달력·트리맵 색과 칸 위치, 가운데 합계와 라벨 숫자가 이전 값에서 새 값으로 이어지며 바뀝니다. 새 항목은 0에서 자라납니다.

움직이는 예제

이 예제의 코드

const chart = createChart('#chart', {
  type: 'line',
  title: '실시간 접속자',
  unit: '명',
  xAxis: { type: 'time' },
  zoom: true,
  realtime: { duration: 60000 },
  data: { series: [{ name: '접속자', data: [] }, { name: '로그인', data: [] }] },
});
let users = 1200, logins = 400;
setInterval(() => {
  users = Math.max(0, users + Math.round((Math.random() - 0.48) * 80));
  logins = Math.max(0, Math.round(users * (0.3 + Math.random() * 0.05)));
  chart.append({ series: [[[Date.now(), users]], [[Date.now(), logins]]] });
}, 500);

위 차트는 0.5초마다 점을 붙이고 최근 1분(duration: 60000)만 남깁니다. 마우스를 올리면 그림이 멈춰 툴팁이 그 점에 머무르고, 손을 떼면 그동안 들어온 데이터를 따라잡습니다(pauseOnHover). 확대(+)하면 그 구간을 보고 있는 동안에는 따라가지 않고 "최신으로" 단추가 나옵니다.

바꾸기: 모든 차트가 부드럽게

움직이는 예제

이 예제의 코드

const chart = createChart('#chart', {
  type: 'donut',
  title: '결제 수단 (실시간)',
  unit: '건',
  realtime: { tween: 400 },
  data: { labels: ['카드', '간편결제', '계좌이체', '휴대폰'], series: [{ name: '결제', data: [520, 310, 120, 50] }] },
});
const counts = [520, 310, 120, 50];
setInterval(() => {
  counts[Math.floor(Math.random() * 4)] += Math.round(Math.random() * 40);
  chart.update({ labels: ['카드', '간편결제', '계좌이체', '휴대폰'], series: [{ name: '결제', data: [...counts] }] });
}, 800);

움직이는 예제

이 예제의 코드

const chart = createChart('#chart', {
  type: 'gauge',
  title: 'CPU 사용률',
  unit: '%',
  realtime: { tween: 500 },
  gauge: { max: 100, target: 80, targetLabel: '경고', bands: [{ to: 60, color: '#1baf7a' }, { to: 80, color: '#eda100' }, { to: 100, color: '#e34948' }] },
  data: { series: [{ name: 'CPU', data: [42] }] },
});
let cpu = 42;
setInterval(() => {
  cpu = Math.min(100, Math.max(3, cpu + (Math.random() - 0.5) * 18));
  chart.setValue(0, 0, Math.round(cpu));
}, 1000);

움직이는 예제

이 예제의 코드

const chart = createChart('#chart', {
  type: 'treemap',
  title: '업종 등락 (실시간)',
  colorBy: 'change',
  deltaColors: 'korea',
  realtime: { tween: 600 },
  data: { series: [
    { name: '반도체', data: [{ x: '삼성전자', y: 420, change: 0.8 }, { x: 'SK하이닉스', y: 140, change: -1.2 }] },
    { name: '자동차', data: [{ x: '현대차', y: 52, change: 0.4 }, { x: '기아', y: 41, change: -0.3 }] },
    { name: '인터넷', data: [{ x: 'NAVER', y: 33, change: 1.5 }, { x: '카카오', y: 18, change: -2.1 }] },
  ] },
});
setInterval(() => {
  const d = chart.getOptions().data;
  chart.update({ series: d.series.map(s => ({ ...s, data: s.data.map(p => ({ ...p, change: +(p.change + (Math.random() - 0.5) * 0.6).toFixed(2) })) })) });
}, 1500);

realtime 옵션

옵션기본값설명
realtime-true 또는 아래 객체. 없으면 update() 는 지금처럼 바로 다시 그림
window-시리즈마다 남길 최대 점 수 (달력은 날 수)
duration-시간 축: 최근 이 밀리초만 남김 (예: 60000 = 1분)
pauseOnHovertrue마우스가 데이터 위에 있는 동안 그림을 멈춤 (데이터는 계속 받음)
fps30초당 최대 그리기 횟수. 한 프레임 안에 들어온 append·update 는 한 번에 그림
tween300update()·setValue() 가 새 값으로 바뀌는 시간(ms). 0 이면 바로. "동작 줄이기" 설정이면 언제나 바로

API

메서드 · 이벤트설명
append(points, { series })점 하나 { x, y }, 한 시리즈의 목록 [[x, y], …], 또는 여러 시리즈 { labels?, series: [[…], […]] }. 숫자 x 는 xAxis: { type: 'time' } 에서 에포크 밀리초
update(data)realtime 이면 프레임에 맞춰 묶고 부드럽게 바꿈
setValue(series, index, y)값 하나만 바꿈 (게이지, 조각 하나, 칸 하나)
clear()모든 점을 지움 (시리즈 이름과 옵션은 그대로)
isLive() / follow()최신 데이터를 따라가는 중인지 / 확대를 풀고 다시 따라가기 ("최신으로" 단추와 같음)
announce()지금 요약을 스크린리더에 바로 읽어 줌
on('append', ({ added, total }) => …)append 할 때마다

서버에서 받기: SSE 와 WebSocket

// 서버가 보내는 이벤트(SSE): data: {"t": 1792800000000, "v": 1234}
const chart = createChart('#chart', { type: 'line', xAxis: { type: 'time' }, realtime: { duration: 5 * 60000 }, data: { series: [{ name: '요청 수', data: [] }] } });
const es = new EventSource('/api/stream');
es.onmessage = (e) => { const { t, v } = JSON.parse(e.data); chart.append([[t, v]]); };
// WebSocket: 한 메시지에 여러 시리즈
const ws = new WebSocket('wss://example.go.kr/metrics');
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);                // { t, cpu, mem }
  chart.append({ series: [[[m.t, m.cpu]], [[m.t, m.mem]]] });
};
// 화면을 떠날 때
// ws.close(); chart.destroy();

접근성과 성능

  • 스크린리더 알림(aria-live)은 데이터가 들어올 때마다가 아니라 많아야 10초에 한 번입니다. 키보드·마우스로 옮기면 바로 읽고, chart.announce() 로 원할 때 읽힐 수 있습니다. 차트 요약(aria-label)은 1초에 한 번 고칩니다.
  • 숨긴 데이터 표와 toCSV() 는 지금 창의 데이터입니다(표는 1초에 한 번 고침).
  • 매 프레임 SVG 를 새로 만들지 않고, 구조가 같으면 기존 요소의 속성과 글자만 바꿉니다. 그래서 요소 수가 늘지 않고 툴팁·강조가 끊기지 않습니다.
  • 실시간 차트는 점진 렌더링을 하지 않고, 점이 그림 폭의 절반보다 많으면 LTTB 로 줄여 그립니다(CSV·표는 원본).
  • "동작 줄이기"(prefers-reduced-motion)가 켜져 있으면 미끄러짐·전환 없이 바로 바꿉니다.
  • 측정(노트북, Chrome, 개발 빌드): 4개 시리즈 × 2,000점 창에 초당 60번 붙이면 한 번 그리는 데 평균 약 7ms(p95 11ms), 시리즈 2개 × 120점 선은 약 1.5ms, 파이·게이지·트리맵 같은 바꾸기 차트는 1ms 안팎입니다.