Google Organization Chartを使ってHugoでVisual Sitemapを作成した時のメモ

# はじめに

Hugoを使って静的サイトを構築していますが、グラフィカルなサイトマップ(Visual Sitemap)を作りたいという意見があったため、実装について検討することにしました。

現状は、[HugoのGetJSON機能](https://gohugo.io/templates/data-templates/#get-remote-data)を多用して動的に変化するデータを元に定期的に自動生成しているため、サイトマップも動的に生成したいと考えています。

当初はHugoが生成してくれるsitemap.xmlを元に、Rubyスクリプトによる前処理によってデータを追加する想定で、サイトマップをJavaScriptを利用してトップページに描画したいと思っていましたが、結果的にはHugoのテンプレート機能だけで目的を達成する目処が立ちました。

描画には、リッチなJavaScriptライブラリが良いだろうと思い、[gojs.net](https://gojs.net/) を利用しようとしましたがライセンスの関係で断念しました。

教育などの非営利目的であれば、[Basic Primitives Diagrams](https://www.basicprimitives.com/) といった商用ライブラリがあります。

GoJSも教育機関向けにはライセンスを徴収しない、裁判に持ち込まないなどの方針はあるようですが、公式情報からは確認できませんでした。標準ライセンスの文面を読む限りは、個別に明示的にライセンスを発行してもらわない限り、ライブラリの利用が差し止められる可能性はあると思います。

Basic Primitivesのように非営利目的であれば問題なく利用できるライセンスもありますが、今回は業務利用も想定しているので、まずはApacheライセンスで利用可能なGoogle Organization Chartを試していこうと思います。

# 参考資料

* [Organization Chart](https://developers.google.com/chart/interactive/docs/gallery/orgchart) by Google
* [Hugo 公式サイト](https://gohugo.io/) 

# ライブラリについて

Googleが提供しているOrganization Chartは、Apache License 2.0で配布されています。

Diagramを描画する分野は商用ライセンスで提供されるライブラリが高品質なこともあり、オープンソースの有力なライブラリは存在しないようです。

Google Organization Chartのデータポリシーは以下のようになっていて、データの流出について言及していています。これは他のソリューションが必ずしもローカルブラウザ内で全ての処理を行っていないことを示唆しています。

```text:OrgChartのデータポリシー
Data Policy
All code and data are processed and rendered in the browser. No data is sent to any server.
```

利用するライブラリについては、ライセンスとその実装についてよく調査することをお勧めします。

# sitemap.xmlの構造

今回は使いませんでしたが、当初は sitemap.xml が利用できないか調査していました。

このファイルの構造自体は、XMLのnamespaceとして [http://www.sitemaps.org/schemas/sitemap/0.9](http://www.sitemaps.org/schemas/sitemap/0.9) が指定されていて、ここで定義されています。

i18nを利用する環境でHugoが出力するトップレベルの sitemap.xml は次のような内容です。

```xml:第一階層のsitemap.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  
  <sitemap>
    <loc>https://example.org/ja/sitemap.xml</loc>
    <lastmod>2023-02-21T13:48:46+09:00</lastmod>
  </sitemap>
  
  <sitemap>
    <loc>https://example.org/en/sitemap.xml</loc>
    <lastmod>2022-04-25T08:44:52+09:00</lastmod>
  </sitemap>
</sitemapindex>
```

この下にある ja/sitemap.xml は次のような内容になっています。

```xml:ja/sitemap.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://example.org/ja/project/manual/client/</loc>
    <lastmod>2023-02-21T13:48:46+09:00</lastmod>
  </url><url>
    <loc>https://example.org/ja/project/manual/</loc>
    <lastmod>2023-02-18T08:53:41+09:00</lastmod>
  </url><url>
    <loc>https://example.org/ja/project/</loc>
    <lastmod>2022-04-25T08:46:10+09:00</lastmod>
    <xhtml:link
                rel="alternate"
                hreflang="en"
                href="https://example.org/en/project/"
                />
    <xhtml:link
                rel="alternate"
                hreflang="ja"
                href="https://example.org/ja/project/"
                />
  </url>
  ...
</urlset>
```

基本的にはURLのタイトルなどのVisual Sitemapに必要な要素は不足していて、単純にURLの階層構造と更新日時を伝えるための内容になっています。

# Hugo と Google Organization Chart の組み合わせ

今回はトップページにだけ Visual Sitemap を出力したいので、hugoの layout/index.html.html ファイルを編集しています。

サンプルコード中は、data.addRows() で一気にデータを登録していますが、Hugoの {{ range }} と、data.addRow() を使って、1件ずつデータを登録していきます。

```html:layout/index.html.html
{{ define "main" }}
{{ .Content }}
<!-- Diagram of Contents Structure -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
  google.charts.load('current', {packages:["orgchart"]});
  google.charts.setOnLoadCallback(drawChart);
  
  function drawChart() {
      var data = new google.visualization.DataTable();
      data.addColumn('string', 'Name');
      data.addColumn('string', 'Manager');
      data.addColumn('string', 'ToolTip');
      
      // For each orgchart box, provide the name, manager, and tooltip to show.
      {{ range $sec := .Sections }}
      {{ $name := $sec.Title }}
      {{ $url := $sec.Permalink }}
      data.addRow(
	  [
	      {
		  v: '{{ $name }}',
		  f: '<a href="{{ $url }}">{{ $name }}</a>'		  
	      },
	      "TOP",
	      "{{ $name }}"
	  ]
      );
      {{ end }} {{/* end of range $sec */}}

      // Create the chart.
      var chart = new google.visualization.OrgChart(document.getElementById('chart_div'));
      // Draw the chart, setting the allowHtml option to true for the tooltips.
      chart.draw(data, {'allowHtml':true, 'allowCollapse':true});
  }
</script>

<div class="row">
  <h3>{{ i18n "visual_sitemap" }}</h3>
  <div id="chart_div" class="col-sm-12 overflow-auto"></div>
</div>

{{ end }}
```

自分の外向けのWebページに適用した結果が次のようになりました。

![Screenshot_2023-03-03_23-25-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78296/ce2f6083-1502-2353-9043-aac9307c87ce.png)

HugoのSectionsだけを描画しているのでトップレベルだけの構造が描画されています。少し工夫すれば選択的に各.Pagesを表示させることもできます。

# さいごに

実際のコードはもう少し複雑で、順序を変更したり、特定の階層のデータは追加しなかったり、いろいろ操作しています。
とはいえHugoを使っているサイトであれば、このまま layout/index.html.html を配置して改造することができるでしょう。

一般的なサイトでこれをそのまま適用してもあまりおもしろくないかもしれませんが、Google Organization Chartはお手軽に使えるので、応用を効かせることができればおもしろいと思います。

ただ階層を深く描画するには、子要素を垂直方向に並べることができると限られたスペースに多くの情報を掲載できるのですが、水平方向にしか要素を描画できない点が、このライブラリの限界かなと思います。

以上