D3是一个JavaScript函数库,用于创建动态、交互式数据可视化,支持各种图表、地图和复杂的视觉表现,广泛应用于展示。
1 D3概述
- D3版本下载:https://github.com/d3/d3/releases、https://d3js.org。
- GitHub下载的ZIP包只是未进行编译的D3源代码文件,不能直接使用。
- 选择元素(选择集)、绑定数据是D3最基础的内容,通常需要一起使用。
- 选择元素:
d3.select()
(选所有指定元素的第一个)、d3.selectAll()
(选指定的全部元素)。 - 绑定数据:
datum()
(绑定数据到选择集)、data()
(绑定数组到选择集,各项值对应元素绑定)。
- 选择元素:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>D3概述</title>
<!-- 使用官网链接的引入,需要梯子 -->
<!-- <script src='http://d3js.org/d3.v3.min.js' charset='utf-8'> -->
<!-- </script> -->
<!-- 使用CDN节点的链接引入 -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body style='text-align: center'><br />
<p></p>
<p></p>
<p></p><br />
<h3></h3>
<h3></h3>
<h3></h3>
<script>
// datum()
var str = 'datum()';
// 选择元素
var body = d3.select('body');
var p = body.selectAll('p');
// 绑定数据
p.datum(str);
p.text(function (x, y) {
return '第 ' + y + ' 个元素绑定的数据是 ' + x;
});
// data()
var dataset = ['耐心', '坚持', '勇敢'];
var body = d3.select('body');
var h = body.selectAll('h3');
h.data(dataset)
.text(function (x, y) {
return '我要' + x + '!';
});
</script>
</body>
</html>
1-1 选择元素
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>选择元素</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body style='text-align: center'><br />
<p id='p1' class='c1'>富强</p>
<p id='p2' class='c2'>民主</p>
<p id='p3' class='c3'>文明</p>
<p id='p4' class='c4'>和谐</p>
<p id='p5' class='c5'>自由</p>
<p id='p6' class='c6'>平等</p>
<p id='p7' class='c6'>公正</p>
<p id='p8' class='c6'>法治</p>
<script>
// 选择所有元素
d3.select('body')
.selectAll('p')
.style('color', 'blue');
// 选择第一个元素
d3.select('body')
.select('p')
.style('color', 'red');
// 选择第二个元素
d3.select('#p2')
.style('color', 'green');
// 选择后三个元素
d3.selectAll('.c6')
.style('color', 'orange');
</script>
</body>
</html>
1-2 插入元素
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>插入元素</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body style='text-align: center'><br />
<p id='p1' class='c1'>富强</p>
<p id='p2' class='c2'>民主</p>
<p id='p3' class='c3'>文明</p>
<p id='p4' class='c4'>和谐</p>
<p id='p5' class='c5'>自由</p>
<p id='p6' class='c6'>平等</p>
<p id='p7' class='c6'>公正</p>
<p id='p8' class='c6'>法治</p>
<script>
// insert():在选择集前面插入元素
d3.select('body')
.insert('p', '#p1')
.text('社会主义核心价值观');
// append():在选择集末尾插入元素
d3.select('body')
.append('p')
.text('爱国');
d3.select('body')
.append('p')
.text('敬业');
d3.select('body')
.append('p')
.text('诚信');
d3.select('body')
.append('p')
.text('友善');
</script>
</body>
</html>
1-3 删除元素
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>删除元素</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body style='text-align: center'><br />
<p id='p1' class='c1'>富强</p>
<p id='p2' class='c2'>民主</p>
<p id='p3' class='c3'>文明</p>
<p id='p4' class='c4'>和谐</p>
<p id='p5' class='c5'>自由</p>
<p id='p6' class='c6'>平等</p>
<p id='p7' class='c6'>公正</p>
<p id='p8' class='c6'>法治</p>
<script>
// remove():删除一个元素
d3.select('#p8')
.remove();
</script>
</body>
</html>
2 矩形图
- HTML5提供了SVG和Canvas两种强有力的画布。
- SVG:D3提供了众多的SVG图形生成器,都只支持SVG,建议使用该画布。
- SVG绘制的是矢量图,将图像进行放大不会失真,可为每个元素添加JavaScript事件处理器。
- 每个图形均视为对象,更改对象属性,图形也将改变,x轴正方向水平向右,y轴则垂直向下。
- Canvas
- 一旦图形被绘制完成,就不会继续得到浏览器的关注。
- 如果位置发生了变化,那么整个场景也需要重新绘制。
2-1 横向矩形
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>横向矩形</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
// 数据:矩形的宽度
var dataset = [280, 210, 170, 130, 90, 160, 110];
// 添加画布:画布的宽度
var width = 300;
// 添加画布:画布的高度
var height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 每个矩形所占的像素高度(包括空白)
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');
</script>
</body>
</html>
2-2 纵向矩形
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>纵向矩形</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
// 数据:矩形的宽度
var dataset = [280, 210, 170, 130, 90, 160, 110];
// 添加画布:画布的宽度
var width = 300;
// 添加画布:画布的高度
var height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 每个矩形所占的像素高度(包括空白)
var rectHeight = 25;
// 绘制纵向矩形
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('y', function (d, i) {
return height - d;
})
.attr('x', function (d, i) {
return i * rectHeight;
})
.attr('height', function (d) {
return d;
})
.attr('width', rectHeight - 2)
.attr('fill', 'steelblue');
</script>
</body>
</html>
2-3 常用比例尺
- 比例尺就是一个数据映射函数,例如线性比例尺可实现类似
y = ax + b
的变换。 - D3共有三类比例尺:数量型、序数型(序数、颜色比例尺)、时间型(时间比例尺)。
- 数量型:线性、恒等、乘方、对数、量化比例尺、分位数比例尺、临界值比例尺。
- D3中的比例尺,有定义域和值域,分别被称为domain和range。
- 常用的比例尺
- 线性比例尺:能将一个连续的区间,映射到另一区间。
- 序数比例尺:定义域和值域不一定连续,属于离散值。
(1) 线性比例尺
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>线性比例尺</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body><br />
<script>
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
var min = d3.min(dataset);
var max = d3.max(dataset);
// 将dataset中最小的值映射成0,最大的值映射成300
var linear = d3.scaleLinear()
.domain([min, max])
.range([0, 300]);
console.log(linear(0.9));
console.log(linear(2.3));
console.log(linear(3.3));
</script>
</body>
</html>
(2) 序数比例尺
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>序数比例尺</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
</head>
<body><br />
<script>
var index = [0, 1, 2, 3, 4, 5, 6];
var color = [
'red', 'orange', 'yellow',
'green', 'cyan', 'blue', 'purple'
];
// 0对应颜色red,1对应orange,以此类推
var ordinal = d3.scaleOrdinal()
.domain(index)
.range(color);
console.log(ordinal(0));
console.log(ordinal(2));
console.log(ordinal(6));
</script>
</body>
</html>
2-4 添加比例尺
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>添加比例尺</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
var min = d3.min(dataset);
var max = d3.max(dataset);
// 将dataset中最小的值设为0
var linear = d3.scaleLinear()
.domain([0, max])
// 最大的值映射成300
.range([0, 300]);
// 添加画布:画布的宽度
var width = 300;
// 添加画布:画布的高度
var height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 每个矩形所占的像素高度(包括空白)
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 linear(d);
})
.attr('height', rectHeight - 2)
.attr('fill', 'steelblue');
</script>
</body>
</html>
2-5 定义坐标轴
- 要生成坐标轴,则需要用到比例尺,二者经常一起使用。
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>定义坐标轴</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
</head>
<body><br />
<script>
// 数据
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
// 定义比例尺
var linear = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, 300]);
// 定义坐标轴
var axis = d3.axisBottom(linear)
// 指定刻度数量
.ticks(7);
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', 300)
// 设定高度
.attr('height', 300);
// 每个矩形所占的像素高度(包括空白)
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 linear(d);
})
.attr('height', rectHeight - 2)
.attr('fill', 'steelblue');
// 添加坐标轴
svg.append('g')
.call(axis)
// 默认的样式不美观,在head中增加style样式
.attr('transform', 'translate(20,130)')
.call(axis);;
</script>
</body>
</html>
2-6 完整矩形图
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>完整矩形图</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
// 添加SVG画布,设置画布大小
width = 300;
height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 画布周边的空白
var padding = {
left: 30,
right: 30,
top: 30,
bottom: 30
};
// 定义一个数组
var dataset = [10, 30, 50, 70, 33, 24, 17, 9];
// 定义x轴比例尺
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, width - padding.left - padding.right]);
// 定义y轴比例尺
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
// 定义x轴
var xAxis = d3.axisBottom(xScale);
// 定义y轴
var yAxis = d3.axisLeft(yScale);
// 矩形之间的空白
var rectPadding = 5;
// 添加矩形元素
var rects = svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.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.bandwidth() - rectPadding)
.attr('height', function (d) {
return height - padding.top - padding.bottom - yScale(d);
})
.attr('fill', 'steelblue');
// 添加文字
var texts = svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
)
.attr('x', function (d, i) {
return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
})
.attr('y', function (d) {
return yScale(d) - 5;
})
.attr('dx', '0.1em')
.attr('dy', '1.5em')
.text(function (d) {
return d;
})
.style('fill', '#FFF')
.style('text-anchor', 'middle');
// 添加x轴
svg.append('g')
.call(xAxis)
.attr(
'transform',
'translate(' + padding.left + ','
+ (height - padding.bottom) + ')'
);
// 添加y轴
svg.append('g')
.call(yAxis)
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
);
</script>
</body>
</html>
3 动态效果
- D3提供了4种方法用于实现图形的过渡,即动态效果,代表从状态A变成了状态B。
- transition():启动过渡效果,前后是图形变化前后的状态(形状、位置、颜色等)。
- duration():指定过渡的持续时间,单位为毫秒。
- ease():指定过渡的方式,常用方式有以下几种。
- 线性变化(d3.easeLinear)、弹性变化(d3.easeElastic)。
- 弹跳变化(d3.easeBounce)、圆形变化(d3.easeCircle)。
- delay():指定延迟时间,表示一定时间后才开始转变,可对整体或个别指定延迟。
3-1 简单动态效果
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>简单动态效果</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg width='410' height='410'>
<circle id='c1'></circle>
<circle id='c2'></circle>
<circle id='c3'></circle>
</svg>
<script>
// 获取SVG画布
var svg = d3.select('svg');
// 第一个圆移动x坐标
var circle1 = svg.select('#c1')
.attr('cx', 100)
.attr('cy', 100)
.attr('r', 20)
.style('fill', 'green');
// 在1秒(1000毫秒)内将圆心坐标由100变为300
circle1.transition()
.duration(1000)
.attr('cx', 300);
// 第二个圆移动x坐标,并改变颜色
var circle2 = svg.select('#c2')
.attr('cx', 100)
.attr('cy', 200)
.attr('r', 20)
.style('fill', 'green');
// 在1.5秒(1500毫秒)内将圆心坐标由100变为300
circle2.transition()
.duration(1500)
.attr('cx', 300)
// 将颜色从绿色变为橘色
.style('fill', 'orange');
// 第三个圆移动x坐标
var circle3 = svg.select('#c3')
.attr('cx', 100)
.attr('cy', 300)
// 改变颜色和半径
.attr('r', 20)
.style('fill', 'green');
// 在2秒(2000毫秒)内将圆心坐标由100变为300
circle3.transition()
// 并将颜色从绿色变为橘色
.duration(2000)
.ease(d3.easeBounce)
.attr('cx', 300)
.style('fill', 'orange')
// 半径从20变成35
.attr('r', 35);
</script>
</body>
</html>
3-2 矩形动态效果
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>矩形动态效果</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
// 添加SVG画布,设置画布大小
width = 300;
height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 画布周边的空白
var padding = {
left: 30,
right: 30,
top: 30,
bottom: 30
};
// 定义一个数组
var dataset = [10, 30, 50, 70, 33, 24, 17, 9];
// 定义x轴比例尺
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, width - padding.left - padding.right]);
// 定义y轴比例尺
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
// 定义x轴
var xAxis = d3.axisBottom(xScale);
// 定义y轴
var yAxis = d3.axisLeft(yScale);
// 矩形之间的空白
var rectPadding = 5;
// 添加矩形元素
var rects = svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.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.bandwidth() - rectPadding)
.attr('height', function (d) {
return height - padding.top - padding.bottom - yScale(d);
})
.attr('fill', 'steelblue');
// 添加文字
var texts = svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
)
.attr('x', function (d, i) {
return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
})
// y轴文字稍作修改
.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) - 5;
})
.attr('dx', '0.1em')
.attr('dy', '1.5em')
.text(function (d) {
return d;
})
.style('fill', '#FFF')
.style('text-anchor', 'middle');
// 添加x轴
svg.append('g')
.call(xAxis)
.attr(
'transform',
'translate(' + padding.left + ','
+ (height - padding.bottom) + ')'
);
// 添加y轴
svg.append('g')
.call(yAxis)
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
);
</script>
</body>
</html>
3-3 数量关系处理
- update()、enter()、exit()是D3处理选择集和数据之间数量关系不确定的重要函数。
- update()
- 绑定数据数量 = 对应元素,实际并不存在这样的一个函数。
- 但是对应的元素正好满足时,直接操作即可,后面跟style等。
- enter()
- 绑定数据数量 > 对应元素,即:对应的元素数量不足。
- 需添加元素,使之与绑定数据的数量相等,常跟append。
- exit()
- 绑定数据数量 < 对应元素,即:对应的元素数量过多。
- 需删除元素,使之与绑定数据的数量相等,常跟remove。
3-4 图表交互操作
- 交互操作,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应。
- 用户用于交互的工具:鼠标、键盘、触屏。
- 鼠标事件:click、dblclick、mouseover、mouseout、mousemove、mousedown、mouseup。
- 键盘事件:keydown(按任意键)、keypress(按字符键)、keyup(释放键,不区分字母大小写)。
- 触屏事件:touchstart(触摸点放在屏上)、touchmove(触摸点移动)、touchend(触摸点移开)。
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>图表交互操作</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<script>
// 添加SVG画布,设置画布大小
width = 300;
height = 300;
// 选择文档中的body元素
var svg = d3.select('body')
// 添加一个svg元素
.append('svg')
// 设定宽度
.attr('width', width)
// 设定高度
.attr('height', height);
// 画布周边的空白
var padding = {
left: 30,
right: 30,
top: 30,
bottom: 30
};
// 定义一个数组
var dataset = [10, 30, 50, 70, 33, 24, 17, 9];
// 定义x轴比例尺
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, width - padding.left - padding.right]);
// 定义y轴比例尺
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
// 定义x轴
var xAxis = d3.axisBottom(xScale);
// 定义y轴
var yAxis = d3.axisLeft(yScale);
// 矩形之间的空白
var rectPadding = 5;
// 添加矩形元素
var rects = svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.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.bandwidth() - rectPadding)
.attr('height', function (d) {
return height - padding.top - padding.bottom - yScale(d);
})
.attr('fill', 'steelblue')
// 鼠标移至矩形条,更改颜色
.on('mouseover', function (d, i) {
d3.select(this)
.attr('fill', 'purple');
})
.on('mouseout', function (d, i) {
d3.select(this)
.transition()
.duration(500)
.attr('fill', 'steelblue');
});
// 添加文字
var texts = svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
)
.attr('x', function (d, i) {
return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
})
.attr('y', function (d) {
return yScale(d) - 5;
})
.attr('dx', '0.1em')
.attr('dy', '1.5em')
.text(function (d) {
return d;
})
.style('fill', '#FFF')
.style('text-anchor', 'middle');
// 添加x轴
svg.append('g')
.call(xAxis)
.attr(
'transform',
'translate(' + padding.left + ','
+ (height - padding.bottom) + ')'
);
// 添加y轴
svg.append('g')
.call(yAxis)
.attr(
'transform',
'translate(' + padding.left + ',' + padding.top + ')'
);
</script>
</body>
</html>
4 布局详解
- Layout,将不适合用于绘图的数据转换成了适合用于绘图的数据(数据转换)。
- D3常用的布局
5 饼状图形
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>饼状图形</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
);
// 数据
var dataset = [30, 10, 43, 55, 13];
// 定义一个布局
var pie = d3.pie();
// 数据转换
var piedata = pie(dataset);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 绘制图形,这里绘制的是饼图,需要使用弧生成器,设置外半径和内半径
var outerRadius = 100;
// 内半径,为0则中间没有空白
var innerRadius = 70;
// 弧生成器,设置外半径和内半径
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
// 在SVG中添加图形元素
var arcs = svg.selectAll('g')
// 添加足够数量的分组元素(5个)
.data(piedata)
.enter()
.append('g')
// 每个分组用于存放一段弧的相关元素
.attr('class', 'arc');
// 对每个g元素添加path
arcs.append('path')
.attr('fill', function (d, i) {
return color(i);
})
.attr('d', function (d) {
// 调用弧生成器,得到路径值,参数d是被绑定的数据
return arc(d);
});
// 在每个弧线中心添加文本
arcs.append('text')
.attr('transform', function (d) {
// arc.centroid(d)能算出弧线的中心
return 'translate(' + arc.centroid(d) + ')';
})
.attr('text-anchor', 'middle')
// text()返回的是d.data,被绑定的数据是对象
// 包含d.startAngle、d.endAngle、d.data等
.text(function (d) {
return d.data;
});
</script>
</body>
</html>
6 力导向图
- 力导向图,英文叫Force-Directed Graph,是绘图的一种算法。
- 在二维或三维空间里配置节点,节点之间用线连接,称为连线。
- 节点之间的各个连线长度几乎都相等,并且会尽可能地不相交。
- 节点和连线都被施加了力的作用,而力则根据节点和连线的相对位置计算。
- nodes(节点)和edges(边)都是必备属性,节点和边数组都可以有额外属性。
- nodes数据是一个表示节点的对象数组,而edges是一个表示边的对象数组。
- 其中,source和target子属性也是必备的,其值代表了nodes数组中的索引。
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>力导向图</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 800;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 4 + ',' + height / 4 + ')'
);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 初始数据
var nodes = [
{ name: '福建' }, { name: '广东' },
{ name: '福州' }, { name: '莆田' },
{ name: '广州' }, { name: '茂名' }, { name: '深圳' }
];
var edges = [
{ source: 0, target: 1 }, { source: 0, target: 2 },
{ source: 0, target: 3 }, { source: 1, target: 4 },
{ source: 1, target: 5 }, { source: 1, target: 6 }
];
// 定义一个力导向图的布局,连接力,排斥力,中心力
var simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink().links(edges).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 5, height / 5));
// 绘制连线
var svg_edges = svg.selectAll('line')
.data(edges)
.enter()
.append('line')
.style('stroke', '#ccc')
.style('stroke-width', 1);
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('r', 15)
.style('fill', function (d, i) {
return color(i);
})
// 确保使用新的拖动API
.call(
d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
);
// 添加文字
var svg_texts = svg.selectAll('text')
.data(nodes)
.enter()
.append('text')
.style('fill', 'black')
.attr('dx', 20)
.attr('dy', 8)
.text(function (d) {
return d.name;
});
// 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
simulation.on('tick', function () {
// 每个tick更新节点和连线的位置
svg_edges.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; });
svg_nodes.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
svg_texts.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; });
});
// 拖动的处理函数
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
6-1 分子式
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>分子式</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 800;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 4 + ',' + height / 3 + ')'
);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 数据
var graph = {
'nodes': [
{ 'atom': '福建', 'size': 30 },
{ 'atom': '福州', 'size': 10 },
{ 'atom': '莆田', 'size': 10 },
{ 'atom': '广东', 'size': 30 },
{ 'atom': '广州', 'size': 10 },
{ 'atom': '茂名', 'size': 10 },
{ 'atom': '深圳', 'size': 10 }
],
'links': [
{ 'source': 0, 'target': 0, 'bond': 1 },
{ 'source': 0, 'target': 1, 'bond': 1 },
{ 'source': 0, 'target': 2, 'bond': 1 },
{ 'source': 0, 'target': 3, 'bond': 1 },
{ 'source': 3, 'target': 3, 'bond': 1 },
{ 'source': 3, 'target': 4, 'bond': 1 },
{ 'source': 3, 'target': 5, 'bond': 1 },
{ 'source': 3, 'target': 6, 'bond': 1 }
]
}
// 定义一个力导向图的布局,连接力,排斥力
var simulation = d3.forceSimulation(graph.nodes)
.force('charge', d3.forceManyBody().strength(-100))
.force('link', d3.forceLink(graph.links).distance(function (d) {
// 根据大小调整距离
return 20 + 2 * (d.source.size + d.target.size);
}));
// 定义一个半径比例尺
var radius = d3.scaleSqrt()
.range([0, 10])
.domain([1, 20]);
// 绘制连线
var svg_edges = svg.selectAll('line')
.data(graph.links)
.enter()
.append('line')
.style('stroke', '#ccc')
.style('stroke-width', 1);
svg_edges.filter(function (d) { return d.bond > 1; })
.append('line')
.attr('class', 'separator');
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(graph.nodes)
.enter()
.append('circle')
.attr('r', function (d) { return radius(d.size); })
.style('fill', function (d, i) {
return color(i);
})
// 确保使用新的拖动API
.call(
d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
);
// 添加文字
var svg_texts = svg.selectAll('text')
.data(graph.nodes)
.enter()
.append('text')
.style('fill', 'black')
.attr('dx', 20)
.attr('dy', 8)
.text(function (d) {
return d.atom;
});
// 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
simulation.on('tick', function () {
// 每个tick更新节点和连线的位置
svg_edges.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; });
svg_nodes.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
svg_texts.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; });
});
// 拖动的处理函数
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
6-2 带文字
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>带文字</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 800;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 4 + ',' + height / 3 + ')'
);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 初始数据,链接数组
var links = [
{ source: '福建', target: '福建', type: 'node', weight: 1 },
{ source: '福建', target: '福州', type: 'node', weight: 1 },
{ source: '福建', target: '莆田', type: 'node', weight: 1 },
{ source: '广东', target: '福建', type: 'node', weight: 1 },
{ source: '广东', target: '广东', type: 'node', weight: 1 },
{ source: '广东', target: '广州', type: 'node', weight: 1 },
{ source: '广东', target: '茂名', type: 'node', weight: 1 },
{ source: '广东', target: '深圳', type: 'node', weight: 1 }
];
// 初始化一个空的节点对象映射
var nodes = {};
// 从链接中分离出不同的节点
links.forEach(function (link) {
link.source = nodes[link.source]
|| (nodes[link.source] = { name: link.source });
link.target = nodes[link.target]
|| (nodes[link.target] = { name: link.target });
});
// 定义一个力导向图的布局,连接力,排斥力,中心力
var simulation = d3.forceSimulation(Object.values(nodes))
.force('link', d3.forceLink().links(links).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 5, height / 5));
// 绘制连线
var svg_edges = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.style('stroke', '#ccc')
.style('stroke-width', 1);
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(Object.values(nodes))
.enter()
.append('circle')
.attr('r', 15)
.style('fill', function (d, i) {
return color(i);
})
// 确保使用新的拖动API
.call(
d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
);
// 添加文字
var svg_texts = svg.selectAll('text')
.data(Object.values(nodes))
.enter()
.append('text')
.style('fill', 'black')
.attr('dx', 20)
.attr('dy', 8)
.text(function (d) {
return d.name;
});
// 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
simulation.on('tick', function () {
// 每个tick更新节点和连线的位置
svg_edges.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; });
svg_nodes.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
svg_texts.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; });
});
// 拖动的处理函数
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
6-3 弧箭头*
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>弧箭头</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 800;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 4 + ',' + height / 3 + ')'
);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 初始数据,链接数组
var links = [
{ source: '福建', target: '福建', type: 'node', weight: 1 },
{ source: '福建', target: '福州', type: 'node', weight: 1 },
{ source: '福建', target: '莆田', type: 'node', weight: 1 },
{ source: '广东', target: '福建', type: 'node', weight: 1 },
{ source: '广东', target: '广东', type: 'node', weight: 1 },
{ source: '广东', target: '广州', type: 'node', weight: 1 },
{ source: '广东', target: '茂名', type: 'node', weight: 1 },
{ source: '广东', target: '深圳', type: 'node', weight: 1 }
];
// 初始化一个空的节点对象映射
var nodes = {};
// 从链接中分离出不同的节点
links.forEach(function (link) {
link.source = nodes[link.source]
|| (nodes[link.source] = { name: link.source });
link.target = nodes[link.target]
|| (nodes[link.target] = { name: link.target });
});
// 定义一个力导向图的布局,连接力,排斥力,中心力
var simulation = d3.forceSimulation(Object.values(nodes))
.force('link', d3.forceLink().links(links).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 5, height / 5));
// 创建箭头
svg.append('svg:defs')
.selectAll('marker')
.data(['suit'])
.enter()
.append('svg:marker')
// 确保ID与引用时使用的字符串匹配
.attr('id', 'suit')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 15)
.attr('refY', -1.5)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('svg:path')
// M0,-5表示路径的起点,坐标(0, -5)
// L10,0表示从上一步的起点绘制一条直线到坐标(10, 0)的点
// L0,5表示从上一步的点绘制一条直线到坐标(0, 5)的点
.attr('d', 'M0,-5L10,0L0,5');
// 绘制连线
var svg_edges = svg.selectAll('.link')
.data(links)
.enter()
.append('svg:path')
.attr('class', function (d) {
return 'link ' + d.type;
})
// 使用默认的箭头标记
.attr('marker-end', function (d) {
return 'url(#' + d.type + ')';
})
// 调整线宽,使其更细
.style('stroke-width', 1)
// 添加虚线效果
.style('stroke-dasharray', '0,2 1');
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(Object.values(nodes))
.enter()
.append('circle')
.attr('r', 15)
.style('fill', function (d, i) {
return color(i);
})
// 确保使用新的拖动API
.call(
d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
);
// 添加文字
var svg_texts = svg.selectAll('text')
.data(Object.values(nodes))
.enter()
.append('text')
.style('fill', 'black')
.attr('dx', 20)
.attr('dy', 8)
.text(function (d) {
return d.name;
});
// 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
simulation.on('tick', function () {
// 每个tick更新节点和连线的位置
svg_edges.attr('d', function (d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
// 计算距离
dr = Math.sqrt(dx * dx + dy * dy);
// 增大系数,使弧线更弯曲
// 系数越大好像越接近直线,非箭头连线,设置没效果???
dr = dr * 0.5 + 100;
// dr + ' 0 0,1 ',第一个0指弧线的大弧形或小弧形
// 第二个0,指弧线的扫掠方向,0代表顺时针,1代表逆时针
// 第三个1,指弧线结束于起点顺时针旋转超过180度的位置
return 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ','
+ dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
});
svg_nodes.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
svg_texts.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; });
});
// 拖动的处理函数
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
6-4 带图片*
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>带图片</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 画布
var width = 800;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 4 + ',' + height / 3 + ')'
);
// color颜色比例尺,能根据传入的索引号获取相应的颜色值
var color = d3.scaleOrdinal(d3.schemeCategory10);
// 初始数据,链接数组
var links = [
{ source: '福建', target: '福建', type: 'node', weight: 1 },
{ source: '福建', target: '福州', type: 'node', weight: 1 },
{ source: '福建', target: '莆田', type: 'node', weight: 1 },
{ source: '广东', target: '福建', type: 'node', weight: 1 },
{ source: '广东', target: '广东', type: 'node', weight: 1 },
{ source: '广东', target: '广州', type: 'node', weight: 1 },
{ source: '广东', target: '茂名', type: 'node', weight: 1 },
{ source: '广东', target: '深圳', type: 'node', weight: 1 }
];
// 初始化一个空的节点对象映射
var nodes = {};
// 从链接中分离出不同的节点
links.forEach(function (link) {
link.source = nodes[link.source]
|| (nodes[link.source] = { name: link.source });
link.target = nodes[link.target]
|| (nodes[link.target] = { name: link.target });
});
// 定义一个力导向图的布局,连接力,排斥力,中心力
var simulation = d3.forceSimulation(Object.values(nodes))
.force('link', d3.forceLink().links(links).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 5, height / 5));
// 绘制连线
var svg_edges = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.style('stroke', '#ccc')
.style('stroke-width', 1);
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(Object.values(nodes))
.enter()
.append('circle')
.attr('r', 15)
.style('fill', function (d, i) {
return color(i);
})
.call(
d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
);
// 添加图片
// svg_nodes.append('image')
// .attr('xlink:href', './file/avater1.png')
// .attr('x', -8)
// .attr('y', -8)
// .attr('width', 16)
// .attr('height', 16);
// 为不同节点设置不同图片,图片效果出不来???
svg_nodes.append('image')
.attr('xlink:href', function (d) {
// 根据节点数据返回不同的图片URL
return d.name === '福建' ? './file/avater2.png' :
d.name === '广东' ? './file/avater3.png' :
'./file/avater1.png';
})
.attr('x', -8)
.attr('y', -8)
.attr('width', 16)
.attr('height', 16);
// 添加提示,这里和添加文字是否有区别???
svg_nodes.append('title')
.text(function (d) { return d.name; });
// 添加文字
var svg_texts = svg.selectAll('text')
.data(Object.values(nodes))
.enter()
.append('text')
.style('fill', 'black')
.attr('dx', 20)
.attr('dy', 8)
.text(function (d) {
return d.name;
});
// 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
simulation.on('tick', function () {
// 每个tick更新节点和连线的位置
svg_edges.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; });
svg_nodes.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
svg_texts.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; });
});
// 拖动的处理函数
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
7 可视化地图
- 制作地图需要Json文件,将Json格式应用于地理上的文件,叫GeoJson文件。
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>可视化地图</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
</script>
<!-- 引入TopoJSON -->
<script src='https://cdn.bootcdn.net/ajax/libs/topojson/3.0.2/topojson.js'>
</script>
<style>
svg {
display: block;
margin: auto;
}
.country {
fill: steelblue;
stroke: white;
stroke-width: 0.5px;
}
.country:hover {
fill: orange;
}
</style>
</head>
<body><br />
<svg></svg>
<script>
// 设置SVG容器
var width = 960;
height = 600;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height);
// 投影设置:GeoJson中的地图数据都是三维的经纬度,而网页显示为二维
// 此时需要设定一个投影函数,用于经纬度转换
const projection = d3.geoMercator()
// scale为缩放因子,数值越小缩放效果越大
.scale(90)
.translate([width / 2, height / 2]);
// 地理路径生成器
const path = d3.geoPath()
.projection(projection);
// 加载数据
d3.json('https://d3js.org/world-110m.v1.json')
.then(data => {
// 转换TopoJSON数据
const countries = topojson.feature(
data, data.objects.countries
).features;
// 绘制国家
svg.selectAll('.country')
.data(countries)
.enter()
.append('path')
.attr('class', 'country')
.attr('d', path)
.attr('fill', 'steelblue')
.attr('stroke', 'white')
.attr('stroke-width', 0.5)
.on('mouseover', function (event) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'orange');
})
.on('mouseout', function (event) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'steelblue');
});
});
</script>
</body>
</html>