283 lines
12 KiB
JavaScript
283 lines
12 KiB
JavaScript
/*!
|
|
* D3 Mind Map
|
|
* v1.0.0
|
|
* author Dustdusk
|
|
*/
|
|
var d3 = require('d3');
|
|
(function(window,angular, d3){
|
|
'use strict';
|
|
angular.module('material.components.mindmap', [])
|
|
.provider('$mdMindMap', function(){
|
|
|
|
var loadMindMap = function(options){
|
|
var m = options.margin||[8, 16, 12, 16], //margin
|
|
i = 0, body_id = options.body_id||'mind-map',
|
|
root, _max_x = [0, 0], _max_y = [0, 0],
|
|
_node_width = options.node_width||150, _node_height = options.node_height||24, _link_size = options.link_size||75;
|
|
|
|
|
|
var selectNode = function(target){
|
|
if(target){
|
|
var sel = d3.selectAll('#'+ body_id + ' svg .node').filter(function(d){return d.id==target.id})[0][0];
|
|
if(sel){
|
|
select(sel);
|
|
}
|
|
}
|
|
};
|
|
|
|
var select = function(node){
|
|
// Find previously selected, unselect
|
|
d3.select(".selected").classed("selected", false);
|
|
// Select current item
|
|
d3.select(node).classed("selected", true);
|
|
};
|
|
|
|
//node click
|
|
var handleClick = function(d, index){
|
|
select(this);
|
|
if(d._children === undefined && d.children === undefined){
|
|
if(typeof options.node.click === 'function'){
|
|
options.node.click(d, index, function(node_data){
|
|
if(!node_data)
|
|
return;
|
|
|
|
if(!Array.isArray(node_data))
|
|
node_data = [node_data];
|
|
|
|
if(node_data.length == 0)
|
|
return;
|
|
|
|
d.children = [];
|
|
for(var i = 0; i < node_data.length;i++){
|
|
var _new_node = d3.hierarchy(node_data[i], function(d){
|
|
return d[options.node.children];
|
|
});
|
|
var _max_height = 0;
|
|
d.ancestors().forEach(function(sd){
|
|
if(sd.height == 0){
|
|
_max_height = sd.height + _new_node.height+1;
|
|
sd.height = _max_height;
|
|
} else if(sd.height <= _max_height){
|
|
sd.height = _max_height;
|
|
}
|
|
_max_height +=1;
|
|
});
|
|
_new_node.descendants().forEach(function(sd){
|
|
sd.depth = d.depth + 1 + sd.depth;
|
|
if(!sd.parent)
|
|
sd.parent = d;
|
|
//sd.id = Date.now();
|
|
});
|
|
d.children.push(_new_node);
|
|
}
|
|
update(d);
|
|
});
|
|
}
|
|
} else {
|
|
toggle(d);
|
|
update(d);
|
|
}
|
|
};
|
|
|
|
var tree= d3.tree().nodeSize([_node_height+8, _node_width + _link_size]);
|
|
|
|
// celc link path
|
|
var connector = function (d, i){
|
|
var source = {x : d.source.x, y : d.source.y + _node_width};
|
|
var target = {x : d.target.x, y : d.target.y};
|
|
var hy = (target.y-source.y)/2;
|
|
return "M" + source.y + "," + source.x
|
|
+ "H" + (source.y+hy)
|
|
+ "V" + target.x + "H" + target.y;
|
|
};
|
|
|
|
//建立基本的SVG
|
|
var _body = d3.select('#'+ body_id);
|
|
var _vis = _body.html('').append('svg:svg');
|
|
var vis = _vis.append("svg:g")
|
|
.attr("transform", "translate(" + (0+m[3]) + "," + (_vis.node().getBoundingClientRect().height/2+m[0]) + ")") //設定起始點
|
|
;
|
|
|
|
var toArray = function(item, arr){
|
|
if(arr == undefined){
|
|
_max_x = [0, 0];
|
|
_max_y = [0, 0];
|
|
arr = [];
|
|
}
|
|
|
|
var i = 0, l = item.children?item.children.length:0;
|
|
arr.push(item);
|
|
item.y = 1 * item.y;
|
|
//_max_depth = _max_depth < item.depth?item.depth:_max_depth;
|
|
if(item.x < 0 && item.x < _max_x[0]){
|
|
_max_x[0] = item.x;
|
|
} else if(item.x > 0 && item.x > _max_x[1]){
|
|
_max_x[1] = item.x;
|
|
}
|
|
|
|
if(item.y < 0 && item.y < _max_y[0]){
|
|
_max_y[0] = item.y;
|
|
} else if(item.y > 0 && item.y > _max_y[1]){
|
|
_max_y[1] = item.y;
|
|
}
|
|
|
|
for(; i < l; i++){
|
|
toArray(item.children[i], arr);
|
|
}
|
|
//if()
|
|
return arr;
|
|
};
|
|
|
|
|
|
// Toggle node children.
|
|
function toggle(d) {
|
|
if (d.children) {
|
|
d._children = d.children;
|
|
d.children = null;
|
|
} else {
|
|
d.children = d._children;
|
|
d._children = null;
|
|
}
|
|
}
|
|
|
|
// load JSON data
|
|
var loadJSON = function(json){
|
|
if(json){
|
|
root = json;
|
|
root = d3.hierarchy(root, function(d){
|
|
return d[options.node.children];
|
|
});
|
|
root.x0 = 0;
|
|
root.y0 = 0;
|
|
//update tree
|
|
update(root, true);
|
|
}
|
|
};
|
|
|
|
// update node
|
|
// Source : 資料來源
|
|
// slow : 展示速度
|
|
function update(source, slow) {
|
|
var duration = (d3.event && d3.event.altKey) || slow ? 600 : 200;
|
|
|
|
// Compute the new tree layout.
|
|
// all nodes
|
|
var _treeData = tree(root);
|
|
var nodes = _treeData.descendants(),
|
|
_links = _treeData.descendants().slice(1);
|
|
|
|
|
|
//nodes.forEach(function(d){ d.y = d.depth * 180});
|
|
toArray(nodes[0]);
|
|
var _height = _max_x[1] - _max_x[0] + _node_height;
|
|
var _with = _max_y[1] - _max_y[0] + _node_width;
|
|
//tree.size([h, _with]);
|
|
_vis.transition()
|
|
.duration(duration)
|
|
.attr("width", _with + m[1] + m[3])
|
|
.attr("height", _height + m[0] + m[2]);
|
|
vis.transition()
|
|
.duration(duration)
|
|
.attr("transform", "translate(" + (0+m[3]) + "," + (_node_height+m[0]-_max_x[0]) + ")");
|
|
|
|
// Update the nodes…
|
|
var node = vis.selectAll("g.node")
|
|
.data(nodes, function(d) { return d.id || (d.id = ++i); });
|
|
|
|
// Enter any new nodes at the parent's previous position.
|
|
var nodeEnter = node.enter().append("svg:g")
|
|
.attr("class", function(d){ return d.selected?"node selected":"node"; })
|
|
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
|
|
.on("click", handleClick);
|
|
|
|
//節點
|
|
nodeEnter.append("svg:rect")
|
|
.attr("dx", "0")
|
|
.attr("y", 0-(_node_height/2))
|
|
.attr("rx", "8")
|
|
.attr("ry", "8" )
|
|
.attr("width", _node_width)
|
|
.attr("height", _node_height);
|
|
//.append("svg:circle").attr("r", 1e-6);
|
|
//.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
|
|
|
|
//節點文字
|
|
nodeEnter.append("svg:text")
|
|
.attr("dx", 8)
|
|
.attr("dy", 3) //文字下移
|
|
.attr("text-anchor", "left") //靠左
|
|
.text(function(d) {
|
|
if(typeof options.node.label === 'function')
|
|
return options.node.label(d.data);
|
|
else
|
|
return d.data[options.node.label];
|
|
})
|
|
.style("fill-opacity", 1);
|
|
|
|
// Transition nodes to their new position.
|
|
var nodeUpdate = nodeEnter.merge(node);
|
|
nodeUpdate.transition()
|
|
.duration(duration)
|
|
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
|
|
|
|
//nodeUpdate.select("text").text(function(d) { return (d.data.name); });
|
|
//nodeUpdate.select("rect");
|
|
|
|
// Transition exiting nodes to the parent's new position.
|
|
var nodeExit = node.exit().transition()
|
|
.duration(duration)
|
|
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
|
|
.remove();
|
|
|
|
nodeExit.select("rect");
|
|
//.attr("r", 1e-6);
|
|
|
|
nodeExit.select("text")
|
|
.style("fill-opacity", 1e-6);
|
|
|
|
// Update the links…
|
|
|
|
var link = vis.selectAll("path.link")
|
|
.data(_links, function(d) { return d.id; });
|
|
|
|
// Enter any new links at the parent's previous position.
|
|
var linkEnter = link.enter().insert("svg:path", "g")
|
|
.attr("class", "link")
|
|
.attr("d", function(d) {
|
|
var o = {x: source.x0, y: source.y0};
|
|
return connector({source: o, target: o});
|
|
});
|
|
|
|
var linkUpdate = linkEnter.merge(link);
|
|
// Transition links to their new position.
|
|
linkUpdate.transition()
|
|
.duration(duration)
|
|
.attr("d", function(d) {
|
|
return connector({source: d, target: d.parent});
|
|
});
|
|
|
|
// Transition exiting nodes to the parent's new position.
|
|
var linkExit = link.exit().transition()
|
|
.duration(duration)
|
|
.attr("d", function(d) {
|
|
var o = {x: source.x, y: source.y};
|
|
return connector({source: o, target: o});
|
|
})
|
|
.remove();
|
|
|
|
// Stash the old positions for transition.
|
|
nodes.forEach(function(d) {
|
|
d.x0 = d.x;
|
|
d.y0 = d.y;
|
|
});
|
|
}
|
|
loadJSON(options.data);
|
|
}
|
|
this.$get = ['$rootScope', '$timeout', function($rootScope, $timeout){
|
|
return {
|
|
load:loadMindMap
|
|
};
|
|
}];
|
|
});
|
|
})(window, window.angular, d3);
|