d3js 笔记

155 阅读15分钟

安装

//当前d3.v7.js
npm install d3
<script src="https://d3js.org/d3.v5.js"></script>

D3.js 是一个JavaScript库,用于使用Web标准可视化数据。 D3帮助您使用SVG,Canvas和HTML使数据栩栩如生。 D3将强大的可视化和交互技术与数据驱动的DOM操作方法相结合,为您提供现代浏览器的全部功能,并为您的数据设计正确的可视界面提供了自由。

选择元素和绑定数据

//如何选择元素
var body = d3.select("body"); //选择文档中的body元素
var svg = body.select("svg"); //选择body中的svg元素
var p = body.selectAll("p"); //选择body中所有的p元素
var p1 = body.select("p"); //选择body中第一个p元素
var rects = svg.selectAll("rect"); //选择svg中所有的rect元素
var p2 = body.select("#pear-id");//id选择
var p = body.selectAll(".myclass");//class选择


//如何绑定数据
//datum():绑定一个数据到选择集上
var str = "World";
var body = d3.select("body");
var p = body.selectAll("p");
p.datum(str);
p.text(function(d, i){
    return "第 "+ i + " 个元素绑定的数据是 " + d;
});
//data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定
var dataset = ["I like dog","I like cat","I like snake"];
var body = d3.select("body");
var p = body.selectAll("p");
p.data(dataset)
  .text(function(d, i){
      return d;
  });


插入、删除元素

// append():在选择集末尾插入元素
body.append("p")
    .text("append p element");


// insert():在选择集前面插入元素
//在 body 中 id 为 myid 的元素前添加一个段落元素
body.insert("p","#pear-id")
  .text("insert p element");


// 删除元素
var p = body.select("#pear-id");
p.remove();

简单的图表

柱形图是一种最简单的可视化图标,主要有矩形、文字标签、坐标轴组成。本文为简单起见,只绘制矩形的部分,用以讲解如何使用 D3 在 SVG 画布中绘图。

//添加画布
var width = 300;  //画布的宽度
var height = 300;   //画布的高度

var svg = d3.select("body")     //选择文档中的body元素
    .append("svg")          //添加一个svg元素
    .attr("width", width)       //设定宽度
    .attr("height", height);    //设定高度

var dataset = [ 250 , 210 , 170 , 130 , 90 ];  //数据(表示矩形的宽度)

var rectHeight = 25;   //每个矩形所占的像素高度(包括空白)

svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("x",20)
    .attr("y",function(d,i){
         return i * rectHeight;
    })
    .attr("width",function(d){
         return d;
    })
    .attr("height",rectHeight-2)
    .attr("fill","steelblue");


比例尺的使用

比例尺是 D3 中很重要的一个概念,上一章里曾经提到过直接用数值的大小来代表像素不是一种好方法,如果数值过大或过小,非常具有局限性。将某一区域的值映射到另一区域,其大小关系不变。这就是比例尺(Scale)。 在数学中,x 的范围被称为定义域,y 的范围被称为值域。D3 中的比例尺,也有定义域和值域,分别被称为 domain 和 range。开发者需要指定 domain 和 range 的范围。

//线性比例尺,最小的值,映射成 0;将最大的值,映射成 300
var min = d3.min(dataset); //得到最小值
var max = d3.max(dataset); //得到最大值
var scaleLinear = d3.scaleLinear()
        			.domain([min, max])
        			.range([0, 300]);
scaleLinear(0.9);    //返回 0
scaleLinear(2.3);    //返回 175
scaleLinear(3.3);    //返回 300


//序数比例尺,我们希望 0 对应颜色 red,1 对应 blue,依次类推。
var index = [0, 1, 2, 3, 4];
var color = ["red", "blue", "green", "yellow", "black"];
var scaleOrdinal = d3.scaleOrdinal()
        			.domain(index)
        			.range(color);

scaleOrdinal (0); //返回 red
scaleOrdinal (2); //返回 green
scaleOrdinal (4); //返回 black

坐标轴

坐标轴,是可视化图表中经常出现的一种图形,由一些列线段和刻度组成。坐标轴在 SVG 中是没有现成的图形元素的,需要用其他的元素组合构成。D3 提供了坐标轴的组件。

//画布大小
var width = 400;
var height = 400;

//在 body 里添加一个 SVG 画布   
var svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

//画布周边的空白
 var padding = {left:30, right:30, top:20, bottom:20};

//定义数据和比例尺////////////////////////////////////////////////

//定义一个数组
var dataset = [10, 20, 30, 40, 33, 24, 12, 5];

//x轴的比例尺(创建一个序列化比例尺)
var xScale = d3.scaleBand()
        .domain(d3.range(dataset.length))
        .rangeRound([0, width - padding.left - padding.right]);

//y轴的比例尺
var yScale = d3.scaleLinear()
    .domain([0,d3.max(dataset)])
    .range([height - padding.top - padding.bottom, 0]);


//定义坐标轴//////////////////////////////////////////////
var xAxis = d3.axisBottom(xScale); //定义x轴
var yAxis = d3.axisLeft(yScale); //定义y轴

//添加矩形和文字元素////////////////////////////////
//矩形之间的空白
var rectPadding = 4;
//添加矩形元素
var rects = svg.selectAll(".MyRect")
  .data(dataset)
  .enter()
  .append("rect")
  .attr("class", "MyRect")
  .attr(
    "transform",
    "translate(" + padding.left + "," + padding.top + ")"
  )
  .attr("x", function(d, i) {
    return xScale(i) + rectPadding / 2;
  })
  .attr("y", function(d) {
    return yScale(d);
  })
  .attr("width", xScale.step() - rectPadding)
  .attr("height", function(d) {
    return height - padding.top - padding.bottom - yScale(d);
  })
  .attr("fill","blue");
//添加文字元素
var texts = svg.selectAll(".MyText")
  .data(dataset)
  .enter()
  .append("text")
  .attr("class", "MyText")
  .attr(
    "transform",
    "translate(" + padding.left + "," + padding.top + ")"
  )
  .attr("x", function(d, i) {
    return xScale(i) + rectPadding / 2;
  })
  .attr("y", function(d) {
    return yScale(d);
  })
  .attr("dx", function() {
    return (xScale.step() - rectPadding) / 2;
  })
  .attr("dy", function(d) {
    return 20;
  })
  .text(function(d) {
    return d;
  });


//添加坐标轴的元素////////////////////////////////////
//添加x轴
svg.append("g")
  .attr("class","axis")
  .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
  .call(xAxis); 

//添加y轴
svg.append("g")
  .attr("class","axis")
  .attr("transform","translate(" + padding.left + "," + padding.top + ")")
  .call(yAxis);

//样式//////////////////////////
<style>
  .axis path,
  .axis line{
      fill: none;
      stroke: black;
      shape-rendering: crispEdges;
  }

  .axis text {
      font-family: sans-serif;
      font-size: 11px;
  }
</style>

过渡效果

D3 支持制作动态的图表。有时候,图表的变化需要缓慢的发生,以便于让用户看清楚变化的过程。

//transition()启动过渡效果
.attr("fill","red")         //初始颜色为红色
.transition()               //启动过渡
.attr("fill","steelblue")   //终止颜色为铁蓝色

//duration()指定过渡的持续时间,单位为毫秒。
//ease()指定过渡的方式,常用的有
// delay()指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒
.transition()
.duration(1000)
.delay(500)


// 给柱形图加上动态效果
.attr("y",function(d){
    var min = yScale.domain()[0];
    return yScale(min);
})
.transition()
.delay(function(d,i){
    return i * 200;
})
.duration(2000)
.ease(d3.easeBounce)
.attr("y",function(d){
    return yScale(d);
});
//文字元素的过渡前后,发生变化的是 y 坐标。其起始状态是在 y 轴等于 0 的位置(但要注意,不能在起始状态直接返回 0,要应用比例尺计算画布中的位置)。终止状态是目标值。

理解 Update、Enter、Exit

Update、Enter、Exit 是 D3 中三个非常重要的概念,它处理的是当选择集和数据的数量关系不确定的情况。

//有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。
svg.selectAll("rect")   //选择svg内所有的矩形
    .data(dataset)      //绑定数组
    .enter()            //指定选择集的enter部分
    .append("rect")     //添加足够数量的矩形元素

// update 部分的处理办法一般是:更新属性值
// enter 部分的处理办法一般是:添加元素后,赋予属性值
// exit 部分的处理办法一般是:删除元素(remove)

交互式操作

与图表的交互,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应。

var circle = svg.append("circle");
circle.on("click", function(e){
    //在这里添加交互内容
});

// 鼠标常用的事件有:
// click:鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。
// mouseover:光标放在某元素上。
// mouseout:光标从某元素上移出来时。
// mousemove:鼠标被移动的时候。
// mousedown:鼠标按钮被按下。
// mouseup:鼠标按钮被松开。
// dblclick:鼠标双击。

// 键盘常用的事件有三个:

// keydown:当用户按下任意键时触发,按住不放会重复触发此事件。该事件不会区分字母的大小写,例如“A”和“a”被视为一致。
// keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件。该事件区分字母的大小写。
// keyup:当用户释放键时触发,不区分字母的大小写。 触屏常用的事件有三个:
// touchstart:当触摸点被放在触摸屏上时。
// touchmove:当触摸点在触摸屏上移动时。
// touchend:当触摸点从触摸屏上拿开时。


//带有交互的柱形图///////////////////
var rects = svg.selectAll(".MyRect")
        .data(dataset)
        .enter()
        .append("rect")
        .attr("class","MyRect")   //把类里的 fill 属性清空
        .attr("transform","translate(" + padding.left + "," + padding.top + ")")
        .attr("x", function(d,i){
            return xScale(i) + rectPadding/2;
        } )
        .attr("y",function(d){
            return yScale(d);
        })
        .attr("width", xScale.step() - rectPadding )
        .attr("height", function(d){
            return height - padding.top - padding.bottom - yScale(d);
        })
        .attr("fill","steelblue")       //填充颜色不要写在CSS里
        .on("mouseover",function(d,i){
            d3.select(this)
                .attr("fill","yellow");
        })
        .on("mouseout",function(d,i){
            d3.select(this)
                .transition()
                .duration(500)
                .attr("fill","steelblue");
        });

官网一个坐标轴的例子

var mFun = ()=>{
  // Declare the chart dimensions and margins.
  const width = 640;
  const height = 400;
  const marginTop = 20;
  const marginRight = 20;
  const marginBottom = 30;
  const marginLeft = 40;

  // Declare the x (horizontal position) scale.
  const x = d3.scaleUtc()
      .domain([new Date("2023-01-01"), new Date("2024-01-01")])
      .range([marginLeft, width - marginRight]);

  // Declare the y (vertical position) scale.
  const y = d3.scaleLinear()
      .domain([0, 100])
      .range([height - marginBottom, marginTop]);

  // Create the SVG container.
  const svg = d3.create("svg")
      .attr("width", width)
      .attr("height", height);

  // Add the x-axis.
  svg.append("g")
      .attr("transform", `translate(0,${height - marginBottom})`)
      .call(d3.axisBottom(x));

  // Add the y-axis.
  svg.append("g")
      .attr("transform", `translate(${marginLeft},0)`)
      .call(d3.axisLeft(y));

  // Return the SVG element.
  return svg.node();
}
var mNode = mFun();
document.body.appendChild(mNode);

鼠标事件,改变子节点的颜色

var width = 300;  //画布的宽度
var height = 300;   //画布的高度

var svg = d3.select("body")     //选择文档中的body元素
    .append("svg")          //添加一个svg元素
    .attr("width", width)       //设定宽度
    .attr("height", height);    //设定高度

var mG = svg.append("g")
    .attr("transform", "translate(50,50)")
// 鼠标移入事件
mG.on("mouseover", function() {
    d3.select(this).select("rect").style("fill", "red");
    d3.select(this).select("line").style("stroke", "yellow");
    d3.select(this).select("text").style("fill", "white");
});
// 鼠标移出事件
mG.on("mouseout", function() {
    d3.select(this).select("rect").style("fill", "steelblue");
    d3.select(this).select("line").style("stroke", "black");
    d3.select(this).select("text").style("fill", "red");
});
// 添加矩形
mG.append("rect")
    .attr("x",20)
    .attr("y",20)
    .attr("width",100)
    .attr("height",100)
    .attr("fill","steelblue");
// 添加线
mG.append('line')
  .attr('x1', 20)
  .attr('y1', 20)
  .attr('x2', 180)
  .attr('y2', 180)
  .attr('stroke', 'black');
 
// 添加文本
mG.append('text')
  .text('Hello, D3.js!')
  .attr('x', 40)
  .attr('y', 50)
  .attr('fill', 'red');  

布局

布局,可以理解成 “制作常见图形的函数”,有了它制作各种相对复杂的图表就方便多了。 布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据。 D3 总共提供了 12 个布局:饼状图(Pie)、力导向图(Force)、弦图(Chord)、树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、矩阵树图(Treemap)、层级图(Hierarchy)。

饼状图

在布局的应用中,最简单的就是饼状图

// 1、数据准备
var marge = {top:60,bottom:60,left:60,right:60}
var svg = d3.select("svg")
var width = svg.attr("width")
var height = svg.attr("height")
var g = svg.append("g")
	.attr("transform","translate("+marge.top+","+marge.left+")");
	
var dataset = [ 30 , 10 , 43 , 55 , 13 ];//需要将这些数据变成饼状图的数据

// 2、设置一个颜色比例尺
//设置一个color的颜色比例尺,为了让不同的扇形呈现不同的颜色
var colorScale = d3.scaleOrdinal()
	.domain(d3.range(dataset.length))
	.range(d3.schemeCategory10);

// 3、新建一个饼状图
var pie = d3.pie();

// 4、新建一个弧形生成器
//新建一个弧形生成器
var innerRadius = 0;//内半径
var outerRadius = 100;//外半径
var arc_generator = d3.arc()
	.innerRadius(0)
	.outerRadius(100);

// 5、利用饼状图生成器转换数据
//将原始数据变成可以绘制饼状图的数据,
var pieData = pie(dataset);
//在浏览器的控制台打印pieData
console.log(pieData);

// 6、开始绘制,老规矩,先为每一个扇形及其对应的文字建立一个分组
var gs = g.selectAll(".g")
    		.data(pieData)
    		.enter()
    		.append("g")
    		.attr("transform","translate("+width/2+","+height/2+")")//位置信息

// 7、绘制扇形
//绘制饼状图的各个扇形
gs.append("path")
	.attr("d",function(d){
		return arc_generator(d);//往弧形生成器中出入数据
	})
	.attr("fill",function(d,i){
		return colorScale(i);//设置颜色
	});

// 8、文字
//绘制饼状图上面的文字信息
gs.append("text")
  .attr("transform",function(d){//位置设在中心处
    return "translate("+arc_generator.centroid(d)+")";
  })
  .attr("text-anchor","middle")
  .text(function(d){
    return d.data;
  })

力导向图

力导向图(Force-Directed Graph),是绘图的一种算法。在二维或三维空间里配置节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,最终达到一种能量很低的安定状态。

  • d3.forceSimulation() ,新建一个力导向图,
  • d3.forceSimulation().force(),添加或者移除一个力;对于d3.forceSimulation().force(name),也就是当force中只有一个参数,这个参数是某个力的名称,那么这段代码返回的是某个具体的力,(根据上面图片官方对force的说明也可以知道),例如d3.forceSimulation().force(“link”),则返回的是d3.forceLink()这个力,注意对照上面的图片,这个用法在下面会经常被用到
  • d3.forceSimulation().nodes(),输入是一个数组,然后将这个输入的数组进行一定的数据转换,例如添加坐标什么的
  • d3.forceLink.links(),这里输入的也是一个数组(边集),然后对输入的边集进行转换,等一下我们会看到,它到底被转换成什么样子的 tick函数,这个函数对于力导向图来说非常重要,因为力导向图是不断运动的,每一时刻都在发生更新,所以需要不断更新节点和连线的位置
  • d3.drag(),是力导向图可以被拖动
// 1、数据准备
var marge = {top:60,bottom:60,left:60,right:60}
var svg = d3.select("svg")
var width = svg.attr("width")
var height = svg.attr("height")
var g = svg.append("g")
	.attr("transform","translate("+marge.top+","+marge.left+")");
	
//准备数据
var nodes = [//节点集
	{name:"湖南邵阳"},
	{name:"山东莱州"},
	{name:"广东阳江"},
	{name:"山东枣庄"},
	{name:"泽"},
	{name:"恒"},
	{name:"鑫"},
	{name:"明山"},
	{name:"班长"}
];

var edges = [//边集
	{source:0,target:4,relation:"籍贯",value:1.3},
	{source:4,target:5,relation:"舍友",value:1},
	{source:4,target:6,relation:"舍友",value:1},
	{source:4,target:7,relation:"舍友",value:1},
	{source:1,target:6,relation:"籍贯",value:2},
	{source:2,target:5,relation:"籍贯",value:0.9},
	{source:3,target:7,relation:"籍贯",value:1},
	{source:5,target:6,relation:"同学",value:1.6},
	{source:6,target:7,relation:"朋友",value:0.7},
	{source:6,target:8,relation:"职责",value:2}
];


// 2、设置一个颜色比例尺
//设置一个color的颜色比例尺,为了让不同的扇形呈现不同的颜色
var colorScale = d3.scaleOrdinal()
	.domain(d3.range(nodes.length))
	.range(d3.schemeCategory10);

// 3、新建一个力导向图
var forceSimulation = d3.forceSimulation()
    		.force("link",d3.forceLink())
    		.force("charge",d3.forceManyBody())
    		.force("center",d3.forceCenter());


// 4、生成节点数据
//生成节点数据
forceSimulation.nodes(nodes)
	.on("tick",ticked);//这个函数很重要,后面给出具体实现和说明

// 5、生成边集数据
//生成边数据
forceSimulation.force("link")
	.links(edges)
	.distance(function(d){//每一边的长度
		return d.value*100;
	})

// 6、设置图形中心位置
//设置图形的中心位置	
forceSimulation.force("center")
	.x(width/2)
	.y(height/2);

// 7、输出顶点集和边集
console.log(nodes);
console.log(edges);

// 8、绘制边
//绘制边
  var links = g.append("g")
    .selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .attr("stroke",function(d,i){
      return colorScale(i);
    })
    .attr("stroke-width",1);


// 9、边上的文字
var linksText = g.append("g")
    		.selectAll("text")
    		.data(edges)
    		.enter()
    		.append("text")
    		.text(function(d){
    			return d.relation;
    		})

// 10、老规矩,先建立用来放在每个节点和对应文字的分组
var gs = g.selectAll(".circleText")
    		.data(nodes)
    		.enter()
    		.append("g")
    		.attr("transform",function(d,i){
    			var cirX = d.x;
    			var cirY = d.y;
    			return "translate("+cirX+","+cirY+")";
    		})
    		.call(d3.drag()
    			.on("start",started)
    			.on("drag",dragged)
    			.on("end",ended)
    		);

// 11、节点和文字
//绘制节点
  gs.append("circle")
    .attr("r",10)
    .attr("fill",function(d,i){
      return colorScale(i);
    })
  //文字
  gs.append("text")
    .attr("x",-10)
    .attr("y",-20)
    .attr("dy",10)
    .text(function(d){
      return d.name;
    })

// 12、ticked函数的实现
// 这里写的都是位置信息,所以你在绘制相应的图形元素的时候,位置信息就不那么重要的,而且,建议先写完这个函数后,在进行测试
function ticked(){
	links.attr("x1",function(d){return d.source.x;})
		.attr("y1",function(d){return d.source.y;})
		.attr("x2",function(d){return d.target.x;})
		.attr("y2",function(d){return d.target.y;});
		
	linksText.attr("x",function(d){
		return (d.source.x+d.target.x)/2;
	}).attr("y",function(d){
		return (d.source.y+d.target.y)/2;
	});
		
	gs.attr("transform",function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

// 13、drag
function started(e,d){
	if(e.active){
		forceSimulation.alphaTarget(0.8).restart();//设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]
	}
	d.fx = d.x;
	d.fy = d.y;
}
function dragged(e,d){
	d.fx = e.x;
	d.fy = e.y;
}
function ended(e,d){
	if(!e.active){
		forceSimulation.alphaTarget(0);
	}
	d.fx = null;
	d.fy = null;
}

树状图

树状图,可表示节点之间的包含与被包含关系

  • d3.hierarchy(),层级布局,需要和tree生成器一起使用,来得到绘制树所需要的节点数据和边数据;
  • d3.hierarchy().sum() ,后序遍历;
  • d3.tree(),创建一个树状图生成器;
  • d3.tree().size(),定义树的大小;
  • d3.tree().separation(),定义邻居节点的距离;
  • node.descendants()得到所有的节点,已经经过转换的数据;
  • node.links(),得到所有的边,已经经过转换的数据;
  • d3.linkHorizontal(),创建一个贝塞尔生成曲线生成器,当然不止只有水平的,还有垂直的(d3.linkVertical())

d3.hierarchy

  • node.ancestors - 从当前节点开始返回其祖先节点数组.
  • node.leaves - 返回当前节点为根节点的子树的叶节点.
  • node.find - 查找指定节点.
  • node.path - 返回从当前节点到指定目标节点的最短路径.
  • node.sum - 评估和汇总定量值.
  • node.sort - 排序所有的后代兄弟节点.
  • node.count - 统计叶节点的个数.
  • node.each - 广度优先遍历当前子树.
  • node.eachAfter - 后续遍历当前子树.
  • node.eachBefore - 前序遍历当前子树.
  • node.copy - 拷贝一个当前节点为根节点的子树的副本.
  • node.data - 关联的数据
  • node.depth - 当前节点的深度, 根节点为 0.
  • node.height - 当前节点的高度, 叶节点为 0.
  • node.parent - 当前节点的父节点, 根节点为 null.
  • node.children - 当前节点的孩子节点(如果有的话); 叶节点为 undefined.
  • d3.hierarchy默认子节点取得是children属性,当然也可以自定义: d3.hierarchy(data, d => d.child)

d3.stratify

对于扁平数据,我们可以用d3.stratify来实现数据的层次化

let data = [
  {"name": "Eve",   "parent": ""},
  {"name": "Cain",  "parent": "Eve"},
  {"name": "Seth",  "parent": "Eve"},
  {"name": "Enos",  "parent": "Seth"},
  {"name": "Noam",  "parent": "Seth"},
  {"name": "Abel",  "parent": "Eve"},
  {"name": "Awan",  "parent": "Eve"},
  {"name": "Enoch", "parent": "Awan"},
  {"name": "Azura", "parent": "Eve"}
]
 
const dataSet = d3.stratify()
      .id(function(d) { return d.name; })
      .parentId(function(d) { return d.parent; })
      (data)
//画布大小
var width = 1000;
var height = 700;

//在 body 里添加一个 SVG 画布   
var svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);
// 1、数据准备
//定义边界
var marge = {top:50, bottom:0, left:10, right:0};

var svg = d3.select("svg");
var width = svg.attr("width");
var height = svg.attr("height");

var g = svg.append("g")
  .attr("transform","translate("+marge.top+","+marge.left+")");

var scale = svg.append("g")
  .attr("transform","translate("+marge.top+","+marge.left+")");

//指定缩放范围,缩放功能
const zoom = d3
  .zoom()
  .scaleExtent([0.2, 4])
  .on("zoom", (e) => {
    g.attr("transform", `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`);
  });
//动画持续时间
g.transition()
  .duration(this.duration)
  .call(zoom.transform, d3.zoomIdentity);
svg.call(zoom);

//数据
var dataset = {
  name:"中国",
  children:[
    {
      name:"浙江",
      children:[
        {name:"杭州" ,value:100},
        {name:"宁波",value:100},
            {name:"温州",value:100},
            {name:"绍兴",value:100}
      ]
    },
    {
      name:"广西",
      children:[
        {
          name:"桂林",
          children:[
            {name:"秀峰区",value:100},
                  {name:"叠彩区",value:100},
                  {name:"象山区",value:100},
                  {name:"七星区",value:100}
          ]
        },
        {name:"南宁",value:100},
            {name:"柳州",value:100},
            {name:"防城港",value:100}
      ]
    },
    {
      name:"黑龙江",
      children:[
        {name:"哈尔滨",value:100},
            {name:"齐齐哈尔",value:100},
            {name:"牡丹江",value:100},
            {name:"大庆",value:100}
      ]
    },
    {
      name:"新疆" , 
        children:
        [
            {name:"乌鲁木齐"},
            {name:"克拉玛依"},
            {name:"吐鲁番"},
            {name:"哈密"}
        ]
    }
  ]
};


// 2、创建一个层级布局
var hierarchyData = d3.hierarchy(dataset)
    		.sum(function(d){
    			return d.value;
    		});

// 3、创建一个树状图
//创建一个树状图
var tree = d3.tree()
  .size([width-400,height-200])
  .separation(function(a,b){
    return (a.parent==b.parent?1:2)/a.depth;
  }) 

// 4、初始化树状图,也就是传入数据,并得到绘制树基本数据
var treeData = tree(hierarchyData);

// 5、得到边和节点(已经完成转换的)
var nodes = treeData.descendants();
var links = treeData.links();

// 6、输出边和节点
console.log(nodes);
console.log(links);

// 7、创建一个贝塞尔生成曲线生成器
var Bézier_curve_generator = d3.linkHorizontal()
    		.x(function(d) { return d.y; })
    		.y(function(d) { return d.x; });
// 直角连接线 by wushengyuan
var drawLink = ({ source, target }) =>{
  const halfDistance = (target.y - source.y) / 2;
  const halfY = source.y + halfDistance;
  return `M${source.x},${source.y} L${source.x},${halfY} ${target.x},${halfY} ${target.x},${target.y}`;
}

// 8、绘制边
g.append("g")
  .selectAll("path")
  .data(links)
  .enter()
  .append("path")
  .attr("d",function(d){
    var start = {x:d.source.x,y:d.source.y};
    var end = {x:d.target.x,y:d.target.y};
    returnzier_curve_generator({source:start,target:end});
  })
  .attr("fill","none")
  .attr("stroke","yellow")
  .attr("stroke-width",1);

// 9、老规矩,先建立用来放在每个节点和对应文字的分组
var gs = g.append("g")
    		.selectAll("g")
    		.data(nodes)
    		.enter()
    		.append("g")
    		.attr("transform",function(d){
    			var cx = d.x;
    			var cy= d.y;
    			return "translate("+cy+","+cx+")";
    		});

// 10、绘制节点和文字
//绘制节点
gs.append("circle")
  .attr("r",6)
  .attr("fill","white")
  .attr("stroke","blue")
  .attr("stroke-width",1);
  
//文字
gs.append("text")
  .attr("x",function(d){
    return d.children?-40:8;
  })	// 如果某节点有子节点,则对应的文字前移
  .attr("y",-5)
  .attr("dy",10)
  .text(function(d){
    return d.data.name;
  })