LoginSignup
4
9

More than 5 years have passed since last update.

Google Slidesでつくるリアルタイム更新のグラフダッシュボード(スクリプト付き)

Last updated at Posted at 2017-08-10

概要

リアルタイムで更新されるグラフのダッシュボードを作るのに、Google SlidesとGoogle Spreadsheetの連携が便利だったので、ご紹介。末尾にnodejsスクリプトもあります。

動機

DBの可視化をするときはre:dashやsupersetといったツールがありますが、今回は表示したいグラフのデータがDB内のデータに限らないケースでした。また、ダッシュボードのデザインの自由度を確保したいといった要件もありました。何か良いツールはないかと探していたところ・・・、

Google Slidesのグラフインポート機能を使うと、Google Spreadsheetからグラフをインポートできることがわかりました。さらに、スプレッドシート上でグラフが更新されると、スライド上のグラフも同期されます。これでリアルタイム更新のグラフダッシュボードを実現できそうです!

こんなときにおすすめ

  • グラフのダッシュボードを作りたい
  • リアルタイムにグラフを更新したい
  • デザインの自由度を確保したい・プレゼンテーションで使えるようなデザインにしたい
  • クラウドで管理しながら、データやデザインを共同編集したい
  • 無料で使いたい

作り方

今回は以下のような手順でダッシュボードを作りました。

  • スプレッドシートにデータを入れる
  • スプレッドシート上で、データからグラフを作成する
  • スライドに、スプレッドシートからグラフをインポートする
  • スプレッドシートのデータをリアルタイム更新する(Google SpreadsheetのAPIが使える)
  • スライド内のグラフを同期する

一つ面倒なのは、スライド内のグラフを同期するには、スライド上で毎回更新ボタンをクリックする必要があることです。Google SlidesのAPIでグラフの更新アクションが提供されているので、グラフ更新用のnodejsスクリプトを書き、スケジューラで定期的に更新スクリプトを実行するようにしました(末尾にスクリプトがあります)。

ちなみに、Google SlidesのAPIでは、スライド内のオブジェクトごとにさまざまな操作が可能です。今回表示したのはグラフだけでしたが、スライド内のテキストや数値をAPIで操作できるので、アイディア次第でいろいろな応用がありそうです。

グラフ更新スクリプトの実行

準備

スクリプトを実行するのに、いくつか準備が必要です。

  • Google SlidesへアクセスするのにOAuth認証が必要なので、Google API consoleからOAuthのクライアントIDを発行します。JSON形式のファイルをダウンロードして、スクリプトと同じフォルダに配置します。

  • スクリプト内のTOKEN_DIR・TOKEN_PATH・PRESENTATION_ID・CLIENT_SECRETの各定数を設定します。

    • TOKEN_DIR - OAuthトークンを保存するフォルダ
    • TOKEN_PATH - OAuthトークンを保存するファイル名
    • PRESENTATION_ID - Google SlidesのプレゼンテーションID(スライドのURLがhttps://docs.google.com/presentation/d/0123456789abcdef の場合、「0123456789abcdef」がプレゼンテーションIDになります)
    • CLIENT_SECRET - OAuthクライアントIDのJSONファイル名
  • 下にあるgoogle_slide_updator.js・package.jsonと、上でダウンロードしたOAuthクライアントIDのJSONファイルを同じフォルダに配置します。ここまで準備ができたら、npm installからのnode google_slide_updator.jsで実行できます。

  • 初回実行時に、対話的にOAuthの認証が始まるので、指示にしたがって認証を行ってください。TOKEN_DIR・TOKEN_PATHで指定したファイルにOAuthトークンが保存されます。

スクリプト

今回動作確認したnodeのバージョンは、v7.3.0です。

google_slides_updator.js

let fs = require('fs');
let readline = require('readline');
let request = require('request');
let google = require('googleapis');
let googleAuth = require('google-auth-library');

// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/slides.googleapis.com-nodejs-quickstart.json
let SCOPES = ['https://www.googleapis.com/auth/presentations', 'https://www.googleapis.com/auth/spreadsheets.readonly'];
let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
    process.env.USERPROFILE) + '/.credentials/'; // OAuthトークンを保存するフォルダ
let TOKEN_PATH = TOKEN_DIR + 'awesome_dashboard.json'; // OAuthトークンを保存するファイル名
let PRESENTATION_ID = '0123456789abcdef'; // Google SlidesのプレゼンテーションID
let CLIENT_SECRET = 'client_secret.json'; // OAuthクライアントIDのJSONファイル名

// Load client secrets from a local file.
fs.readFile(CLIENT_SECRET, function processClientSecrets(err, content) {
  if (err) {
    console.log('Error loading client secret file: ' + err);
    return;
  }
  // Authorize a client with the loaded credentials, then call the
  // Google Slides API.
  authorize(JSON.parse(content), updateSlides);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  let clientSecret = credentials.installed.client_secret;
  let clientId = credentials.installed.client_id;
  let redirectUrl = credentials.installed.redirect_uris[0];
  let auth = new googleAuth();
  let oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, function(err, token) {
    if (err) {
      getNewToken(oauth2Client, callback);
    } else {
      oauth2Client.credentials = JSON.parse(token);
      callback(oauth2Client);
    }
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 *
 * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback to call with the authorized
 *     client.
 */
function getNewToken(oauth2Client, callback) {
  let authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES
  });
  console.log('Authorize this app by visiting this url: ', authUrl);
  let rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.question('Enter the code from that page here: ', function(code) {
    rl.close();
    oauth2Client.getToken(code, function(err, token) {
      if (err) {
        console.log('Error while trying to retrieve access token', err);
        return;
      }
      oauth2Client.credentials = token;
      storeToken(token);
      callback(oauth2Client);
    });
  });
}

/**
 * Store token to disk be used in later program executions.
 *
 * @param {Object} token The token to store to disk.
 */
function storeToken(token) {
  try {
    fs.mkdirSync(TOKEN_DIR);
  } catch (err) {
    if (err.code != 'EEXIST') {
      throw err;
    }
  }
  fs.writeFile(TOKEN_PATH, JSON.stringify(token));
  console.log('Token stored to ' + TOKEN_PATH);
}

/**
 * Refresh all charts which are imported from source spreadsheets.
 *
 * @param {google.auth.OAuth2} auth The OAuth2 client keeping token.
 */
function updateSlides(auth) {
  let slides = google.slides('v1');

  // get chart object IDs
  slides.presentations.get({
    auth: auth,
    presentationId: PRESENTATION_ID
  }, function(err, presentation) {
    if (err) {
      console.log('The API returned an error: ' + err);
      return;
    }
    let length = presentation.slides.length;
    let chartObjectIds = [];
    for (i = 0; i < length; i++) {
      let pageElements = presentation.slides[i].pageElements;
      if (pageElements) {
        pageElements.forEach(function (pageElement, i){
          if ('sheetsChart' in pageElement) {
            chartObjectIds.push({
                "refreshSheetsChart": {
                  "objectId": pageElement.objectId
                }
            });
          }
        });
      }
    }

    // refresh charts
    slides.presentations.batchUpdate({
      auth: auth,
      presentationId: PRESENTATION_ID,
      resource: {
        "requests": chartObjectIds
      }
    }, function(err, presentation) {
      if (err) {
        console.log('The API returned an error: ' + err);
        return;
      }
      console.log('The slide updated successfully');
    });
  });
}

package.json

{
  "name": "google-slides-updator",
  "version": "0.1.0",
  "dependencies": {
    "request": "^2.81.0",
    "readline": "^1.3.0",
    "googleapis": "^20.1.0",
    "google-auth-library": "^0.9.7"
  }
}
4
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
9