// // 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, _adder_anim_duration: 200, _adder_inner_radius: 15, _adder_outer_radius: 30, _colors: d3.scale.category10(), _svg : null, _path: null, _circle: null, _create_agreement: null, _selected_link: null, _mousedown_link: null, _source_node: null, _source_node_html: null, _target_node: null, _drag_line: 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(); } function add_labels(type, color, adder_group) { var label_radius = 3; var plus = adder_group .append('text') .classed('plus', true) .classed(type + '_plus', true) .text('\uf067'); var label = adder_group.append('path') .attr('id', type + '_label'); if (type === 'ca') { plus.attr('dx', '18') .attr('dy', '4'); var adder_label = adder_group.append('text') .append('textPath') .classed('adder_label', true) .style('fill', color) .attr('xlink:href', '#' + type + '_label') .text(type); var str_size = self._count_string_size(type, 'adder_label'); var str_translate = str_size + self._adder_outer_radius + 3; label.attr('d', 'M 33 3 L ' + str_translate + ' 3'); adder_group.insert('rect', 'text') .attr('x', '33') .attr('y', '-11') .attr('rx', label_radius) .attr('ry', label_radius) .attr('width', str_size) .attr('height', '18') .style("fill", "white"); } else { plus.attr('dx', '-26') .attr('dy', '4'); adder_label = adder_group.append('text') .append('textPath') .classed('adder_label', true) .style('fill', color) .attr('xlink:href', '#' + type + '_label') .text(type); str_size = self._count_string_size(type, 'adder_label'); str_translate = str_size + self._adder_outer_radius + 3; label.attr('d', 'M -' + str_translate + ' 3 L -33 3'); adder_group.insert('rect', 'text') .attr('x', '-'+str_translate) .attr('y', '-11') .attr('rx', label_radius) .attr('ry', label_radius) .attr('width', str_size) .attr('height', '18') .style('fill', 'white'); } } function create_semicircle(d, type) { var color = d3.rgb(self._colors(type)).toString(); var adder_group = d3.select(this).select('g'); var scale = '1.05'; adder_group.append("path") .classed(type+'_adder', true) .classed('adder', true) .attr("d", d[type + '_adder']) .attr("fill", color) .on('mouseover', function(d) { window.clearTimeout(d._timeout_hide); d3.select(this).attr('transform', 'scale('+scale+')'); adder_group.select('text.' + type + '_plus') .attr('transform', 'scale('+scale+')'); }) .on('mouseout', function(d) { d3.select(this).attr('transform', ''); adder_group.select('text.' + type + '_plus') .attr('transform', ''); }) .on('click', function(d) { d3.event.preventDefault(); d3.event.stopPropagation(); self.emit('link-selected', { link: null }); hide_semicircles.bind(this, d)(); // select node if (!self._source_node) { self._source_node = d; self._source_node_html = d3.select(this) .select('circle').node(); self._create_agreement = true; } self._selected_link = null; var translate = self._get_stored_transformation(); var x = self._source_node.x; var y = self._source_node.y; // add position of node + translation of whole graph + relative // position of the mouse var mouse_x = d.x + d3.mouse(this)[0]; var mouse_y = d.y + d3.mouse(this)[1]; // reposition drag line self._drag_line .style('marker-end', 'url(#' + type + '-end-arrow)') .style('stroke', color) .classed('hidden', false) .attr('suffix', type) .attr('d', 'M' + x + ',' + y + 'L' + mouse_x + ',' + mouse_y); self.restart(); }.bind(this)) .on('mousedown.drag', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) .transition() .duration(self._adder_anim_duration) .attr("d", d[type + '_adder'] .outerRadius(self._adder_outer_radius)) .each('end', function() { add_labels(type, color, adder_group); }); } function show_semicircles(d) { if(!d3.select(this).select('g path').empty()) return; if (!d.drag_mode && !self._create_agreement) { // append invisible circle which covers spaces between node // and adders it prevents hiding adders when mouse is on the space d3.select(this).append('g') .append('circle') .attr('r', self._adder_outer_radius) .style('opacity', 0); create_semicircle.bind(this, d, 'ca')(); create_semicircle.bind(this, d, 'domain')(); //move the identification text d3.select(this).select('text') .transition() .duration(self._adder_anim_duration) .attr('dy', '45'); } } function hide_semicircles(d) { var curr_nod = d3.select(this); curr_nod.selectAll('.plus,.adder_label,rect') .transition() .ease('exp') .duration(100) .style('font-size', '0px') .remove(); curr_nod.select('path.domain_adder') .transition() .attr("d", d.domain_adder .outerRadius(self._adder_inner_radius)) .duration(self._adder_anim_duration); curr_nod.select('path.ca_adder') .transition() .attr("d", d.ca_adder .outerRadius(self._adder_inner_radius)) .duration(self._adder_anim_duration); curr_nod.select('g') .transition() .duration(self._adder_anim_duration) .remove(); curr_nod.select('text') .transition() .attr('dy', '30') .duration(self._adder_anim_duration); } function is_suffix_shown(suffix) { var links = self._source_node.targets[self._target_node.id]; if (!links) return false; for (var i=0, l=links.length; i