绑定数组数据
在熟悉 d3 的selection 和 数据绑定思想后,就可以编写简单的数据展示页面了。
代码如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.h-bar {
min-height: 15px;
min-width: 10px;
background-color: steelblue;
margin-bottom: 2px;
font-size: 11px;
color: #f0f8ff;
text-align: right;
padding-right: 2px;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
const data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8]
function render(data) {
let bars = d3.select('body').selectAll('div.h-bar')
.data(data)
bars.enter()
.append('div')
.attr('class', 'h-bar')
.merge(bars)
.style('width', function (d) {
return (d * 3) + 'px'
})
.text(function (d) {
return d
})
bars.exit()
.remove()
}
setInterval(function () {
data.shift()
data.push(Math.round(Math.random() * 100))
render(data);
}, 1500)
render(data)
</script>
</body>
</html>
效果如下:
让我们来一步步解析这段简单的示例:
我们先定义了一个data数组,里面存放了我们要准备展示的数据,render函数包含了把data渲染到页面上的操作。我们通过定时器,不断删除data的第一个元素,并在末尾添加一个随机的数据,然后重新渲染。
在 render 函数中,我们首先是通过选择器获取到 body 下所有类名为 h-bar 的 div 元素,但是实际上页面中并没有 div 元素,所以这是个空集,按照我们之前的介绍,在这种情况下使用 data() 方法绑定数据,就相当是创建了许多个空的占位符,之后我们调用 bars.enter().append('div').attr('class', 'h-bar') 为 enter 选择集添加了 dom 元素,所以 11个 div 被加入了 body 里面。然后我们使用 merge() 方法,将对 enter 选择集和 update 选择集的操作集中起来,避免代码重复。 之后添加对 exit 选择集的 dom 删除操作。
在render 中,我们要理解为什么使用 merge() 方法:在第一次的渲染中,页面中没有任何匹配的div 元素, 我们的 update 和 exit 选择集是空的,只有enter 选择集,所以在第一次的渲染中我们要对 enter 选择集进行数据展示到dom的操作。同样的,在第一次render 后 body 下已经存在了 11 个 div 元素,所以在第二次及以后的渲染中,我们没有改变data的数据个数,所以第一次之后的渲染中,只有 update 选择集,enter 和 exit 选择集都是没有的,所以我们对数据展示到 dom 上的操作要对 update选择集操作。那我们既要对 enter 选择集操作,又要对update选择集操作,自然是使用 merge() 合并到一起操作。
其中 bars.exit().remove() 是没有起到作用的,只不过是为了展示一个完整的过程。如果我们在第二次渲染中将 data 的数据项变为 10 项,这段代码就会起作用,帮我么移除多余的那个没有匹配到数据项的 dom 元素。
绑定复杂数据
在上面的例子中,我们只对简单的数字数据进行了操作,在对复杂的对象进行操作时,其实也是同样的操作。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="../style.css">
<title>Document</title>
</head>
<body>
<script src="../d3.js"></script>
<script>
const data = [
{width: 10, color: 23},
{width: 15, color: 33},
{width: 30, color: 40},
{width: 50, color: 60},
{width: 80, color: 22},
{width: 65, color: 10},
{width: 55, color: 5},
{width: 30, color: 30},
{width: 20, color: 60},
{width: 10, color: 90},
{width: 8, color: 10},
]
let colorScale = d3.scaleLinear().domain([0, 100]).range(['#add8e6', 'blue'])
function render(data) {
let bars = d3.select('body').selectAll('div.h-bar').data(data)
bars.enter()
.append('div')
.attr('class', 'h-bar')
.merge(bars)
.style('width', function (d) {
return (d.width * 5) + 'px'
})
.style('background-color', function (d) {
return colorScale(d.color)
})
.text(function (d) {
return d.width
})
bars.exit().remove()
}
function randomValue() {
return Math.round(Math.random() * 100)
}
setInterval(function () {
data.shift()
data.push({width: randomValue(), color: randomValue()})
render(data)
}, 1500)
render(data)
</script>
</body>
</html>
效果如下:
这里使用到了scale,我们马上会在后面讲解,我们现在只需要知道colorScale()函数会返回一个对应的颜色即可。
对于对象的数据项,我们也只是 d.color 这么简单的一步就解决了。
绑定函数数据
data() 方法除了绑定数组数据项外,还可以绑定返回一个数组的函数。函数的参数为 d(数据) i(索引) nodes(父节点),函数内部 this 指向当前分组的父元素。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.v-bar {
min-width: 30px;
background-color: #4682b4;
margin-right: 2px;
font-size: 10px;
color: #f0f8ff;
text-align: center;
display: inline-block;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="../d3.js"></script>
<script>
const data = []
const datum = function (x) {
return 15 + x * x
}
let newData = function () {
data.push(datum)
return data
}
function render() {
let divs = d3.select('#container').selectAll('div').data(newData)
divs.enter()
.append('div').append('span')
divs.attr('class', 'v-bar')
.style('height', function (d, i) {
return d(i) + 'px'
})
.select('span')
.text(function (d, i) {
return d(i)
})
divs.exit().remove()
}
setInterval(function () {
render()
}, 1000)
render()
</script>
</body>
</html>
将函数绑定为数据项其实最实用的还是为子选择集绑定子数据,如:
const matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
const tr = d3.select("body").append("table").selectAll("tr")
.data(matrix)
.enter()
.append("tr");
const td = tr.selectAll("td")
.data(function(d) { return d; })
.enter()
.append("td")
.text(function(d) { return d; });
数据的过滤
我们可以使用 selection.filter() 方法,来实现对筛选的元素进行操作。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.h-bar {
min-height: 15px;
min-width: 10px;
background-color: steelblue;
margin-bottom: 2px;
font-size: 11px;
color: #f0f8ff;
text-align: right;
padding-right: 2px;
}
.selected {
background-color: #f08080;
}
</style>
</head>
<body>
<div class="control-group">
<button onclick="select('Retail')">
Retail
</button>
<button onclick="select('Gas')">
Gas
</button>
<button onclick="select('Dining')">
Dining
</button>
<button onclick="select()">
Clear
</button>
</div>
<script src="../d3.js"></script>
<script>
const data = [
{expense: 10, category: 'Retail'},
{expense: 15, category: 'Gas'},
{expense: 30, category: 'Retail'},
{expense: 50, category: 'Dining'},
{expense: 80, category: 'Gas'},
{expense: 65, category: 'Retail'},
{expense: 55, category: 'Gas'},
{expense: 30, category: 'Dining'},
{expense: 20, category: 'Retail'},
{expense: 10, category: 'Dining'},
{expense: 8, category: 'Gas'},
]
function render(data, category) {
let bars = d3.select('body').selectAll('div.h-bar')
.data(data)
bars.enter()
.append('div')
.attr('class', 'h-bar')
.style('width', function (d) {
return (d.expense * 5) + 'px'
})
.append('span')
.text(function (d) {
return d.category
})
d3.selectAll('div.selected').classed('selected', false)
bars.filter(function (d, i) {
return d.category === category
})
.classed('selected', true)
}
render(data)
function select(category) {
render(data, category)
}
</script>
</body>
</html>
效果如下:
数据的排序
同样的使用 selector.sort() 可以达到常用的排序效果。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.h-bar {
min-height: 15px;
min-width: 10px;
background-color: steelblue;
margin-bottom: 2px;
font-size: 11px;
color: #f0f8ff;
text-align: right;
padding-right: 2px;
}
</style>
</head>
<body>
<div class="control-group">
<button onclick="sort(compareByExpense)">
Sort by Expense
</button>
<button onclick="sort(compareByCategory)">
Sort by Category
</button>
<button onclick="sort()">
Reset
</button>
</div>
<script src="../d3.js"></script>
<script>
let data = [
{expense: 10, category: 'Retail'},
{expense: 15, category: 'Gas'},
{expense: 30, category: 'Retail'},
{expense: 50, category: 'Dining'},
{expense: 80, category: 'Gas'},
{expense: 65, category: 'Retail'},
{expense: 55, category: 'Gas'},
{expense: 30, category: 'Dining'},
{expense: 20, category: 'Retail'},
{expense: 10, category: 'Dining'},
{expense: 8, category: 'Gas'},
]
function render(data, comparator) {
let bars = d3.select('body').selectAll('div.h-bar')
.data(data)
bars.enter()
.append('div')
.attr('class', 'h-bar')
.append('span')
d3.selectAll('div.h-bar')
.style('width', function (d) {
return (d.expense * 5) + 'px'
})
.select('span')
.text(function (d) {
return d.category
})
if (comparator) {
bars.sort(comparator)
}
}
const compareByExpense = function (a, b) {
return a.expense < b.expense ? -1 : 1
}
const compareByCategory = function (a, b) {
return a.category < b.category ? -1 : 1
}
render(data)
function sort(comparator) {
render(data, comparator)
}
</script>
</body>
</html>
效果如下: