2023年9月8日金曜日

Oracle JETのGanttチャートを直接Oracle APEXで操作する

Oracle APEXのチャートのひとつにGanttチャートが含まれています。このチャートにはOracle JETのGanttチャートが使われていますが、使用できる機能はかなり限定されています。

Oracle JETのGanttチャートを(Oracle APEXのチャートとしてではなく)、直接APEXアプリケーションに実装してみます。理論上はOracle JETのGanttチャートが提供している機能をすべて利用できるようになります。

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


Oracle JET Cookbookで紹介されているGanttのOverviewを実装しています。
https://www.oracle.com/webfolder/technetwork/jet/jetcookbook.html?component=gantt&demo=overview


いくつかのテキストやアイコンが表示されていません。CSS変数の定義の違いに影響されているのだと思われますが、原因については調べていません。

以下より実装について説明します。

Oracle JET CookbookではGanttチャートの表示に使用するデータを、rowData.jsonとdepData.jsonの2つの静的ファイルから読み込んでいます。このデータについては、データベースに表を作成してデータを投入します。

以下のDDLを実行し、表JGANTT_ROWSJGANTT_TASKSJGANTT_REFERENCE_OBJECTSJGANTT_DEPENDENCIESの4つの表を作成します。

-- create tables
create table jgantt_rows (
row_id varchar2(80 char) not null,
parent_row_id varchar2(80 char),
label varchar2(80 char)
)
;
-- table index
create index jgantt_rows_i1 on jgantt_rows (row_id);
create table jgantt_tasks (
task_id varchar2(80 char) not null,
row_id varchar2(80 char) not null,
attribute_desc varchar2(80 char),
svg_class_name varchar2(80 char),
start_date date,
end_date date,
downtime_start_date date,
downtime_end_date date,
overtime_start_date date,
overtime_end_date date
)
;
-- table index
create index jgantt_tasks_i1 on jgantt_tasks (row_id);
create index jgantt_tasks_i52 on jgantt_tasks (task_id);
create table jgantt_reference_objects (
row_id varchar2(80 char) not null,
id number generated by default on null as identity
constraint jgantt_reference_o_id_pk primary key,
start_date date,
end_date date
)
;
-- table index
create index jgantt_reference_o_i1 on jgantt_reference_objects (row_id);
-- create tables
create table jgantt_dependencies (
id varchar2(80 char) not null,
predecessor varchar2(80 char),
successor varchar2(80 char)
)
;
-- load data
SQLワークショップSQLスクリプトから実行します。


表が作成されたら、データをロードします。

ネットワーク経由でrowData.jsonを取得し、JSONをパースして表に投入します。以下のスクリプトを実行します。

declare
l_response clob;
l_response_json json_array_t;
e_load_data_failed exception;
/* 前後のごみ取り */
l_start pls_integer;
l_top_row_count pls_integer;
/* rows object */
l_row_object json_object_t;
procedure process_row(
p_row_object in json_object_t
,p_parent_row_id in jgantt_rows.parent_row_id%type default null
)
as
l_row_id jgantt_rows.row_id%type;
l_label jgantt_rows.label%type;
l_parent_row_id jgantt_rows.parent_row_id%type;
/* referenceObjects */
l_reference_objects json_array_t;
l_reference_object_count pls_integer;
l_reference_object json_object_t;
r_reference_object jgantt_reference_objects%rowtype;
/* task object */
l_tasks json_array_t;
l_task_count pls_integer;
l_task_object json_object_t;
r_task jgantt_tasks%rowtype;
/* row object */
l_rows json_array_t;
l_row_count pls_integer;
l_row_object json_object_t;
r_reference_objects jgantt_reference_objects%rowtype;
begin
l_row_id := p_row_object.get_string('id');
l_label := p_row_object.get_string('label');
insert into jgantt_rows(row_id, label, parent_row_id) values(l_row_id, l_label, p_parent_row_id);
-- referenceObjectsの扱い。
l_reference_objects := p_row_object.get_array('referenceObjects');
if l_reference_objects is not null then
l_reference_object_count := l_reference_objects.get_size();
for j in 1..l_reference_object_count
loop
l_reference_object := treat(l_reference_objects.get(j-1) as json_object_t);
r_reference_objects.row_id := l_row_id;
r_reference_objects.start_date := l_reference_object.get_timestamp('start');
r_reference_objects.end_date := l_reference_object.get_timestamp('end');
insert into jgantt_reference_objects values r_reference_objects;
end loop;
end if;
-- tasksの扱い。
l_tasks := p_row_object.get_array('tasks');
if l_tasks is not null then
l_task_count := l_tasks.get_size();
for j in 1..l_task_count
loop
l_task_object := treat(l_tasks.get(j-1) as json_object_t);
r_task.row_id := l_row_id;
r_task.task_id := l_task_object.get_string('id');
r_task.attribute_desc := l_task_object.get_string('attributeDesc');
r_task.svg_class_name := l_task_object.get_string('svgClassName');
r_task.start_date := l_task_object.get_timestamp('start');
r_task.end_date := l_task_object.get_timestamp('end');
r_task.downtime_start_date := l_task_object.get_timestamp('downtimeStart');
r_task.downtime_end_date := l_task_object.get_timestamp('downtimeEnd');
r_task.overtime_start_date := l_task_object.get_timestamp('overtimeStart');
r_task.overtime_end_date := l_task_object.get_timestamp('overtimeEnd');
insert into jgantt_tasks values r_task;
end loop;
end if;
-- rowsの扱い。
l_rows := p_row_object.get_array('rows');
if l_rows is not null then
l_row_count := l_rows.get_size();
for j in 1..l_row_count
loop
l_row_object := treat(l_rows.get(j-1) as json_object_t);
process_row(l_row_object, l_row_id);
end loop;
end if;
end process_row;
begin
apex_web_service.set_request_headers('Content-Type','application/json');
l_response := apex_web_service.make_rest_request(
p_url => 'https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/gantt/overview/rowData.json'
,p_http_method => 'GET'
);
if apex_web_service.g_status_code <> 200 then
raise e_load_data_failed;
end if;
l_start := instr(l_response, '[');
l_response := substr(l_response, l_start);
-- dbms_output.put_line(substr(l_response,1,100));
l_response_json := json_array_t(l_response);
l_top_row_count := l_response_json.get_size();
for i in 1..l_top_row_count
loop
l_row_object := treat(l_response_json.get(i-1) as json_object_t);
process_row(l_row_object);
end loop;
end;

SQLワークショップSQLコマンドから実行します。


同様にdepData.jsonを取得し、表にロードします。

declare
l_response clob;
l_response_json json_array_t;
e_load_data_failed exception;
/* 前後のごみ取り */
l_start pls_integer;
l_count pls_integer;
/* rows object */
l_object json_object_t;
r_dependencies jgantt_dependencies%rowtype;
begin
apex_web_service.set_request_headers('Content-Type','application/json');
l_response := apex_web_service.make_rest_request(
p_url => 'https://www.oracle.com/webfolder/technetwork/jet/cookbook/dataVisualizations/gantt/overview/depData.json'
,p_http_method => 'GET'
);
if apex_web_service.g_status_code <> 200 then
raise e_load_data_failed;
end if;
l_start := instr(l_response, '[');
l_response := substr(l_response, l_start);
-- dbms_output.put_line(substr(l_response,1,100));
l_response_json := json_array_t(l_response);
l_count := l_response_json.get_size();
for i in 1..l_count
loop
l_object := treat(l_response_json.get(i-1) as json_object_t);
r_dependencies.id := l_object.get_string('id');
r_dependencies.predecessor := l_object.get_string('predecessor');
r_dependencies.successor := l_object.get_string('successor');
insert into jgantt_dependencies values r_dependencies;
end loop;
end;


続いて反対に、表に保存されているデータをJSON形式で取り出すRESTサービスを作成します。返されるデータが大きいため、Ajaxコールバックでは実装できません。

モジュール・ベース・パス/jgantt/URLテンプレートoverviewメソッドGETを指定して、リソース・ハンドラを作成します。

ソース・タイプとしてPL/SQLを選択し、ソースに以下を記述します。

declare
l_response_json json_object_t;
l_response blob;
/* rowData.jsonの生成に使う */
l_rows json_array_t;
l_row_object json_object_t;
/* depData.jsonの生成に使う */
l_deps json_array_t;
l_dep json_object_t;
/*
* rowオブジェクトを生成する
*/
function get_row_object(
p_row_id in jgantt_rows.row_id%type
)
return json_object_t
as
l_row_object json_object_t;
/* referenceObjects */
l_reference_objects json_array_t;
l_reference_object json_object_t;
/* tasks */
l_tasks json_array_t;
l_task json_object_t;
/* rows */
l_rows json_array_t;
begin
l_row_object := json_object_t();
/* row_idでの検索なので、選択されるのは一行だけ */
for r in (select * from jgantt_rows where row_id = p_row_id)
loop
l_row_object.put('id', r.row_id);
l_row_object.put('label', r.label);
/* referenceObjectsの生成 */
l_reference_objects := json_array_t();
for e in (select * from jgantt_reference_objects where row_id = r.row_id)
loop
l_reference_object := json_object_t();
if e.start_date is not null then l_reference_object.put('start', e.start_date); end if;
if e.end_date is not null then l_reference_object.put('end', e.end_date); end if;
l_reference_objects.append(l_reference_object);
end loop;
if l_reference_objects.get_size() > 0 then
l_row_object.put('referenceObjects', l_reference_objects);
end if;
/* tasksの生成 */
l_tasks := json_array_t();
for t in (select * from jgantt_tasks where row_id = r.row_id)
loop
l_task := json_object_t();
l_task.put('id', t.task_id);
if t.attribute_desc is not null then l_task.put('attributeDesc', t.attribute_desc); end if;
if t.svg_class_name is not null then l_task.put('svgClassName', t.svg_class_name); end if;
if t.start_date is not null then l_task.put('start', t.start_date); end if;
if t.end_date is not null then l_task.put('end', t.end_date); end if;
if t.downtime_start_date is not null then l_task.put('downtimeStart', t.downtime_start_date); end if;
if t.downtime_end_date is not null then l_task.put('downtimeEnd', t.downtime_end_date); end if;
if t.overtime_start_date is not null then l_task.put('overtimeStart', t.downtime_start_date); end if;
if t.overtime_end_date is not null then l_task.put('overtimeEnd', t.downtime_end_date); end if;
l_tasks.append(l_task);
end loop;
if l_tasks.get_size() > 0 then
l_row_object.put('tasks', l_tasks);
end if;
/* 子供のrowsの生成 */
l_rows := json_array_t();
for c in (select row_id from jgantt_rows where parent_row_id = r.row_id)
loop
l_rows.append(get_row_object(c.row_id));
end loop;
if l_rows.get_size() > 0 then
l_row_object.put('rows', l_rows);
end if;
end loop;
return l_row_object;
end get_row_object;
begin
/*
* rowData.jsonに相当するデータを生成する
*/
l_rows := json_array_t();
for c in (select row_id from jgantt_rows where parent_row_id is null)
loop
l_rows.append(get_row_object(c.row_id));
end loop;
/*
* depData.jsonに相当するデータを生成する。
*/
l_deps := json_array_t();
for c in (select * from jgantt_dependencies)
loop
l_dep := json_object_t();
l_dep.put('id', c.id);
l_dep.put('predecessor', c.predecessor);
l_dep.put('successor', c.successor);
l_deps.append(l_dep);
end loop;
l_response_json := json_object_t();
l_response_json.put('rowData', l_rows);
l_response_json.put('depData', l_deps);
l_response := l_response_json.to_blob();
owa_util.mime_header('application/json');
wpg_docload.download_file(l_response);
end;
APEXアプリケーションからは完全なURLを呼び出してデータを取得するため、このURLを記録しておきます。


アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前JET Native Ganttとします。作成されたアプリケーションのホーム・ページをページ・デザイナで開きます。

Breadcrumb BarにあるリージョンJET Native Ganttを削除します。その後にBodyに新規にリージョンを作成します。

識別タイトルGanttタイプととして静的コンテンツを選択します。ソースHTMLコードに以下を記述します。これはOracle JET Cookbookのdemo.htmlとほぼ同じで、<head>...</head>を取り除いています。

<html lang="en-us" style="height:100%;" dir="ltr">
<body class="demo-disable-bg-image">
<div id="sampleDemo" class="demo-padding demo-container">
<div id="componentDemoContent" style="width: 1px; min-width: 100%;">
<div id="container">
<div class="oj-panel oj-bg-info-30 oj-sm-margin-4x-bottom">
<h2 id="h1" class="oj-typography-subheading-md">Options To Control The Gantt Below</h2>
<oj-form-layout user-assistance-density="reflow" max-columns="1" direction="row">
<oj-checkboxset
id="task-elements"
class="oj-choice-direction-row"
label-hint="Task elements to show"
value="[[taskElementsDetails]]"
on-value-changed="[[handleTaskElementsSettings]]">
<oj-option value="attribute">Attribute</oj-option>
<oj-option value="overtime">Overtime</oj-option>
<oj-option value="downtime">Downtime</oj-option>
</oj-checkboxset>
<oj-checkboxset
id="toggles"
class="oj-choice-direction-row"
label-hint="Controls"
value="[[togglesDetails]]"
on-value-changed="[[handleTogglesSettings]]">
<oj-option value="timeCursor">Time Cursor</oj-option>
<oj-option value="zooming">Zooming</oj-option>
</oj-checkboxset>
</oj-form-layout>
</div>
<oj-gantt
id="gantt"
start="[[projectStartDate.toISOString()]]"
end="[[projectEndDate.toISOString()]]"
viewport-start="[[viewportStart.toISOString()]]"
viewport-end="[[viewportEnd.toISOString()]]"
gridlines.vertical="visible"
expanded="{{expanded}}"
zooming="[[zooming]]"
time-cursor="[[timeCursor]]"
row-axis.rendered="on"
row-axis.width="210px"
major-axis.scale="days"
major-axis.converter.days="[[dateConverter]]"
major-axis.zoom-order='[[ ["weeks","days",custom8HrScale,"hours"] ]]'
minor-axis.scale="[[custom8HrScale]]"
minor-axis.zoom-order='[[ ["weeks","days",custom8HrScale,"hours"] ]]'
selection-mode="multiple"
selection-behavior="highlightDependencies"
dnd.move.tasks="enabled"
on-oj-move="[[handleMove]]"
task-defaults.resizable="enabled"
on-oj-resize="[[handleResize]]"
task-aggregation="on"
dependency-line-shape="straight"
reference-objects="[[referenceObjects]]"
row-data="[[dataProvider]]"
dependency-data="[[dependenciesDataProvider]]"
:aria-label='[["Gantt Chart. Current date is " + currentDateFormatted ]]'
class="demo-gantt">
<template slot="rowMappingTemplate" data-oj-as="row">
<oj-gantt-row
reference-objects="[[row.data.referenceObjects]]"
tasks="[[row.data.tasks]]"
label="[[row.data.label]]"
short-desc="[[getRowDesc(row)]]"></oj-gantt-row>
</template>
<template slot="taskMappingTemplate" data-oj-as="task">
<oj-gantt-task
task-id="[[task.data.id]]"
start="[[task.data.start]]"
end="[[task.data.end]]"
height="[[task.data.svgClassName === 'demo-gantt-task-hold' ? 12 : null]]"
border-radius="[[task.data.svgClassName === 'demo-gantt-task-hold' ? '0' : null]]"
attribute.rendered="[[!showAttribute() || task.data.svgClassName === 'demo-gantt-task-hold' ? 'off' : 'on']]"
attribute.short-desc="Attribute Description"
downtime.start="[[showDowntime() ? task.data.downtimeStart : null]]"
downtime.end="[[showDowntime() ? task.data.downtimeEnd : null]]"
overtime.start="[[showOvertime() ? task.data.overtimeStart : null]]"
overtime.end="[[showOvertime() ? task.data.overtimeEnd : null]]"
svg-class-name="[[task.data.svgClassName]]"></oj-gantt-task>
</template>
<template slot="dependencyTemplate" data-oj-as="dependency">
<oj-gantt-dependency
predecessor-task-id="[[dependency.data.predecessor]]"
successor-task-id="[[dependency.data.successor]]"></oj-gantt-dependency>
</template>
<template slot="referenceObjectMappingTemplate" data-oj-as="ref">
<oj-gantt-reference-object
start="[[ref.data.start]]"
end="[[ref.data.end]]"></oj-gantt-reference-object>
</template>
<template slot="rowAxisLabelTemplate" data-oj-as="rowAxisLabel">
<svg class="demo-gantt-row-label">
<foreignObject
:x="[[getRowLabelX(rowAxisLabel.maxWidth)]]"
y="0"
:width="[[rowAxisLabel.maxWidth]]"
:height="[[rowAxisLabel.maxHeight]]">
<div class="oj-flex oj-sm-align-items-center demo-full-size">
<span :class='[[ {"oj-typography-semi-bold": !rowAxisLabel.leaf} ]]'>
<oj-bind-text value="[[rowAxisLabel.rowData.label]]"></oj-bind-text>
</span>
<oj-bind-if test="[[!rowAxisLabel.leaf && !expanded().has(rowAxisLabel.rowData.id)]]">
<span class="oj-badge oj-badge-success oj-badge-subtle oj-sm-margin-2x-start">
<oj-bind-text value="[['+' + rowAxisLabel.data.rows.length]]"></oj-bind-text>
</span>
</oj-bind-if>
<oj-bind-if test="[[!rowAxisLabel.leaf]]">
<span
class="oj-icon-color-danger oj-ux-ico-triangle-up-s oj-ux-icon-size-2x oj-sm-margin-2x-start"></span>
</oj-bind-if>
</div>
</foreignObject>
</svg>
</template>
</oj-gantt>
<br />
<p>
Task Action:
<span id="results" class="italic oj-typography-body-md">
<oj-bind-text value="[[dndAction]]"></oj-bind-text>
</span>
</p>
</div>
</div>
</div>
</body>
</html>
外観テンプレートとして、Blank with Attributes (No Grid)を選択しています。


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

[require jet]

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

var view;

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

require(["require", "exports", "knockout", "ojs/ojbootstrap", "ojs/ojarraydataprovider", "ojs/ojarraytreedataprovider", "ojs/ojconverter-datetime", "ojs/ojknockout-keyset", "ojs/ojknockout", "ojs/ojgantt", "ojs/ojcheckboxset", "ojs/ojformlayout"], function (require, exports, ko, ojbootstrap_1, ArrayDataProvider, ArrayTreeDataProvider, ojconverter_datetime_1, ojknockout_keyset_1) {
"use strict";
class DemoCustomScaleNHr {
constructor(N) {
this.converter = new ojconverter_datetime_1.IntlDateTimeConverter({
hour: '2-digit',
hour12: true
});
this.hour = 60 * 60 * 1000;
this.name = `${N}hr`;
this.N = N;
}
formatter(date) {
return this.converter.format(date);
}
getNextDate(date) {
return new Date(new Date(date).getTime() + this.N * this.hour).toISOString();
}
getPreviousDate(date) {
const d = new Date(date);
d.setHours(Math.floor(d.getHours() / this.N) * this.N, 0, 0, 0);
return d.toISOString();
}
}
class ViewModel {
constructor(data) {
this.dataProvider = new ArrayTreeDataProvider(data.rowData, {
keyAttributes: 'id',
childrenAttribute: 'rows'
});
this.dependenciesDataProvider = new ArrayDataProvider(data.depData, {
keyAttributes: 'id'
});
this.taskElementsDetails = [];
this.togglesDetails = [];
this.showAttribute = ko.observable(false);
this.showOvertime = ko.observable(false);
this.showDowntime = ko.observable(false);
this.timeCursor = ko.observable('off');
this.zooming = ko.observable('off');
this.dndAction = ko.observable('(Move or Resize a Task)');
this.handleTaskElementsSettings = (event) => {
this.taskElementsDetails = event.detail.value;
this.handleSettings(this.taskElementsDetails.concat(this.togglesDetails));
};
this.handleTogglesSettings = (event) => {
this.togglesDetails = event.detail.value;
this.handleSettings(this.taskElementsDetails.concat(this.togglesDetails));
};
this.handleSettings = (details) => {
this.showAttribute(details.indexOf('attribute') !== -1);
this.showOvertime(details.indexOf('overtime') !== -1);
this.showDowntime(details.indexOf('downtime') !== -1);
this.timeCursor(details.indexOf('timeCursor') !== -1 ? 'on' : 'off');
this.zooming(details.indexOf('zooming') !== -1 ? 'on' : 'off');
};
this.handleMove = (event) => {
const taskContexts = event.detail.taskContexts;
const rowContext = event.detail.rowContext;
const dropDate = event.detail.value;
this.dndAction(`${taskContexts.length} task(s) dropped on ${rowContext.rowData.label} at ${dropDate}`);
};
this.handleResize = (event) => {
const taskContexts = event.detail.taskContexts;
const dropDate = event.detail.value;
this.dndAction(`${taskContexts.length} task(s) resized to ${dropDate}`);
};
this.expanded = new ojknockout_keyset_1.ObservableKeySet().add(['Mixer A', 'Packaging A']);
this.projectStartDate = new Date('2020-10-01T00:00:00');
this.projectEndDate = new Date('2020-10-31T00:00:00');
// 8 hours scale
this.custom8HrScale = new DemoCustomScaleNHr(8);
// Date converter
this.dateConverter = new ojconverter_datetime_1.IntlDateTimeConverter({
formatType: 'date',
dateFormat: 'long'
});
this.timeConverter = new ojconverter_datetime_1.IntlDateTimeConverter({
formatType: 'time'
});
this.currentDate = new Date('Oct 03, 2020, 17:00:00');
this.currentDateString = this.currentDate.toISOString();
this.currentDateFormatted = this.dateConverter.format(this.currentDateString);
// set viewport to cover two days before and after
this.day = 1000 * 60 * 60 * 24;
this.viewportStart = new Date('Oct 03, 2020');
this.viewportEnd = new Date(this.viewportStart.getTime() + 3 * this.day);
this.referenceObjects = [
{
value: this.currentDateString,
label: this.timeConverter.format(this.currentDateString),
svgClassName: 'demo-current-time-indicator'
}
];
this.getRowDesc = (row) => {
const desc = [row.data.label];
if (row.data.rows) {
desc.push(`${row.data.rows.length} siblings`);
}
desc.push('1 issue');
return desc.join(', ');
};
// Helper function to get appropriate row label x position depending on document reading direction
this.getRowLabelX = (rowAxisWidth) => {
const dir = document.documentElement.getAttribute('dir');
return dir === 'ltr' ? '0' : -rowAxisWidth;
};
}
}
(0, ojbootstrap_1.whenDocumentReady)().then(() => {
fetch("&G_DATA_URL!RAW.")
.then((response) => response.json())
.then( (data) => {
view = new ViewModel(data);
ko.applyBindings(view, document.getElementById('container'));
}
);
});
});
CSSのファイルURLに以下を記述します。

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

CSSインラインに以下を記述します。本来はもっと調整が必要な内容です。

.demo-gantt {
width: 100%;
height: 25rem;
}
.demo-full-size {
width: 100%;
height: 100%;
}
.demo-gantt .demo-gantt-row-label {
overflow: visible;
}
.demo-current-time-indicator {
stroke: var(--oj-dvt-danger-color);
}
.demo-gantt-task {
fill: rgb(var(--oj-palette-neutral-rgb-0));
}
.demo-gantt-task-emphasis-low {
fill: rgba(var(--oj-palette-dvt-rgb-2), 0.2);
}
.demo-gantt-task-emphasis-high {
fill: rgb(var(--oj-palette-dvt-rgb-7));
}
.demo-gantt-task-hold {
fill: rgb(var(--oj-palette-neutral-rgb-130));
}


JSONデータを返すRESTサービスのURLは、アプリケーション定義に置換文字列G_DATA_URLとして定義します。


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

Oracle JET自体はオープンソースのJavaScriptライブラリであり、利用に当たって費用が発生しないかわりに、開発の主体であるオラクルからサポートを受けることもできません。Oracle APEXでは、APEXが使用している範囲であればOracle JETについてもSRを受け付けますが、Oracle JETをサポートしているわけではありません。

世の中には、Ganttチャートを実装したJavaScriptライブラリがいくつがあります。例えばAnyChartのAnyGanttやHighchartsのHighcarts Ganttなどです。これらは有償ですが、技術サポートが含まれています。単純に無料で使えるのでOracle JETを使って実装しようと考える前に、サポートが付いている商用のライブラリの使用を検討しても良いかと思います。

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

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