效果图
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>d3 tree</title>
<style type="text/css">
/* svg style */
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="js/d3.min.js"></script>
<script type="text/javascript" src="js/tree.js"></script>
</body>
</html>
json
{
"success": true,
"data": {
"1": {
"id": "1",
"parent": "0",
"title": "根节点",
"url": "NULL",
"status": "0",
"shape": "root",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"2": {
"id": "2",
"parent": "1",
"title": "一级节点1",
"url": "NULL",
"status": "1",
"shape": "suqare",
"product_id": "300000001",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"3": {
"id": "3",
"parent": "2",
"title": "三级节点1",
"url": "NULL",
"status": "1",
"shape": "square",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"4": {
"id": "4",
"parent": "2",
"title": "三级节点2",
"url": "NULL",
"status": "1",
"shape": "square",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"5": {
"id": "5",
"parent": "14",
"title": "子节点1",
"url": "NULL",
"status": 1,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"6": {
"id": "6",
"parent": "14",
"title": "子节点2",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"7": {
"id": "7",
"parent": "14",
"title": "子节点3",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"8": {
"id": "8",
"parent": "14",
"title": "子节点4",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"9": {
"id": "9",
"parent": "14",
"title": "子节点5",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"10": {
"id": "10",
"parent": "14",
"title": "子节点6",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"11": {
"id": "11",
"parent": "14",
"title": "子节点7",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"12": {
"id": "12",
"parent": "14",
"title": "子节点8",
"url": "NULL",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": true,
"checkIfHeatAvailable": false
},
"13": {
"id": "13",
"parent": "14",
"title": "子节点9",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"14": {
"id": "14",
"parent": "3",
"title": "子节点10",
"url": "NULL",
"status": "1",
"shape": "square",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"15": {
"id": "15",
"parent": "4",
"title": "四级节点2",
"url": "http://www.baidu.com",
"status": 0,
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"16": {
"id": "16",
"parent": "1",
"title": "一级节点2",
"url": "NULL",
"status": "0",
"shape": "square",
"product_id": "300000002",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"17": {
"id": "17",
"parent": "16",
"title": "子节点11",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"18": {
"id": "18",
"parent": "16",
"title": "子节点12",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"19": {
"id": "19",
"parent": "16",
"title": "子节点13",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
},
"20": {
"id": "20",
"parent": "16",
"title": "子节点14",
"url": "NULL",
"status": "0",
"shape": "round",
"product_id": "0",
"checkIfTrendAvailable": false,
"checkIfHeatAvailable": false
}
}
}
tree.js
var api = {
'tree': 'json/tree.json'
},
base = '/img/',
trendUrl = 'a.php?nodeId=',
hotUrl = 'b.php?nodeId=';
/* d3.layout.tree */
var margin = {top: 20, right: 20, bottom: 20, left: 120},
width = $('#chart').parent().width()-$(".sidebar").width(),
//height = 1900 - margin.top - margin.bottom, // 1900
//height = $(window).height()-$("#header").height()-$("#footer").height() -58,
height = $(window).height()-58,
//height = 1500;
_height = 100,
min_height = 25,
max_height = 100;
i = 0,
duration = 750,
rootOrig = {},
root = {};
var img_width = 14,
img_height = 14,
round_img_width = 20,
round_img_height = 20,
squ_img_width = 40,
squ_img_height = 20,
t_width = 14,
t_height = 14;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("#chart").append("svg")
.attr("width", '100%')
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svgWrapper")
.append("g")
.attr("id", "svg")
.attr("transform", "translate(" + margin.left + "," + margin.top + ") scale(0.9)");
/* d3.layout.tree */
function renderTreeData(){
var request = $.ajax({
url : api.tree,
type : 'GET',
data : {},
async: false,
dataType : 'json'
});
request.done(function(json){
if (json.success) {
var data = json.data;
rootOrig = data,
dataArr = [];
for (var prop in data) {
dataArr.push(data[prop]);
}
var treeData = [],
dataMap = dataArr.reduce(function(map, node) {
map[node.id] = node;
return map;
}, {});
dataArr.forEach(function(node) {
// add to parent
var parent = dataMap[node.parent];
if (parent) {
// create child array if it doesn't exist
(parent.children || (parent.children = []))
// add node to child array
.push(node);
} else {
// parent is null or missing
treeData.push(node);
}
});
root = treeData[0];
root.x0 = 300;
root.y0 = 0;
function collapse(d) {
if (d.status == 1) { // abnormal
if (d.children){ // abnormal and has children
d.children.forEach(collapse);
} else { // abnormal and has not children
}
} else { // normal
if (d.children) { // normal and has children
d._children = d.children;
d.children = null;
} else {
}
}
}
root.children.forEach(collapse);
update(root);
resizeLayout();
} else {
}
});
}
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {return d.id });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.attr("id", function(d){
return "node" + d.id;
});
nodeEnter.append("svg:image")
.attr("xlink:href", function(d){
return getPicture(d);
})
.attr('width', function(d){
return img_width;
})
.attr('height', function(d){
return img_height;
})
.attr('x', function(d){
return -img_width/2;
})
.attr('y', function(d){
return -img_height/2;
})
.style('cursor', function(d){ return d.children || d._children ?"pointer":"default" })
.on("click", click);
var nodeEnterText = nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -20 : 20; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) {
return d.title;
})
.style("fill-opacity", 1e-6);
var nodeTrend = nodeEnter.append("svg:a")
.attr("xlink:href", function(d){
var url = "javascript:;";
if (d.checkIfTrendAvailable) {
url = trendUrl +d.id+ '&name='+ encodeURI(d.title);
} else {
url = (d.url !="NULL") ? d.url : "javascript:;";
}
return url;
})
.attr("target", function(d){
var target = "_self";
if (d.checkIfTrendAvailable) {
target = "_blank";
} else {
target = (d.url !="NULL") ? "_blank" : "_self";
}
return target;
});
nodeTrend.append("svg:image")
.attr("xlink:href", function(d){
var imgUrl;
if (d.checkIfTrendAvailable) {
imgUrl = base + 'trend.png';
} else {
if (d.url!="NULL") {
imgUrl = base + 'text.png';
}
}
return imgUrl;
})
.attr('width', function(d){
return t_width;
})
.attr('height', function(d){
return t_height;
})
.attr('x', function(d){
var len = getLength(d.title),
preCountLen = len*6 + 40,
countLen = len*6 + 30;
return d.children || d._children ? -preCountLen : countLen;
})
.attr('y', function(d){
return -t_height/2;
});
var nodeHot = nodeEnter.append("svg:a")
.attr("xlink:href", function(d){
var url = "javascript:;";
if (d.checkIfHeatAvailable) {
url = hotUrl +d.id+ '&name='+ encodeURI(d.title);
}
return url;
})
.attr("target", function(d){
var target = "_self";
if (d.checkIfHeatAvailable) {
target = "_blank";
}
return target;
});
nodeHot.append("svg:image")
.attr("xlink:href", function(d){
var imgUrl;
if (d.checkIfHeatAvailable) {
imgUrl = base + 'hot.png';
}
return imgUrl;
})
.attr('width', function(d){
return t_width;
})
.attr('height', function(d){
return t_height;
})
.attr('x', function(d){
var len = getLength(d.title),
preCountLen = len*6 + 60,
countLen = len*6 + 50;
return d.children || d._children ? -preCountLen : countLen;
})
.attr('y', function(d){
return -t_height/2;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("image")
.attr("xlink:href", function(d){
return getPicture(d);
})
.attr('width', function(d){
return img_width;
})
.attr('height', function(d){
return img_height;
})
.attr('x', function(d){
return -img_width/2;
})
.attr('y', function(d){
return -img_height/2;
})
.style('cursor', function(d){ return d.children || d._children ?"pointer":"default" });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// 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("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr('style', function (d){
if ( d.target.status == 0 || d.target.status ==2) {
// return 'fill:none; stroke:#c7dbd6; stroke-width:2px;'
return 'fill:none; stroke:#cdcdcd; stroke-width:2px;'
} else {
return 'fill:none; stroke:#f3dcdd; stroke-width:2px;'
}
})
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
resizeLayout();
}
function computeDistance(_tree) {
if ( _tree.children ) {
var _children = _tree.children,
length = _children.length;
for ( var i=0; i<length; ++i ) {
if ( i<length-1 ) {
if ( Math.abs(_children[i].x0-_children[i+1].x0)<_height ) {
_height = Math.ceil(Math.abs(_children[i].x0-_children[i+1].x0));
}
}
computeDistance(_children[i]);
}
}
}
function resizeLayout(){
_height = 1000;
computeDistance(root);
if ( _height<min_height ) {
var scale = min_height / _height,
new_height = d3.select('#svgWrapper').attr('height')*scale;
var _size = tree.size();
tree.size([_size[0]*scale, _size[1]]);
update(root);
} else if ( _height>max_height && root.children ) {
var scale = max_height / _height,
new_height = d3.select('#svgWrapper').attr('height')*scale;
if ( new_height < 700 ) {
new_height = 700;
}
var _size = tree.size();
tree.size([_size[0]*scale, _size[1]]);
update(root);
}
}
/**
@description get the node icon
*/
function getPicture(d){
if(root.id == d.id) {
return base + 'logo-new-s.png';
} else {
if (d.shape=="round") {
var path = '';
if (d.status==0) {
path = base + 'child.png';
} else if (d.status==1) {
path = base + 'child-red.gif';
} else {
path = base + 'child-yellow.png';
}
return path;
} else {
var path = '';
if (d.status==0) {
path = base + 'add.png';
} else if (d.status==1) {
path = base + 'add-red.png';
} else {
path = base + 'add-yellow.png';
}
return path;
}
}
}
/**
@description calculate the string length
*/
function getLength(str){
if (str) {
return str.replace(/[\u4E00-\u9FA5]|[^\x00-\xff]/ig, "cc").replace(/[A-Z]/g, 'cc').length;
}
return 0;
}
$(function(){
renderTreeData();
d3.select('#chart').call(d3.behavior.zoom().scaleExtent([0.7, 1.2]).on("zoom", function(){
var transform = d3.select("#svg").attr('transform');
d3.event.scale=d3.event.scale-0.15;
d3.select("#svg").attr("transform",
transform.slice(0, transform.indexOf(' '))+" scale("+d3.event.scale+")"
);
}));
d3.select('#chart').call(d3.behavior.drag().on("drag", function() {
var transform = d3.select("#svg").attr('transform');
var x = parseInt(transform.slice(10, transform.indexOf(','))) + d3.event.dx;
var y = parseInt(transform.slice(transform.indexOf(',')+1, transform.indexOf(')')))+ d3.event.dy;
if(y>300) {
y=300;
}
d3.select("#svg").attr("transform",
"translate(" + x +","+y + ") "+transform.slice(transform.indexOf(' ')+1));
}));
});