Oracle JETのCookbookではCalendarとなっていますが、Picto Chart(oj-picto-chart)を使ってカレンダー形式で東京の最高気温を表示してみます。
今までOracle JET Cookbookに載っているサンプルをOracle APEXに実装してきましたが、概ねOracle JET Cookbookによるビジュアリゼーション自体は変えていません。今回はOracle JET Cookbookのサンプルが扱っているデータが米国ニューヨーク市の2015年の気温で、少々面白くありません。そして、データを変更するには(色々とハードコードされていて)、コードの変更が必要でした。
それで今回はコードに手を加えて、指定した期間の東京の最高気温を表示するようにJET Picto Chartを実装しました。以下は、作成したアプリケーションによる最高気温の表示です。
2023年3月から8月までの東京都の最高気温をカレンダーに表示しています(取得できた履歴データが8月26日までなので、8月の表示は完全ではありません)。
2022年3月から8月までの東京の最高気温の表示は以下なので、今年は暑いことがよくわかります。
Oracle JET Cookbookの以下の実装を元にしています。
最初にOpen-Meteo.comを呼び出して取得した、最高気温のデータを保存する表HMT_TEMPERATURESを作成します。表の作成には、以下のクイックSQLのモデルを使用します。
追加する対話グリッドのページは、ページ名をTemperatures、表またはビューとしてHMT_TEMPERATURESを指定します。今回は対話グリッドからデータを更新することは想定していませんが、編集を許可を選択しています。
# prefix: hmt
temperatures
tag vc20 /nn
date_rec date /nn
temperature_2m_max num
SQLの作成、SQLスクリプトを保存、レビューおよび実行を順次実行します。表の作成までを行い、アプリケーションは作成しません。
続いてアプリケーション作成ウィザードを起動します。
アプリケーションの名前はJET Picto Chartとします。デフォルトで作成されているホーム・ページを削除し、表HMT_TEMPERATURESをソース表とした対話グリッドのページを追加します。
以上でアプリケーションの作成を実行します。
作成されたアプリケーションのページ番号1、ページ名Temperaturesに今回の実装を行います。
アプリケーション定義を開き、置換文字列G_MONTHとして一度に表示する月数を設定します。今回は置換値に6を設定しています。
ページ・デザイナでページTemperaturesを開きます。
Breadcrumb BarにあるリージョンJET Picto Chartを削除します。JET Picto Chart上でコンテキスト・メニューを表示させ、削除を実行します。
主にOpen MeteoのAPI呼び出しの引数となる値を保持するページ・アイテムを作成します。これらのページ・アイテムを配置するリージョンを作成します。
識別のタイトルはMaximum Temperatures (2m)とし、タイプとして静的コンテンツを選択します。対話グリッドHmt Temperaturesの上に配置します。
作成したリージョンにページ・アイテムを作成します。
1度のAPI呼び出しで得られたデータにつけるタグを指定するページ・アイテムを作成します。
識別の名前はP1_TAG、タイプはテキスト・フィールド、ラベルはTagとします。デフォルトのタイプに静的を選び、静的値としてTokyo (2023)を設定します。
識別の名前はP1_YEAR、タイプは選択リスト、ラベルはYearとします。ページ・アイテムP1_TAGの右隣に配置するため、レイアウトの新規行の開始はオフにします。検証の必須の値はオンです。
LOVのタイプにSQL問合せを選択し、SQL問合せとして以下を記述します。現在の年から過去30年を表示の対象として選択できます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
select y as d, y as r from ( | |
select (extract(year from sysdate) - l + 1) as y from ( | |
select level as l from dual connect by level <= 30 | |
) | |
) |
追加値の表示、NULL値の表示はオフとします。
デフォルトのタイプとして静的を選択し、静的値として2023を設定します。
データの取得および表示を開始する月を指定するページ・アイテムを作成します。これはページ・アイテムP1_YEARを重複させて作成します。
識別の名前をP1_MONTH、ラベルをMonth、LOVのSQL問合せを以下に変更します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
select l as d, l as r from ( | |
select level as l from dual connect by level <= 12 | |
) |
デフォルトの静的値は1に変更します。
観測地点の緯度を指定するページ・アイテムを作成します。
識別の名前をP1_LAT、タイプを数値フィールド、ラベルをLatitudeとします。レイアウトの新規行の開始はオフです。検証の必須の値はオンにします。
デフォルトのタイプとして静的を選択し、静的値として東京の緯度である35.6895を設定します。
観測地点の経度を指定するページ・アイテムを作成します。これはページ・アイテムP1_LATを重複させて作成します。
識別の名前をP1_LON、ラベルをLongitude、デフォルトの静的値を東京の緯度である139.6917を設定します。
タイムゾーンを指定するページ・アイテムを作成します。
識別の名前をP1_TIMEZONE、タイプをテキスト・フィールド、ラベルをTimezoneとします。レイアウトの新規行の開始はオフです。検証の必須の値はオンにします。
デフォルトのタイプとして静的を選択し、静的値としてAsia/Tokyoを設定します。
Open-Meteo.comにAPIを発行してデータを取得し、表HMT_TEMPERATURESに保存するボタンを作成します。
識別のボタン名はLOAD、ラベルはLoad from Open-Meteo.comとします。外観のテンプレート・オプションのWidthとしてStretchを選択します。
動作のアクションはデフォルトのページの送信から変更しません。
プロセス・ビューを開き、ボタンを押した時に実行されるプロセスを作成します。
識別の名前はLoad from Open-Meteo.comとします。タイプとしてコードを実行を選びます。ソースのPL/SQLコードに以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
/* データ取得期間 */ | |
l_start_date_str varchar2(10); | |
l_end_date_str varchar2(10); | |
l_start_date date; | |
l_end_date date; | |
l_months constant number := :G_MONTH; -- 表示する月数は固定。 | |
/* API呼び出し */ | |
l_response clob; | |
l_response_json json_object_t; | |
l_parm_name constant varchar2(400) := 'latitude:longitude:start_date:end_date:daily:temperature_unit:timezone'; | |
l_parm_value varchar2(400); | |
/* レスポンスから日付と最高気温を取り出す。 */ | |
l_daily json_object_t; | |
l_count pls_integer; | |
l_date_arr json_array_t; | |
l_temp_arr json_array_t; | |
l_date date; | |
l_temp number; | |
e_open_meteo_get_failed exception; | |
begin | |
/* データ取得期間を決める。 */ | |
l_start_date_str := :P1_YEAR || '-' || case when length(:P1_MONTH) = 1 then '0' else '' end || :P1_MONTH || '-01'; | |
l_start_date := to_date(l_start_date_str, 'YYYY-MM-DD'); | |
l_end_date := add_months(l_start_date, l_months) - 1; | |
/* 終了日は今日の日付を超えてはいけない。 */ | |
if l_end_date > trunc(sysdate) then | |
l_end_date := trunc(sysdate); | |
end if; | |
l_end_date_str := to_char(l_end_date, 'YYYY-MM-DD'); | |
/* Open Meteoの呼び出しパラメータ */ | |
l_parm_value := :P1_LAT || ':' || :P1_LON || ':' || l_start_date_str || ':' || l_end_date_str || ':temperature_2m_max:celsius:' || :P1_TIMEZONE; | |
apex_debug.info(l_parm_value); | |
apex_web_service.set_request_headers('Content-Type','application/json'); | |
l_response := apex_web_service.make_rest_request( | |
p_url => 'https://archive-api.open-meteo.com/v1/archive' | |
,p_http_method => 'GET' | |
,p_parm_name => apex_string.string_to_table(l_parm_name) | |
,p_parm_value => apex_util.string_to_table(l_parm_value) | |
); | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info(l_response); | |
raise e_open_meteo_get_failed; | |
end if; | |
l_response_json := json_object_t(l_response); | |
/* 本当はレスポンスの検証は必要 */ | |
l_daily := l_response_json.get_object('daily'); | |
l_date_arr := l_daily.get_array('time'); | |
l_temp_arr := l_daily.get_array('temperature_2m_max'); | |
l_count := l_date_arr.get_size(); -- l_temp_arrのサイズも同じ。 | |
/* 同じタグのデータは消去する。 */ | |
delete from hmt_temperatures where tag = :P1_TAG; | |
for i in 1..l_count | |
loop | |
l_date := to_date(l_date_arr.get_string(i-1), 'YYYY-MM-DD'); | |
l_temp := l_temp_arr.get_number(i-1); | |
insert into hmt_temperatures(tag, date_rec, temperature_2m_max) | |
values(:P1_TAG, l_date, l_temp); | |
end loop; | |
end; |
サーバー側の条件のボタン押下時としてLOADを選択します。
対話グリッドHmt Tempereturesの属性を開き、ヘッダーの固定としてリージョンを選択します。固定のレポートの高さを300ピクセルとして、対話グリッドの高さを制限します。
以上でアプリケーションを実行し、ボタンLoad from Open-Meteo.comをクリックします。最高気温のデータが読み込まれることを確認します。
プロセス・ビューを開き、Picto Chartに読み込むデータを作成するプロセスをAjaxコールバックとして作成します。
識別の名前はGET_DATA、タイプはコードを実行、ソースのPL/SQLコードとして以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_response_json json_object_t; | |
l_days json_array_t; | |
l_day pls_integer; | |
l_date_str varchar2(7); | |
l_start_date date; | |
l_end_date date; | |
begin | |
/* 表示に使用する期間 */ | |
l_start_date := to_date(:P1_YEAR || case when length(:P1_MONTH) = 1 then '0' else '' end || :P1_MONTH, 'YYYYMM'); | |
l_end_date := add_months(l_start_date, :G_MONTH) - 1; | |
/* JET Picto Chartが扱う形式で出力 */ | |
l_response_json := json_object_t(); | |
for r in ( | |
select | |
trunc(date_rec, 'MONTH') year_month | |
,json_arrayagg( | |
json_object( | |
key 'date' value extract(day from date_rec) | |
,key 'value' value temperature_2m_max | |
) | |
order by date_rec asc | |
) as value | |
from hmt_temperatures where tag = :P1_TAG | |
and date_rec between l_start_date and l_end_date | |
and temperature_2m_max is not null | |
group by trunc(date_rec, 'MONTH') | |
order by trunc(date_rec, 'MONTH') | |
) | |
loop | |
l_date_str := to_char(r.year_month, 'YYYY-MM'); | |
l_days := json_array_t(r.value); | |
/* 日曜日から1日までの穴埋めをする */ | |
l_day := to_number(to_char(r.year_month,'D')); | |
for i in 1..(l_day-1) | |
loop | |
l_days.put(0, json_object_t('{ "date": 0, "value": null }')); | |
end loop; | |
l_response_json.put(l_date_str, l_days); | |
end loop; | |
htp.p(l_response_json.to_clob()); | |
end; |
レンダリング・ビューを開き、Oracle JETのPicto Chartを表示するリージョンを作成します。
識別のタイトルはTemperatures、タイプとして動的コンテンツを選択します。リージョンMaximum Temperatures (2m)と対話グリッドHmt Temperaturesの間に配置します。
ソースのCLOBを返すPL/SQLファンクション本体として以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_chart clob; | |
l_month_str varchar2(7); | |
l_month date; | |
l_months constant number := :G_MONTH; -- 表示する月数は固定。 | |
begin | |
l_chart := q'~ | |
<div id="chart-container" class="oj-sm-padding-2x-horizontal"> | |
<div class="oj-typography-body-lg oj-typography-bold">Daily Temperatures For ~' || :P1_TAG || q'~</div> | |
<div class="oj-flex oj-sm-flex-items-initial">~'; | |
/* 最初に表示する月を設定する。 */ | |
l_month_str := :P1_YEAR || '-' || case when length(:P1_MONTH) = 1 then '0' else '' end || :P1_MONTH; | |
for i in 1..l_months -- 月数は固定 | |
loop | |
l_chart := l_chart || q'~ | |
<div class="oj-flex-item oj-sm-margin-4x-end"> | |
<div class="oj-typography-body-sm oj-typography-bold oj-sm-margin-4x-vertical">~' || l_month_str || q'~</div> | |
<div class="oj-sm-margin-1x-start demo-datavisualizations-blockcalendar-wordspacing"> | |
S M T W T F S | |
</div> | |
<oj-picto-chart | |
id="pictochart1" | |
data="[[m~' || i || q'~DataProvider]]" | |
layout="horizontal" | |
row-height="20" | |
column-count="7"> | |
<template slot="itemTemplate" data-oj-as="item"> | |
<oj-picto-chart-item | |
short-desc='[[getTooltip("~' || l_month_str || q'~", item.data.date, item.data.value)]]' | |
color="[[getColor(item.data.value)]]"> | |
</oj-picto-chart-item> | |
</template> | |
</oj-picto-chart> | |
</div>~'; | |
/* 次に表示する月を設定する. */ | |
l_month := to_date(l_month_str, 'YYYY-MM'); | |
l_month := add_months(l_month, 1); | |
l_month_str := to_char(l_month, 'YYYY-MM'); | |
end loop; | |
/* レジェンドの表示領域 */ | |
l_chart := l_chart || q'~ | |
</div> | |
<oj-legend | |
id="legend1" | |
class="oj-sm-padding-6x-horizontal demo-datavisualizations-blockcalendar-style" | |
orientation="horizontal" | |
data="[[legendDataProvider]]" | |
symbol-width="15" | |
symbol-height="15"> | |
<template slot="itemTemplate" data-oj-as="item"> | |
<oj-legend-item text="[[item.data.text]]" color="[[item.data.color]]"></oj-legend-item> | |
</template> | |
</oj-legend> | |
</div>~'; | |
return l_chart; | |
end; |
余計な装飾を省くため、外観のテンプレートとして、Blank with Attributes (No Grid)を選択します。
ページ・プロパティのJavaScriptのファイルURLに以下を記述します。
[require jet]
ページ・ロード時の実行に以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require(["require", "exports", "knockout", "ojs/ojbootstrap", "ojs/ojarraydataprovider", "ojs/ojpalette", "ojs/ojpaletteutils", "ojs/ojknockout", "ojs/ojpictochart", "ojs/ojlegend"], function (require, exports, ko, ojbootstrap_1, ArrayDataProvider, ojpalette_1, ojpaletteutils_1) { | |
"use strict"; | |
class PictoChartModel { | |
constructor(data) { | |
this.data = data; | |
/* データ・プロバイダをobservableArrayで初期化 */ | |
Object.keys(this.data).forEach( (element, index) => { | |
let pname = "m" + (index+1) + "DataProvider"; | |
this[pname] = ko.observableArray(); | |
this[pname](new ArrayDataProvider(this.data[element], { | |
keyAttributes: "date" | |
})); | |
}); | |
/* ツールチップの表示形式を決める */ | |
this.colors = (0, ojpalette_1.getColorValuesFromPalette)("viridis", 7); | |
this.getTooltip = (month, date, value) => { | |
return date === 0 | |
? "" | |
: `${month}-${date.toString()} (${value.toString()})°C`; | |
}; | |
/* 気温による表示色を決める */ | |
this.getColor = (value) => { | |
return value === null | |
? "rgba(0,0,0,0)" | |
: (0, ojpaletteutils_1.getColorValue)(this.colors, (value + 30) / 70); | |
}; | |
/* レジェンドの初期化 - 摂氏に変更 */ | |
this.legendItems = []; | |
this.temp = [ | |
"-30〜-20\xB0C", | |
"-20〜-10\xB0C", | |
"-10〜0\xB0C", | |
"0〜10\xB0C", | |
"10〜20\xB0C", | |
"20〜30\xB0C", | |
"30〜40\xB0C", | |
]; | |
this.legendDataProvider = ko.observableArray(); | |
/* レジェンドとして表示する項目の生成 */ | |
for (let i = 0; i < this.temp.length; i++) { | |
this.legendItems.push({ text: this.temp[i], color: this.colors[i] }); | |
} | |
this.legendDataProvider(new ArrayDataProvider(this.legendItems, { | |
keyAttributes: "text", | |
})); | |
} | |
} | |
(0, ojbootstrap_1.whenDocumentReady)().then(() => { | |
apex.server.process ( "GET_DATA", { | |
pageItems: ["P1_TAG","P1_YEAR","P1_MONTH"] | |
}, | |
{ | |
success: (data) => { | |
ko.applyBindings(new PictoChartModel(data), document.getElementById("chart-container")); | |
} | |
} | |
); | |
}); | |
}); |
CSSのファイルURLに以下を記述します。
#JET_CSS_DIRECTORY#redwood/oj-redwood-notag-min.css
インラインは、Oracle JET Cookbookにdemo.cssとして記載されている内容を元に、若干の変更を加えています。
.demo-datavisualizations-blockcalendar-wordspacing {
word-spacing: 0.375rem;
}
.demo-datavisualizations-blockcalendar-style {
height: 3.125rem;
max-width: 67.5rem;
}
以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭にある画面が表示されます。
対話グリッドからのデータの修正は想定していないため、動的アクションは作成しません。
今回のAPEXアプリケーションではOracle JETのチャートとなるHTMLは、動的コンテンツとしてPL/SQLコードによって生成しています。動的コンテンツのリージョンには遅延ロードのオプションがあります。今回の実装で遅延ロードをオンにすると、チャートが描画されなくなります。これはHTMLがすべて生成される前に、以下のハンドラが呼び出されるためです。
(0, ojbootstrap_1.whenDocumentReady)().then(() => {
apex.server.process ( "GET_DATA", {
pageItems: ["P1_TAG","P1_YEAR","P1_MONTH"]
},
{
success: (data) => {
ko.applyBindings(new PictoChartModel(data), document.getElementById("chart-container"));
}
}
);
});
動的イベントのリフレッシュ後のタイミングで上記のハンドラを呼び出すと正常に動くかもしれませんが、そもそもknockoutによりチャートの描画は非同期になっています。動的コンテンツの遅延ロードの設定は2重の遅延ロードになるため、設定は不要です。
https://github.com/ujnak/apexapps/blob/master/exports/jet-picto-chart.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完