Power BI カスタムビジュアル開発 : Capabilities の Sorting、SupportsHighlight、AdvancedEditModeSupport を詳しく見る

[前回の記事](https://qiita.com/kenakamu/items/3eaf0cb8ada9c3c41074) ではビジュアルのプロパティにあたる Objects を見ていきました。今回は Sorting, SupportsHighlight、AdvancedEditModeSupport を見ていきます。

情報元: [GitHub の Sorting の説明](https://github.com/Microsoft/PowerBI-visuals/blob/master/Capabilities/Sorting.md)
情報元: [GitHub の SupportsHighlight の説明](https://github.com/Microsoft/PowerBI-visuals/blob/master/Capabilities/Highlighting.md)
情報元: [GitHub の AdvancedEditModeSupport の説明](https://github.com/Microsoft/PowerBI-visuals/blob/master/Capabilities/AdvancedEditMode.md)

この記事では、[DataViewMapings で紹介したカテゴリーの例](https://qiita.com/kenakamu/items/32d83de4f525ecad4435#%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D-1) を使って検証してみます。

# `Sorting` : データの並べ替え設定

データセットをビジュアルに渡す際の、並べ替えを操作できます。

## 既定の並べ替え

`default` オプションを使用すると `DataMappings` で定義されたデータでの並べ替えができます。capabilities.json に以下の sorting を追加するだけです。

```json
"sorting": {
    "default": {}
}
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/70010667-6eca-fa42-2ee0-68b62959797e.png)


## 明示的な並べ替え

`implicit` オプションを使用すると、指定したフィールドでの並べ替えが行えます。

- role : 並べ替えに使うデータロール
- direction: 1 が昇順、2 が降順

以下の例ではメジャー列で降順に並べ替えるように指定。

```json
"sorting": {
    "implicit": {
        "clauses": [
            {
                "role": "myGrouping",
                "direction": 2
            }        
        ]
    }
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/4ee6fba9-367a-c0b8-94b5-a66d9f1e0fd5.png)

## カスタム並べ替え

`custom` オプションは、コード内で開発者が直接 DataView の値をコードで並べ替えることを意味します。

# `supportsHighlight` : アイテムのハイライト

PowerBI では、レポート上のビジュアルはそれぞれが連動し、あるビジュアルで項目が選択されると、dataView の結果もフィルターされた状態で返ります。この際、DataView の values は本来と同じ値が返り、highlights にフィルターされた結果が渡ります。

## supportsHighlight の動作確認

以下の例では、supportsHighlight を有効にし、update 関数内で、hightlights の有無によってデータの表示の色を変えています。

```json:capabilities.json
{
    "dataRoles": [
        {
            "displayName": "サンプルカテゴリ",
            "name": "myCategory",
            "kind": "Grouping"
        },
        {
            "displayName": "グルーピングフィールド",
            "name": "myGrouping",
            "kind": "Grouping"
        },
        {
            "displayName": "サンプルメジャー",
            "name": "myMeasure",
            "kind": "Measure"
        }
    ],
    "dataViewMappings": [
        {
            "categorical": {
                "categories": {
                    "for": {
                        "in": "myCategory"
                    }
                },
                "values": {
                    "group": {
                        "by": "myGrouping",
                        "select": [
                            {
                                "bind": {
                                    "to": "myMeasure"
                                }
                            }
                        ]
                    }
                }
            }
        }
    ],
    "supportsHighlight": true
}
```

```typescript:visual.ts
module powerbi.extensibility.visual {
    "use strict";

    export class Visual implements IVisual {

        private area: d3.Selection<HTMLElement>
        private table: d3.Selection<HTMLElement>
        private thead: d3.Selection<HTMLElement>
        private tbody: d3.Selection<HTMLElement>
        private host: IVisualHost;
        private settings: VisualSettings;

        constructor(options: VisualConstructorOptions) {
            // カスタムビジュアルを配置しているホストの情報を取得
            this.host = options.host;
            // カスタムビジュアルのエリアを取得
            this.area = d3.select(options.element);
            this.table = this.area.append("table");
            this.thead = this.table.append("thead").append("tr");
            this.tbody = this.table.append("tbody");
        }

        public update(options: VisualUpdateOptions) {
            debugger;

            this.thead.selectAll("th").remove();
            this.tbody.selectAll("tr").remove();

            this.thead
                .selectAll("th")
                .data(options.dataViews[0].categorical.categories[0].values)
                .enter()
                .append("th")
                .text((value) => { return value.toString(); });

            // Add first blank header
            this.thead.insert("th", ":first-child");

            options.dataViews[0].categorical.values.forEach((values) => {

                let tr = this.tbody.append("tr");

                tr.selectAll("td")
                    .data(values.values)
                    .enter()
                    .append("td")
                    .style("color", (value, index) => {
                        if (values.highlights == undefined) {
                            return "black";
                        }
                        else if (values.highlights[index] != null) {
                            return "black";
                        }
                        else {
                            return "lightgray";
                        }
                    })
                    .text((value) => {
                        return value.toString();
                    });

                tr.insert("td", ":first-child").text(() => { return values.source.groupName.toString() });
            });
        }

        private static parseSettings(dataView: DataView): VisualSettings {
            return VisualSettings.parse(dataView) as VisualSettings;
        }

        public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
            return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
        }
    }
}
```

レポート上にカスタムビジュアルと既定のビジュアルを配置して検証。

[フィルターしていない状態]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/153174b6-f3d4-c7c5-98ee-10715be20ee5.png)
[東京でフィルターした状態]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/9cb2b47b-a30a-1729-9d54-6a84898121cb.png)

以前のブログでも棒グラフの例を紹介しています。[インタラクションの追加 - ハイライトのサポート](https://qiita.com/kenakamu/items/cf4acb91d8ea366bc45c#%E3%83%8F%E3%82%A4%E3%83%A9%E3%82%A4%E3%83%88%E3%81%AE%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88) を参照してください。

# `AdvancedEditModeSupport` : ビジュアルの編集機能

プロパティの変更だけではカバーできないレベルの編集をユーザーにさせたい場合、`advancedEditModeSupport` を使えば、カスタムビジュアルをユーザーが編集モードにすることができるます。

設定値は以下の 3 種類

- 0 : 編集をサポートしない (既定)
- 1 : 編集をサポートする 
- 2 : 編集をサポートし、編集時には全画面にする

```json
"advancedEditModeSupport": 2
```
ユーザーが編集モードに入ったタイミングで update メソッドが呼び出され、options.editMode で編集モードかどうか判定します。

```typescript
public update(options: VisualUpdateOptions) {
    if(options.editMode === EditMode.Advanced){
        // 編集モードで必要な処理
    }
    else{
        // 通常モードでの処理
    }
...
```
# まとめ

SupportHighlight は既定でオンでも良いような必須機能です。また sorting や advancedEditModeSupport もシナリオによって必要となると思います。特に数値を扱うようなグラフでは sorting は重要になるでしょう。既存のビジュアルで advancedEditModeSupport を使っているものは、例えば ArcGIS Map があります。

次回は drilldown を見ていきます。

[次の記事へ](https://qiita.com/kenakamu/items/69ac2f90a5b54d4fc6cb)
[目次へ戻る](https://qiita.com/kenakamu/items/f57ab7f73d6ea10bc9d9)