Google Apps ScriptでVue対応組織図を生成する【GAS】

以前、GoogleのVisualization APIを使っての組織図をGASで作成したことがあります。スプレッドシートをベースに作成するタイプのOrgChartというものを使っていました。ただ、作り方が若干面倒くさいデータ構造になっていて素直ではなく、スプシから作れるのは良い点なのですが、効率が悪かったです(部署とそこにぶら下がる人を別々に管理する必要がある)。

そこで、Vue.js対応のVue-Organization-Chartと呼ばれるプラグインを利用して、同じくスプシからより簡単な構造のデータより生成できないか?チャレンジしてみました。

Google Apps Scriptで組織図を作ろう【GAS】

今回利用するファイル等

今回のコードではスプレッドシートのデータをツリー構造のデータに変換する必要があります。こちらのサイトのコードを改造し適合するように修正しています。このコードは配列データの親子関係を元にツリー構造のJSONデータに変換してくれるもので、クラスを利用して作られています。

通常のロジックではツリー構造を構築するのは非常に大変ですが、このクラスの仕組みによって、OrgChartライブラリに適した形にデータを変換できる為、利用者は親子関係のみを意識すればそのまま組織図に変換できる為、前回の仕組みよりもより効率的に組織図を構築することが可能です。

Google Apps Scriptでクラス構文を活用する【GAS】

データの準備

ライブラリのCDN

今回の描画用ライブラリはVue.js用に作られています。HTML側コードにて以下のライブラリを参照するよう追加しています。

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-organization-chart@1.1.6/dist/orgchart.css">
<script src="https://cdn.jsdelivr.net/npm/vue-organization-chart@1.1.6/dist/orgchart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

スプシのデータ構造とルール

今回のライブラリ用にスプレッドシートのデータ構造を構築しています。

  • 1行目のタイトル列は重要な参照項目となってるため、今回のコードでは変更すると読み込めなくなります。変更したい場合は、データを整形する1行目に今回のスプシのタイトルと同じ名称の配列を追加してあげる必要があります。
  • データ部分の一番最初の行となる2行目は必ず組織トップの社長である必要があり、codeやparentCodeの値は0である必要があります。
  • codeは各組織の持つ固有のコードですので重複してはいけません。
  • parentCodeは親となるcodeの値を指定します。これが親子関係を構築する重要なキーとなっています。
  • 1つの組織に複数の子組織が所属できるので、parentCode自体は重複して問題はありませんが、存在しないcodeの指定は出来ません。
  • ID列は特に今回のケースでは意味がありません。ただし改造次第ではユニークな連番としておくことで、対象者のレコードの特定などに使えます
  • nameに組織名を入れますtitleにはその組織の所属長の人間の名前を今回は入れています。

図:シンプルになったデータ構造

ソースコード

利用する場合には、スプレッドシートを開いて出てくるメニューの組織図=>セットアップを必ず実行します。スプレッドシートのIDがスクリプトプロパティに格納されウェブアプリとして利用する場合に必要になります。

また、必ずウェブアプリケーションとしてデプロイしてから利用する必要があります。以下のエントリーを参照し、デプロイをした後に末尾がexecのURLをSitesなどに貼り付けると良いでしょう。

Google Apps Scriptでウェブアプリケーション作成入門【GAS】

GAS側コード

ウェブアプリの生成

//自分自身のIDを取得するコード
function getMySheetId(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var myid = sheet.getId();
 
  var Properties = PropertiesService.getScriptProperties();
  Properties.setProperty("sheetid", myid);
  
  return myid;
}

//外部貼り付け用
function doGet(e){
  //paramを元にHTMLをレンダリング
  var html = HtmlService.createHtmlOutputFromFile("index")
          .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
          .addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui');
  
  return html;
}

こちらは単純にウェブアプリとして出力やスプレッドシートのIDを格納するだけのコードです。

ツリーデータを生成するコード

//ツリー構造を生成するクラス
class treenode {
    //構造体に変数を定義
    constructor(id, code, name, title) {
        this.id = id;
        this.code = code;
        this.name = name;
        this.title = title
        this.children = [];
    }

    //childrenに追加する
    addChild(child) {
        this.children.push(child);
    }

    //子要素を追加する
    addChildren(childrenArray) {
        for (let i = 0; i < childrenArray.length; i++) {
            this.addChild(childrenArray[i]);
        }
    }

    //childrenが無い場合、children変数を削除する
    deleteField(){
      delete this.children;
    }
}

//スプシデータをJSON形式に加工する
function backjson() {
  //スプレッドシートデータを取得する
  var Properties = PropertiesService.getScriptProperties(); 
  var id = Properties.getProperty("sheetid");
  var sheet = SpreadsheetApp.openById(id).getSheetByName("orgchart");
  var ss = sheet.getDataRange().getValues();
  
  //タイトル行を取得する
  let title = ss.splice(0, 1)[0];

  //1行目は社長なのでデータを独立して取得
  let topman = [ss[0][1],ss[0][2],ss[0][3],ss[0][4]];

  //社長データは除外する
  ss.splice(0, 1)[0];
  
  //JSONデータを生成する
  let jsondata = ss.map(function(row) {
    var json = {}
    row.map(function(item, index) {
      json[title[index]] = item;
    });
    return json;
  });

  //処理データを返す
  return [topman,jsondata];
}

//スプシデータを元にツリーデータを生成する
function geneNodedata(){
  //スプシデータを取得する
  let ret = backjson();

  //ツリーを生成する
  let treeman = createTreeNode(ret[0],ret[1]);

  //取得結果を整形して出力
  console.log(JSON.stringify(treeman, null, 2));

  //整形結果を返す
  return JSON.stringify(treeman);
}

//ツリー構造を生成する
function createTreeNode(topman,groups){
  //treenodeクラスインスタンスを生成、parentCodeとセットにする
  const arrayTmp = groups.map(eachGroup => {
      return {
        groupInstance : new treenode(eachGroup.id,eachGroup.code, eachGroup.name, eachGroup.title), 
        parentCode : eachGroup.parentCode
      };
  });

  //parentCode の値が指定のものになっているGroupクラスインスタンスの配列を取得
  const searchInstanceByParentCode = function(code) {
    const returnArray = [];
    for (let i = 0; i < arrayTmp.length; i++) {
      const instance = arrayTmp[i].groupInstance;
      const parentCode = arrayTmp[i].parentCode;
      if (parentCode === code) {
          returnArray.push(instance);
      }
    }
    return returnArray;
  }

  //社長のデータを構築する
  const rootman = new treenode(topman[0], topman[1], topman[2], topman[3]);

  //ツリー構造を形成する
  for (let i = 0; i < arrayTmp.length; i++) {
      const eachGroup = arrayTmp[i].groupInstance;
      const childrenGroups = searchInstanceByParentCode(eachGroup.code);
      
      //返り値が空ならばchildren変数は削除する
      if(childrenGroups.length == 0){
        eachGroup.deleteField();
      }else{
        //childrenにレコードを追加
        eachGroup.addChildren(childrenGroups);
      }

      // 最上層組織は社長直下に
      if (arrayTmp[i].parentCode === 0) {
          rootman.addChild(eachGroup);
      }
  }

  return rootman;
}
  • treenodeクラスが今回の各部署のノードを構築する重要なクラスになります。
  • backjson関数がスプレッドシートをJSON形式に変換するコードになります。
  • createTreeNode関数がJSON形式のデータおよび社長のレコードデータを元にツリー構造のJSONデータに変換するコードの本体になります。
  • 返り値のchildren配列が空っぽの場合はconstractorの変数自体を削除するようにしています(つまり子要素が無いケース)
  • 最終的に社長のノードに全ての要素が子要素を携えてぶら下がるようになります。
  • geneNodedata関数がHTML側からの要求に答えてツリー構造のJSONデータを返す関数になります。

GoogleスプレッドシートのデータをJSONで取得する【GAS】

HTML側コード

<html>
  <head>
    <base target="_top">
    <!-- vue.jsをロード -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-organization-chart@1.1.6/dist/orgchart.css">
    <script src="https://cdn.jsdelivr.net/npm/vue-organization-chart@1.1.6/dist/orgchart.umd.min.js"></script>
    <script>
      function onSuccess(data){
        //データを取得する
        let json = JSON.parse(data);

        //変数に格納する
        vm.ds = json;
      }
    </script>
  </head>
  <body>
    <!-- 基本となるビュー -->
    <div id="app">
      <organization-chart :datasource="ds"></organization-chart>
    </div>
        
    <script>
      //ライブラリを初期化
      const OrganizationChart = window['orgchart'].default;

      //ビューの初期化
      var vm = new Vue({
        el: '#app',
        components: {
          'organization-chart':OrganizationChart
        },
        data: {
          ds: []
        },
        mounted : function(){
          //スプシからツリーデータを生成して取得
          google.script.run.withSuccessHandler(onSuccess).geneNodedata();
        },
      })
    </script>  
  </body>
</html>

今回は本当にシンプルにGAS側からデータをロードして、はめ込んでるだけの構造です。ライブラリを初期化しComponentsに追加が必要なので要注意。dsソースに対してツリー構造のJSONデータが格納されます。

生成されたツリー構造のJSONデータ

{
  "id": 0,
  "code": 0,
  "name": "社長",
  "title": "山田太郎",
  "children": [
    {
      "code": 1002,
      "name": "専務",
      "title": "桜田華魅理亜",
      "children": [
        {
          "code": 1003,
          "name": "営業部",
          "title": "森脇 貴夫",
          "children": [
            {
              "code": 1008,
              "name": "法人営業課",
              "title": "下川原 里美"
            },
            {
              "code": 1009,
              "name": "第一営業課",
              "title": "長村 信吾"
            },
            {
              "code": 1010,
              "name": "第二営業課",
              "title": "亀谷 貴子"
            }
          ]
        },
        {
          "code": 1004,
          "name": "マーケ部",
          "title": "井上 卓也",
          "children": [
            {
              "code": 1011,
              "name": "広報課",
              "title": "南出 大祐"
            },
            {
              "code": 1012,
              "name": "調査室",
              "title": "熊谷 あずさ"
            }
          ]
        },
        {
          "code": 1005,
          "name": "開発部",
          "title": "田中 奈津美",
          "children": [
            {
              "code": 1013,
              "name": "メインフレームチーム",
              "title": "原田 泰士"
            },
            {
              "code": 1014,
              "name": "オープンチーム",
              "title": "原田 泰士(兼務)"
            },
            {
              "code": 1015,
              "name": "クラウドチーム",
              "title": "山原 了"
            }
          ]
        },
        {
          "code": 1006,
          "name": "リセール部",
          "title": "佐藤 康典",
          "children": [
            {
              "code": 1016,
              "name": "サービス課",
              "title": "北野 紘一"
            },
            {
              "code": 1017,
              "name": "ヘルプデスク",
              "title": "土屋 拓生"
            }
          ]
        },
        {
          "code": 1007,
          "name": "管理部",
          "title": "山口 喜弘",
          "children": [
            {
              "code": 1018,
              "name": "人事室",
              "title": "梅田 京"
            },
            {
              "code": 1019,
              "name": "経理課",
              "title": "高野 大地"
            },
            {
              "code": 1020,
              "name": "総務課",
              "title": "中村 大輔"
            }
          ]
        }
      ]
    }
  ]
}

サンプル表示

サンプルでは横スクロールすると全体像が見えます。Google Sitesなどに貼り付けておくと良いでしょう。今回は殆ど無改造で使っていますが、ClickイベントなどをセットしたらGoogle Workspaceのディレクトリ参照して詳細な情報を出すといったような改造を施すことでより組織図が生きてくると思います。

サイトに貼り付けた事例はこちらから見ることが可能です。

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)