Updated fn-spec to include classNames
Removed Classnames file and added code to fn.js
Fixed typo dimentions to dimensions
Moved Device/Link logic from Topo2D3 into the model
Model now calls onChange when any property is changed via the set Method
WIP - Added d3 force layout for devices and lines
Change-Id: I4d1afd3cd4cecf2f719e27f4be5d1e874bd9e342
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
new file mode 100644
index 0000000..8cfaadf
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Topology Layout Module.
+ Module that contains the d3.force.layout logic
+ */
+
+(function () {
+ 'use strict';
+
+ var $log, sus, t2rs, t2d3, t2vs;
+
+ var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
+ var link, linkLabel, node;
+
+ var nodes, links;
+
+ var force;
+
+ // default settings for force layout
+ var defaultSettings = {
+ gravity: 0.4,
+ friction: 0.7,
+ charge: {
+ // note: key is node.class
+ device: -8000,
+ host: -5000,
+ _def_: -12000
+ },
+ linkDistance: {
+ // note: key is link.type
+ direct: 100,
+ optical: 120,
+ hostLink: 3,
+ _def_: 50
+ },
+ linkStrength: {
+ // note: key is link.type
+ // range: {0.0 ... 1.0}
+ //direct: 1.0,
+ //optical: 1.0,
+ //hostLink: 1.0,
+ _def_: 1.0
+ }
+ };
+
+ // configuration
+ var linkConfig = {
+ light: {
+ baseColor: '#939598',
+ inColor: '#66f',
+ outColor: '#f00'
+ },
+ dark: {
+ // TODO : theme
+ baseColor: '#939598',
+ inColor: '#66f',
+ outColor: '#f00'
+ },
+ inWidth: 12,
+ outWidth: 10
+ };
+
+ // internal state
+ var settings, // merged default settings and options
+ force, // force layout object
+ drag, // drag behavior handler
+ network = {
+ nodes: [],
+ links: [],
+ linksByDevice: {},
+ lookup: {},
+ revLinkToKey: {}
+ },
+ lu, // shorthand for lookup
+ rlk, // shorthand for revLinktoKey
+ showHosts = false, // whether hosts are displayed
+ showOffline = true, // whether offline devices are displayed
+ nodeLock = false, // whether nodes can be dragged or not (locked)
+ fTimer, // timer for delayed force layout
+ fNodesTimer, // timer for delayed nodes update
+ fLinksTimer, // timer for delayed links update
+ dim, // the dimensions of the force layout [w,h]
+ linkNums = []; // array of link number labels
+
+ var tickStuff = {
+ nodeAttr: {
+ transform: function (d) {
+ var dx = isNaN(d.x) ? 0 : d.x,
+ dy = isNaN(d.y) ? 0 : d.y;
+ return sus.translate(dx, dy);
+ }
+ },
+ linkAttr: {
+ x1: function (d) { return d.get('position').x1; },
+ y1: function (d) { return d.get('position').y1; },
+ x2: function (d) { return d.get('position').x2; },
+ y2: function (d) { return d.get('position').y2; }
+ },
+ linkLabelAttr: {
+ transform: function (d) {
+ var lnk = tms.findLinkById(d.get('key'));
+ if (lnk) {
+ return t2d3.transformLabel(lnk.get('position'));
+ }
+ }
+ }
+ };
+
+ function init(_svg_, forceG, _uplink_, _dim_, opts) {
+
+ $log.debug("Initialising Topology Layout");
+
+ settings = angular.extend({}, defaultSettings, opts);
+
+ linkG = forceG.append('g').attr('id', 'topo-links');
+ linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
+ numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
+ nodeG = forceG.append('g').attr('id', 'topo-nodes');
+ portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
+
+ link = linkG.selectAll('.link');
+ linkLabel = linkLabelG.selectAll('.linkLabel');
+ node = nodeG.selectAll('.node');
+
+ force = d3.layout.force()
+ .size(t2vs.getDimensions())
+ .nodes(t2rs.regionNodes())
+ .links(t2rs.regionLinks())
+ .gravity(settings.gravity)
+ .friction(settings.friction)
+ .charge(settings.charge._def_)
+ .linkDistance(settings.linkDistance._def_)
+ .linkStrength(settings.linkStrength._def_)
+ .on('tick', tick);
+ }
+
+ function tick() {
+ // guard against null (which can happen when our view pages out)...
+ if (node && node.size()) {
+ node.attr(tickStuff.nodeAttr);
+ }
+ if (link && link.size()) {
+ link.call(calcPosition)
+ .attr(tickStuff.linkAttr);
+ // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
+ }
+ if (linkLabel && linkLabel.size()) {
+ linkLabel.attr(tickStuff.linkLabelAttr);
+ }
+ }
+
+ function update() {
+ _updateNodes();
+ _updateLinks();
+ }
+
+ function _updateNodes() {
+
+ var regionNodes = t2rs.regionNodes();
+
+ // select all the nodes in the layout:
+ node = nodeG.selectAll('.node')
+ .data(regionNodes, function (d) { return d.get('id'); });
+
+ var entering = node.enter()
+ .append('g')
+ .attr({
+ id: function (d) { return sus.safeId(d.get('id')); },
+ class: function (d) { return d.svgClassName() },
+ transform: function (d) {
+ // Need to guard against NaN here ??
+ return sus.translate(d.node.x, d.node.y);
+ },
+ opacity: 0
+ })
+ // .on('mouseover', tss.nodeMouseOver)
+ // .on('mouseout', tss.nodeMouseOut)
+ .transition()
+ .attr('opacity', 1);
+
+ entering.filter('.device').each(t2d3.deviceEnter);
+ entering.filter('.host').each(t2d3.hostEnter);
+
+ // operate on both existing and new nodes:
+ // node.filter('.device').each(function (device) {
+ // t2d3.updateDeviceColors(device);
+ // });
+ }
+
+ function _updateLinks() {
+
+ // var th = ts.theme();
+ var regionLinks = t2rs.regionLinks();
+
+ link = linkG.selectAll('.link')
+ .data(regionLinks, function (d) { return d.get('key'); });
+
+ // operate on existing links:
+ link.each(function (d) {
+ // this is supposed to be an existing link, but we have observed
+ // occasions (where links are deleted and added rapidly?) where
+ // the DOM element has not been defined. So protect against that...
+ if (d.el) {
+ restyleLinkElement(d, true);
+ }
+ });
+
+ // operate on entering links:
+ var entering = link.enter()
+ .append('line')
+ .call(calcPosition)
+ .attr({
+ x1: function (d) { return d.get('position').x1; },
+ y1: function (d) { return d.get('position').y1; },
+ x2: function (d) { return d.get('position').x2; },
+ y2: function (d) { return d.get('position').y2; },
+ stroke: linkConfig['light'].inColor,
+ 'stroke-width': linkConfig.inWidth
+ });
+
+ entering.each(t2d3.linkEntering);
+
+ // operate on both existing and new links:
+ //link.each(...)
+
+ // add labels for how many links are in a thick line
+ // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
+
+ // apply or remove labels
+ // t2d3.applyLinkLabels();
+
+ // operate on exiting links:
+ link.exit()
+ .attr('stroke-dasharray', '3 3')
+ .attr('stroke', linkConfig['light'].outColor)
+ .style('opacity', 0.5)
+ .transition()
+ .duration(1500)
+ .attr({
+ 'stroke-dasharray': '3 12',
+ 'stroke-width': linkConfig.outWidth
+ })
+ .style('opacity', 0.0)
+ .remove();
+ }
+
+ function calcPosition() {
+ var lines = this,
+ linkSrcId,
+ linkNums = [];
+
+ lines.each(function (d) {
+ if (d.get('type') === 'hostLink') {
+ d.set('position', getDefaultPos(d));
+ }
+ });
+
+ function normalizeLinkSrc(link) {
+ // ensure source device is consistent across set of links
+ // temporary measure until link modeling is refactored
+ if (!linkSrcId) {
+ linkSrcId = link.source.id;
+ return false;
+ }
+
+ return link.source.id !== linkSrcId;
+ }
+
+ lines.each(function (d) {
+ d.set('position', getDefaultPos(d));
+ });
+ }
+
+ function getDefaultPos(link) {
+
+ return {
+ x1: link.get('source').x,
+ y1: link.get('source').y,
+ x2: link.get('target').x,
+ y2: link.get('target').y
+ };
+ }
+
+ function setDimensions() {
+ if (force) {
+ force.size(t2vs.getDimensions());
+ }
+ }
+
+
+ function start() {
+ force.start();
+ }
+
+ angular.module('ovTopo2')
+ .factory('Topo2LayoutService',
+ [
+ '$log', 'SvgUtilService', 'Topo2RegionService',
+ 'Topo2D3Service', 'Topo2ViewService',
+
+ function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
+
+ $log = _$log_;
+ t2rs = _t2rs_;
+ t2d3 = _t2d3_;
+ t2vs = _t2vs_;
+ sus = _sus_;
+
+ return {
+ init: init,
+ update: update,
+ start: start,
+
+ setDimensions: setDimensions
+ }
+ }
+ ]
+ );
+})();