jui.define("chart.topology.edge", [], function() { /** * @class chart.topology.edge * */ var TopologyEdge = function(start, end, in_xy, out_xy, scale) { var connect = false, element = null; this.key = function() { return start + ":" + end; } this.reverseKey = function() { return end + ":" + start; } this.connect = function(is) { if(arguments.length == 0) { return connect; } connect = is; } this.element = function(elem) { if(arguments.length == 0) { return element; } element = elem; } this.set = function(type, value) { if(type == "start") start = value; else if(type == "end") end = value; else if(type == "in_xy") in_xy = value; else if(type == "out_xy") out_xy = value; else if(type == "scale") scale = value; } this.get = function(type) { if(type == "start") return start; else if(type == "end") return end; else if(type == "in_xy") return in_xy; else if(type == "out_xy") return out_xy; else if(type == "scale") return scale; } } return TopologyEdge; }); jui.define("chart.topology.edgemanager", [ "util.base" ], function(_) { /** * @class chart.topology.edgemanager * */ var TopologyEdgeManager = function() { var list = [], cache = {}; this.add = function(edge) { cache[edge.key()] = edge; list.push(edge); } this.get = function(key) { return cache[key]; } this.is = function(key) { return (cache[key]) ? true : false; } this.list = function() { return list; } this.each = function(callback) { if(!_.typeCheck("function", callback)) return; for(var i = 0; i < list.length; i++) { callback.call(this, list[i]); } } } return TopologyEdgeManager; }); jui.define("chart.brush.topologynode", [ "util.base", "util.math", "chart.topology.edge", "chart.topology.edgemanager" ], function(_, math, Edge, EdgeManager) { /** * @class chart.brush.topologynode * @extends chart.brush.core */ var TopologyNode = function() { var self = this, edges = new EdgeManager(), g, tooltip, point, textY = 14, padding = 7, anchor = 7, activeEdges = []; // 선택된 엣지 객체 function getDistanceXY(x1, y1, x2, y2, dist) { var a = x1 - x2, b = y1 - y2, c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)), dist = (!dist) ? 0 : dist, angle = math.angle(x1, y1, x2, y2); return { x: x1 + Math.cos(angle) * (c + dist), y: y1 + Math.sin(angle) * (c + dist), angle: angle, distance: c } } function getNodeData(key) { for(var i = 0; i < self.axis.data.length; i++) { var d = self.axis.data[i], k = self.getValue(d, "key"); if(k == key) { return self.axis.data[i]; } } return null; } function getEdgeData(key) { for(var i = 0; i < self.brush.edgeData.length; i++) { if(self.brush.edgeData[i].key == key) { return self.brush.edgeData[i]; } } return null; } function getTooltipData(edge) { for(var j = 0; j < self.brush.edgeData.length; j++) { if(edge.key() == self.brush.edgeData[j].key) { return self.brush.edgeData[j]; } } return null; } function getTooltipTitle(key) { var names = [], keys = key.split(":"); self.eachData(function(data, i) { var title = _.typeCheck("function", self.brush.nodeTitle) ? self.brush.nodeTitle.call(self.chart, data) : ""; if(data.key == keys[0]) { names[0] = title || data.key; } if(data.key == keys[1]) { names[1] = title || data.key; } }); if(names.length > 0) return names; return key; } function getNodeRadius(data) { var r = self.chart.theme("topologyNodeRadius"), scale = 1; if(_.typeCheck("function", self.brush.nodeScale) && data) { scale = self.brush.nodeScale.call(self.chart, data); r = r * scale; } return { r: r, scale: scale } } function getEdgeOpacity(data) { var opacity = self.chart.theme("topologyEdgeOpacity"); if(_.typeCheck("function", self.brush.edgeOpacity) && data) { opacity = self.brush.edgeOpacity.call(self.chart, data); } return opacity; } function createNodes(index, data) { var key = self.getValue(data, "key"), xy = self.axis.c(index), color = self.color(index, 0), title = _.typeCheck("function", self.brush.nodeTitle) ? self.brush.nodeTitle.call(self.chart, data) : "", text =_.typeCheck("function", self.brush.nodeText) ? self.brush.nodeText.call(self.chart, data) : "", size = getNodeRadius(data); var node = self.svg.group({ index: index }, function() { if(_.typeCheck("function", self.brush.nodeImage)) { self.svg.image({ "xlink:href": self.brush.nodeImage.call(self.chart, data), width: (size.r * 2) * xy.scale, height: (size.r * 2) * xy.scale, x: -size.r, y: -size.r, cursor: "pointer" }); } else { self.svg.circle({ "class": "circle", r: size.r * xy.scale, fill: color, cursor: "pointer" }); } if(text && text != "") { var fontSize = self.chart.theme("topologyNodeFontSize"); self.chart.text({ "class": "text", x: 0.1 * xy.scale, y: (size.r / 2) * xy.scale, fill: self.chart.theme("topologyNodeFontColor"), "font-size": fontSize * size.scale * xy.scale, "text-anchor": "middle", cursor: "pointer" }, text); } if(title && title != "") { self.chart.text({ "class": "title", x: 0.1 * xy.scale, y: (size.r + 13) * xy.scale, fill: self.chart.theme("topologyNodeTitleFontColor"), "font-size": self.chart.theme("topologyNodeTitleFontSize") * xy.scale, "font-weight": "bold", "text-anchor": "middle", cursor: "pointer" }, title); } }).translate(xy.x, xy.y); node.on(self.brush.activeEvent, function(e) { onNodeActiveHandler(data); self.chart.emit("topology.nodeclick", [ data, e ]); }); // 맨 앞에 배치할 노드 체크 if(self.axis.cache.nodeKey == key) { node.order = 1; } // 노드에 공통 이벤트 설정 self.addEvent(node, index, null); return node; } function createEdges() { edges.each(function(edge) { var in_xy = edge.get("in_xy"), out_xy = edge.get("out_xy"); var node = self.svg.group(); node.append(createEdgeLine(edge, in_xy, out_xy)); node.append(createEdgeText(edge, in_xy, out_xy)); g.append(node); }); } function createEdgeLine(edge, in_xy, out_xy) { var g = self.svg.group(), size = self.chart.theme("topologyEdgeWidth"), opacity = getEdgeOpacity(getEdgeData(edge.key())); if(!edge.connect()) { g.append(self.svg.line({ cursor: "pointer", x1: in_xy.x, y1: in_xy.y, x2: out_xy.x, y2: out_xy.y, stroke: self.chart.theme("topologyEdgeColor"), "stroke-width": size * edge.get("scale"), "stroke-opacity": opacity, "shape-rendering": "geometricPrecision" })); } else { var reverseElem = edges.get(edge.reverseKey()).element(); reverseElem.get(0).attr({ "stroke-opacity": opacity }); reverseElem.get(1).attr({ "fill-opacity": opacity }); } g.append(self.svg.circle({ fill: self.chart.theme("topologyEdgeColor"), "fill-opacity": opacity, stroke: self.chart.theme("backgroundColor"), "stroke-width": (size * 2) * edge.get("scale"), r: point * edge.get("scale"), cx: out_xy.x, cy: out_xy.y })); g.on(self.brush.activeEvent, function(e) { onEdgeActiveHandler(edge); }); g.on("mouseover", function(e) { onEdgeMouseOverHandler(edge); }); g.on("mouseout", function(e) { onEdgeMouseOutHandler(edge); }); edge.element(g); return g; } function createEdgeText(edge, in_xy, out_xy) { var text = null; var edgeAlign = (out_xy.x > in_xy.x) ? "end" : "start", edgeData = getEdgeData(edge.key()); if(edgeData != null) { var edgeText = _.typeCheck("function", self.brush.edgeText) ? self.brush.edgeText.call(self.chart, edgeData, edgeAlign) : null; if (edgeText != null) { if (edgeAlign == "end") { text = self.svg.text({ x: out_xy.x - 9, y: out_xy.y + 13, cursor: "pointer", fill: self.chart.theme("topologyEdgeFontColor"), "font-size": self.chart.theme("topologyEdgeFontSize") * edge.get("scale"), "text-anchor": edgeAlign }, edgeText) .rotate(math.degree(out_xy.angle), out_xy.x, out_xy.y); } else { text = self.svg.text({ x: out_xy.x + 8, y: out_xy.y - 7, cursor: "pointer", fill: self.chart.theme("topologyEdgeFontColor"), "font-size": self.chart.theme("topologyEdgeFontSize") * edge.get("scale"), "text-anchor": edgeAlign }, edgeText) .rotate(math.degree(in_xy.angle), out_xy.x, out_xy.y); } text.on(self.brush.activeEvent, function (e) { onEdgeActiveHandler(edge); }); text.on("mouseover", function (e) { onEdgeMouseOverHandler(edge); }); text.on("mouseout", function (e) { onEdgeMouseOutHandler(edge); }); } } return text; } function setDataEdges(index, targetIndex) { var data = self.getData(index), key = self.getValue(data, "key"), targetKey = self.getValue(data, "outgoing", [])[targetIndex]; // 자신의 키와 동일한지 체크 if(key == targetKey) return; var targetData = getNodeData(targetKey), target = self.axis.c(targetKey), xy = self.axis.c(index), in_dist = (getNodeRadius(data).r + point + 1) * xy.scale, out_dist = (getNodeRadius(targetData).r + point + 1) * xy.scale, in_xy = getDistanceXY(target.x, target.y, xy.x, xy.y, -in_dist), out_xy = getDistanceXY(xy.x, xy.y, target.x, target.y, -out_dist), edge = new Edge(key, targetKey, in_xy, out_xy, xy.scale); if(edges.is(edge.reverseKey())) { edge.connect(true); } edges.add(edge); } function showTooltip(edge, e) { if(!_.typeCheck("function", self.brush.tooltipTitle) || !_.typeCheck("function", self.brush.tooltipText)) return; var rect = tooltip.get(0), text = tooltip.get(1); // 텍스트 초기화 rect.attr({ points: "" }); text.element.textContent = ""; var edge_data = getTooltipData(edge), in_xy = edge.get("in_xy"), out_xy = edge.get("out_xy"), align = (out_xy.x > in_xy.x) ? "end" : "start"; // 커스텀 이벤트 발생 self.chart.emit("topology.edgeclick", [ edge_data, e ]); if(edge_data != null) { // 엘리먼트 생성 및 추가 var title = document.createElementNS("http://www.w3.org/2000/svg", "tspan"), contents = document.createElementNS("http://www.w3.org/2000/svg", "tspan"), y = (padding * 2) + ((align == "end") ? anchor : 0); text.element.appendChild(title); text.element.appendChild(contents); title.setAttribute("x", padding); title.setAttribute("y", y); title.setAttribute("font-weight", "bold"); title.textContent = self.brush.tooltipTitle.call(self.chart, getTooltipTitle(edge_data.key), align); contents.setAttribute("x", padding); contents.setAttribute("y", y + textY + (padding / 2)); contents.textContent = self.brush.tooltipText.call(self.chart, edge_data, align); // 엘리먼트 위치 설정 var size = text.size(), w = size.width + padding * 2, h = size.height + padding * 2, x = out_xy.x - (w / 2) + (anchor / 2) + (point / 2); text.attr({ x: w / 2 }); rect.attr({ points: self.balloonPoints((align == "end") ? "bottom" : "top", w, h, anchor) }); tooltip.attr({ visibility: "visible" }); if(align == "end") { tooltip.translate(x, out_xy.y + (anchor / 2) + point); } else { tooltip.translate(x, out_xy.y - anchor - h + point); } } } function onNodeActiveHandler(data) { var color = self.chart.theme("topologyEdgeColor"), activeColor = self.chart.theme("topologyActiveEdgeColor"), size = self.chart.theme("topologyEdgeWidth"), activeSize = self.chart.theme("topologyActiveEdgeWidth"); activeEdges = []; for(var i = 0; i < data.outgoing.length; i++) { var key = data.key + ":" + data.outgoing[i], edge = edges.get(key); if(edge != null) { activeEdges.push(edge); if (edge.connect()) { // 같이 연결된 노드도 추가 activeEdges.push(edges.get(edge.reverseKey())); } } } edges.each(function(edge) { var elem = edge.element(), circle = (elem.children.length == 2) ? elem.get(1) : elem.get(0), line = (elem.children.length == 2) ? elem.get(0) : null; if(_.inArray(edge, activeEdges) != -1) { // 연결된 엣지 var lineAttr = { stroke: activeColor, "stroke-width": activeSize * edge.get("scale") }, circleAttr = { fill: activeColor }; if(line != null) { line.attr(lineAttr); } circle.attr(circleAttr); tooltip.attr({ visibility: "hidden" }); } else { // 연결되지 않은 엣지 if(line != null) { line.attr({ stroke: color, "stroke-width": size * edge.get("scale") }); } circle.attr({ fill: color }); } }); } function onEdgeActiveHandler(edge) { edges.each(function(newEdge) { var elem = newEdge.element(), circle = (elem.children.length == 2) ? elem.get(1) : elem.get(0), line = (elem.children.length == 2) ? elem.get(0) : null, color = self.chart.theme("topologyEdgeColor"), activeColor = self.chart.theme("topologyActiveEdgeColor"), size = self.chart.theme("topologyEdgeWidth"), activeSize = self.chart.theme("topologyActiveEdgeWidth"); if(edge != null && (edge.key() == newEdge.key() || edge.reverseKey() == newEdge.key())) { if(line != null) { line.attr({ stroke: activeColor, "stroke-width": activeSize * newEdge.get("scale") }); } circle.attr({ fill: activeColor }); // 툴팁에 보여지는 데이터 설정 if(edge.key() == newEdge.key()) { // 엣지 툴팁 보이기 showTooltip(edge); } activeEdges = [ edge ]; if(edge.connect()) { // 같이 연결된 노드도 추가 activeEdges.push(edges.get(edge.reverseKey())); } } else { if(line != null) { line.attr({ stroke: color, "stroke-width": size * newEdge.get("scale") }); } circle.attr({ fill: color }); } }); } function onEdgeMouseOverHandler(edge) { if(_.inArray(edge, activeEdges) != -1) return; var elem = edge.element(), circle = (elem.children.length == 2) ? elem.get(1) : elem.get(0), line = (elem.children.length == 2) ? elem.get(0) : null, color = self.chart.theme("topologyHoverEdgeColor"), size = self.chart.theme("topologyHoverEdgeWidth"); if(line != null) { line.attr({ stroke: color, "stroke-width": size * edge.get("scale") }); } circle.attr({ fill: color }); } function onEdgeMouseOutHandler(edge) { if(_.inArray(edge, activeEdges) != -1) return; var elem = edge.element(), circle = (elem.children.length == 2) ? elem.get(1) : elem.get(0), line = (elem.children.length == 2) ? elem.get(0) : null, color = self.chart.theme("topologyEdgeColor"), size = self.chart.theme("topologyEdgeWidth"); if(line != null) { line.attr({ stroke: color, "stroke-width": size * edge.get("scale") }); } circle.attr({ fill: color }); } this.drawBefore = function() { g = self.svg.group(); point = self.chart.theme("topologyEdgePointRadius"); tooltip = self.svg.group({ visibility: "hidden" }, function() { self.svg.polygon({ fill: self.chart.theme("topologyTooltipBackgroundColor"), stroke: self.chart.theme("topologyTooltipBorderColor"), "stroke-width": 1 }); self.chart.text({ "font-size": self.chart.theme("topologyTooltipFontSize"), "fill": self.chart.theme("topologyTooltipFontColor"), y: textY }); }); } this.draw = function() { var nodes = []; this.eachData(function(data, i) { for(var j = 0; j < data.outgoing.length; j++) { setDataEdges(i, j); } }); // 엣지 그리기 createEdges(); // 노드 그리기 this.eachData(function(data, i) { var node = createNodes(i, data); g.append(node); nodes[i] = { node: node, data: data }; }); // 툴팁 숨기기 이벤트 (차트 배경 클릭시) this.on("axis.mousedown", function(e) { if(self.axis.root.element == e.target) { onEdgeActiveHandler(null); tooltip.attr({ visibility: "hidden" }); } }); // 액티브 엣지 선택 (렌더링 이후에 설정) if(_.typeCheck("string", self.brush.activeEdge)) { this.on("render", function(init) { if(!init) { var edge = edges.get(self.brush.activeEdge); onEdgeActiveHandler(edge); } }); } // 액티브 노드 선택 (렌더링 이후에 설정) if(_.typeCheck("string", self.brush.activeNode)) { this.on("render", function(init) { if(!init) { onNodeActiveHandler(getNodeData(self.brush.activeNode)); } }); } return g; } } TopologyNode.setup = function() { return { /** @cfg {Boolean} [clip=true] If the brush is drawn outside of the chart, cut the area. */ clip: true, // topology options /** @cfg {Function} [nodeTitle=null] */ nodeTitle: null, /** @cfg {Function} [nodeText=null] */ nodeText: null, /** @cfg {Function} [nodeImage=null] */ nodeImage: null, /** @cfg {Function} [nodeScale=null] */ nodeScale: null, /** @cfg {Array} [edgeData=[]] */ edgeData: [], /** @cfg {String} [edgeText=null] */ edgeText: null, /** @cfg {Function} [edgeText=null] */ edgeOpacity: null, /** @cfg {Function} [tooltipTitle=null] */ tooltipTitle: null, /** @cfg {Function} [tooltipText=null] */ tooltipText: null, /** @cfg {String} [activeNode=null] */ activeNode: null, /** @cfg {String} [activeEdge=null] */ activeEdge: null, /** @cfg {String} [activeEvent="click"] */ activeEvent: "click" } } /** * @event topoloygy_nodeclick * Event that occurs when click on the topology node. (real name ``` topoloygy.nodeclick ```) * @param {Object} data The node data. * @param {jQueryEvent} e The event object. */ /** * @event topoloygy_edgeclick * Event that occurs when click on the topology edge. (real name ``` topoloygy.edgeclick ```) * @param {Object} data The edge data. * @param {jQueryEvent} e The event object. */ return TopologyNode; }, "chart.brush.core");