A Guide to Diagram – Part 7 – Configurable Layouts

In the previous three parts in this series on <dvt:diagram> I've introduced the mechanics of laying out your nodes and links using a JavaScript layout function.  As we've seen, we can introduce some flexibility through intelligent use of the available node and label sizing metrics, however, in this article I want to explore how you can go one step further and pass contextual information into the JavaScript function to make the layout itself configurable and, possibly more importantly, make the layout sensitive to the actual data that is being represented.

Providing Layout Level Configuration

Let's start out with exploring how you can pass configuration information into the diagram as a whole.

To continue in the theme of the previous articles, I'll take the example LearningDiagramLayouts.simpleVertical() layout function and build on a version of that. I've called this new layout LearningDiagramLayouts.columnVertical and you'll find it linked at the end of the article for reference. To make things a little more interesting I've added a couple more nodes to the data model and altered the node size. Here's the starting point, you'll notice that the existing (simpleVertical) layout handled the extra nodes and different sizing without a problem, so that's a good start.

Starting point in Pert 7 with the extended model

As a next step, to enhance this diagram, I want to be able to stagger the nodes into virtual columns so that the information can be displayed in a smaller amount of vertical space. However, rather than just creating a layout to produce say a two-column view and then a separate third layout for a three-column view, the aim is to create a single adaptable layout that can be configured for any number of columns and then lay the nodes out in reading order.

Here's the end result (as with the previous examples this is a purely illustrative layout, I'm not claiming at it will be useful):

Adaptable multi-column layout

That will then be further extended to make the virtual column width configurable as well:

Varying virtual column widths

In technical terms, the loop that positions the nodes now has to calculate a x position for the node rather than always setting that to zero as we did before. This is accomplished using two variables in the function, virtualColumns, and virtualColumnsWidth.

for (var i = 0;i < nodeCount;i++) {
  var node = layoutContext.getNodeByIndex(i);
  ...
  xPos = ((i)%virtualColumns) * virtualColumnWidth;
  node.setPosition(new DvtDiagramPoint(xPos, yPos));
  ...
So, how do we pass the values for virtualColumns and  virtualColumnWidth into the JavaScript? Let's look at that.

Layout Attributes

The good news is that passing layout configuration attributes like this is really easy to do.  To pass values into a layout, all you need to do is to nest <f:attribute> tags inside of the existing <dvt:clientLayoutTag> in your page. For example:
<dvt:clientLayout name="learningVerticalInColumns" 
                  method="LearningDiagramLayouts.columnVertical"
                  featureName="LearningDiagramLayouts">
   <f:attribute name="columns" value="3"/>
   <f:attribute name="columnWidth" value="100"/>
</dvt:clientLayout>                 
You can supply literal values as I have here (3 and 100 respectively), or use expression language. 
Then, in your JavaScript, these values can be obtained from the layoutContext using the getLayoutAttributes() API referring to the same names that were supplied to <f:attribute>:
    ...
    var virtualColumns = 2;
    var virtualColumnWidth = 100;
    if (layoutContext.getLayoutAttributes()) {
      if (layoutContext.getLayoutAttributes()["columns"]) {
        virtualColumns = 
          parseFloat(layoutContext.getLayoutAttributes()["columns"]);
      }
      if (layoutContext.getLayoutAttributes()["columnWidth"]) {
        virtualColumnWidth = 
          parseFloat(layoutContext.getLayoutAttributes()["columnWidth"]);
      }
    }
    ...
You'll notice here that I have defined a sensible set of default values for the settings, this is to ensure that the parameters are effectively optional for the developer.  Additionally, when passing numeric values in as attributes to a layout you must be sure to use the parseInt() and parseFloat() JavaScript functions to ensure the correct datatype is received. 

Node Level Attributes

The attributes that I've used so far, are global to the layout, but what if you want to control the diagram to a much finer level?  Generally this will mean passing information that will be needed in the context of an individual node. 
To do this, you extend your use of the <dvt:clientLayout>, and nest within it, an additional tag called <dvt:nodeAttributes>.  The nodeAttributes tag takes a node component id in its node attribute.  This will correspond to the JSF id attribute on one of the <dvt:diagramNode> tags nested within the diagram.  So if you have multiple node type definitions within your diagram you will need to configure the additional attributes for each one.  This gives you the flexibility to pass different contextual information to different node types. Within the nodeAttributes tag you then have a series of <f:attribute> children. 
To illustrate this I've extended the datamodel that we've been using so far, to add a new attribute called preferredColumn to the SampleNode.java object.  This is the value that we want to pass down into the layout.
Here's the new definition for the Java SampleNode object:
public class SampleNode {
  private int _uniqueNodeId;
  private String _nodeLabel;
  private int _preferredColumn;

  public SampleNode(int id, String label, int column){
    _uniqueNodeId = id;
    _nodeLabel = label;
    _preferredColumn = column;
  }

  public int getUniqueNodeId() {
    return _uniqueNodeId;
  }
  public String getNodeLabel() {
    return _nodeLabel;
  }
  public int getPreferredColumn() {
    return _preferredColumn;
  }
}
Now to pass that new bit of information into the layout. The clientLayout definition now looks like this:
<dvt:diagram id="d1" summary="Example Diagram" layout="learningVerticalInColumns">
  <dvt:clientLayout name="learningVerticalInColumns" 
                    method="LearningDiagramLayouts.columnVertical"
                    featureName="LearningDiagramLayouts">
    <f:attribute name="columns" value="3"/>
    <f:attribute name="columnWidth" value="200"/>
    <dvt:nodeAttributes node="dn1">
      <f:attribute name="displayColumn" value="preferredColumn"/>
    </dvt:nodeAttributes>
  </dvt:clientLayout>
You'll notice that the <f:attribute> tag has a value attribute set to the name of the property in the node object. This value is not supplied as an EL expression. It directly names the property in the underlying model object.  As such, the usage of <f:attribute> for the main layout is slightly different from this usage embedded within a nodeAttributes, it passes by reference, not value. You cannot pass hardcoded or EL based values through the value property, although if your EL resolves correctly to the name of an attribute in the underlying model you're OK. 

Accessing Node Level Attributes in JavaScript

The process for accessing a node level attribute in JavaScript  is much the same as for the top level layout attributes. The node object exposes a getLayoutAttributes() API that works in just the same way.  So we can update our node-positioning loop slightly to use a passed in column hint:
for (var i = 0;i < nodeCount;i++) {
  var node = layoutContext.getNodeByIndex(i);
  ...
  //Set a default value 
  var candidateColumn = (i) % virtualColumns;
  //Check to see if the node asks for a different column
  if (node.getLayoutAttributes()) {
    if (node.getLayoutAttributes()["displayColumn"]) {
      candidateColumn = parseInt(node.getLayoutAttributes()["displayColumn"]);
    }
  }
  xPos = candidateColumn * virtualColumnWidth;
  node.setPosition(new DvtDiagramPoint(xPos, yPos));
  ...
And this results in an even more flexible layout with a swim-lane like effect based on the passed in preferences (In this case nodes First and Second ask for column 0, Third and Forth for column 2 and Fifth for column 1):

Swimlanes!

The Power of Node Level Layout Attributes

Node level layout attributes allow you to effectively extend the logical model in JavaScript for a particular node and make layout-positioning decisions based on that extended model.  Using them means that you are no longer a slave to the iteration order of the nodes as they are delivered to you in the getNodes() array. In an extreme example you could even pass the desired x and y coordinates into the layout with the node, and effectively store the layout in your server side model rather than re-calculating it.

The Full JavaScript Source

Now that we have developed a second layout for this columnVertical example, I've linked the source for that here

In the Next Article

Over the last seven articles I've covered the core artifacts and principles of layouts within diagram.  The next topic builds on many of the concepts that you've already read about and dives into node groups.
Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

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.

Search

Categories
Archives
« April 2015
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  
       
Today