2024年12月11日水曜日

Oracle JETのScrollable Heat MapをOracle APEXに組み込む

Oracle JET Cookbookの以下のリンクで紹介されているScrollable Heat MapをOracle APEXに組み込んでみます。使用しているコンポーネントとしてはData Gridになります。

Scrollable Heat Mapを組み込んだAPEXアプリケーションは以下のように動作します。

JET Cookbookに載っているサンプルとまったく同じですが、データ・ソースはファイルではなくデータベースに変更しています。


Oracle JET Cookbookに掲載されているサンプルをOracle APEXに実装する手順は、過去にいくつかの記事で紹介しています。今回のScrollable Heat Mapの組み込みも、ほぼ同様の手順です。

以下より実装手順を紹介します。

最初に表示に使用するデータをデータベースに取り込みます。以下のDDLを実行し表EBAJ_DEMO_HOUSE_PRICEを作成します。
create table ebaj_demo_house_price (
    id        number generated by default on null as identity
              constraint ebaj_demo_house_price_id_pk primary key,
    region    varchar2(40 char) not null,
    period    varchar2(40 char) not null,
    price     number not null
);
以下はSQLコマンドでの実行結果です。


JET Cookbookのサンプルは、以下のファイルをデータとして読み込んでいます。
https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataCollections/dataCollections/heatmapGrid/housePriceData.json

このJSONファイルを表EBAJ_DEMO_HOUSE_PRICEに読み込みます。以下のスクリプトを実行します。

declare
l_response clob;
l_regions json_array_t;
l_region_object json_object_t;
l_keys json_key_list;
l_key varchar2(4000);
l_region ebaj_demo_house_price.region%type;
l_period ebaj_demo_house_price.period%type;
l_price ebaj_demo_house_price.price%type;
begin
apex_web_service.clear_request_headers();
l_response := apex_web_service.make_rest_request(
p_url => 'https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataCollections/dataCollections/heatmapGrid/housePriceData.json'
,p_http_method => 'GET'
);
l_regions := json_array_t(l_response);
for i in 1..l_regions.get_size()
loop
l_region_object := treat(l_regions.get(i-1) as json_object_t);
/* Regionを取り出して削除する。年月と値が残る */
l_region := l_region_object.get_string('Region');
l_region_object.remove('Region');
/* 年月と値を表EBAJ_HM_DATAへ投入する */
l_keys := l_region_object.get_keys();
for j in 1..l_keys.count
loop
l_period := l_keys(j);
l_price := l_region_object.get_number(l_period);
insert into ebaj_demo_house_price(region, period, price) values(l_region, l_period, l_price);
end loop;
end loop;
commit;
end;
以下はSQLコマンドでの実行結果です。


元のデータにスペルの間違いがあるので修正します。

update ebaj_demo_house_price set period = 'February2013' where period = 'Febrary2013';


以上でヒートマップの表示に使うデータをデータベースにロードできました。

SELECT文を実行しデータがロードされていることを確認します。

select * from ebaj_demo_house_price;


APEXアプリケーションの作成に取り掛かります。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前JET Scrollable Heat Mapとします。ヒートマップはデフォルトで作成されるホーム・ページに実装します。


アプリケーションが作成されます。


JET Cookbookが参照しているJavaScriptのクラスDemoArrayDataGridProviderは、そのままAPEXでも使用します。以下のファイルをダウンロードし、静的アプリケーション・ファイルとして保存します。

共有コンポーネント静的アプリケーション・ファイルを開きます。

ファイルの作成をクリックします。


ディレクトリdataProviderとします。コンテンツとしてDemoArrayDataGridProvider.jsを選択します。

作成をクリックします。


アップロードされたDemoArrayDataGridProvider.jsがスクリプト・エディタで開かれます。この時点ではミニファイされたファイルは作成されていません。

変更の適用をクリックします。


以上でDemoArrayDataGridProvider.jsが静的アプリケーション・ファイルとして保存されました。取消をクリックして静的アプリケーション・ファイルの一覧画面に戻ると、保存されたJavaScriptのファイルが確認できます。


静的アプリケーション・ファイルとして保存したDemoArrayDataGridProvider.jsを、Oracle JETから参照するために(相対ではなく)完全なURLが必要です。完全なURLを作るための設定を行います。

アプリケーション・アイテムAPEX_PATHおよびアプリケーションの計算を設定し、APEX_UTIL.HOST_URL('APEX_PATH')の値を参照できるようにします。

共有コンポーネントアプリケーション・アイテムを開き、作成をクリックします。


アプリケーション・アイテムの名前APEX_PATHとします。その他は一番制限の厳しいデフォルトの設定のまま変更しません。

アプリケーション・アイテムの作成をクリックします。


アプリケーション・アイテムAPEX_PATHが作成されました。


作成したアプリケーション・アイテムAPEX_PATHアプリケーションの計算を作成します。

共有コンポーネントアプリケーションの計算を開き、作成をクリックします。


計算アイテムとしてAPEX_PATHを選択します。頻度計算ポイント新規インスタンス(新規セッション)開始時です。

計算タイプ言語PL/SQL計算としてAPEX_UTIL.HOST_URL('APEX_PATH')を指定します。

以上で、計算の作成をクリックします。


以上で計算が作成されました。アプリケーション・アイテムAPEX_PATHより、http:またはhttps:から始まるベースURLを参照できます。


ヒートマップの表示に使用するデータを、データベースよりJSONドキュメントとして取り出すプロセスを作成します。

AjaxコールバックにプロセスGET_DATAを作成します。タイプコードの実行を選択し、ソースPL/SQLコードに以下を記述します。データベースにロードしたhousePrice.jsonと同じ形式のJSONドキュメントを、表EBAJ_DEMO_HOUSE_PRICEから逆に生成しています。

declare
l_response clob;
begin
select json_arrayagg(nobj returning clob) into l_response from
(
select json_mergepatch(obj, '{"Region": "' || rgn || '"}') nobj
from (
select p.region rgn, json_objectagg(p.period value p.price) obj
from ebaj_demo_house_price p group by p.region
)
);
htp.p(l_response);
end;
view raw get_data.sql hosted with ❤ by GitHub

ホーム・ページ上にScrollable Heat Mapを表示するリージョンを作成します。タイプ静的コンテンツです。

ソースHTMLコードとして以下を記述します。JET Cookbookのdemo.htmlからoj-data-grid要素の部分を抜粋しています。

<oj-data-grid
id="datagrid"
aria-label="Data Grid Heatmap Demo"
class="demo-data-grid"
data="[[dataGridProvider]]"
header.row.style="width:16em;"
header.column.class-name="demo-cell-alignment"
header.column.style="height:13em;width:2.5em"
cell.renderer="[[cellRenderer]]"
cell.class-name="[[setCellClass]]">
<template slot="columnHeaderTemplate" data-oj-as="header">
<div class="demo-content-container oj-helper-text-align-left">
<oj-bind-text value="[[header.item.data]]"></oj-bind-text>
</div>
</template>
</oj-data-grid>

ページ・プロパティJavaScriptファイルURLに以下を記述します。

[require jet]

ページ・ロード時に実行に以下を記述します。

JET Cookbookのdemo.jsそのままですが、表示に使用するデータをデータベースから取得するように変更しています。

requirejs.config({
paths: {
'dataProvider': "&APEX_PATH!RAW.#APP_FILES#dataProvider",
}
});
require(["require", "exports", "knockout", "ojs/ojbootstrap", "dataProvider/DemoArrayDataGridProvider", "ojs/ojknockout", "ojs/ojdatagrid"], function (require, exports, ko, ojbootstrap_1, DemoArrayDataGridProvider_1) {
"use strict";
class ViewModel {
constructor(data) {
// this.jsonData = JSON.parse(jsonData);
this.jsonData = data;
this.rowHeaderProperties = ['Region'];
this.rowHeaders = this.rowHeaderProperties.map((prop) => {
return this.jsonData.map((item) => {
return item[prop];
});
});
this.columnHeaders = [
Object.keys(this.jsonData[0]).filter((key) => {
return this.rowHeaderProperties.indexOf(key) === -1;
})
];
this.data = this.jsonData.map((item) => {
return this.columnHeaders[0].map((header) => {
return { data: item[header] };
});
});
this.dataGridProvider = ko.observable(new DemoArrayDataGridProvider_1.DemoArrayDataGridProvider({
data: this.data,
rowHeader: this.rowHeaders,
columnHeader: this.columnHeaders
}));
this.columnHeaderRenderer = (headerContext) => {
// container div to rotate the text
var container = document.createElement('div');
container.className = 'demo-content-container';
container.appendChild(document.createTextNode(headerContext.data));
return { insert: container };
};
this.cellRenderer = (cellContext) => {
// set the value as aria-label for screen reader on the cell
var cell = cellContext.parentElement;
cell.setAttribute('aria-label', cellContext.data);
};
this.setCellClass = (cellContext) => {
const cell = cellContext.cell;
const data = cell.data;
if (data < -1.25) {
return 'oj-bg-success-30';
}
// -1% > data >= -1.25%
if (data < -1) {
return 'oj-bg-brand-30';
}
// -0.75% > data >= -1%
if (data < -0.75) {
return 'oj-bg-warning-30';
}
// -0.5% > data >= -0.75%
if (data < -0.5) {
return 'oj-bg-warning-20';
}
// -0.25% > data >= -0.5%
if (data < -0.25) {
return 'oj-bg-warning-10';
}
// data > 2.25%
if (data > 2.25) {
return 'oj-bg-neutral-200';
}
// 2% < data <= 2.25%
if (data > 2) {
return 'oj-bg-neutral-170';
}
// 1.75% < data <= 2%
if (data > 1.75) {
return 'oj-bg-success-20';
}
// 1.5% < data <= 1.75%
if (data > 1.5) {
return 'oj-bg-brand-20';
}
// 1.25% < data <= 1.5%
if (data > 1.25) {
return 'oj-bg-info-30';
}
// 1% < data <= 1.25%
if (data > 1) {
return 'oj-bg-info-20';
}
// 0.75% < data <= 1%
if (data > 0.75) {
return 'oj-bg-info-10';
}
// 0.5% < data <= 0.75%
if (data > 0.5) {
return 'oj-bg-neutral-20';
}
// 0.25% < data <= 0.5%
if (data > 0.25) {
return 'oj-bg-neutral-10';
}
// between -0.25 and 0.25
return 'oj-bg-neutral-0';
};
}
}
(0, ojbootstrap_1.whenDocumentReady)().then(() => {
/* データベースからJSONでデータを取得する。 */
apex.server.process( "GET_DATA", {},
{
success: (data) => {
/* datagridのviewModelをバインド */
ko.applyBindings(new ViewModel(data), document.getElementById('datagrid'));
}
}
);
});
});
CSSファイルURLに以下を記述します。

#JET_CSS_DIRECTORY#redwood/oj-redwood-notag-min.css

インラインに以下を記述します。

.demo-data-grid {
width: 100%;
height: 27.75rem;
max-width: 48.875rem;
}
.demo-cell-alignment {
align-items: stretch;
}
.demo-content-container {
position: relative;
transform: rotate(180deg);
writing-mode: vertical-lr;
width: 9.625rem;
}

以上でアプリケーションは完成です。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/jet-scrollable-heat-map.zip

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