Google Apps Scriptでガントチャートを作る【GAS】
主にシステム関係者や建設関係者の間でよく使われてる工程管理の為のチャートに「ガントチャート」。ただ単純にタイムラインを表示してるわけではなく、タスクとタスクの関係性を意識して作られてるこのチャートですが、頭の中でだけでごちゃごちゃとスケジュール確認やタスク管理をするよりずっと効果的なチャートです。見える化の代表例とも言えます。
Aタスクが終わらないと実行できないBタスクといったように、仕事のフローも表示してくれます。今回はこのガントチャートをスプレッドシート連携で表示できるようにするスクリプトを作ってみました。これまでのチャート系のスクリプトと大きな違いはないのですが、注意点がいくつかあります。
目次
使用する資材・メソッド類
VIsualization APIを使った事例の1つではありますが、より高度でキレイなタイムラインとして構築できるライブラリでも作っています。興味ある方は以下のエントリーを参考にしてみてください。データの構築はちょっと難しいです。
実行と結果
今回使用するスプレッドシートのメニューより「▶カレンダー」⇒「苺の栽培」で、現在表示されてるスプレッドシートデータをガントチャートにしてくれます。
図:苺の栽培スケジュールをガントチャート化
注意点と解説
ガントチャートを作るプログラム自体はそれほど難しいものではありません。しかし、スプレッドシートを見て頂ければわかりますが、入力ルールが少し変則的になっています。また、スクリプトに於いてもちょっとだけ特殊な処理が入っていますので、ここではそれらの注意点およびチャートの解説をしてみたいと思います。
注意点
スプレッドシートの注意点
変則的な入力になっています。タスクIDとタスク名までは普通ですが、ここから先が異なります。
- 基本的に開始日付を入れるのは親タスクです。
- 終了日付は全てのタスクに入力が必要です。
- タスクIDは重複しない名称や数値を入れる必要性があります。
- 基本的に日数を入れるのは子タスクです。これは終了日から見て何日前までの期間という意味で考えるとわかりやすいです。1ヶ月前までなら、30を入力する感じです。
- 進捗率はそのまんま直接数値で入力します。最大値は100です。
- 親IDは、ぶら下がる親タスクのタスクIDを入力します。よって、子タスクにしか入力しません。尚、親IDにはカンマ区切りで複数入力が出来、複数の場合、複数のタスクが完了しないと実行できないといった意味になります。
チャートの注意点
チャートも少々難しい点があります。チャートを見た時にルールに従っていないチャートの場合、おかしな表示になったりしますので、注意しましょう。
- 親タスクと子タスクとの間に日数に余裕がない場合には、ラインの色が変わるように今回オプションを付けてます。これをクリティカルパスと言います。
- 子タスクの開始日(日数から逆算)が、親タスクの開始日より前になるような矛盾したケースの場合、ラインが壊れたりします。
- 親タスクと子タスクが重複する場合には、日数の値に注意して下さい。
- 子タスクが複数の親IDにぶら下がる場合には、それら2つの日付の関係に注意。矛盾が生じやすいです。
- 進捗率でバーの色が変わります。
スクリプトの解説
他のチャート系のスクリプトと殆ど変わらないのですが、チャートを表示させる上で嵌るポイントがいくつかあります。
- GAS側から送られてきたデータをJSON.parseして配列に格納しても、それをそのままdata.addRowsで突っ込む事が出来ません。HTML上で1ラインずつ配列を組んでaddRowしなければなりません。
- 日数は日数でグラフを描画出来ません。そのため、daysToMilliseconds関数で日をミリセカンドに変換してあげています。必須の作業です。
- 値のブランクが入ってるとチャートに表示されません。null値でなければならないので、判定式を使ってブランクの時はnullを代入するようにしています。
- GAS側でnew Date()で日付型に変換したデータをそのまま入れてもNGです。HTML側のスクリプトでnew Date()で変換してあげないと受け付けてくれないようです。
- addColumnで定義した型と不一致の場合当然addRowでエラーとなります。明示的に型変換してあげるようにしましょう(StringやNumberなど)。
- ダイアログで使用する場合には、heightとwidthを明示的にoptionで指定してあげないと見切れたり、妙な横スクロールバーが出たりします。
ソースコード
GAS側
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//スプレッドシートメニュー function onOpen(){ var ui = SpreadsheetApp.getUi(); ui.createMenu('▶カレンダー') .addItem('初期化', 'getMySheetId') .addItem('苺の栽培', 'berryman') .addToUi(); } //自分自身のIDを取得するコード function getMySheetId(){ var sheet = SpreadsheetApp.getActiveSpreadsheet(); var myid = sheet.getId(); var Properties = PropertiesService.getScriptProperties(); Properties.setProperty("sheetid", myid); return myid; } //栽培カレンダーを表示する function berryman() { var html = HtmlService.createHtmlOutputFromFile('berry') .setSandboxMode(HtmlService.SandboxMode.IFRAME) .setWidth(800) .setHeight(500); SpreadsheetApp.getUi() .showModalDialog(html, '苺栽培カレンダー'); } //シートデータを取得しHTML側へ返す関数 function berrydata(){ //IDを取得する var prop = PropertiesService.getScriptProperties(); var ssid = prop.getProperty("sheetid") //シートデータを取得する var sheet = SpreadsheetApp.openById(ssid); var ss = sheet.getSheetByName("gantt").getRange("A2:G").getValues(); return JSON.stringify(ss); } |
HTML側
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
<html> <head> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> var dataman; google.charts.load('current', {'packages':['gantt'], 'language': 'ja'}); google.script.run.withSuccessHandler(onSuccess).berrydata(); //日をミリセカンドへ変換する関数 function daysToMilliseconds(days) { return days * 24 * 60 * 60 * 1000; } //シートデータを格納する function onSuccess(data){ //グローバル配列に格納する dataman = JSON.parse(data); drawChart(); } //ガントチャートを描画する function drawChart() { //カラムの設定 var data = new google.visualization.DataTable(); data.addColumn('string', 'Task ID'); data.addColumn('string', 'Task Name'); data.addColumn('date', 'Start Date'); data.addColumn('date', 'End Date'); data.addColumn('number', 'Duration'); data.addColumn('number', 'Percent Complete'); data.addColumn('string', 'Dependencies'); //データをセットする var length = dataman.length; var dataArray = []; for(var i = 0;i<length;i++){ var tempArray = []; tempArray.push(String(dataman[i][0])); tempArray.push(String(dataman[i][1])); if(dataman[i][2] == ""){ tempArray.push(null); }else{ tempArray.push(new Date(dataman[i][2])); } if(dataman[i][3] == ""){ tempArray.push(null); }else{ tempArray.push(new Date(dataman[i][3])); } tempArray.push(Number(daysToMilliseconds(dataman[i][4]))); tempArray.push(Number(dataman[i][5])); if(dataman[i][6] == ""){ tempArray.push(null); }else{ tempArray.push(String(dataman[i][6])); } data.addRow(tempArray); } //グラフオプション var options = { height: 500, }; //描画場所を取得 var chart = new google.visualization.Gantt(document.getElementById('chart_div')); //ガントチャートを表示する chart.draw(data, options); } </script> </head> <body> <div id="chart_div" style="width:100%;"></div> </body> </html> |
ポイント
- 入力ルールが変則的ですので、手入力ではなく、変則ルールに従って入力してくれるGUIを別途用意すると格段に入力が楽になります。また、他のシートからのデータを変換して自動的にチャート化するのも面白いですね。
- 色などはoption部分で割りと自由に構築する事が可能です。
- チャート化する時に、スプレッドシートのデータの全てがガント化されますので、完全に終わって、子タスクも完了してる項目についてはデータをフィルタしてあげる処理があると、見た目が冗長にならずに済みます。
- ダイアログだと見た目が見辛いので、ウェブアプリケーションとして公開して運用するほうが便利です。
- 現在このチャート機能はβ版です。仕様が変わる可能性もあります。また、古いIEではSVG未対応なので表示が出来ません。
- chart_divのstyleにwidth:100%を指定しないと、妙に縮まったガントチャートになるので注意。
- こちらのサイトのCSSにちょっと手を加えると映えるガントチャートになります。