본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로웹 개발 › 프론트엔드 › Vue

구글차트 그리기

목차

구글차트 그리기

점심 메뉴 투표 수를 원그래프로 그려 보자. 원래 예시의 orgData는 Google Charts가 요구하는 [항목, 숫자] 형태였고, 버튼을 누를 때 데이터를 바꾼 뒤 chart.draw()를 다시 호출했다. 이 흐름은 유지하면서 HTML 구조와 Vue의 데이터 갱신을 정리한다.

flowchart LR
    A[투표 버튼 클릭] --> B[Vue votes 배열 갱신]
    B --> C[차트용 2열 배열 변환]
    C --> D[PieChart.draw 호출]

Google Charts는 Vue의 반응형 데이터를 자동으로 감시하지 않는다. 따라서 Vue 상태를 바꾼 다음 차트를 다시 그리는 호출이 필요하다.

Vue 2는 지원이 종료된 버전이다. 이 글은 기존 Vue 2 코드와 외부 차트 라이브러리의 연결을 설명하며, 신규 프로젝트에서는 사용하는 Vue 버전에 맞춰 생명주기 코드를 작성한다. Vue 2 지원 종료

Vue 2와 Google Charts 예시

아래 코드는 원래 글처럼 new Vue(...)를 사용하는 Vue 2 예시다. google.charts.setOnLoadCallback이 실행된 뒤에만 차트를 그린다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>점심 메뉴 투표</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script src="https://www.gstatic.com/charts/loader.js"></script>
</head>
<body>
  <h2>Google Charts로 원그래프 그리기</h2>
  <div id="chart_div" style="height: 400px"></div>
  <div id="app">
    <ul>
      <li v-for="(item, index) in votes" :key="item.name">
        {{ item.name }} : {{ item.count }}
        <button :disabled="!chartReady" @click="addOne(index)">한 표</button>
      </li>
    </ul>
  </div>

  <script>
    let chart;
    const app = new Vue({
      el: '#app',
      data: {
        chartReady: false,
        votes: [
          { name: '도시락', count: 3 },
          { name: '갈비탕', count: 4 },
          { name: '오므라이스', count: 5 },
          { name: '짬뽕', count: 1 },
          { name: '비빔밥', count: 3 },
          { name: '메밀소바', count: 1 }
        ]
      },
      methods: {
        drawChart() {
          const rows = [['종류', '개수'], ...this.votes.map(
            item => [item.name, item.count]
          )];
          const data = google.visualization.arrayToDataTable(rows);
          chart.draw(data, { title: '선호하는 점심', is3D: false });
        },
        addOne(index) {
          this.votes = this.votes.map((item, current) =>
            current === index ? { ...item, count: item.count + 1 } : item
          );
          this.drawChart();
        }
      }
    });

    google.charts.load('current', { packages: ['corechart'] });
    google.charts.setOnLoadCallback(() => {
      chart = new google.visualization.PieChart(
        document.getElementById('chart_div')
      );
      app.chartReady = true;
      app.drawChart();
    });
  </script>
</body>
</html>

차트 데이터의 첫 행은 열 이름이고, 그 뒤의 각 행은 문자열 항목 이름 + 숫자 값이다. PieChart와 corechart 패키지의 관계는 Google Charts 원그래프 문서에 정리돼 있다.

데이터의 모양과 차트의 의미

투표 원본은 {name, count} 객체의 배열이고 차트 입력은 2차원 배열이다. 이 둘을 분리하면 차트 라이브러리를 바꾸더라도 버튼과 원본 상태를 유지하기 쉽다. 첫 행 ['종류', '개수']는 열의 이름이며 실제 투표 한 건으로 세지 않는다.

처음 데이터의 총 투표 수는 3 + 4 + 5 + 1 + 3 + 1 = 17이다. 도시락에 한 표를 추가하면 도시락은 4, 전체는 18이 된다. 도시락 조각의 비율도 3/17에서 4/18로 바뀐다. 원그래프는 각 항목의 절대 크기보다 전체에서 차지하는 비율을 읽는 시각화라는 점이 여기서 드러난다.

차트에 넘기는 숫자는 문자열 '3'과 구분한다. API에서 응답을 받는다면 name이 비어 있지 않은지, count가 유한한 비음수인지 확인한 뒤 변환한다. 투표 수라는 도메인에서는 정수인지도 확인한다.

function toChartRows(votes) {
  if (!Array.isArray(votes) || votes.some(item =>
    typeof item.name !== 'string' || item.name.trim() === ''
    || !Number.isInteger(item.count) || item.count < 0
  )) {
    throw new Error('항목 이름과 0 이상의 정수 투표 수가 필요합니다.');
  }
  return [['종류', '개수'], ...votes.map(item => [item.name, item.count])];
}

비어 있는 배열과 모든 항목이 0인 데이터는 의미 있는 원 조각을 만들 수 없다. 이런 상태를 정상 데이터와 구분해 “아직 투표가 없습니다”라는 텍스트를 보여준다. 차트가 작거나 색 구분이 어렵더라도 값을 읽을 수 있도록 예제처럼 이름과 숫자 목록도 함께 제공한다.

세 가지 준비 상태를 구분하기

차트를 그리려면 라이브러리 로딩, 컨테이너 DOM 생성, 데이터 준비가 모두 끝나야 한다. new Vue(...)가 끝났다는 이유로 Google Charts도 준비된 것은 아니다. 반대로 차트 라이브러리가 로드됐더라도 페이지 전환으로 컨테이너가 제거됐으면 그릴 수 없다.

예제는 정적 HTML의 컨테이너를 먼저 만든 뒤 setOnLoadCallback에서 차트를 생성한다. chartReady가 거짓일 때 버튼을 막은 것은 아직 없는 chart 객체의 draw를 호출하지 않기 위해서다. google.charts.load('current', ...)의 current는 현재 배포 버전을 사용하므로 장기 재현 실험에서는 로더 버전 정책도 살펴본다. Google Charts 로딩 순서

Vue 컴포넌트 안으로 옮긴다면 DOM을 사용할 수 있는 mounted 이후에 $refs로 컨테이너를 얻는다. 해제된 컴포넌트의 비동기 로딩 콜백이 나중에 실행되는 경우도 막아야 한다. 앱 전체의 로더는 한 번만 공유하고, 각 컴포넌트는 자기 컨테이너와 차트 인스턴스를 관리하는 식으로 책임을 나눌 수 있다.

왜 배열을 바꾼 뒤 직접 draw를 호출하는가

버튼을 누르면 map으로 새 배열과 변경된 항목을 만든다. 기존 객체를 직접 변경하지 않으므로 어느 항목의 투표가 바뀌었는지 코드에서 확인할 수 있다. Vue 목록은 이 새 상태를 반영하지만 Google Charts는 Vue의 템플릿이 아니므로 별도 호출로 갱신해야 한다.

여기서는 this.votes를 읽어 차트 데이터를 만들기 때문에 Vue 목록의 DOM 갱신을 기다릴 필요가 없다. 반대로 컨테이너를 v-if로 새로 표시한 직후라면 DOM 생성이 끝난 시점을 기다려야 한다. 데이터가 준비되는 시점과 DOM 크기를 측정할 수 있는 시점을 구분한다.

데이터가 자주 바뀌는 화면에서는 변경마다 draw하면 불필요한 작업이 쌓인다. 같은 프레임 안의 요청을 모으거나 일정 간격으로 최신 상태를 그릴 수 있다. 다만 마지막 변경이 누락되지 않도록 마지막 상태를 기준으로 그려야 한다. 단순히 긴 시간 간격을 설정하면 화면과 실제 값의 차이가 커질 수 있다.

화면 크기 변경과 원그래프 선택

부모 컨테이너 폭이 바뀌면 차트도 새 크기에 맞춰 다시 그릴 필요가 있다. 컴포넌트에서는 ResizeObserver 등을 이용해 컨테이너 변화에 반응하고 해제 시 observer를 정리한다. 숨겨진 컨테이너의 폭이 0인 상태에서 그리면 정상적으로 표시되지 않을 수 있으므로 표시 후 크기를 확인한다.

예제는 수치 비교를 방해하는 원근감을 줄이기 위해 3D 옵션을 끈다. 원그래프는 전체에 대한 몇 가지 항목의 비율을 보여 줄 때 적합하지만 항목이 많거나 비슷한 수치의 순위를 정밀하게 비교해야 하면 막대그래프가 더 읽기 쉬울 수 있다. 차트 종류를 바꾸기 전에 사용자가 “가장 많은 항목”을 찾는지, “전체 중 비율”을 읽는지부터 정한다.

같은 카테고리의 글