Plotly の活用 ‐ Sunburst チャートによる再帰的データの扱い

前回、[『Chart.js の活用(基礎編)』](https://qiita.com/LoveOceanFJ/items/456ba5844ef839b6c8e6)と題して、JavaScript ライブラリーの活用に関する記事を投稿しました。今回は Plotly というライブラリーを使った Sunburst チャートの作成例を紹介したいと思います。ファイルシステムや組織階層など、階層の深さが定義できないデータを再帰的 (Recursive) に処理することにも適したチャートです。

### 1. 事前準備
#### 1-1. ライブラリーの配置
Plotly を活用するためには、Yellowfin がアクセスできる場所にライブラリーを配置する必要があります。https://plotly.com/javascript/ にアクセスして、[Getting started] > [Download] と進み、plotly.jsファイルを取得します。
取得したファイルは、Yellowfin サーバー上の以下のディレクトリに配置します。これで、Yellowfin がライブラリーを活用できるようになります。
```(Yellowfin)/appserver/webapps/ROOT/js/chartingLibraries/plotly/```

#### 1-2. JavaScriptグラフの有効化
JavaScriptグラフを作成するためには、いくつかYellowfinサーバーで事前設定が必要です。必要な設定内容に関しては、以下Wikiの情報を参考にしてください。
https://wiki.yellowfin.co.jp/pages/viewpage.action?pageId=2293926

### 2. データの準備
ライブラリーを配置し、JavaScript グラフを有効化したら、早速グラフの作成段階に進みます。以下の手順では、Yellowfin インストール時に規定でインストールされる Ski Team のデータとビューを利用します。

レポートを新規で作成し、Ski Team ビューを選択します。

[データ] ステップで、軸に [Camp Region] と [Camp Country]、集計値に [Camp Rating] をそれぞれ選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/34c147ee-a413-56c2-4a18-4db6ba120be1.png)

画面左からサブクエリーを選択し、タイプに [結合]、スタイルに [基本] を選択し、[OK] ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/a45d9ad8-8055-ba4a-6109-6bbe0004f843.png)

サブクエリー設定画面で、計算フィールドを2つ作成します。

|計算フィールド名|値|備考|
|:----------:|:----------:|:-----------|
|Region2|’’|null値|
|Rating2|0|半角ゼロ|
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/da3d8a71-f189-f24a-895b-91fdc5ba6903.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/eaca4bd5-481f-f794-cf09-85ea87bf88e7.png)

マスタークエリーフィールドとサブクエリーフィールドを以下の通りに設定します。

|マスタークエリーフィールド|サブクエリーフィールド|
|:----------:|:----------:|
|Camp Region|Region2|
|Camp Country|Camp Region|
|Camp Rating|Rating2|
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/fbc52907-392b-2aa7-bb52-b23d03cec55f.png)
[保存] ボタンを押下し、サブクエリー設定画面を閉じます。

結果、以下のような表が出来上がりました。後続の作業では、Camp Region 列を親、Camp Country 列を子として扱います。階層が特定できないデータであっても、このデータ構造であれば、際限なく深い階層のデータを管理できます。もちろん、データソース上で最初からこの構造でデータが管理されている場合、今回実施したデータ構造の変換作業は必要ありません。ちなみに、Camp Region (親)が null の行は、最上位の階層であることを示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/a86a8f25-1a5b-7692-c662-fbe7c1611e95.png)


### 3. グラフの作成
[グラフ] ステップに進み、画面右側 [グラフの選択] から [JavaScriptグラフ] を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/fe9729ac-51ed-4dc0-3329-a71eb13b17d3.png)

雛形を全て削除し、以下のコードに置き換えます。

```javascript:JavaScript
generateChart = function(options) {
   console.log(JSON.stringify(options));
   var $chartDrawDiv = $(options.divSelector);
   var height = options.dataset.chart_information.height;
   var width = options.dataset.chart_information.width;
   var processedData = processData(options.dataset.data);
   doDrawing(processedData, $chartDrawDiv, height, width, options.errorCallback);
},

processData = function(dataset) {
   data={ids:[], labels: [], parents: [], values: []}
   for (i=0;i<dataset['camp_rating'].length;i++){
        data['ids'].push(dataset['camp_country'][i].raw_data);
        data['labels'].push(dataset['camp_country'][i].formatted_data);
        data['parents'].push(dataset['camp_region'][i].formatted_data);
        data['values'].push(dataset['camp_rating'][i].raw_data);
    }
    return data
},

doDrawing = function(data, $chartDiv, height, width, errorFunction) {
    require(['js/chartingLibraries/plotly/plotly-2.8.3.min.js'], function(Plotly) {
    var trace = [{
        ids: data['ids'],
        labels: data['labels'],
        parents: data['parents'],
        values: data['values'],
        outsidetextfont: {size: 20, color: "black"},
        leaf: {opacity: 0.4},
        marker: {line: {width: 5}},
        type: 'sunburst'
    }];
    var layout = {
        margin: {l: 0, r: 0, b: 0, t: 0}
    };
    Plotly.newPlot($chartDiv[0], trace, layout);
   });
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/017ce081-5693-2cea-7260-a4e0e48e4a6d.png)

コードの内容を説明する前に、作成されるチャートと取り扱うデータの中身を先に確認します。[プレビュー] に移ると、Sunburst と呼ばれるチャートが表示されます。円の中心部を最上位とし、円の周囲に移動するに従って、階層を下ります。例えば、Asia の下に Japan が位置づきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/fa81576f-2ab2-3951-4738-353093645486.png)

グラフ作成に使われているデータの内容を確認するために、options に格納されるデータの中身を見てみましょう（以下 Chrome の手順）。[プレビュー] 画面で [F12] ボタンを押下するか、ブラウザ右上の [Google Chrome の設定] > [その他のツール] > [デベロッパー ツール] の順に進んで、デベロッパーツールを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/d14de338-4192-76ec-cf37-f61782d78f3b.png)

画面下部で [情報] の内容を確認すると、[グラフ] ステップで集計した [Camp Region] [Camp Country] 軸と [Camp Rating] 集計値が JSON の形で表示されています。後述のコードの説明の中で、データフィールドを特定する個所がありますが、そこで指定するフィールド名は以下の情報と一致させる必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/8ff84f09-a80b-7de8-4b38-49ffd5c8fc32.png)

### 4. コードの説明
作成されるチャートとデータの中身を確認したところで、コードの説明に移ります。

```javascript:generateChart
generateChart = function(options) {
   console.log(JSON.stringify(options));
   var $chartDrawDiv = $(options.divSelector);
   var height = options.dataset.chart_information.height;
   var width = options.dataset.chart_information.width;
   var processedData = processData(options.dataset.data);
   doDrawing(processedData, $chartDrawDiv, height, width, options.errorCallback);
},
```
JavaScript グラフの処理において、最初に呼び出されるのが ```generateChart = function(options)``` です。

options の中身をJSON 形式でコンソールに出力する処理をする行が ```console.log(JSON.stringify(options));``` です。先ほどデベロッパーツールを用いて中身を確認した情報は、この行が出力した情報です。

以下の3行で、グラフを出力する div を選択し、グラフの高さと幅に関する変数を扱います。
```var $chartDrawDiv = $(options.divSelector);```
```var height = options.dataset.chart_information.height;```
```var width = options.dataset.chart_information.width;```

以下の2行で、別の関数を呼び出して、データ処理とグラフ描画処理を行っています。呼び出し先の関数での処理内容は後続の個所で説明をします。
```var processedData = processData(options.dataset.data);```
```doDrawing(processedData, $chartDrawDiv, height, width, options.errorCallback);```

```javascript:processData
processData = function(dataset) {
   data={ids:[], labels: [], parents: [], values: []}
   for (i=0;i<dataset['camp_rating'].length;i++){
        data['ids'].push(dataset['camp_country'][i].raw_data);
        data['labels'].push(dataset['camp_country'][i].formatted_data);
        data['parents'].push(dataset['camp_region'][i].formatted_data);
        data['values'].push(dataset['camp_rating'][i].raw_data);
    }
    return data
},
```

Yellowfin のデータセットを Plotly が扱う形式に変換して、data に格納しています。ids, labels, parents, values 項目はいずれも Plotly が Sunburst チャートを作成するために必須の項目で、for 分の中で Yellowfin のデータセットから、それぞれ camp_country, camp_country, camp_region, camp_rating を受け渡しています。

```javascript:doDrawing
doDrawing = function(data, $chartDiv, height, width, errorFunction) {
    require(['js/chartingLibraries/plotly/plotly-2.8.3.min.js'], function(Plotly) {
    var trace = [{
        ids: data['ids'],
        labels: data['labels'],
        parents: data['parents'],
        values: data['values'],
        outsidetextfont: {size: 20, color: "black"},
        leaf: {opacity: 0.4},
        marker: {line: {width: 5}},
        type: 'sunburst'
    }];
    var layout = {
        margin: {l: 0, r: 0, b: 0, t: 0}
    };
    Plotly.newPlot($chartDiv[0], trace, layout);
   });
}
```

```require(['js/chartingLibraries/plotly/plotly-2.8.3.min.js'], ``` で、plotly.js ライブラリーの場所を指定します。今回であれば、『ライブラリーの配置』 手順でplotly-2.8.3.min.js を保管したディレクトリを相対パスで指定します。

```var trace``` の中で、Yellowfin から受け取った data の中身を、Plotly の各項目に受け渡しています。```Outsidetextfont```, ```leaf```, ```marker``` などでチャートの属性情報を設定します。他にも多くのオプションがあるので、以下でご確認ください。
https://plotly.com/javascript/reference/sunburst/

```Plotly.newPlot($chartDiv[0], trace, layout);```で、上記手順で設定した情報を用いて、実際にグラフを描画します。

### 5. 最後に 
いかがだったでしょう。私を含め、Sunburst チャートの存在を初めて知った人もいるのではないでしょうか。ちなみに、ここまでの手順で取り扱ったデータは、2 階層のデータでした。
一方で、階層の数が定義できないファイルシステムや組織階層などのデータを扱った場合にはどうなるでしょう。下の例は、ダミーの組織階層を使って作成した Sunburst チャートの例です。このように、階層数が読めないデータの処理においても、データ構造を可視化できます。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/9ebb8f62-6e96-521a-d004-e0a930009293.png)

同じものを作成してみたい方は、[こちら](https://github.com/ChasingHH/ShareFiles/blob/Share/OrgTree.csv) に CSV 形式のダミーデータを共有しているので、Yellowfin に取り込んで試してみてください。
CSV データのインポート  https://wiki.yellowfin.co.jp/pages/viewpage.action?pageId=2294195

それではまた、Cheers!