2023年8月31日木曜日

Oracle JETのSankey LayoutのダイアグラムをOracle APEXで扱う

今回はOracle JETのUse CaseにあるSankey Layoutを実装したダイアグラムをOracle APEXで扱ってみます。

作成されたアプリケーションは以下のように動作します。


Use Case: Sankey Layoutは、Oracle JET Cookbookの以下のページで紹介されています。


実装手順はこちらの記事で紹介している基本的なDiagramと同じです。以下より、実装手順の詳細は省いたSankey Layoutの設定を紹介します。

Sankey LayoutのダイアグラムのソースとなるJSONファイルはsochiOlympics.jsonです。ソチオリンピックでの国別および競技別のメダルの個数が記録されています。

このファイルに含まれるノードを保存する表としてDG_SANKEY_NODESリンク(エッジ)を保存する表としてDG_SANKEY_LINKSを作成します。表の作成には以下のクイックSQLのモデルを使用します。
# prefix: dg_sankey
# pk: none
nodes
    id
    name vc40 /nn
    category vc20 /nn

links
    id
    source_node /fk nodes
    target_node /fk nodes
    items num /nn
それぞれの列IDはNUMBER型の主キーとして作成されますが、実際のデータは文字列です。クイックSQLが生成したDDLを、以下のように修正します。表DG_SANKEY_NODESの列IDとそれを参照している列の型はVARCHAR2(40 CHAR)、表DG_SANKEY_LINKSの列IDの型はVARCHAR2(4 CHAR)に変更しています。表DG_SANKEY_LINKSの列IDのデータにはidという名前に関わらず重複した値が含まれているため、主キー制約を外しています
-- create tables
create table dg_sankey_nodes (
    id                             varchar2(40 char) not null constraint dg_sankey_nodes_id_pk primary key,
    name                           varchar2(40 char) not null,
    category                       varchar2(20 char) not null
)
;

create table dg_sankey_links (
    id                             varchar2(4 char) not null,
    source_node                    varchar2(40 char)
                                   constraint dg_sankey_links_source_node_fk
                                   references dg_sankey_nodes on delete cascade,
    target_node                    varchar2(40 char)
                                   constraint dg_sankey_links_target_node_fk
                                   references dg_sankey_nodes on delete cascade,
    items                          number not null
)
;

-- table index
create index dg_sankey_links_i1 on dg_sankey_links (source_node);
create index dg_sankey_links_i52 on dg_sankey_links (target_node);

-- load data
APEXアプリケーションの名前JET Diagram Sankeyとし、空のアプリケーションを作成します。ダイアグラムはホーム・ページに実装します。


データ・ロード定義は、グラフのデータ(sochiOlympics.json)よりノードをロードするGraph Nodesと、リンクをロードするGraph Linksを作成します。


データ・ロード定義ロード・メソッド置換とします。

データ・ロード定義Graph Nodes行セレクタnodesデータ・プロファイルとしてID(セレクタはid)、NAME(同name)、CATEGORY(同category)を定義します。


データ・ロード定義Graph Links行セレクタlinksデータ・プロファイルとしてID(セレクタはid)、SOURCE_NODE(同source)、TARGET_NODE(同target)、ITEMS(同items)を定義します。列ITEMSはデータ型がNUMBERになります。


ホーム・ページにはデータを初期化するボタンINIT、ダイアグラムの表示を行なう静的コンテンツのリージョンDiagram、表DG_SANKEY_NODESを編集する対話グリッドNodesと、DG_SANKEY_LINKSを編集する対話グリッドLinksを作成します。


リージョンDiagramHTMLソースの記述は以下になります。

<div id="diagram-container">
<oj-diagram
id="diagram1"
node-data="[[nodeDataProvider]]"
link-data="[[linkDataProvider]]"
layout="[[layoutFunc]]"
style-defaults="[[styleDefaults]]"
class="demo-diagram-sankeylayout-height-style">
</oj-diagram>
</div>
データのロードは、ボタンINITを押した時に実行される実行チェーン初期化に含まれるプロセス(タイプデータのロード)、ノードリンクが行います。

設定データ・ソース型としてSQL Queryを選択し、SQL問合せとして以下を記述します。

select
apex_web_service.make_rest_request_b(
p_url => :G_DATA_SOURCE_URL
,p_http_method => 'GET'
) as blob_content
from dual;
ソースとなるsochiOlympics.jsonのURLは、置換文字列G_DATA_SOURCE_URLに設定します。


アプリケーション定義置換に置換文字列G_DATA_SOURCE_URLを設定します。置換値となるURLは以下になります。

https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/diagram/resources/sochiOlympics.json


グラフのデータを取得するAjaxコールバックGET_DATAソースPL/SQLコードは以下になります。

JSON_OBJECTを使ったSELECT文は、32676文字を超えるレスポンスを生成することができません。レスポンスがそれ以上になる場合は、JSON_OBJECT_TやJSON_ARRAY_Tといったタイプ扱うPL/SQLコードを記述します。

declare
l_response_json json_object_t;
l_nodes json_array_t;
l_links json_array_t;
begin
/* create node array */
l_nodes := json_array_t();
for r in (
select json_object(
key 'id' value id
,key 'name' value name
,key 'category' value category
) as jo from dg_sankey_nodes
)
loop
l_nodes.append(json_object_t(r.jo));
end loop;
/* create link array */
l_links := json_array_t();
for r in (
select json_object(
key 'id' value id
,key 'source' value source_node
,key 'target' value target_node
,key 'items' value items
) as jo from dg_sankey_links
)
loop
l_links.append(json_object_t(r.jo));
end loop;
/* create reponse */
l_response_json := json_object_t();
l_response_json.put('nodes', l_nodes);
l_response_json.put('links', l_links);
htp.p(l_response_json.to_clob());
end;


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

[require jet]

ファンクションおよびグローバル変数の宣言に以下の1行を記述します。

var diagram;

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

requirejs.config({
paths: {
'diagramLayouts': "&APEX_PATH!RAW.#APP_FILES#layouts",
}
});
require(["require", "exports", "knockout", "diagramLayouts/DemoSankeyLayout", "ojs/ojbootstrap", "ojs/ojarraydataprovider", "ojs/ojattributegrouphandler", "ojs/ojknockout", "ojs/ojdiagram"], function (require, exports, ko, layout, ojbootstrap_1, ArrayDataProvider, ojattributegrouphandler_1) {
"use strict";
class DiagramModel {
/*
* Diagramで表示するデータを更新する。
*/
update(data) {
/* モデルをリセットする */
this.nodesMap = {};
this.nodes = [];
this.links = [];
/* ノードとリンクのデータを更新する。 */
this.data = data;
for (let i = 0; i < this.data.nodes.length; i++) {
this.nodesMap[this.data.nodes[i]['id']] = this.data.nodes[i];
}
for (let i = 0; i < this.data.links.length; i++) {
this.links.push(this.createLink(this.data.links[i]));
}
for (let nodeId in this.nodesMap) {
this.nodes.push(this.createNode(this.nodesMap[nodeId]));
}
this.nodeDataProvider(new ArrayDataProvider(this.nodes, {
keyAttributes: "id",
}));
this.linkDataProvider(new ArrayDataProvider(this.links, {
keyAttributes: "id",
}));
}
createLink(o) {
this.updateNodesWeight(o);
const source = o.source, target = o.target;
return {
id: o.id,
startNode: source,
endNode: target,
width: o.items * 3,
shortDesc: this.nodesMap[source]['category'] === 'award'
? o.items + ' ' + source + ' medals for ' + this.nodesMap[target]['name']
: this.nodesMap[source]['name'] +
' won ' +
o['items'] +
' medals in ' +
this.nodesMap[target]['name']
};
}
constructor() {
this.nodes = [];
this.links = [];
this.colorHandler = new ojattributegrouphandler_1.ColorAttributeGroupHandler();
this.nodesMap = {};
this.updateNodesWeight = (link) => {
const s = link.source, t = link.target;
if (s === 'Gold' || s === 'Silver' || s === 'Bronze')
this.nodesMap[s]['weight'] = this.nodesMap[s]['weight']
? this.nodesMap[s]['weight'] + link['items']
: link['items'];
this.nodesMap[t]['weight'] = this.nodesMap[t]['weight']
? this.nodesMap[t]['weight'] + link.items
: link.items;
};
// this.data = JSON.parse(jsonData);
this.createNode = (o) => {
const id = o['id'];
const weight = this.nodesMap[id]['weight'];
return {
id: id,
label: id,
shortDesc: o['name'],
icon: { color: this.colorHandler.getValue(id), height: weight * 3 }
};
};
this.layoutFunc = layout.layout;
this.nodeDataProvider = ko.observableArray();
this.linkDataProvider = ko.observableArray();
/*
this.nodeDataProvider = new ArrayDataProvider(this.nodes, {
keyAttributes: 'id'
});
this.linkDataProvider = new ArrayDataProvider(this.links, {
keyAttributes: 'id'
});
*/
this.styleDefaults = {
nodeDefaults: {
labelStyle: { fontSize: '30px', fontWeight: 'bold' },
icon: { width: 70, shape: 'rectangle' }
},
linkDefaults: { svgStyle: { strokeOpacity: 0.5, vectorEffect: 'none' } }
};
/*
for (let i = 0; i < this.data.nodes.length; i++) {
this.nodesMap[this.data.nodes[i]['id']] = this.data.nodes[i];
}
for (let i = 0; i < this.data.links.length; i++) {
this.links.push(this.createLink(this.data.links[i]));
}
for (let nodeId in this.nodesMap) {
this.nodes.push(this.createNode(this.nodesMap[nodeId]));
}
*/
}
}
(0, ojbootstrap_1.whenDocumentReady)().then(() => {
diagram = new DiagramModel();
ko.applyBindings(diagram, document.getElementById('diagram-container'));
/* ページ・ロード時の表示 */
apex.actions.invoke("update-diagram");
});
});
/*
* Diagramを更新する。
*/
apex.actions.add([
{
name: "update-diagram",
action: () => {
apex.server.process ( "GET_DATA", {},
{
success: (data) => {
// console.log(data);
diagram.update(data);
}
}
);
}
}
]);
CSSファイルURLに以下を記述します。

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

インラインは、Oracle JET Cookbookにdemo.cssとして記載されている内容を転記します。
.demo-diagram-sankeylayout-height-style {
    height: 37.5rem;
}

SankeyLayoutが実装されているファイルはDemoSankeyLayout.jsです。
https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/diagram/layouts/DemoSankeyLayout.js

このファイルを静的アプリケーション・ファイルとしてアップロードします。アップロードする際にディレクトリとしてlayoutsを指定します。


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


対話グリッドの保存がクリックされたとき(表DG_SANKEY_NODESまたはDG_SANKEY_LINKSが変更されたとき)、ダイアグラムが更新されるように動的アクション(のTRUEアクション)を作成します。

apex.actions.invoke("update-diagram"); 


Oracle JETのUse Case: Sankey Layoutを使ったダイアグラムの実装の紹介は以上になります。

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

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

2023年8月30日水曜日

Oracle JETのDiagramをOracle APEXで扱う

Oracle JETのTreemap、Tag CloudそしてTimelineをOracle APEXに実装してきました。今回はDiagramをOracle APEXで扱ってみます。

作成するアプリケーションは以下のように動作します。


Oracle JET Cookbookで紹介されているDiagramのBasicを実装しています。


以下より実装手順を説明します。

Oracle JET CookbookではJSON形式のファイルdiagramDataSample.jsonをデータソースとしています。Oracle APEXのアプリケーションでは、データベースの表をデータソースとして使用します。このJSONデータは後ほど、データ・ロード定義を作成する際にサンプルとして使うため、手元のPCにファイルとしてダウンロードしておきます。

diagramDataSample.jsonにはグラフのノードの配列である属性nodesとリンク(またはエッジ)の配列である属性linksの2種類の配列が含まれています。ノードの配列は表DG_BASIC_NODES、リンクの配列は表DG_BASIC_LINKSへ保存します。

以下のクイックSQLのモデルより、表DG_BASIC_NODESと表DG_BASIC_LINKSを作成します。設定にpk: noneを指定し、主キー列IDの生成を抑止します。その上で列idを定義しています。クイックSQLでは列idはつねにNUMBER型かつ主キーとしてDDLが生成されます。pk:none以外で自動的にID列を生成すると、主キーの値が自動採番される設定になります。列idを記述している場合は主キーとなりますが、自動採番の設定はされません。
# prefix: dg_basic
# pk: none
nodes
    id
    category num /nn

links
    id
    category num /nn
    start_node /fk nodes
    end_node  /fk nodes
ノードのidの値はN0,N1,N2といった形式、リンクのidはL0,L1,L2といった形式で数値ではありません。列IDのデータ型については、クイックSQLから生成されたDDLを編集することで対応します。

SQLの生成SQLスクリプトを保存レビューおよび実行を順次実施します。


スクリプト・エディタが開いたら、表DG_BASIC_NODESの列ID、表DG_BASIC_LINKSの列IDSTART_NODDEEND_NODEのデータ型をVARCHAR2(4)に変更します。

DDLの変更後スクリプトを実行し、ふたつの表を作成します。アプリケーションの作成は行ません。


ダイアグラムを組み込むAPEXのアプリケーションを作成します。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。アプリケーションの名前JET Diagram Basicとします。

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


アプリケーションが作成されます。ダイアグラムはページ番号ホームのページに実装します。


diagramDataSample.jsonを表DG_BASIC_NODESにロードする際に使用する、データ・ロード定義を作成します。

共有コンポーネントデータ・ロード定義を開きます。


作成済みのデータ・ロード定義が一覧されます。作成をクリックします。


データ・ロードの作成として最初からを選択します。

へ進みます。


データ・ロード定義の名前Graph Nodesとします。ターゲット・タイプ表名としてDG_BASIC_NODESを選択します。

へ進みます。


サンプル・データのソース・タイプとしてファイルのアップロードを選択し、すでにダウンロードしているdiagramDataSample.jsonサンプル・ファイルとして選択します。

へ進みます。


ウィザードによるデータ・ロード定義では、行セレクタを指定できません。(今回の場合では、属性nodeslinksのどちらかを選択できない。)そのため、ここでは列のマッピングはそのままにしておきます。

データ・ロードの作成をクリックします。


データ・ロード定義Graph Nodesが作成されます。設定を調整するためにGraph Nodesを開きます。


静的IDをgraph_nodesからGRAPH_NODESに変更します。PL/SQLのコード中で使用するIDは大文字にしたいという理由なので、必ずしも必要な作業ではありません。設定ロード・メソッド置換に変更します。

データ・プロファイルの編集をクリックします。


データ・プロファイル行セレクタとしてnodesを設定します。列ID主キーをNoからYesに変更しておきます。この主キーの設定はロード・メソッドマージのときに使用されます。今回は置換を選択しているため、この設定は必ずしも必要ではありません。

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


変更の適用をクリックし、データ・ロード定義の編集画面を閉じます。


作成をクリックし、グラフのリンク(エッジ)をロードするためのデータ・ロード定義を作成します。


データ・ロードの作成として最初からを選択します。

へ進みます。


データ・ロード定義の名前Graph Linksとします。ターゲット・タイプ表名としてDG_BASIC_LINKSを選択します。

へ進みます。


サンプル・データのソース・タイプとしてファイルのアップロードを選択し、サンプル・ファイルとしてdiagramDataSample.jsonを選択します。

へ進みます。


ソース列END(Varchar2(50))マップ先としてEND_NODE(Varchar2)を指定します。次に、ソース列START_(Varchar2(50))マップ先としてSTART_NODE(Varchar2)を指定します。

データ・ロードの作成をクリックします。


データ・ロード定義Graph Linksが作成されます。設定を調整するためにGraph Linksを開きます。


静的IDをgraph_linksからGRAPH_LINKSに変更します。必ずしも必要な作業ではありません。設定ロード・メソッド置換に変更します。

データ・プロファイルの編集をクリックします。


データ・プロファイル行セレクタlinksであることを確認します。ロード・メソッド置換なので必ずしも必要ではありませんが、列ID主キーYesに変更しておきます。

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


変更の適用をクリックし、データ・ロード定義の編集画面を閉じます。


以上でデータ・ロード定義の作成は完了です。

ページ・デザイナでページホームを開きます。

Breadcrumb BarにあるJET Diagram Basicを削除します。JET Diagram Basicの上でコンテキスト・メニューを表示させ、削除を実行します。


表DG_BASIC_NODESを操作する対話グリッドを作成します。

Body以下にリージョンを作成します。

識別タイトルNodesタイプとして対話グリッドを選択します。ソース表名としてDG_BASIC_NODESを選択します。

対話グリッドでは編集対象の表の主キーは通常は非表示、更新は不可です。表DG_BASIC_NODESの列IDは主キーですが、表示も更新も行えるように、ROWID列を含めるチェックを入れ、対話グリッドがROWIDを主キーとして扱うように指示します。


IDを選択し、編集ができる設定に変更します。

識別タイプテキスト・フィールドに変更し、ヘッダーとしてIDを設定します。検証必須の値オンソース主キーオフにします。


対話グリッドの属性を開き、編集有効オンにします。空の場合に行を追加オフにします。対話グリッドがあまり大きく表示されないように、ヘッダー固定リージョンとして、固定のレポートの高さ300ピクセルに制限します。


表DG_BASIC_NODESを操作する対話グリッドは以上で完成です。

この対話グリッドを重複させて表DG_BASIC_LINKSを操作する対話グリッドを作成します。DG_BASIC_NODESとDG_BASIC_LINKSはデータに親子関係がありますが、マスター・ディテール関係を設定するとリンクの編集がしにくくなりました。そのため、双方の対話グリッドは独立させています。

リージョンNodes上でコンテキスト・メニューを表示させ、重複を実行します。


作成された対話グリッドの識別タイトルLinksに、ソース表名DG_BASIC_LINKSに変更します。


表DG_BASIC_LINKSを操作する対話グリッドも完成しました。

表DG_BASIC_NODESとDG_BASIC_LINKSに初期データを投入するボタンを作成します。

識別ボタン名INITラベルInitとします。対話グリッドNodesの上に配置します。外観テンプレート・オプションWidthStretchに変更し、画面の横幅いっぱいにボタンを表示させます。

動作アクションはデフォルトのページの送信のまま変更しません。


左ペインでプロセス・ビューを開き、ボタンINITを押した時に実行するプロセスを作成します。

表DG_BASIC_NODESへのデータ・ロードと表DG_BASIC_LINKSへのデータ・ロードをそれぞれ実行する必要があることから、最初に実行チェーンを作成して、その下にデータ・ロードを行なうプロセスを作成します。

識別名前初期化タイプとして実行チェーンを選択します。設定バックグラウンドで実行オフとします。

サーバー側の条件ボタン押下時としてINITを選択します。


作成した実行チェーン上でコンテキスト・メニューを表示させ、子プロセスの追加を実行します。


参照制約の設定より、最初にノードの情報をロードします。

識別名前ノードタイプとしてデータのロードを選択します。実行チェーン初期化になります。設定データ・ロード定義としてGraph Nodesを選択し、ソース・データ型SQL Queryを選んで、SQL問合せに以下を記述します。

select
apex_web_service.make_rest_request_b(
p_url => 'https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/diagram/resources/diagramDataSample.json'
,p_http_method => 'GET'
) as blob_content
from dual;


プロセスノードを重複させ、リンクをロードするプロセスとして設定します。

プロセスノード上でコンテキスト・メニューを表示させ、重複を実行します。


作成されたプロセスの識別名前リンクに、設定データ・ロード定義Graph Linksに変更します。


以上でデータ・ロードを行なうプロセスの設定は完了です。

表DB_BASIC_LINKSを操作する対話グリッドは表DB_BASIC_NODESの対話グリッドを重複させて作成しています。この場合、データベースを操作する行の自動処理のプロセスが作成されません。

プロセスNodes - 対話グリッド・データの保存上でコンテキスト・メニューを表示させ、重複を実行します。


作成されたプロセスの識別名前Links - 対話グリッド・データの保存に、編集可能リージョンLinksに変更します。


アプリケーションを実行しボタンInitをクリックし、データ・ロードを実行します。

表DG_BASIC_NODESに9行、表DB_BASIC_LINKSに8行データがロードされました。


ノードが削除されると参照制約の設定により、そのノードを起点または終点としたリンクも削除されます。そのため、ノードの変更が発生した後に、リンクの対話グリッドをリフレッシュする必要があります。

対話グリッドNodesに動的アクションを作成します。

動的アクション識別名前onSave Nodesとします。タイミングイベントとして保存[対話グリッド]を選択します。


TRUEアクションとしてリフレッシュを選択し、影響を受ける要素選択タイプとしてリージョンリージョンとしてLinksを指定します。


ダイアグラムを表示するリージョンを作成します。

識別タイトルDiagramとします。タイプとして静的コンテンツを選択します。ボタンINITと対話グリッドの間にリージョンを配置します。

ソースHTMLコードとして以下を記述します。Oracle JET Cookbookのdemo.htmlからPanningとZoomingの設定を切り替えるラジオボタンを除いています。これらはAPEXのページ・アイテムを使って実装します。

<div id="diagram-container">
<oj-diagram
id="diagram1"
animation-on-data-change="auto"
panning="[[panningValue]]"
zooming="[[zoomingValue]]"
min-zoom=".5"
max-zoom="2"
node-data="[[nodeDataProvider]]"
link-data="[[linkDataProvider]]"
layout="[[layoutFunc]]"
aria-label="This is a simple diagram that shows how to render Nodes and Links">
<template slot="nodeTemplate" data-oj-as="node">
<oj-diagram-node
label="[[node.data.id]]"
short-desc='[["Node " + node.data.id + ", Category " + node.data.category]]'
icon.color="[[colorHandler.getValue(node.data.category)]]"
icon.width="50"
icon.height="50">
</oj-diagram-node>
</template>
<template slot="linkTemplate" data-oj-as="link">
<oj-diagram-link
start-node="[[link.data.start]]"
end-node="[[link.data.end]]"
short-desc='[["Link " + link.data.id + ", Category " + link.data.category + ", connects " + link.data.start + " to " + link.data.end]]'
color="[[colorHandler.getValue(link.data.category)]]"
start-connector-type="none"
end-connector-type="arrow">
</oj-diagram-link>
</template>
</oj-diagram>
</div>

余計な装飾を除くため、外観テンプレートとしてBlank with Attributes (No Grid)を選択します。


PanningとZoomingを設定するページ・アイテムを配置するリージョンを作成します。

識別タイトルControlsタイプ静的コンテンツです。外観テンプレートとしてBlank with Attributesを選択します。ボタンINITの下、リージョンDiagramの上に配置します。


このリージョンに自動でPanningを行うか決めるページ・アイテムP1_PANNINGを作成します。

タイプとしてラジオ・グループを選択します。ラベルPanningとします。設定列の数2とします。


LOVタイプとして静的値を選択します。静的値としてnoneautoを設定します(スクリーンショットは後に添付します)。追加値の表示NULL値の表示はともにオフです。

デフォルトタイプ静的を選び、静的値としてnoneを設定します。


静的値の設定です。表示値戻り値が両方ともnoneおよびautoの2行を設定します。


ページ・アイテムP1_PANNINGの設定は以上です。

ZoomingについてはPanningと設定はほぼ変わらないため、ページ・アイテムP1_PANNINGを重複させて作成します。

ページ・アイテムP1_PANNING上でコンテキスト・メニューを表示させ、重複を実行します。


作成されたページ・アイテムの識別名前P1_ZOOMINGに、ラベルZoomingに変更します。ページ・アイテムP1_PANNINGの右隣に配置するため、レイアウト新規行の開始オフにします。


ダイアグラムとして表示するグラフのデータは、Ajaxコールバックを呼び出して取得します。

プロセス・ビューを開き、Ajaxコールバックにプロセスを作成します。Ajaxコールバックは、表DG_BASIC_NODESとDG_BASIC_LINKSの内容をそれぞれJSONの配列として含む、JSONオブジェクトを返します。

識別名前GET_DATAタイプとしてコードを実行を選択します。ソースPL/SQLコードとして以下を記述します。

declare
l_response clob;
begin
select json_object(
key 'nodes' value (
select json_arrayagg(
json_object(
key 'id' value id
,key 'category' value category
)
) from dg_basic_nodes
)
,key 'links' value (
select json_arrayagg(
json_object(
key 'id' value id
,key 'category' value category
,key 'start' value start_node
,key 'end' value end_node
)
) from dg_basic_links
)
) into l_response
from dual;
htp.p(l_response);
end;

ダイアグラムの表示にはDemoCircleLayout.jsおよびDemoLayoutSupport.jsが必要です。
https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/diagram/layouts/DemoCircleLayout.js
https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/diagram/layouts/DemoLayoutSupport.js

この2つのファイルを手元のPCにダウンロードし、APEXアプリケーションに静的アプリケーション・ファイルとしてアップロードします。

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


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


ディレクトリとしてlayoutsを指定し、ファイルをドラッグ・アンド・ドロップの領域にすでにダウンロードしてあるDemoLayoutSupport.jsを選択します。

作成をクリックします。


ファイルがアップロードされエディタが開きます。

変更の保存をクリックするとminifyされたファイルが作成されます。ブレッドクラムの静的アプリケーション・ファイルをクリックしして、ファイルの一覧画面へ戻ります。


同様の手順にて静的アプリケーション・ファイルとしてDemoCircleLayout.jsを作成します。


最終的に静的アプリケーション・ファイルとして、DemoCircleLayouot.jsDemoLayoutSupport.jsと、双方のMinifyされたファイルDemoCircleLayouot.min.jsDemoLayoutSupport.min.jsが作成されます。


このJavaScriptファイルをロードする際に置換文字列#APP_FILES#がうまく使えません。#APP_FILES#はホスト名などを含まない相対パスに置換されますが、Oracle JETのコンポーネントはAPEXの外にあるため、これらのファイルはホスト名も含む絶対パスで指定する必要があります

APEX_UTIL.HOST_URL('APEX_PATH')で取得されるURLをJavaScriptから参照できるように、アプリケーション・アイテムAPEX_PATHを作成し、APEX_UTIL.HOST_URL('APEX_PATH')が返す値を設定します。

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


作成済みのアプリケーション・アイテムが一覧されます。作成をクリックします。


名前としてAPEX_PATHを設定し、アプリケーション・アイテムの作成をクリックします。

デフォルトの有効範囲アプリケーションセキュリティセッション・ステート保護制限付き - ブラウザからの設定不可です。


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


共有コンポーネントアプリケーションの計算を開きます。


作成済みのアプリケーションの計算が一覧されます。作成をクリックします。


計算アイテムとしてAPEX_PATHを選択します。頻度計算ポイントはデフォルトの新規インスタンス(新規セッション)開始時を選択します。すでにAPEXアプリケーションが開始している場合、ページ・アイテムAPEX_PATHに値を設定するには、一旦サインアウトして再度サインインする必要があります

計算計算タイプを選択し、計算として以下を記述します。

APEX_UTIL.HOST_URL('APEX_PATH')

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


アプリケーションの計算としてAPEX_PATHが作成されます。


ページ・デザイナホーム・ページを開きます。

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

[require jet]

ファンクションおよびグローバル変数の宣言に以下を記述します。

var diagram;

ページ・ロード時に実行に以下を記述します。ダイアグラムのモデルとなるクラスDiagramModelと、ダイアグラムを更新するapex.actionのupdate-diagramを定義しています。内容は概ねOracle JET Cookbookのdemo.jsを踏襲しています。

requirejs.config({
paths: {
'diagramLayouts': "&APEX_PATH!RAW.#APP_FILES#layouts",
}
});
require(["require", "exports", "knockout", "ojs/ojbootstrap", "diagramLayouts/DemoCircleLayout", "ojs/ojarraydataprovider", "ojs/ojattributegrouphandler", "ojs/ojknockout", "ojs/ojdiagram"], function (require, exports, ko, ojbootstrap_1, layout, ArrayDataProvider, ojattributegrouphandler_1) {
"use strict";
class DiagramModel {
/*
* Diagramで表示するデータを更新する。
*/
update(data) {
this.nodeDataProvider(new ArrayDataProvider(data.nodes, {
keyAttributes: "id",
}));
this.linkDataProvider(new ArrayDataProvider(data.links, {
keyAttributes: "id",
}));
}
constructor() {
// this.data = JSON.parse(jsonData);
this.colorHandler = new ojattributegrouphandler_1.ColorAttributeGroupHandler();
this.layoutFunc = layout.circleLayoutWithLayoutArgs(150);
this.panningValue = ko.observable(apex.items.P1_PANNING.value);
this.zoomingValue = ko.observable(apex.items.P1_ZOOMING.value);
/*
* this.nodeDataProvider, linkDataProviderはknockoutのobservableArrayに変更し、
* データはコンストラクタの外から設定する。
*/
this.nodeDataProvider = ko.observableArray();
this.linkDataProvider = ko.observableArray();
}
}
(0, ojbootstrap_1.whenDocumentReady)().then(() => {
diagram = new DiagramModel();
ko.applyBindings(diagram, document.getElementById('diagram-container'));
/* ページ・ロード時の表示 */
apex.actions.invoke("update-diagram");
});
});
/*
* Diagramを更新する。
*/
apex.actions.add([
{
name: "update-diagram",
action: () => {
apex.server.process ( "GET_DATA", {},
{
success: (data) => {
// console.log(data);
diagram.update(data);
}
}
);
}
}
]);
CSSファイルURLに以下を指定します。

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


対話グリッドでデータを変更し保存したときに、ダイアグラムを更新します。

対話グリッドNodesはすでに動的アクションが作成済みなので、TRUEアクションを追加します。

作成したTRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下の1行を記述します。

apex.actions.invoke("update-diagram");        


対話グリッドLinksを更新したときにもダイアグラムが更新されるように、動的アクションを作成します。

識別名前onSave Linksとします。タイミングイベントとして保存[対話グリッド]を選択し、選択タイプリージョンリージョンとしてLinksを指定します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードに以下の1行を記述します。

apex.actions.invoke("update-diagram");


Panningの変更をダイアグラムに通知します。ページ・アイテムP1_PANNINGに動的アクションを作成します。

識別名前onChange Panningタイミングイベントはデフォルトの変更です。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下の1行を記述します。

diagram.panningValue($v(this.triggeringElement));


Zoomingの変更をダイアグラムに通知します。ページ・アイテムP1_ZOOMINGに動的アクションを作成します。

識別名前onChange Zoomingタイミングイベントはデフォルトの変更です。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下の1行を記述します。

diagram.zoomingValue($v(this.triggeringElement));


以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

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

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