Dynamic number of Oracle APEX charts

Table of contents

Some time ago, I was asked to develop a feature that would display a dynamic number of Status Meter Gauge charts. While Oracle APEX makes it easy and intuitive to create a predefined number of charts, things get complicated when the number of charts needs to be dynamic. By default, there’s no straightforward way to generate a dynamic number of Chart regions in APEX.
In my first blog post on Hashnode, I’d like to share a solution to handle this challenge.
How does Oracle APEX renders charts?
Let’s start with an important question: how does Oracle APEX actually render charts?
APEX uses the Oracle JET (JavaScript Extension Toolkit) library to display charts. Here's an excerpt from the documentation explaining how Oracle APEX integrates Oracle JET:
Oracle JET is a collection of open source JavaScript libraries (modules) with a set of Oracle contributed JavaScript libraries[…]
Currently APEX integrates some parts of Oracle JET, primarily with Chart region types[…]
Oracle JET uses a module loader (
RequireJS
) to only load modules that are required for specific functionality to work. This means that you do not have to load the entire Oracle JET library for certain functionality and also enables each module to define its own dependencies. At runtime, when a module is requested, the module loader looks at the module being requested for any dependencies to other modules. If there are any dependencies, these will also be loaded[…]
In short, before a chart is rendered on the page, the required Oracle JET module must be loaded. This is handled by RequireJS, a module loader that loads only the necessary components. Once the module is loaded, chart can be rendered.
Challenge
Imagine you're working on a sales commission system. In this system, you have sales representatives who earn commissions based on their performance. Each salesperson has specific sales goals - for example, weekly, monthly, quarterly, or yearly targets. As a manager, you'd like to track how well your team is doing by displaying each salesperson's progress using Status Meter Gauge charts.
Here's where the challenge begins:
One salesperson might have only a single sales goal (e.g. yearly), while another might have multiple goals (monthly, quarterly, and yearly). Of course, you could manually create 3 or 4 Chart regions in Oracle APEX - but what happens when:
The number of sales reps increases?
Existing reps are assigned new goals dynamically?
Native chart regions quickly becomes unmanageable.
You need a flexible solution that can render a dynamic number of charts, depending on the data - not on how many chart regions you’ve manually defined in the APEX builder.
Solution
The key to solving this challenge is using the apex.widget.jetChart.init
function, which is available in the chartBundle.min.js
file. Combined with a flexible layout like a Cards region, this function allows you to dynamically render charts by providing a static HTML container ID and a few required parameters.
chartBundle.min.js
file is included when a JET chart is included on a page (for example, when a Chart
region, or Interactive Grid
region is added to a page), and the page is not in debug mode. When debugging, the bundle is not loaded and requireJS
loads all the individual resources separately.Understanding apex.widget.jetChart.init
function
The apex.widget.jetChart.init
function (in its non-minified form) is available here. It accepts several parameters, but the most important ones are the first four:
pRegionId
- the static ID of the HTML container (region) where the chart should be rendered.pWidth
- the width of the region. The default value is 100%.pHeight
- the height of the region. By default, this is not defined.pOptions
- the chart configuration object. A full list of supported attributes for Status Meter Gauge chart is available here.
These four parameters are enough to successfully render a JET chart in Oracle APEX.
This function gives you full control over how and where the chart is displayed, making it a powerful tool for building dynamic and data-driven dashboards outside the standard Chart region.
Initial setup
Let’s start by preparing the data. I create a simple table that will hold information about the progress of sales goals.
create table "SALES_GOALS" (
"ID" number generated by default on null as identity,
"SELLER_NAME" varchar2(255 char) not null,
"GOAL_TIME_PERIOD" varchar2(255 char) not null,
"GOAL_MAX_VALUE" number not null,
"GOAL_CURRENT_VALUE"number not null,
constraint "SALES_GOALS_PK" primary key ( "ID" )
);
insert into SALES_GOALS(SELLER_NAME, GOAL_TIME_PERIOD, GOAL_MAX_VALUE, GOAL_CURRENT_VALUE) values ('John', '2025-M6', 10000, 2500);
insert into SALES_GOALS(SELLER_NAME, GOAL_TIME_PERIOD, GOAL_MAX_VALUE, GOAL_CURRENT_VALUE) values ('John', '2025-Q2', 30000, 10000);
insert into SALES_GOALS(SELLER_NAME, GOAL_TIME_PERIOD, GOAL_MAX_VALUE, GOAL_CURRENT_VALUE) values ('John', '2025', 100000, 60000);
insert into SALES_GOALS(SELLER_NAME, GOAL_TIME_PERIOD, GOAL_MAX_VALUE, GOAL_CURRENT_VALUE) values ('Steve', '2025-M6', 5000, 4000);
insert into SALES_GOALS(SELLER_NAME, GOAL_TIME_PERIOD, GOAL_MAX_VALUE, GOAL_CURRENT_VALUE) values ('Steve', '2025', 50000, 10000);
commit;
Next, I create a new application or an empty page within any existing Oracle APEX application.
Loading the chartBundle.min.js
file
The next step is to load the chartBundle.min.js file into the File URLs attribute, located in the JavaScript section at the page level. We have to do this because this file won’t be loaded automatically as there is no Chart region on this page.
[require requirejs]#APEX_FILES#libraries/apex/minified/chartBundle.min.js
The following line consists of three important parts worth explaining:
[require requirejs]
→ this part ensures that the RequireJS module loader is loaded beforechartBundle.min.js
. RequireJS is required because theapex.widget.jetChart.init
function loads Oracle JET modules using this module loader.#APEX_FILES#
→ this is a substitution string that determines the virtual path the web server uses to point to the images directory distributed with Oracle APEX.libraries/apex/minified/
→ this is the file directory I found in the documentation.
Creating a cards region
In the next step, I create a Cards region named Sales goals - cards
with the following SQL Query:
select 'my_chart_container_' || ID as REGION_STATIC_ID,
SELLER_NAME || ' - ' || GOAL_TIME_PERIOD as CARD_TITLE,
GOAL_MAX_VALUE,
GOAL_CURRENT_VALUE
from SALES_GOALS
order by ID asc;
Then, I go to the Attributes tab and set the following values:
Appearance section → Grid Columns =
3 Columns
Title section → Column =
CARD_TITLE
Body section → Advanced Formatting =
True
Body section → HTML Expression =
<div id="®ION_STATIC_ID." style="position: relative;"> <div id="®ION_STATIC_ID._jet"></div> </div>
apex.widget.jetChart.init
function requires a parent container with a static region ID and a child container with a static ID ending with the _jet
postfix. If either element is missing, the function will throw an error during chart rendering.After saving the changes, I now have containers ready where I can render my charts. They look like this:
Fetching data for the charts
The next step is to create an AJAX process that will fetch data from the table and return it as JSON.
To do this, I create a new AJAX process under the Processing section, name it FETCH_SALES_GOAL_DATA
, and use the following PL/SQL code as the Source:
declare
c_sales_goals sys_refcursor;
begin
open c_sales_goals for
select 'my_chart_container_' || ID as "regionStaticId"
, GOAL_CURRENT_VALUE as "salesGoalCurrentValue"
, GOAL_MAX_VALUE as "salesGoalMaxValue"
, round((GOAL_CURRENT_VALUE / GOAL_MAX_VALUE) * 100, 2) as "realizationPercentage"
, '<b>' || GOAL_CURRENT_VALUE || '</b> of ' || GOAL_MAX_VALUE || ' PLN (' || round((GOAL_CURRENT_VALUE/GOAL_MAX_VALUE) * 100, 2) || '%)' as "tooltipHTML"
from SALES_GOALS
order by ID asc;
apex_json.open_object;
apex_json.write('salesGoals', c_sales_goals);
apex_json.close_object;
exception
when others then
-- Exception handling...
raise;
end;
Calling the AJAX process
Now I need to call the AJAX process I created earlier to fetch the data from the database and use it to initialise the charts.
In the Dynamic Actions tab, I create a new dynamic action with the following attributes:
Identification section → Name =
Fetch data and initialize charts
When section → Event =
Page Change [Cards]
When section → Selection Type =
Region
When section → Region =
Sales goals - cards
Then, I add a True Action with the following attributes:
Identification section → Action =
Execute JavaScript Code
Execution section → Fire on Initialization =
true
Settings section → Code =
apex.server.process( "FETCH_SALES_GOAL_DATA", {}, { success: function( data ) { renderStatusMeterGaugeCharts(data.salesGoals); }, error: function( jqXHR, textStatus, errorThrown ) { apex.message.clearErrors(); apex.message.showErrors([ { type: "error", location: "page", message: 'An error occurred while rendering the charts.', unsafe: false } ]) } } );
After completing the steps above, the data will be loaded on page load (thanks to setting Fire on Initialization to true
) and every time the Cards region is refreshed. The Dynamic Actions section should now look like this:
Charts initialization with apex.widget.jetChart.init
The final step is to define a function that will initialise the charts.
To do this, I go to the Rendering tab, and at the page level, I add the following JavaScript code to the Function and Global Variable Declaration in JavaScript section:
function renderStatusMeterGaugeCharts (data) {
for (const dataRow of data) {
apex.widget.jetChart.init(dataRow.regionStaticId, null, null, {
type: "dial",
min: 0,
max: 100,
startAngle: 180,
angleExtent: 180,
orientation: "circular",
indicatorSize: 0.5,
value: dataRow.realizationPercentage,
thresholdDisplay: "all",
thresholds: [
{
max: 33,
color: 'red'
},
{
max: 66,
color: 'yellow'
},
{
max: 100,
color: 'green'
}
],
metricLabel: {
text: dataRow.realizationPercentage + "%",
style: "font-size: 24px; font-weight: 700; color: black;"
},
tooltip:{
renderer: function(context) {
let tooltipContainer = document.createElement("div");
let tooltipContainer$ = $(tooltipContainer);
tooltipContainer$.css({"background": "black", "color": "white", "padding": "5px"});
tooltipContainer$.html(dataRow.tooltipHTML);
return tooltipContainer;
}
}
});
}
}
Done! After saving the changes and refreshing the page, the charts render correctly.
Now, I can add new data to the table without need to modify PL/SQL or JavaScript code - the Cards region will automatically render the new data.
Summary
In this blog post, I shared a practical solution for rendering a dynamic number of charts in Oracle APEX. While APEX allows you to easily define a static number of charts by adding them as separate regions, displaying charts dynamically based on data can be a challenge - fortunately, until today.
Remember, the solution described in this post can be applied not only to Status Meter Gauge charts, but also to almost all chart types supported by Oracle APEX - such as bar, area, line, and more.
Thanks for reading!
Subscribe to my newsletter
Read articles from Kamil Rybicki directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
