// // Copyright (C) 2015 FreeIPA Contributors see COPYING for license // 'use strict'; define([ 'dojo/_base/lang', 'dojo/_base/declare', 'dojo/on', 'dojo/Evented', './jquery', 'libs/d3' ], function(lang, declare, on, Evented, $, d3) { /** * Topology Graph module * @class * @singleton */ var topology_graph = { }; /** * Topology graph visualization * * @class */ topology_graph.TopoGraph = declare([Evented], { width: 960, height: 500, _colors: d3.scale.category10(), _svg : null, _path: null, _circle: null, _selected_link: null, _mousedown_link: null, /** * Nodes - IPA servers * id - int * * @property {Array} */ nodes: [], /** * Links between nodes * @property {Array} */ links: [], /** * List of suffixes * @property {Array} */ suffixes: [], /** * Initializes the graph * @param {HTMLElement} container container where to put the graph svg element */ initialize: function(container) { this._create_svg(container); this.update(this.nodes, this.links, this.suffixes); return; }, /** * Update the graph * @param {Array} nodes array of node objects * @param {Array} links array of link objects * @param {Array} suffixes array of suffixes */ update: function(nodes, links, suffixes) { var curr_trasform = this._get_stored_transformation(); var zoomed = function() { var translate = d3.event.translate; var scale = d3.event.scale; var transform = "translate(" + translate + ")scale(" + scale + ")"; this._svg.selectAll('g.shapes') .attr("transform", transform); this._store_current_transformation(); }.bind(this); // adds zoom behavior to the svg var zoom = d3.behavior.zoom() .translate(curr_trasform.translate) .scale(curr_trasform.scale) .scaleExtent([0.2, 1]) .on("zoom", zoomed); // delete all from svg this._svg.selectAll("*").remove(); this._svg.attr('width', this.width) .attr('height', this.height) .call(zoom); this.links = links; this.nodes = nodes; this.suffixes = suffixes; // load saved coordinates // node.fixed uses integer // this is useful when you need to store the original value and set // the node temporarily fixed for (var i=0,l=nodes.length; i 1 ? 1 : 0, // shift from center? spad = d.left ? 18 : 18, // source padding tpad = d.right ? 18 : 18, // target padding sourceX = d.source.x + (spad * ux) + off * nx * ns * s, sourceY = d.source.y + (spad * uy) + off * ny * ns * s, targetX = d.target.x - (tpad * ux) + off * nx * ns * s, targetY = d.target.y - (tpad * uy) + off * ny * ns * s, dr = s ? dist * Math.log10(dist) : 0; return 'M' + sourceX + ',' + sourceY + 'A' + dr + " " + dr + " 0 0 " + dir +" " + targetX + " " + targetY; }); this._circle.attr('transform', function(d) { self._save_node_info(d); return 'translate(' + d.x + ',' + d.y + ')'; }); }, _get_marker_name: function(suffix, start) { var name = suffix ? suffix.cn[0] : 'drag'; var arrow = start ? 'start-arrow' : 'end-arrow'; return name + '-' + arrow; }, /** * Markers on the end of links */ _add_marker: function(name, color, refX) { this._svg.append('svg:defs') .append('svg:marker') .attr('id', name) .attr('viewBox', '0 -5 10 10') .attr('refX', 6) .attr('markerWidth', 3) .attr('markerHeight', 3) .attr('orient', 'auto') .append('svg:path') .attr('d', refX) .attr('fill', color); }, /** * Suffix hint so user will know which links belong to which suffix */ _append_suffix_hint: function(suffix, x, y) { var color = d3.rgb(this._colors(suffix.cn[0])); this._svg.append('svg:text') .attr('x', x) .attr('y', y) .attr('class', 'suffix') .attr('fill', color) .text(suffix.cn[0]); }, /** * Defines link arrows and colors of suffixes(links) and nodes */ _define_shapes: function() { var name, color; var defs = this._svg.selectAll('defs'); defs.remove(); var x = 10; var y = 20; for (var i=0,l=this.suffixes.length; i> 1; self._layout.resume(); } // add new nodes var g = this._circle.enter() .append('svg:g') .on("dblclick", function(d) { // Stops propagation dblclick event to the zoom behavior. d3.event.preventDefault(); d3.event.stopPropagation(); //xor operation switch value of fixed from 1 to 0 and vice versa d.fixed = d.fixed ^ 1; self._layout.resume(); }) .call(drag); g.append('svg:circle') .attr('class', 'node') .attr('r', 12) .style('fill', function(d) { return self._colors(1); }) .style('stroke', function(d) { return d3.rgb(self._colors(1)).darker().toString(); }); // show node IDs g.append('svg:text') .attr('dx', 0) .attr('dy', 30) .attr('class', 'id') .attr('fill', '#002235') .text(function(d) { return d.id.split('.')[0]; }); // remove old nodes self._circle.exit().remove(); // get previously set position and scale of the graph and move current // graph to the proper position var curr_transform = this._get_stored_transformation(); var transform = "translate(" + curr_transform.translate + ")scale(" + curr_transform.scale + ")"; this._svg.selectAll('g.shapes') .attr("transform", transform); }, resize: function(height, width) { if (!(isNaN(height) || isNaN(width))) { this.height = height < 0 ? 0 : height; this.width = width < 0 ? 0 : width; if (this._svg) { this._svg .attr('width', this.width) .attr('height', this.height); } } }, constructor: function(spec) { lang.mixin(this, spec); } }); return topology_graph; });