Skip to main content

Oracle APEX Gantt Charts - Custom Tooltips

For showing tooltips on Gantt Charts, just go to "Gantt Chart > Attributes > Tooltip" and select "Yes" for "Show" option. However, this will show standard tooltip and there is no option for customization. In APEX ver. 20.1, there are few more granular options to control on data we want to display in the tooltip. However, still we need to choose from existing options.

Luckily, we can implement custom tooltips in relatively easy way.

Gantt Chart Custom Tooltips
If you are new to Gantt charts, I recommend you to read Few tips on Gantt Charts blogpost first.

Gantt Chart Options

First, we need to specify JavaScript function name, which will return a tooltip element, in the Gantt chart options. In below code "showCustomTooltip" is JavaScript function which return the tooltip element.
function (options)
{
  options.tooltip = {"renderer":showCustomTooltip};
  // with older APEX (JET) versions use below code
  // options.tooltip = showCustomTooltip;
  return options;
}
We need to keep this code in "Gantt Chart > Attributes > Advanced > JavaScript Initialization Code" section. Now, whenever we hover mouse on any task bar, then this JS function will be invoked.

"dataContext" Object

By default, "dataContext" object is passed as input parameter for the tooltip function. Using this object, we can access current task data and as-well as current row data. For e.g. if we put below code as function definition for "showCustomTooltip" function, 
function showCustomTooltip(dataContext)
{
  var taskData = dataContext.data
    ,rowData = dataContext.rowData;  
  console.log(dataContext.data);
  console.log(dataContext.rowData);
  return "Custom Tooltip Test";
}
and then, if we hover mouse on any task, then we will see below output (something similar based on the Gantt chart) in browser console log.

Output for "console.log(dataContext.data);"
end: "2020-06-17T23:59:00"​
id: "TK1"​
label: "Employee 1 Task"​
labelPosition: "none"​
start: "2020-06-13T00:00:00"​
svgClassName: " "
Output for "console.log(dataContext.rowData);"
id: "EMP1"​
label: "employee1@xyz.com"​
tasks: [<emp1-task-1>,<emp2-task-2>..]
I also suggest, to try "console.log(dataContext);" and observe all the information available for the tooptip function, especially "dataContext.parentElement" object.

Gantt Chart Series SQL Query

As explained above, "dataContext" Object gives us some information about current task and current row. However, if we want to pass additional information to tooltip function, then we can use "task_name" column (column selected at "Gantt Chart > Series > Column Mapping > Task Name").

For the current example, my query is something like this..
SELECT
    'TK' || task.task_id task_id
    ,'EMP' || emp.emp_id parent_task_id
    ,emp.email_address   email_address
    /* all the data required for tooltip function is stuffed into JSON object */
    ,'{ '
|| '"task_name": "'||apex_escape.json(task.task_name)||'"'
|| ',"task_type": "'||apex_escape.json(task.task_type)||'"'
|| ',"start_date": "'||apex_escape.json(TO_CHAR(task.start_date,'Dy, DD-Mon-YYYY'))||'"'
|| ',"end_date": "'||apex_escape.json(TO_CHAR(task.end_date,'Dy, DD-Mon-YYYY'))||'"'
||'}' task_name

    ,task.start_date     start_date
    ,task.end_date + 1 - 1 / 1440 end_date
    ,task.task_type
    ,CASE
        WHEN task.task_type = 'LEAVE' THEN
            'demo-f-orange'
    END || ' '
|| CASE WHEN task.billable_flag = 'N' THEN 'demo-b-red' ELSE NULL END css_class
FROM emp
JOIN blog_gantt_tasks_v task ON task.email_address = emp.email_address
Observe "task_name" column code. Here, we are preparing a JSON object, with all the data that is required in the tooltip function. "task_name" column data is passed as "label" for "dataContext.data" objecti.e. we can access task_name data using "dataContext.data.label" syntax in the tooltip function. If we don't need any additional data in the tooltip function, then we can just use "task.task_name task_name" for task_name column.

Define Template

Next, let's define a HTML template, which we can use to show the tooltip. In this case, I am using a simple HTML table and I am also using template tag to define the template. 

<template id="tooltip-template">
  <div id="tooltip-main">
    <table>
      <tr id="emp-name"><th align="center" colspan="2"></th></tr>
      <tr id="task-name"><th align="right">Task Name</th><td></td></tr>
      <tr id="task-type"><th align="right">Task Type</th><td></td></tr>
      <tr id="start-date"><th align="right">Start Date</th><td></td></tr>
      <tr id="end-date"><th align="right">End Date</th><td></td></tr>
    </table>
  </div>
</template>

We can put above template code in "Page > Header and Footer > Footer Text" section. 

There is another approach where we don't need to define template and we can dynamically create tooltip template using "document.createElement" JavaScript function. We can observe this approach in JET documentation example. 

Tooltip Function

Here is the tooltip function "showCustomTooltip" code. For explanation, please go through the inline comments.
function showCustomTooltip(dataContext)
{
  // get task and row data from dataContext object
  var taskData = dataContext.data
    ,rowData = dataContext.rowData;
  // we are passing custom data required for tooltip as taskData.label and in JSON format
  // convert label text to JSON object

  var taskDataCustom = JSON.parse(taskData.label);
  // get the template element defined in HTML footer section
  var tooltipTemp = document.getElementById("tooltip-template");
  // clone it to make a copy. don't modify tooltipTemp object as it will impact the template defined in HTML footer section.
  var clon = tooltipTemp.content.cloneNode(true);
  // add data to the cloned template
  // observe, we are reading custom data we have passed as JSON object, in series SQL query (column task_name)
  // and using that data to fill tooptip template

  $("tr#emp-name > th",clon).html(rowData.label);
  $("tr#task-name > td",clon).html(taskDataCustom.task_name);
  $("tr#task-type > td",clon).html(taskDataCustom.task_type);
  $("tr#start-date > td",clon).html(taskDataCustom.start_date);
  $("tr#end-date > td",clon).html(taskDataCustom.end_date);
  // finally return the cloned template filled with task data
  return clon;
}
Note: In 20.1, there is new option "Custom Tooltip" under "Gantt Chart > Series > Column Mapping". This looks like what exactly we need, however, I was not able to get this working.

Here is the demo link

Thank you.

Comments

Peter said…
Very interesting ! Have you been able to create a Gantt chart with dependencies ? Am trying to create a plugin for this but struggle with the 2 datasources (as per cookbook). Just wondered if you have any ideas ?
Thanks in advance,
Peter
Hari said…
Hi Peter,

I have not worked with Gantt chart with dependencies. Regarding handling two data sources for plug-in, that's an interesting question. I suggest you to post it in APEX Forums or in Twitter with #orclapex and #orclapexworld hashtags. I am sure someone might have handled this case already for any other region type plug-in.

Thank you.
Peter said…
Thanks Hari for your reply. Just posted it in the Apex forum.
thanks again!
Peter
Pertti said…
Hi Hari
I realy love your examples, very interrested in building someting like this task-schedule.

But as a almost complete newbie to APEX development is there any easy step-by-step tutorial or dokumentation on how to create the gantt and tables/views needed from scratch?

Best regards
/Pertti
Hari said…
Hi Pertti

Have you checked below blogpost?

https://srihariravva.blogspot.com/2019/12/few-tips-on-gantt-charts.html

If you are stuck at any specific step, please let me know. I can try to share more info.

Regards,
Hari
Pertti said…
Hi Hari
Thanks for the reply.
Yes, I have read all your posts on the matter but I'm still struggling to get it right.
I managed to get it halfway there but the "users" have one row per task in the gantt chart instread of all on the same row as the username.

Would it be possible for you to post the tables/views and queries used in your example?

Thanks in advance!
Regards
/Pertti
Hari said…
Hi Pertti,

I have added "Download" button to below demo page. Please use this button to download APEX application with Gantt chart example.

https://apex.oracle.com/pls/apex/hari/r/demo_app/gantt-chart-tips

Regards,
Hari
Pertti said…
Hi Hari
Yes, that made it a lot easier for me to get grips on things.
Very helpful, thanks!
Would it be possible to get Download links to the other demos as well?
There's still stuff that I'd like to learn from your examples.

Thanks in advance.
Regards
/Pertti
Hari said…
Hi Pertti

Yes, I will do it and that's in my roadmap. But, I need to get time for this as I have other priorities at the moment.

Mean while if you have any specific questions, post in respective blog post or alternatively you can post your questions in APEX Forum.

Regards,
Hari

Popular posts from this blog

Interactive Grid - Conditional Enable/Disable

In this blogpost, I am going to discuss few approaches using which we can conditionally enable/disable Interactive Grid (IG) column(s) based on other column(s) values. Note:    There is a bug  30801170  in APEX 19.2/20.1 with respect to "enable/disable" dynamic actions for IG columns. Workaround for this bug is provided at the end of this blogpost . This bug has been fixed in APEX version 20.2. Client Side Only Conditions If conditions to enable/disable are simple, then we can check those conditions easily on the client side. For e.g. let's consider IG on EMP table with following SQL Query. SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP Let's consider the requirement as - Enable "Commission" column only when JOB is equals to 'SALESMAN' and disable "Commission" column in all other cases. This can be done declaratively using dynamic actions (DA) DA "Enable/Disable Commission - 1" Create DA and give it a prope

Interactive Grid - Bulk Operation on Selected Rows

What's the Problem? Let's say you have an IG on employee table and you want to update selected employees commission based on user's input. Ideally, it should be very simple, where you can write UPDATE statement in page process and select IG region as "Editable Region" under "Execution Options" of the page process. But, when you select rows and submit page, you can see that, this process won't get executed! The reason is  Selection of 'Row Selector' check-boxes is not considered as row-change. Thus selected rows are not submitted to server. So, is there any work around?  Yes! Luckily there are lot of JavaScript (JS) APIs available to work with IG. If you are not already aware, you can refer "APEX IG Cookbook"  or  JavaScript API Reference documentation. If we continue with above Employee IG example, when user selects IG rows, enters "Commission %" and clicks on "Update Commission" button, then we can writ

Interactive Grid - Process Filtered Data on Server Side

Recently one of the APEX developers has reached out to me and asked if it's possible to capture filtered rows data of the Interactive Grid on the server-side and do some processing. In APEX 20.1, there is a new API APEX_IG , using which we can achieve this. Photo by Jakub Kapusnak on Unsplash The approach is very simple and straightforward. Get the internal region id based on the Static ID given for the IG region Get the last viewed report id based on region id Open query context for the region and report using region id and report id Fetch and loop through the rows using the query context Do something with fetched rows And finally, close the query context If you have already done this for interactive reports, then you should be already aware of these steps. The only difference here is, we use APEX_IG APIs instead of APEX_IR APIs. For the demo purpose, let's Build an Interactive Grid on EMP table and let's give it a Static ID as emp Create a Textarea page item with