2024年8月19日月曜日

Observable PlotをOracle APEXのアプリケーションに組み込む

Observable PlotをOracle APEXのアプリケーションに組み込んでみます。

Oracle JETやその他のチャート描画ライブラリの組み込み作業で表示したチャートと、同じチャートを表示してみます。今回はカスタム要素は作成せず、Observable PlotはESモジュールとして読み込んで実装してみます。

チャートを描画するリージョンや列に埋め込まれた(type="module"の)スクリプトは、遅延ロードが完了したときに実行されます。そのため、対話モード・レポートでページ送りを行なったときでもチャートが正しく描画されます。ただし、これは描画にSVGが使われている場合に限られるようです。Canvas要素にチャートを書き込むタイプのライブラリでは、対話モード・レポートで遅延ロードが行われるとチャートは表示されません。


直接Observable Plotで描画



jsDelivrへアクセスし、Observable PlotのESモジュールを指すURLを取得します。TypeESMを選択し、VersionLatest Majorとします。Copy URLを実行します。


スクリプトにESモジュールのURLを直書きする代わりに、別名を指定できるようにします。

ページ・プロパティHTMLヘッダーimportmapを記述します。Observable Plotは、別名observablehqを指定してインポートできるようになります。
<script type="importmap">
    {
        "imports": {
            "observablehq": "https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm"
        }
    }
</script>

Observable PlotのGetting startedを参考にして、div要素にチャートを描画します。

チャートに描画するデータを保持するページ・アイテムとしてP1_VALUEを作成します。タイプ非表示です。

ページ・アイテムP1_VALUE計算を作成し、ページの描画前にデータを設定します。計算タイプとしてSQL問合せ(単一の値を返す)を選択し、SQL問合せに以下を記述します。
select
    json_arrayagg(
        json_object(
            'ename' value ename,
            'sal' value sal
        )
        order by empno asc
    )
from emp where deptno = 10

静的コンテンツのリージョンの名前Observable Plotとし、ソースHTMLコードに以下を記述します。

<div id="myChart""></div>
<script type="module">
import * as observablehq from 'observablehq';
// "apex.items.ITEM_NAME.value" is not available inside module.
// const value = apex.items.P1_VALUE.value;
const value = apex.item("P1_VALUE").getValue();
var data = JSON.parse(value);
const plot = observablehq.plot({
label: null,
width: 400,
marginLeft: 60,
marks: [
observablehq.barX(data, { x: "sal", y: "ename", fill: "#309fdb" } )
]
});
const div = document.getElementById("myChart");
div.append(plot);
</script>


以上で実装は完了です。ページを実行すると以下のように表示されます。



対話モード・レポートへの組み込み



対話モード・レポートのページのページ・プロパティHTMLヘッダーに、先ほどと同じimportmapを記述します。


対話モード・レポートソースSQL問合せとして以下を記述します。
select
    dname,
    json_arrayagg(
        json_object(
            'ename' value ename,
            'sal' value sal
        )
        order by empno asc
    ) value,
    '' chart
from emp_dept_v group by dname


対話モード・レポートの列CHARTを選択します。識別タイププレーン・テキストです。

列の書式HTML式として以下を記述します。

<div id="#APEX$DOM_ID#" class="w400"><div>
<script type="module">
import * as observablehq from 'observablehq';
const value = '#VALUE!RAW#';
var data = JSON.parse(value);
const plot = observablehq.plot({
label: null,
width: 400,
marginLeft: 60,
marks: [
observablehq.barX(data, { x: "sal", y: "ename", fill: "#309fdb" } )
]
});
const div = document.getElementById("#APEX$DOM_ID#");
div.append(plot);
</script>
置換文字列#APEX$DOM_ID#はAPEX 24.1から提供されている新機能で、テンプレート・ディレクティブやテンプレート・コンポーネント毎にユニークな値に置換されます。


以上で実装は完了です。ページを実行すると以下のように表示されます。



テンプレート・コンポーネントの作成



Observable Plotのバー・チャートをテンプレート・コンポーネントとして作成します。カスタム要素は作成しません。

テンプレート・コンポーネントの名前Observable Plot Bar Chartとしました。テンプレート部分には以下を記述しています。

<div id="#APEX$DOM_ID#" class="#CSS_CLASSES#"><div>
<script type="module">
import * as observablehq from 'observablehq';
/* plot chart */
let config = {
label: null,
marks: []
};
// define plot area.
const width = "#WIDTH#";
if ( width ) {
config.width = Number(width);
};
const height = "#HEIGHT#";
if ( height ) {
config.height = Number(height);
};
const marginTop = "#MARGIN_TOP#";
if ( marginTop ) {
config.marginTop = Number(marginTop);
};
const marginRight = "#MARGIN_RIGHT#";
if ( marginRight ) {
config.marginRight = Number(marginRight);
};
const marginLeft = "#MARGIN_LEFT#";
if ( marginLeft ) {
config.marginLeft = Number(marginLeft);
};
const marginBottom = "#MARGIN_BOTTOM#";
if ( marginBottom ) {
config.marginBottom = Number(marginBottom);
};
// define bar.
const groups = "#GROUPS#";
apex.debug.info(groups);
const xy = groups.split(",");
let bar = {
x: xy[0].trim(),
y: xy[1].trim()
};
const color = "#COLOR#";
if ( color ) {
bar.fill = color;
};
// data
const data = JSON.parse('#VALUE#');
const orientation = "#ORIENTATION#";
if ( orientation === null || orientation === "horizontal" ) {
config.marks.push(
observablehq.barX(data, bar)
);
}
else
{
config.marks.push(
observablehq.barY(data, bar)
);
};
/* draw chart */
apex.debug.info(config);
const plot = observablehq.plot(config);
document.getElementById("#APEX$DOM_ID#").appendChild(plot);
</script>


カスタム属性としてCSS ClassesGroupsValueColorOrientationを作成しています。Groupsタイプは、セッション・ステート値ではなくテキストとしています。

加えて、チャートの表示領域を規定するWidthHeightMargin TopMargin RightMargin LeftMargin Bottomカスタム属性として作成しています。これらは全て整数です。


チャートの向きの指定であるOrientation静的LOVは、horizontalの戻り値としてhorizontalverticalの戻り値としてverticalを設定します。


テンプレート・コンポーネントにはimportmapを設定する方法がありません。そのため、設定は以上で完了です。このテンプレート・コンポーネントを使用するページでは、HTMLヘッダーimportmapを記述する必要があります。

対話モード・レポートにObservable Plotのテンプレート・コンポーネントを実装します。

CHARTを選択し、識別タイプObservable Plot Bar Chartとします。

CSS Classesとしてw400Groupsとしてsal,enameValueとしてVALUEColorとして#309fdbOrientationとしてhorizontalを設定します。チャートの描画領域としてWidth400Margin Left60を設定します。


以上で実装は完了です。ページを実行すると以下のように表示されます。


今回の記事は以上になります。

作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/integrating-observable-plot-esm.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。