2025年3月14日金曜日

任意のSELECT文をデータ・ソースとしたJETチャートを動的に生成する

チャートのタイプ(バーまたはパイ・チャート)を選択し、任意のSELECT文をデータ・ソースとしたJETチャートを動的に生成するAPEXアプリケーションを作成します。

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


以下より作成したAPEXアプリケーションについて説明します。

最初に空のAPEXアプリケーションを作成します。名前Dynamic JET Chartとします。


機能はすべてホーム・ページに実装します。ホーム・ページページ・デザイナで開きます。


チャート・タイプを選択するページ・アイテムとしてP1_CHART_TYPEを作成します。タイプ選択リストラベルChart Typeとします。

LOVタイプ静的値を選択します。NULL表示値- Select Chart Type -とします。


LOV静的値として表示値Pie Chartpie表示値Bar Chartbarのペアを設定します。


データ・ソースとなるSELECT文を記述するページ・アイテムとしてP1_SQLを作成します。タイプテキスト・フィールドラベルSource SQL Statementとします。


チャートを生成するボタンとしてGENERATEを作成します。外観ホットオンテンプレート・オプションWidthStretchにします。

動作アクションとして動的アクションで定義を設定します。JETチャートのHTML要素はサーバー側で生成しますが、チャートの描画はクライアント側のJavaScriptで行います。


JETチャートを描画するリージョンを作成します。識別名前Chartタイプ静的コンテンツとし、ソースHTMLコードとして以下を記述します。

<div id="chart"></div>

外観テンプレート・オプションBody Height640pxTop MarginMediumに設定しています。


JETチャートのHTML要素を生成するプロセスを、Ajaxコールバックとして実装します。

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

declare
/* SELECT文の実行 */
l_sql varchar2(32767);
l_cursor integer;
l_col_cnt integer;
l_desc_tab dbms_sql.desc_tab;
e_invalid_sql exception;
l_label varchar2(4000);
l_label_name varchar2(4000);
l_value number;
l_rows number;
/* JETチャートに埋め込むデータ */
l_groups_arr json_array_t;
l_group json_object_t;
l_series_arr json_array_t;
l_series json_object_t;
l_items_arr json_array_t;
l_item json_object_t;
l_series_clob clob;
l_groups_clob clob;
l_items_clob clob;
/* JETチャートのマークアップ */
l_html clob;
l_response_clob clob;
l_response json_object_t;
l_chart_id varchar2(40);
l_chart_type varchar2(8);
e_invalid_chart_type exception;
/*
* バーチャートのテンプレート
*/
C_TEMPLATE_BAR_CHART constant clob := q'~
<oj-chart
id="#CHART_ID#"
type="bar"
orientation="horizontal"
stack="off"
groups='#GROUPS#'
series='[ { "items": #ITEMS# } ]'
animation-on-display="auto"
animation-on-data-change="auto"
hover-behavior="dim">
</oj-chart>
~';
/*
* パイチャートのテンプレート
*/
C_TEMPLATE_PIE_CHART constant clob := q'~
<oj-chart
id="#CHART_ID#"
type="pie"
selection-mode="single"
series='#SERIES#'
animation-on-display="auto"
animation-on-data-change="auto"
hover-behavior="dim">
</oj-chart>
~';
begin
l_chart_type := :P1_CHART_TYPE;
/*
* JETチャートに与えるデータを保持する。
*/
l_groups_arr := json_array_t();
l_series_arr := json_array_t();
l_items_arr := json_array_t();
/*
* P1_SQLには2つの列、最初にラベルとなる列と次に値となる数値列を
* 持つSELECT文が記載されていることを前提としている。
*/
-- 与えられたSQLの検証
l_sql := :P1_SQL;
if l_sql is null or l_sql = '' then
raise e_invalid_sql;
end if;
-- カーソルのオープン
l_cursor := dbms_sql.open_cursor;
-- SQLのパース
dbms_sql.parse(l_cursor, l_sql, dbms_sql.native);
/*
* 最初のラベルとなる列の名前を取得する。ただし、今の所は使用していない。
*/
dbms_sql.describe_columns(l_cursor, l_col_cnt, l_desc_tab);
l_label_name := l_desc_tab(1).col_name;
-- 列の定義
dbms_sql.define_column(l_cursor, 1, l_label, 4000);
dbms_sql.define_column(l_cursor, 2, l_value);
-- SELECT文の実行
l_rows := dbms_sql.execute(l_cursor);
loop
exit when dbms_sql.fetch_rows(l_cursor) = 0;
dbms_sql.column_value(l_cursor, 1, l_label);
dbms_sql.column_value(l_cursor, 2, l_value);
/* JETチャートに含めるデータを作成する */
if l_chart_type = 'bar' then
l_group := json_object_t();
l_group.put('name', l_label);
l_groups_arr.append(l_group);
l_items_arr.append(l_value);
elsif l_chart_type = 'pie' then
l_series := json_object_t();
l_series.put('name', l_label);
l_items_arr := json_array_t();
l_items_arr.append(l_value);
l_series.put('items', l_items_arr);
l_series_arr.append(l_series);
else
raise e_invalid_chart_type;
end if;
end loop;
-- カーソルのクローズ
dbms_sql.close_cursor(l_cursor);
/*
* JETチャートのマークアップを生成する。
*/
l_groups_clob := l_groups_arr.to_clob();
l_series_clob := l_series_arr.to_clob();
l_items_clob := l_items_arr.to_clob();
/*
* JETチャートのテンプレレートにデータを埋め込み、
* マークアップを完成させる
*/
l_chart_id := 'chart' || sys_guid();
if l_chart_type = 'bar' then
l_html := replace(C_TEMPLATE_BAR_CHART, '#CHART_ID#', l_chart_id);
/* バーチャートはgroupsとseriesにデータを与える */
l_html := replace(l_html, '#GROUPS#', l_groups_clob);
l_html := replace(l_html, '#ITEMS#', l_items_clob);
elsif l_chart_type = 'pie' then
l_html := replace(C_TEMPLATE_PIE_CHART, '#CHART_ID#', l_chart_id);
/* パイチャートはseriesにデータを与える */
l_html := replace(l_html, '#SERIES#', l_series_clob);
end if;
/*
* 呼び出し元にJSON形式でJETチャートの要素を返す。
* { "markup": "JETチャートの要素" }
*/
l_response := json_object_t();
l_response.put('markup', l_html);
l_response_clob := l_response.to_clob();
htp.p(l_response_clob);
end;


作成したAjaxコールバックGET_CHARTをボタンGENERATEをクリックしたときに呼び出すように、動的アクションを作成します。

ボタンGENERATE動的アクションを作成します。識別名前onClick GENERATEとします。タイミングイベントはデフォルトのクリックです。


TRUEアクションとして以下のJavaScriptコードを実行します。JETチャートのHTML要素はデータベース・サーバーで生成しますがページの再ロードを避けるため、画面への描画はブラウザ上で実施します。
apex.server.process( "GET_CHART", {
    pageItems: "#P1_CHART_TYPE,#P1_SQL"
}, {
    success: function( data )  {
        apex.debug.info(data.markup);
        const elem = document.getElementById("chart");
        elem.insertAdjacentHTML('afterbegin', data.markup);
    }
} );

最後にページ・プロパティに、JETのライブラリをロードするために必要な設定を追加します。ページにチャート・リージョンが含まれる場合は、Oracle APEXがJETライブラリをロードするためのコードを生成します。しかし、今回作成しているアプリにはJETを必要としているコンポーネントがページにないため、JETライブラリがロードされません。

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

[require jet]

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

require(["ojs/ojchart"], function() {});

CSSファイルURLに以下を記述します。

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


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

Oracle JETのドキュメントへのリンクは以下になります。
https://www.oracle.com/webfolder/technetwork/jet/index.html

主にCookbookとAPI Documentを参照することになりますが、わかりやすいとはいえないです。
https://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html
https://docs.oracle.com/en/middleware/developer-tools/jet/18/reference-api/index.html

Oracle JETはサンプルも少なく扱いにくい印象がありましたが、Claude 3.7 Sonnetに聞くとそれなりの回答が返されます。ドキュメントにあたる前に生成AIに聞くと時短になります。


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

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