Monday, February 05, 2018

Oracle JET - Interactive Force Directed Layout

When developing a user interface for modern enterprise applications one of the challenging tasks is to unlock the data within the enterprise and provide it in a usable manner to the end users. Commonly enterprises have vast amounts of data which are so compartmented users are unable to access them and in some cases the data is presented in ways that it makes it hard for users to grasp the bigger picture.

When developing modern enterprise applications one of the tasks that should be undertaken is ensuring data is not only accessible however that the data is also presented in a way that it makes sense to users. unlocking data in ways that it is easy to understand and that it is easy and intuitive to work with.

When you develop you enterprise application UI based upon Oracle JET you can extend Oracle JET with all different kind of javascript and HTML5 based options. In the below example we showcase an Interactive Force Directed Layout to represent relations between datapoints in an interactive manner.



The above is based upon GOjs in combination with Oracle JET. GoJS is a feature-rich JavaScript library for implementing custom interactive diagrams and complex visualizations across modern web browsers and platforms. GoJS makes constructing JavaScript diagrams of complex nodes, links, and groups easy with customizable templates and layouts.


As you can see from the above screenshot the demo application is build based upon a demo version of GOjs. When you like to use GOjs in a commercial manner you will have to purchase a license. However, a version is available to test the functionality.

To enable the above you will have to download go.js from github and include it into the Oracle JET structure. In our case I included the go.js library under js/libs/custom .

To get the above example running the below code can be used as part of the Oracle JET html code:

<script src="js/libs/custom/go.js"></script>
<script id="code">
// This variation on ForceDirectedLayout does not move any selected Nodes
// but does move all other nodes (vertexes).
function ContinuousForceDirectedLayout() {
go.ForceDirectedLayout.call(this);
this._isObserving = false;
}
go.Diagram.inherit(ContinuousForceDirectedLayout, go.ForceDirectedLayout);
/** @override */
ContinuousForceDirectedLayout.prototype.isFixed = function(v) {
return v.node.isSelected;
}
// optimization: reuse the ForceDirectedNetwork rather than re-create it each time
/** @override */
ContinuousForceDirectedLayout.prototype.doLayout = function(coll) {
if (!this._isObserving) {
this._isObserving = true;
// cacheing the network means we need to recreate it if nodes or links have been added or removed or relinked,
// so we need to track structural model changes to discard the saved network.
var lay = this;
this.diagram.addModelChangedListener(function (e) {
// modelChanges include a few cases that we don't actually care about, such as
// "nodeCategory" or "linkToPortId", but we'll go ahead and recreate the network anyway.
// Also clear the network when replacing the model.
if (e.modelChange !== "" ||
(e.change === go.ChangedEvent.Transaction && e.propertyName === "StartingFirstTransaction")) {
lay.network = null;
}
});
}
var net = this.network;
if (net === null) { // the first time, just create the network as normal
this.network = net = this.makeNetwork(coll);
} else { // but on reuse we need to update the LayoutVertex.bounds for selected nodes
this.diagram.nodes.each(function (n) {
var v = net.findVertex(n);
if (v !== null) v.bounds = n.actualBounds;
});
}
// now perform the normal layout
go.ForceDirectedLayout.prototype.doLayout.call(this, coll);
// doLayout normally discards the LayoutNetwork by setting Layout.network to null;
// here we remember it for next time
this.network = net;
}
// end ContinuousForceDirectedLayout
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
initialAutoScale: go.Diagram.Uniform, // an initial automatic zoom-to-fit
contentAlignment: go.Spot.Center, // align document to the center of the viewport
layout:
$(ContinuousForceDirectedLayout, // automatically spread nodes apart while dragging
{ defaultSpringLength: 30, defaultElectricalCharge: 100 }),
// do an extra layout at the end of a move
"SelectionMoved": function(e) { e.diagram.layout.invalidateLayout(); }
});
// dragging a node invalidates the Diagram.layout, causing a layout during the drag
myDiagram.toolManager.draggingTool.doMouseMove = function() {
go.DraggingTool.prototype.doMouseMove.call(this);
if (this.isActive) { this.diagram.layout.invalidateLayout(); }
}
// define each Node's appearance
myDiagram.nodeTemplate =
$(go.Node, "Auto", // the whole node panel
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "Circle",
{ fill: "CornflowerBlue", stroke: "black", spot1: new go.Spot(0, 0, 5, 5), spot2: new go.Spot(1, 1, -5, -5) }),
$(go.TextBlock,
{ font: "bold 10pt helvetica, bold arial, sans-serif", textAlign: "center", maxSize: new go.Size(100, NaN) },
new go.Binding("text", "text"))
);
// the rest of this app is the same as samples/conceptMap.html
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
$(go.Shape, // the link shape
{ stroke: "black" }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{ fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null }),
$(go.TextBlock, // the label text
{ textAlign: "center",
font: "10pt helvetica, arial, sans-serif",
stroke: "#555555",
margin: 4 },
new go.Binding("text", "text"))
)
);
// create the model for the concept map
var nodeDataArray = [
{ key: 1, text: "Concept Maps" },
{ key: 2, text: "Organized Knowledge" },
{ key: 3, text: "Context Dependent" },
{ key: 4, text: "Concepts" },
{ key: 5, text: "Propositions" },
{ key: 6, text: "Associated Feelings or Affect" },
{ key: 7, text: "Perceived Regularities" },
{ key: 8, text: "Labeled" },
{ key: 9, text: "Hierarchically Structured" },
{ key: 10, text: "Effective Teaching" },
{ key: 11, text: "Crosslinks" },
{ key: 12, text: "Effective Learning" },
{ key: 13, text: "Events (Happenings)" },
{ key: 14, text: "Objects (Things)" },
{ key: 15, text: "Symbols" },
{ key: 16, text: "Words" },
{ key: 17, text: "Creativity" },
{ key: 18, text: "Interrelationships" },
{ key: 19, text: "Infants" },
{ key: 20, text: "Different Map Segments" }
];
var linkDataArray = [
{ from: 1, to: 2, text: "represent" },
{ from: 2, to: 3, text: "is" },
{ from: 2, to: 4, text: "is" },
{ from: 2, to: 5, text: "is" },
{ from: 2, to: 6, text: "includes" },
{ from: 2, to: 10, text: "necessary\nfor" },
{ from: 2, to: 12, text: "necessary\nfor" },
{ from: 4, to: 5, text: "combine\nto form" },
{ from: 4, to: 6, text: "include" },
{ from: 4, to: 7, text: "are" },
{ from: 4, to: 8, text: "are" },
{ from: 4, to: 9, text: "are" },
{ from: 5, to: 9, text: "are" },
{ from: 5, to: 11, text: "may be" },
{ from: 7, to: 13, text: "in" },
{ from: 7, to: 14, text: "in" },
{ from: 7, to: 19, text: "begin\nwith" },
{ from: 8, to: 15, text: "with" },
{ from: 8, to: 16, text: "with" },
{ from: 9, to: 17, text: "aids" },
{ from: 11, to: 18, text: "show" },
{ from: 12, to: 19, text: "begins\nwith" },
{ from: 17, to: 18, text: "needed\nto see" },
{ from: 18, to: 20, text: "between" }
];
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
}
function reload() {
//myDiagram.layout.network = null;
var text = myDiagram.model.toJson();
myDiagram.model = go.Model.fromJson(text);
//myDiagram.layout =
// go.GraphObject.make(ContinuousForceDirectedLayout, // automatically spread nodes apart while dragging
// { defaultSpringLength: 30, defaultElectricalCharge: 100 });
}
</script>
To ensure the script is loaded you will have to ensure the body tag contains an onload="init()" statement and you will have to ensure that you have a div element with id="myDiagramDiv" in your page.

if we look at the script used in the example above you will notice that the data points are hard coded into the script. In a real world situation this would not be preferable and you would use a method to load the data from a REST API. 

No comments: