Application Express Dynamic Actions with d3.js
This tutorial will show you how to get data dynamically into a d3.js graph in your Apex application. I did all my work on http://apex.oracle.com so if you want to mess around you can grab your own free account there and give it a shot.
If you want to get more familiar with d3.js Data Driven Documents visualization framework check out this article. Mike Bostock, the creator of d3js has a tutorial on bar charts available here. This tutorial is a great way to get a taste of basic and advanced d3 topics quickly. Part 2 of the tutorial is the basis for what I have shown here.
This document will focus more on the ApEx integration side of things rather than the d3.js knowledge transfer. There are already a lot of great tutorials out there on how to effectively work with d3.js. If you really want to get your mind blown and get excited about new visualizations for your ApEx apps, hit up the Examples Gallery with a supported browser (sorry no IE8) and check out the examples and their source code.
So here is an example of what I came up with in Apex by adapting the dynamic updating part of the tutorial... I want a bar graph that updates with a new value from a PL/SQL function every 2.5 seconds...
So here is how I went about creating this real-time updating graph in ApEx:
Step 1) Create a Dynamic Action
Dynamic actions allow JavaScript to make a call to ApEx to retrieve data without refreshing the whole page. If you want to learn the ins and outs of Dynamic Actions there is an Oracle By Example tutorial available here: Building Dynamic Actions in Oracle Application Express 4.1.
At this point we are going to create a very basic Dynamic Action to return a random number between 1 and 100 when we call it:
- Edit your page
- Add a Page Process
- Select "PL/SQL"
- Name your process GetRandom
- Select "On Demand - Run this process when requested by AJAX"
- Select Next
- Enter the PL/SQL you want to run On Demand and click Create Process
Here is the code we will use:
BEGIN HTP.PRINT( (1+ABS(MOD(dbms_random.random,100))) ); END;
We are just pushing out a random number between 1 and 100 to the requestor of our Dynamic Action. You are just running PL/SQL and returning text to the caller, so you have all of the wonderful and warty things that come along with PL/SQL. Future tutorials will get fancier with this and show how to push CSV or JSON output for d3 to digest, but for now we'll keep things simple and return 1 number at a time.
You can also create Application Level processes if you need to re-use the process across multiple pages.
Step 2) Make the d3.js Library Available to Your HTML Page
In your HTML Header of the page add the following code. This is just for testing purposes right now and Mike and GitHub graciously allows folks to link to their files directly. After the proof of concept stage I would suggest hosting the .js on your own site to ensure availability and version stability.... well it will be as available as the rest of your site at least :)
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
Step 3) Enter d3.js JavaScript Code in Your Page to Create Chart
Add a HTML region to your page with the following code. Again thanks to Mike Bostock, creator of d3.js for the tutorial this code is based on. Check inline comments for explanations and highlights:
<div id="myviz"></div> <!-- Some CSS to make graphical styling easier --> <style type='text/css'> .chart { margin-left: 10px; } .chart rect { fill: steelblue; stroke: white; } </style> <!-- Heavy lifting is done here. --> <!-- Chart set-up and data gathering/redraw function definitions. --> <script type='text/javascript'> var w = 20, h = 200; var x = d3.scale.linear() .domain([0, 1]) .range([0, w]); var y = d3.scale.linear() .domain([0, 100]) .rangeRound([0, h]); var t = 1, // Time sequence for value number data = d3.range(33).map(next); // Initial data // Function to retrieve value from our PL/SQL Dynamic Action function next() { var ajaxReq = new htmldb_Get(null, $v('pFlowId'), 'APPLICATION_PROCESS=GetRandom', $v('pFlowStepId')); var gReturn = ajaxReq.get(); // Return a JS object with a time sequence and a value return { time: ++t, value: gReturn }; } // Create the chart object var mychart = d3.select("#myviz").append("svg") .attr("class", "chart") .attr("width", w * data.length - 1) .attr("height", h); // Draw a thin base line for chart mychart.append("line") .attr("x1", 0) .attr("x2", w * data.length) .attr("y1", h - .5) .attr("y2", h - .5) .style("stroke", "#000"); // Draw the initial chart redraw(); // What needs to take place after new data is available function redraw() { var rect = mychart.selectAll("rect") .data(data, function(d) { return d.time; }); // Enter rect.enter().insert("rect", "line") .attr("x", function(d, i) { return x(i + 1) - .5; }) .attr("y", function(d) { return h - y(d.value) - .5; }) .attr("width", w) .attr("height", function(d) { return y(d.value); }) .transition() .duration(1000) .attr("x", function(d, i) { return x(i) - .5; }); // Update rect.transition() .duration(1000) .attr("x", function(d, i) { return x(i) - .5; }); // Exit rect.exit().transition() .duration(1000) .attr("x", function(d, i) { return x(i - 1) - .5; }) .remove(); } </script> <!-- Timer code to trigger updates --> <script type='text/javascript'> // Set up a timer to perform update/refresh activities setInterval(function() { data.shift(); data.push(next()); redraw(); d3.timer.flush(); // avoid memory leak when in background tab }, 2500); </script>
Step 4) Run it!
Execute your page. The chart will be populated with a few columns worth of data, then every 2.5 seconds after that the Dynamic Action will be invoked to get a new column on the right and to retire a column from the left of the graph.
You will notice some chopiness may creep in when watching the video. Keep in mind the context of how everyting is working here. JavaScript code in the browser is responsible for waking up every 2.5 seconds and requesting the data from the Apex Server. If there is a sporadic network delay and the request or response is delayed then it may take more than 2.5 seconds inbetween updates. This will in turn cause an appearance that the next update took shorter than usual. This is just a risk of async communications used to get this data. If you had users complaining of lots of network issues and required a smooth transition every time you could probably implement a queueing/buffering mechanism to smooth things out.
But at this point you have your graph updating its data from a PL/SQL function that runs inside the database, a new technique to use for supplying dynamic information on your ApEx pages.