A Guide to Diagram – Part 4 – Starting with Custom Layouts

As I discussed at the beginning of this series, there was no way that, out of the box, diagram would be able to achieve every possible layout that might be demanded of it. Therefore, the whole process of defining the layout is opened up for you to plug your own implementation in. 
In the next four articles in this series I'll be exploring this topic, starting with the basics in the first two, and then building up the complexity as we go along.
As well as following along through the coding in these articles, you can also check out the layout example in the diagram on-line demos.  This provides some alternative examples that will add to your overall knowledge 

Getting Started 

If you look back to Part 2 of this series, recall that we plugged in an example layout using a <dvt:clientLayout> tag nested within the <dvt:diagram>. As a point of fact, you can actually define multiple layouts within a single diagram, something we'll discuss in a later article on containers, so you may have more than one clientLayout tag, but for now let's stick to just one. 
The <dvt:clientLayout> tag has three attributes:
  • name – an identifier that you use for the layout.  This value is then referenced from the layout property of the diagram tag and which of course tells the diagram which layout to use.
  • method – identifies the name of a JavaScript function that will perform the layout.  We'll look at that function in detail later on in the article.
  • featureName – lets the framework identify exactly which JavaScript source files are required to locate and run the function identified in the method attribute.

Defining a Feature

Within the ADF Faces runtime environment, a feature is a named related group of JavaScript files used by the framework.  You may have come across features whilst tuning your ADF applications, using it to  define exactly which sets of JavaScript files are initially downloaded to the browser and which are only downloaded on demand.  If you're not familiar with the concept don't worry, I'll cover everything that you need here. 
So the first step to defining your feature is the creation of an adf-js-features.xml file.  This file needs to be put in your ViewController/src/META-INF directory. That directory should already exist and contain an adf-settings.xml file.
So create a new XML file with the correct name in this location and define the contents like this:
<?xml version="1.0" encoding="utf-8"?>
<features xmlns="http://xmlns.oracle.com/adf/faces/feature">  
You can see that what we have done here is define a feature name and associate that name with a particular JavaScript file on the server side classpath.  If required, you can define multiple <feature-name> entries to add several JavaScript files, and you can also reference other features using the <feature-dependency> element.  In this case though, we just have the one JavaScript file to worry about. 

Defining the JavaScript 

The location defined in the adf-js-features file has to point to a location on the Java classpath, so you need to create your JavaScript file under the projects /scr directory.  In this example ViewController/src/js/layouts.
If you create a js file from the new gallery into this location, it will pop up a warning:
JDeveloper Warnign Dialog
Just select Yes to proceed. At runtime, the framework will ensure that your JavaScript file makes it to the browser.
Recall from the <dvt:clientLayout> component, that we need to have defined a method name which will be the actual function that will be called by diagram to handle the layout. So within the new learningDiagram.js file create a starter function thus:
/* Sample layout used for the diagram series of blog articles */
var LearningDiagramLayouts = {
  simpleVertical : function(layoutContext){
    alert("Hello from your layout code ");
Here I can defined a function with the name of LearningDiagramLayouts.simpleVertical  You'll notice two things:
  1. It does not actually do anything yet – just pops up an alert. Don't worry though, once we have everything wired up we'll do something more constructive here. 
  2. The function is accepting an argument called layoutContext.  This is a key object that will provide the function with all of the information that it needs about the nodes and links that it needs to lay out.

Wiring the Diagram to the layout

Now that we have a custom layout of our own defined we can alter the diagram configuration to use it:
<dvt:diagram id="d1" summary="Example Diagram" layout="learningVertical">
  <dvt:clientLayout name="learningVertical" 
So here, I have defined a arbitrary name of learningVertical to point to the LearningDiagramLayouts.simpleVertical() function in the JavaScript file pointed to by the LearningDiagramLayouts feature definition.

Configuring Your Development Environment

If you just run the page as it stands you'll probably see a stack trace in the console complaining about a missing resource error.  The framework will not be able to find the .js file you've defined. Why?  Well we've put it in the /src directory and that location is not on the classpath at runtime.  We could just put the .js files under the /classes output directory, but then it would be blown away every time you carry out a clean of the project, not a great idea!
Fortunately, we can leave the file safely under the /src directory but tell JDeveloper to copy it across to the /classes directory with every compile.
To do this, double click the project in the Application Navigator to bring up the project properties. Select the Compiler node and update the list of file types shown in the "Copy File Types to Output Directory" field to include .js files (use semi-colon as a delimiter)
Configuring the compile option
Now, whenever, you make a change to the JavaScript, just remember to hit the compile key, or button on the toolbar, to cause the file to be copied across to the /classes directory.  

A Note About your Runtime  and Debugging Environment 

When we are creating layouts, it's all JavaScript code, so you'll probably end up debugging using a tool such as Chrome Developer Tools or FireBug.  These tools make JavaScript debugging really simple, so it's well worth spending a bit of time learning to use them, rather than falling back to using alert() statements.
One thing to be cognizant of is the fact that browsers really love to cache your JavaScript, so if it seems like you're seeing an old version of the code then it's time to clear your browser cache.  Tools like Chrome Developer Tools have special modes that you can switch on to prevent caching all together, so it's a great idea to take advantage of that and save yourself from much head scratching.  

Defining a Basic Layout 

At the moment the layout function LearningDiagramLayouts.simpleVertical() actually does no layout, it just pops up an alert and all the nodes remain huddled in the center of the screen. So let's start to build this simple layout.

By the end of the first example, if you follow along, you'll end up with a layout that distribute the nodes vertically like this:

The final layout

Initially, I'll  start off by hardcoding distances and sizes but as we go along the code will evolve and become more dynamic.

Positioning Your Nodes

The basic process of laying out a diagram is to loop though an array of nodes and an array of links and set positioning information on each of them. So let's start off with nodes. 
The layout context object that is passed to the layout function contains the information about the number of nodes, using the getNodeCount() API, and provides access to each node object using the getNodeByIndex() API. Using these, you can loop through the nodes to access each in turn.
Once you have a node, you can call its setPosition() API to locate it on the diagram space. The position is passed in the form of a DvtDiagramPoint() object which defines the simple x and y coordinates. 
It's worth noting at this point that we're working in relative units here, not absolute.  So if you set a node at point 200,200 in the diagram this does not imply a point at exactly at 200px / 200px relative to the top left hand corner of the diagram component.  The actual position will depend on the zoom level that the diagram is currently operating at, and the setting of the viewport on the diagram.  These are both topics which we'll cover in later articles, so for now just remember that you are just working in relative units. 
So, with the three APIs you've seen so far (getNodeCount, getNodeByIndex and setPosition) you have enough knowledge to do something.  Here's your very first layout:
var LearningDiagramLayouts = {
  simpleVertical : function(layoutContext){
    var nodeGap = 50;
    var nodeCount = layoutContext.getNodeCount();
    var xPos = 0;
    var yPos = 0;
    for (var i = 0;i<nodeCount;i++) {
      var node = layoutContext.getNodeByIndex(i);
      node.setPosition(new DvtDiagramPoint(xPos, yPos));
      yPos += nodeGap;
So in this case the nodes are all at position 0 on the x axis and then incrementally 50 apart vertically. Here's what it looks like:
Nodes vertically distributed
So some progress. The nodes are distributed vertically; however, there's no sign of the link,s and the labels are overlapping the nodes themselves.  As we're dealing with just the nodes at the moment, let's concentrate on sorting out those labels before we move on.
Positioning the labels is just like positioning the nodes themselves.  There is a get/setLabelPosition() API on the node which takes a DvtDiagramPoint to define the new location for the label. 
So by adding a single line, you can offset the labels to the right of their nodes using the same fixed gap size:
node.setLabelPosition(new DvtDiagramPoint((xPos + nodeGap) , yPos));
And that sorts the label out nicely, although we still have a bit of a mess behind the first node that represents all of the links and link labels.
Labels Corrected

Positioning Your Links

No surprises here, the process for laying out links is pretty similar to that of nodes.  You can obtain a count of the number of links from the LayoutContext object using the getLinkCount() API and then loop through them using getLinkByIndex()
Labels are handled in exactly the same way as for the node, so you'll see the code there is very similar, although in this case I'm offsetting the label to the left, approximately midway along the link
When positioning an actual link, however, you have two coordinates to worry about; a position for the start and the position for the end. This is done through the setPoints() API on the link object which takes an array of x,y coordinates rather than just a single pair.
So, here's the next part of the code where you loop through the links as well and set the positions of the links and link labels:
   var nodeSize = 20;
   var startY = nodeSize;
   var startX = nodeSize/2;
   var endY = nodeGap;
   var endX = startX;
   for (var j = 0;j<linkCount;j++) {
      var link = layoutContext.getLinkByIndex(j);
      link.setPoints([startX, startY, endX, endY]);
      link.setLabelPosition(new DvtDiagramPoint((startX - nodeGap) , (startY + 20)));
      startY += nodeGap;
      endY +=nodeGap;
And that combined with the nodes loop results in:
Links moved
So we've made some considerable progress, but there are certainly some issues still, not least that the link "Gamma" leaves from "Third Node" but never arrives at it's destination of "First Node". However, it's a start!

In the Next Article

Now we have the basics of how the layout function works we'll start to look at finishing off this basic layout and in the process remove all the hardcoded sizes from the mix.


Post a Comment:
  • HTML Syntax: NOT allowed

Oracle Data Visualizations provide a broad range of beautiful, interactive components for viewing and understanding data. This blog covers topics on the new features in Oracle Data Visualization components and how-to articles on advanced functionality.


« March 2015