コネヒト開発者ブログ

コネヒト開発者ブログ

Tableauのパラメーターを横並びのラジオボタンにする拡張機能をつくった

こんにちは!コネヒトのエンジニアあぼです。この記事はコネヒト Advent Calendar 2021の24日目です。

今回は、コネヒトでデータ分析や新規プロダクトに使われているTableauの拡張機能をつくったので紹介します。

つくったもの

github.com

この拡張機能は、ダッシュボード上から参照できるパラメーターを1つ、横並びのラジオボタンとして表示するシンプルな拡張です。

「構成」からダイアログを開くと、ダッシュボードから参照できるパラメーターのうち許容値がリストであるものが表示されるので、この中から横並びのラジオボタンにしたいパラメーターを選択します。すると、パラメーターを画像のように横並びで表示することができます。

f:id:aboy_perry:20211223171641p:plain

ちなみにダッシュボード上に拡張機能オブジェクトを複数設置すれば、複数のパラメーターにも対応できます。.trexファイルをダウンロードすれば誰でも使えるので、ぜひ使ってみてください! 💪

つくったきっかけ

業務で必要になったのがきっかけです。Tableauでは許容値がリストであるパラメーターはラジオボタンのようなUI(単一の値のリスト)で表示することができますが、横並びにするような設定がありません。

f:id:aboy_perry:20211223171813p:plain
許容値がリストであるパラメーター。この場合Order ID, Order Date, Customer Nameの3種類の文字列を取りうる。

ダッシュボードのスペースやUI設計の都合上、横並びにしたかったため方法を探していました。Tableauコミュニティでも同様の相談が複数見受けられました。できないからといってクリティカルではないものの、どうにかできないかな〜と考えていました。

こういった公式がまだ機能として提供していないものについては、Tableauのコミュニティでtipsが共有されたりしています。今回のラジオボタンの横並びについてもコミュニティでtipsを見つけることができました。具体的にはこちらのYouTubeの動画や、こちらのナレッジベースのように、ワークシートやダッシュボードアクションを駆使することで横並びのラジオボタンをつくることができるというもので、これはこれで素晴らしいのですが、

  • 多少手間がかかる
  • リストの値を取るデータフィールドがないようなデータ構造だとできない

という問題がありました。前者はしょうがないとしても、今回のきっかけとなったデータ構造は後者に該当するためこの方法ではできませんでした。後者は例えば動画内で出てきた計算フィールドSTR([Region Parameter] = [Region]) + [Region]において、RegionRegion Parameterで定義したリスト内の値をとるデータでなければいけません。Region ParamerterEast, West, Central, Southの4つの文字列を許容値とするリストならば、その4つの文字列を取りうるRegionというデータフィールド(列)が必要ということです。

f:id:aboy_perry:20211223172038p:plain
Tableau付属のデータセット「Superstore」のRegionのようなデータ構造なら可能

そこで、Tableauが提供しているもう一つの選択肢、拡張機能を使うことにしました。ユーザーはTableauのギャラリー(Tableau公認*1)や、ユーザーコミュニティポータルで公開されている拡張機能を探してダッシュボードに組み込めるほか、Tableau Extension APIを活用して自作することもできます。

今回はニーズに合う拡張機能が見つからなかったので自作しました。拡張機能をつくるには、Tableauの知識、Tableau Extension APIの知識、多少のWebアプリケーション開発の知識が必要になります。今回のようなシンプルな拡張機能であれば比較的簡単につくることができました。

以降では、今回の拡張機能をつくるうえで出てきた実装をいくつか紹介します。

実装

Tableau拡張のつくり方の詳細は公式のドキュメントにまとまっていますのでご覧になってみてください。今回JavaScriptのライブラリは、公式サンプルにも登場するjQueryを利用しました。

ダッシュボード内のパラメーターを取得する

ダッシュボード内のパラメーターはdashboardContent.dashboard.getParametersAsync()で取得できます。その中から、許容値がリストであるパラメーターに絞り、inputやlabelを作り反映させています。

tableau.extensions.dashboardContent.dashboard.getParametersAsync().then((params) => {
    params
        .filter((p) => p.allowableValues.type === tableau.ParameterValueType.List)
        .forEach((p) => {
            const hElement = $('<h3>')

            $('<input />', {
                type: 'radio',
                id: p.name,
                name: 'HorizontalRadioButton',
                value: p.name,
            }).appendTo(hElement)

            $('<label>', {
                for: p.name,
                text: p.name,
            }).appendTo(hElement)

            $('#parameters').append(hElement)  
        )      
})

パラメーターとラジオボタンを同期させる

拡張機能側のラジオボタンを押したらパラメーターの値も変わるようにする部分です。また、Tableauはダッシュボードアクションなどでパラメーターに作用できるので、ダッシュボード側の操作によってパラメーターが変わったことを拡張機能側で検知して自身のラジオボタンに反映させられるように、つまり双方向に同期されるようにします。

拡張機能からパラメーターへの反映はparameter.changeValueAsync()で行い、パラメーターから拡張機能への反映はパラメーターへイベントリスナーを登録して行います。

tableau.extensions.dashboardContent.dashboard.getParametersAsync().then((parameters) => {
    const selectedParameter = parameters.find((p) => p.name === savedValue)
    // パラメーターの変更検知
    selectedParameter.addEventListener(tableau.TableauEventType.ParameterChanged, onParameterChange)

        const parameterValuesElement = $('<div id="parameter">')
        selectedParameter.allowableValues.allowableValues.forEach((dataValue) => {
        const eachValueElement = $('<div style="display: inline-block">')

        $('<input />', {
            type: 'radio',
            id: dataValue.value,
            name: selectedParameter.name,
            value: dataValue.formattedValue,
            checked: dataValue.value === selectedParameter.currentValue.value,
                        // 拡張機能 => パラメーター へ同期
            click: () => selectedParameter.changeValueAsync(dataValue.value),
        }).appendTo(eachValueElement)

        $('<label>', {
            for: dataValue.value,
            text: dataValue.formattedValue,
        }).appendTo(eachValueElement)

        parameterValuesElement.append(eachValueElement)
    })
    $('#parameter').replaceWith(parameterValuesElement)
})

// パラメーター => 拡張機能 へ同期
const onParameterChange = (parameterChangeEvent) => {
    parameterChangeEvent.getParameterAsync().then((p) => {
        $(`input:radio[value="${p.currentValue.formattedValue}"]`)
            .prop('checked', true)
    })
}

設定をワークブックに保存し永続化する

拡張機能の実態はホスティングされたWebサイトなので、ワークブックの再読み込みで初期化されます。Tableauの拡張機能は基本的に構成ダイアログから設定を変更できるように作りますが、その設定を保存するためのコードを書く必要があります。

設定はkey-value形式で保存でき、ワークブック単位で保持されます。構成ダイアログをひらく場合はtableau.extensions.ui.displayDialogAsync()、とじる場合は tableau.extensions.ui.closeDialog() も呼んであげます。どちらも引数にはpayloadを渡せますが、今回は設定の読み込みと書き込みは全てtableau.extensions.settings経由で行いたかったので、payloadは空文字でも問題ありません。*2

// 構成ダイアログをひらく
tableau.extensions.ui.displayDialogAsync('config.html', payload)

// 設定をセット
tableau.extensions.settings.set('key', value)
// 構成をワークブックに保存
tableau.extensions.settings.saveAsync().then(() => {
    // 構成ダイアログをとじる
    tableau.extensions.ui.closeDialog(value)
})

keyを指定してsettings.get()で保存された値を取得できるほか、イベントリスナーの登録によって設定が変更されたことを検知できます。拡張機能や構成ダイアログの初回読み込み時は settings.get()、以降はイベントリスナー経由で値を取得するという使い分けになります。

tableau.extensions.initializeAsync({'configure': configure}).then(() => {
    // 初回読み込み時はここで保存された値を取得
    savedValue = tableau.extensions.settings.get('key')
    // イベントリスナーを登録しておいて
    tableau.extensions.settings.addEventListener(tableau.TableauEventType.SettingsChanged, onSettingsChange)
})

const onSettingsChange =  (settingsEvent) => {
    // 最新の値を取得
    savedValue = settingsEvent.newSettings.key
    // UIの更新など
}

checkedのときにinputが表示されない

inputがcheckedのときに消えてしまう不具合がありました。下の画像では「Order Date」がcheckedなのですが、ラジオボタンの丸ポチが消えてしまっています。

f:id:aboy_perry:20211223172324p:plain

原因は特定できなかったため、結局CSSを別途当てることにしました。input[type=radio]に対してdisplay:noneを当てて、labelのbefore/afterに対してスタイルを当てて擬似的にラジオボタンのように見せて対応しました。

おわりに

拡張機能を上手く活用すれば、Tableau Extension APIとWebでできることはだいたいできるので、Tableauの可能性が広がりますね。拡張機能は開発するうえでTableauの理解も深まりますし、コミュニティにも貢献できるのでオススメです!