Usage
This section describes an example implementation of United Code Gantt Pro in an example Oracle APEX application.

The usage guide shows the basic implementation of the plug-in.
Example for basic implementation.
The initial state of page 2 in page designer is presented below:

Implementation Steps
- Add new region of type » - Static Content«
- Add two date picker items (for example: - P2_START_DATE, P2_END_DATE). The gantt chart plug-in requires two date picker items in order to work. We also recommend specifying a default value for these two items. In this case even on the first load of the page the gantt chart will be nicely displayed.
- Add a new Region of type: - United Codes Gantt [Plug-In]
- The following region properties are mandatory in order the plug-in to work: - Source (in below example we use SQL query) 
- Page Items to Submit: - P2_START_DATE,- P2_END_DATE 
 
- Under Attributes the following must be set: - Task ID
- Task name
- Task start date
- Task end date
- Viewpoint start date (P2_START_DATE)
- Viewpoint end date (P2_END_DATE)
 

6. Below is an example of the page after successfully setting up the plugin:

Advanced Settings (Attributes)
- Display events / tasks on timeline  
- Event/Task Progress - SQL QUERY - select T.ID,
 P.ID PROJECT_ID,
 P.PROJECT,
 T.TASK_NAME ||
 case when T.STATUS is not null
 then '['||T.STATUS||']'
 else null
 end TASK_NAME,
 T.START_DATE,
 T.END_DATE,
 T.STATUS,
 T.ASSIGNED_TO,
 T.COST,
 T.BUDGET,
 -- Range to have correct calculations is from 0 - 1. (0.50 is 50%)
 round(DBMS_RANDOM.VALUE(0,1),2) PROGRESS,
 case T.STATUS
 when 'On-Hold'
 then '#ed6647'
 when 'Closed'
 then '#68c182'
 else null
 end COLOR
 from EBA_DEMO_CHART_TASKS T
 inner join EBA_DEMO_CHART_PROJECTS P
 on ( P.ID = T.PROJECT )
- ATTRIBUTES  
 
- Show difference of planning / actual time value (Baseline) - SQL Query - select ID,
 PROJECT,
 PARENT_TASK,
 TASK_NAME,
 ROW_VERSION_NUMBER,
 START_DATE,
 END_DATE,
 STATUS,
 -- Start date of a planning the task
 START_DATE - ROWNUM/ID BASE_START,
 -- End date of a planning the task
 END_DATE + ROWNUM/ID BASE_END,
 ASSIGNED_TO,
 COST,
 BUDGET,
 CREATED,
 CREATED_BY,
 UPDATED,
 UPDATED_BY
 from EBA_DEMO_CHART_TASKS
- Attributes  
 
- Drag and drop event/task option & Resize event/task option  
- Dependency SQL specification 
select ID,  
       PREDECESSOR, 
       SUCCESSOR,  
       RELATION, 
       STATUS,
       SHORTDESC
  from EBA_DEMO_CHART_TASKS_DEPEND_V;
JSON options
{
  "id": "5",
  "predecessor": "3",
  "successor": "2",
  "relation": "finishStart", //  "finishStart" | "finishFinish" | "startStart" | "startFinish" 
  "status": "critical", //used for customization
  "shortDesc": "My Description finishStart|critical"
}
- Additional event/task information (tooltip)  

- Hierarchical event/tasks view - SQL Query - select T.ID,
 T.PARENT_TASK PARENT_TASK_ID,
 P.ID PROJECT_ID,
 P.PROJECT,
 T.TASK_NAME,
 T.START_DATE,
 T.END_DATE,
 T.STATUS,
 T.ASSIGNED_TO,
 T.COST,
 T.BUDGET,
 '{"assigned":"'||T.ASSIGNED_TO||'",
 "borderRadius":"10px",
 "labelPosition":"start",
 "type":"'|| case when T.PARENT_TASK is null
 then 'summary' else 'normal' end ||'"
 }' CUSTOM_SETTINS
 from EBA_DEMO_CHART_TASKS T
 inner join EBA_DEMO_CHART_PROJECTS P
 on P.ID = T.PROJECT
- Attributes  
 
Automatic Time Zone
Since Oracle APEX automatically parse date and timestamp into JavScript format (example: "2023-10-28T22:06:00Z") browsers will automatically calculate offset and display it for you.
The ISO format support short notations where the string must only include the date and not time, as in the following formats: YYYY, YYYY-MM, YYYY-MM-DD.
The ISO format does not support time zone names. You can use the Z position to specify an offset from UTC time. If you do not include a value in the Z position, UTC time is used. The correct format for UTC should always include character 'Z' if the offset time value is omitted. The date-parsing algorithms are browser-implementation-dependent and, for example, the date string '2013-02-27T17:00:00' will be parsed differently in Chrome vs Firefox vs IE.
More details can be found in the OJ GANTT documentation.
In order to avoid auto calculation you can disable that option in "Javascript Initialization Code".
function (pOptions) {
    
    //Use browser TimeZone true/false
    pOptions.automaticTimezone = false;
    return pOptions;
}
Date Formats / Time Scales
Date format is automatically picked up from application language setting. In case you need to change it you can do that in two ways.
- Change with ojConverter
More details about date/time can be found in the OJ GANTT documentation.
function (pOptions) {
   pOptions.callback = function(ViewModdel, rowData, viewPoint, ko, ojconverter, TimeUtils) {
       ViewModdel.weekConverter = new ojconverter.IntlDateTimeConverter({
            pattern: 'd-M'
       })
   }
    pOptions.gantt = {
        "minor-axis.converter.weeks": "[[weekConverter]]"
    }
    return pOptions;
}
- Crate a Custom Converter
function (pOptions) {
    pOptions.gantt = {
        "aria-label":"Custom Date Format",
         "major-axis.scale":"weeks",
         "major-axis.zoom-order":'["quarters", "months", "weeks", "days"]',
         "major-axis.converter.weeks":"[[dateConverter]]",
         "minor-axis.scale":"days",
         "minor-axis.zoom-order":'["quarters", "months", "weeks", "days"]',
         "minor-axis.converter.days":"[[diffDaysConverter]]"
    };
    //pOptions.loadModules = ["ojs/ojdvttimecomponentscale"];
        
    pOptions.callback = function(ViewModel, rowData, viewPoint, ko, ojconverter, TimeUtils) {  
        ViewModel.dateConverter = ko.observable(new ojconverter.IntlDateTimeConverter({
                  formatType: "date",
                  dateFormat: "long",
        }));
        ViewModel.diffDaysConverter = {
            format: (dateString) => {
                const day = 24 * 60 * 60 * 1000;
                const startDate = new Date(viewPoint.start);
                const startTime = startDate.getTime();
                const date = new Date(dateString);
                return Math.round(Math.abs(date.getTime() - startTime) / day + 1);
            },
        };
    };       
    
    return pOptions;
}
- Custom "Time Scales"
In this example you can find, 5 min Time Scale. Be sure you do not load to much data "Start - End date", perhaps one day is enough. Time Scales are always executed, even if you do not zoom in, or zoom out from default setting.
function (pOptions) {
    pOptions.gantt = {
        "aria-label": "Custom Timescales",
            "gridlines.vertical": "visible",
            "major-axis.scale": "hours",
            "major-axis.zoom-order": '[[ ["hours", FiveMScale, "minutes"] ]]',
            "minor-axis.scale": "hours",
            "minor-axis.zoom-order": '[[ ["hours", FiveMScale, "minutes"] ]]'
    };
    pOptions.loadModules = ["ojs/ojdvttimecomponentscale"];
        
    pOptions.callback = function(ViewModel, rowData, viewPoint, ko, ojconverter, TimeUtils) {
        //callback is executed after all task settings and it is highly customizable
        class FiveMScale {
          constructor(fiscalStart, fiscalEnd) {
              this.name = 'FiveMinutes';
               this.converter = new ojconverter.IntlDateTimeConverter({
                        formatType: "time",
                        timeFormat: "short"
              });
              const fiscalStartDate = new Date(fiscalStart);
              const fiscalEndDate = new Date(fiscalEnd);
              this.dates = [fiscalStartDate];
              // Precompute the 4-4-5 weeks intervals
              let nextDate;
              while (this.dates[this.dates.length - 1] < fiscalEndDate) {
                nextDate = new Date(this.dates[this.dates.length - 1]);
                nextDate.setMinutes(nextDate.getMinutes() + 5);
                this.dates.push(nextDate);
              }
          }
          formatter(dateStr) {
              return this.converter.format(dateStr);
          }
          getNextDate(dateStr) {
              // Search for the date after the given date from the precomputed dates
              let date = new Date(dateStr);
              for (let i = 0; i < this.dates.length; i++) {
                  if (this.dates[i] > date) {
                      return this.dates[i].toISOString();
                  }
              }
          }
          getPreviousDate(dateStr) {
              // Search for the date before the given date from the precomputed dates
              let date = new Date(dateStr);
              for (let i = 0; i < this.dates.length - 1; i++) {
                  if (this.dates[i + 1] > date) {
                      return this.dates[i].toISOString();
                  }
              }
          }
      }
      ViewModel.FiveMScale = new FiveMScale(viewPoint.start, viewPoint.end);
    };       
    
    return pOptions;
}
Sorting
By default we sort data in JavaScript section, so the resource data is sorted by labels. You can change sorting with the following action/s.
function (pOptions) {
  
    pOptions.methods = {   
        //Invert resource sort or create custom logic
        sortData : function (data) {
                return data.sort(function (a, b) {
                    return a.resource < b.resource ? 1 : -1;
                })
        },
        //Invert resource tree sort or create custom logic
        sortDataTree : function (data) {
            function sortTreeNodes(nodes) {
                nodes.sort((a, b) => {
                    const resourceA = a.resource.toLowerCase();
                    const resourceB = b.resource.toLowerCase();
                    return resourceA < resourceB ? 1 : -1;
                });
                
                nodes.forEach(node => {
                    if (node.subTasks && node.subTasks.length > 0) {
                        sortTreeNodes(node.subTasks);
                    }
                });
                
                return nodes;
            }
            
            const dataCopy = JSON.parse(JSON.stringify(data));
            return sortTreeNodes(dataCopy);
        }
    };   
    return pOptions;
}
Sometimes you need to disable JavaScript sorting and use your own from the query. You can achieve that with disabling the JavaScript sorting.
function (pOptions) {
  
    pOptions.methods = {   
        //Disable sorting by label
        sortData : function (data) {
            //No sort
            return data;
        },
        //Disable tree sorting
        sortDataTree : function (data) {
            //No sort
            return data;
        }
    };    
    return pOptions;
}
Themes
By default, this plug-in select "Redwood notag". In order to be more open for developers to select one of the following theme we introduced new parameter "themeCSS".
Options are :
- '/alta/oj-alta-min.css'
- '/alta/oj-alta-notag-min.css'
- '/alta-android/oj-alta-min.css'
- '/alta-ios/oj-alta-min.css'
- '/alta-windows/oj-alta-min.css'
- '/redwood/oj-redwood-min.css'
- '/redwood/oj-redwood-notag-min.css'
- '/stable/oj-stable-min.css'
To change default theme please set themeCSS in "Javascript Initialization Code".
function (pOptions) {
    
    //Change theme
    pOptions.themeCSS = '/stable/oj-stable-min.css';
    return pOptions;
}
Timeline, Weekends and Time Buckets
By default, timeline and weekends are enabled and displayed out of the box. But sometimes there is a use case to disable coloring or remove timeline. That is possible with custom options via "Javascript Initialization Code".
function (pOptions) {
    
    //disable timeline
    pOptions.display.timeLine = false;
    
    //disable weekend coloring
    pOptions.display.weekend = false;
    return pOptions;
}
To be even more flexible with coloring timeline you can set current time, multiple times, and add a class on it.
function (pOptions) {
    
    //disable timeline
    pOptions.display.timeLineData = [{
                        value: new Date().toISOString(),
                        shortDesc: "Current Date",
                        svgClassName:'demo-current-time-indicator'
                    }];
    return pOptions;
}
Sometimes there is a need to ave different Time Buckets, to display phase of project. You can use declarative option "Time Buckets" and put query in there. This can be useful for coloring sprints, adding holidays, or maybe just adding colored lines where needed.
select TYPE,
       START_TIME,-- transformed to "start" used only when type = area
       END_TIME,  -- transformed to "end"   used only when type = area
       LINE_TIME, -- transformed to "value" used only when type = line
       SHORTDESC,
       SVGSTYLE
  from YOUR_HOLIDAY_DATES_TABLE;
Or if there specific use case you can also go with setting up Time Bucket JSON in "Javascript Initialization Code".
function (pOptions) {
    
    let firstStart = new Date();
    let firstEnd   = new Date();
    let secondStart = new Date();
    let secondEnd   = new Date();
    firstStart.setMonth(firstStart.getMonth() - 1);
    firstEnd.setDate(firstEnd.getDate() - 10);
    secondStart.setDate(secondStart.getDate() + 10);
    secondEnd.setMonth(secondEnd.getMonth() + 1);
    pOptions.display.timeBucketsData = [
        {
            type: "area",
            start: firstStart.toISOString(),
            end: firstEnd.toISOString(),
            svgStyle: { fill: "#32925e", opacity: "0.18" },
            shortDesc: "Time Bucket 1",
        },
        {
            type: "area",
            start: secondStart.toISOString(),
            end: secondEnd.toISOString(),
            svgStyle: { fill: "#eb9632", opacity: "0.18" },
            shortDesc: "Time Bucket 2",
        },       
    ];
    return pOptions;
}
Result:

Javascript Initialization Code
Sometimes you can hit charters imit. In that case declare variable in page "Function and Global Variable Declaration" and reference on it within plug-in "Javascript Initialization Code".
function (pOptions) {
    pOptions.gantt = {
        "gridlines.horizontal":"visible",
        "gridlines.vertical":"visible",
        "major-axis.scale":"weeks",
        "major-axis.zoom-order":'["quarters", "months", "weeks", "days"]',
        "major-axis.converter.weeks":"[[dateConverter]]",
        "minor-axis.scale":"days",
        "minor-axis.zoom-order":'["quarters", "months", "weeks", "days"]',
        "minor-axis.converter.days":"[[diffDaysConverter]]"
        "selection-mode":"multiple",
        "dnd.move.tasks":"enabled",
        "task-defaults.resizable":"enabled",
        "style":"width:100%;height:70vh",
        "translations.component-name":"Component Name !",
        "translations.label-invalid-data":"Something wrong with date fields!",
        "task-defaults.height":40,
        "task-defaults.border-radius":5,
        "task-defaults.label-position":"innerStart",
        "task-defaults.type":"summary",
        "task-defaults.svg-style":{"fill":"#b366ff"},
        "task-defaults.overlap.behavior":"stack"
    };
    //Use browser TimeZone true/false
    pOptions.automaticTimezone = false;
    //load additional modules 
    pOptions.loadModules = ["ojs/ojmenu", "ojs/ojdvttimecomponentscale", "ojs/ojavatar"];
    pOptions.methods = {   
        //Invert resource sort
        sortData : 
            function (data) {
                return data.sort(function (a, b) {
                    return a.resource < b.resource ? 1 : -1;
                })
        },
        // Custom validation, for more details check "Custom validations section"
        validateClick : function(task) {
            console.log("Validate CLICK on", task);
            return false;
        },
        validateMove : function(task, moveData) {
            console.log("Validate MOVE on", task, moveData);         
            return true;
        },
        validateResize : function(task, resizeData) {
            console.log("Validate RESIZE on", task, resizeData);
            return true;
        }
        /*
        sortData : function (data) {
            //No sort
            return data;
        },*/
        
    };    
    //add elements in region before oj-gantt element
    pOptions.elements = [
        `<svg height="0" width="0"><defs><marker
                  id="demoCircleMarker" 
                  viewBox="0 0 12 12" refX="6" refY="6" markerWidth="12" markerHeight="12"
                  orient="auto"
                  markerUnits="userSpaceOnUse">
                  <circle class="demo-circle-marker" cx="6" cy="6" r="5" />
                </marker>
                <marker
                  id="demoArrowMarker"
                  viewBox="0 0 10 10" refX="10" refY="5" markerWidth="12" markerHeight="12"
                  orient="auto-start-reverse"
                  markerUnits="userSpaceOnUse">
                  <path class="demo-arrow-marker" d="M 0 0 L 10 5 L 0 10 z" />
                </marker>
                <marker
                  id="demoArrowMarkerCritical"
                  viewBox="0 0 10 10" refX="10" refY="5" markerWidth="12" markerHeight="12"
                  orient="auto-start-reverse"
                  markerUnits="userSpaceOnUse">
                  <path class="demo-arrow-critical-marker" d="M 0 0 L 10 5 L 0 10 z" />
                </marker>
                </defs></svg>`
    ];
    //add templates and elements inside oj-gantt element
    pOptions.templates = [
          `<template slot="rowAxisLabelTemplate" data-oj-as="rowAxisLabel"> 
              <svg class="demo-gantt-row-label">
                <g>
                <foreignobject :x="0" y="0" width="32" height="32">
                    <oj-avatar
                      src="[[rowAxisLabel.data.resourceData.profile]]"
                      size="xxs"></oj-avatar>
                  </foreignobject>
                  <text :x="25" y="19">
                    <oj-bind-text value="[[rowAxisLabel.data.resource]]"></oj-bind-text>
                  </text>
                </g>
              </svg>
            </template>`,
            `<oj-menu
                id="ctxMenu"
                slot="contextMenu"
                aria-label="Match Edit"
                on-oj-menu-action="[[menuItemAction]]"
                on-oj-before-open="[[beforeOpenFunction]]">
                <oj-option value="Action 1">Action 1</oj-option>
                <oj-option value="Action 2">Action 2</oj-option>
                <oj-option value="Action 3">Action 3</oj-option>
                </oj-menu>
            </oj-gantt>`
    ];
    
    pOptions.callback = function(ViewModel, rowData, viewPoint, ko, ojconverter, TimeUtils) { 
        // add additional logic and handlers after data is assigned to te ViewModel 
        ViewModel.dateConverter = ko.observable(new ojconverter.IntlDateTimeConverter({
                  formatType: "date",
                  dateFormat: "long",
        }));
        ViewModel.diffDaysConverter = {
            format: (dateString) => {
                const day = 24 * 60 * 60 * 1000;
                const startDate = new Date(viewPoint.start);
                const startTime = startDate.getTime();
                const date = new Date(dateString);
                return Math.round(Math.abs(date.getTime() - startTime) / day + 1);
            }
        };
    };       
    return pOptions;
}