enter-update-exit
将数据和图形分为两个领域,分别记作A
和B
,相互之间绑定才能做出图形展示,可以产生下面四种情况
领域 | 状态 | 表示 |
---|---|---|
数据 | 未绑定图形 | A−(A⋂B)A - (A \bigcap B)A−(A⋂B) A0A_0A0 |
数据 | 已绑定图形 | A⋂BA\bigcap BA⋂B A1A_1A1 |
图形 | 未绑定数据 | B−A⋂BB - A\bigcap BB−A⋂B B0B_0B0 |
图形 | 已绑定数据 | A⋂BA\bigcap BA⋂B B1B_1B1 |
可以看到,整个集合被分割为了三个部分
A∪B=(A−A∩B)+A∩B+(B−A∩B)
A \cup B = (A - A\cap B) + {A\cap B} + (B - A\cap B)
A∪B=(A−A∩B)+A∩B+(B−A∩B)
data{A0A1⋯B1B0}graph \text{data}\left\{ \begin{matrix} A_0 & & \\ A_1 & \cdots & B_1 \\ & & B_0 \end{matrix} \right\}\text{graph} data⎩⎨⎧A0A1⋯B1B0⎭⎬⎫graph
基础数据
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style type="text/css">
.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 type="text/javascript">
let data = [1,2,3,4,5,6,7,8,9];
let last_index = 8;
let direction = true;
function render(data){
// 选择元素
d3.select("body").selectAll("div.h-bar")
// 指定数据
.data(data)
// 绑定
.enter()
.append("div")
.attr("class", "h-bar")
.append("span");
d3.select("body").selectAll("div.h-bar")
// 获取数据
.data(data)
// 操作数据
.style("width", function (d){
return (d * 3) + "px";
})
.select("span")
// 填充数据
.text(function (d){
return d;
});
// 选择元素
d3.select("body").selectAll("div,h-bar")
// 获取数据
.data(data)
// 解绑
.exit()
.remove();
}
setInterval(function(){
if(direction && data[last_index] > 100){
direction = false;
} else if(!direction && data[last_index] < 1){
direction = true;
}
data.shift();
data.push(data[last_index-1] + (direction ? 1 : -1));
render(data);
}, 10);
render(data);
</script>
</body>
</html>
对象数据
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style type="text/css">
.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 type="text/javascript">
// each内拿到的是集合中的单个对象,可以直接使用
var 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}
];
var colorScale = d3.scale.linear()
.domain([0, 100])
.range(["#add8e6", "blue"]);
function render(data) {
d3.select("body").selectAll("div.h-bar")
.data(data)
.enter().append("div")
.attr("class", "h-bar")
.append("span");
d3.select("body").selectAll("div.h-bar")
.data(data)
.exit().remove();
d3.select("body").selectAll("div.h-bar")
.data(data)
.attr("class", "h-bar")
.style("width", function (d) {
return (d.width * 5) + "px";
})
.style("background-color", function(d){
return colorScale(d.color);
})
.select("span")
.text(function (d) {
return d.width;
});
}
function randomValue() {
return Math.round(Math.random() * 100);
}
setInterval(function () {
data.shift();
data.push({width: randomValue(), color: randomValue()});
render(data);
}, 100);
render(data);
</script>
</body>
</html>
方法数据
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style type="text/css">
.v-bar {
min-height: 1px;
min-width: 30px;
background-color: #4682b4;
margin-right: 2px;
font-size: 10px;
color: #f0f8ff;
text-align: center;
width: 10px;
display: inline-block;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/javascript">
var data = [];
var counter = 0;
var boundary = 50;
var period = 10;
var flag = true;
var scale = 10;
// 元素是方法
var next = function (x) {
x += counter;
if((x % 2 === 0)&&(x % period) === 0){
flag = !flag;
}
x = x % period;
if(flag){
return x * scale;
} else {
return (period - x) * scale;
}
};
var newData = function () {
counter += 1;
if(data.length > boundary){
data.shift();
}
data.push(next);
return data;
};
function render(){
var selection = d3.select("#container")
.selectAll("div")
// 数据是方法
.data(newData);
selection.enter().append("div").append("span");
selection.attr("class", "v-bar")
// 这里只是单纯的元素遍历,具体元素类型应该参考传入data
.style("height", function (d, i) {
return d(i) + "px";
})
.select("span")
.text(function(d, i){
return d(i);
});
selection.exit().remove();
}
setInterval(function () {
render();
}, 100);
render();
</script>
</body>
</html>
数组操作
api | description |
---|---|
d3.min | 最小值 |
d3.max | 最大值 |
d3.extent | [最小值,最大值] |
d3.sum | 求和 |
d3.medium | 中位数 |
d3.mean | 平均数 |
d3.ascending | 正序 |
d3.descending | 倒序 |
d3.quantile | P(x≤X)P(x \leq X)P(x≤X) |
d3.bisect | 插入点left<X<rightleft < X < rightleft<X<right |
d3.nest | 转换树状结构 |
var array = [3, 2, 11, 7, 6, 4, 10, 8, 15];
d3.select("#min").text(d3.min(array));
d3.select("#max").text(d3.max(array));
d3.select("#extent").text(d3.extent(array));
d3.select("#sum").text(d3.sum(array));
d3.select("#median").text(d3.median(array));
d3.select("#mean").text(d3.mean(array));
d3.select("#asc").text(array.sort(d3.ascending));
d3.select("#desc").text(array.sort(d3.descending));
d3.select("#quantile").text(
d3.quantile(array.sort(d3.ascending), 0.25)
);
d3.select("#bisect").text(
d3.bisect(array.sort(d3.ascending), 6)
);
var records = [
{quantity: 2, total: 190, tip: 100, type: "tab"},
{quantity: 2, total: 190, tip: 100, type: "tab"},
{quantity: 1, total: 300, tip: 200, type: "visa"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 1, total: 100, tip: 0, type: "cash"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 2, total: 90, tip: 0, type: "tab"},
{quantity: 2, total: 200, tip: 0, type: "cash"},
{quantity: 1, total: 200, tip: 100, type: "visa"}
];
var nest = d3.nest()
.key(function (d) {
return d.type;
})
.key(function (d) {
return d.tip;
})
.entries(records);
d3.select("#nest").html(printNest(nest, ""));
function printNest(nest, out, i) {
if(i === undefined) i = 0;
var tab = ""; for(var j = 0; j < i; ++j) tab += " ";
nest.forEach(function (e) {
if (e.key)
out += tab + e.key + "<br>";
else
out += tab + printObject(e) + "<br>";
if (e.values)
out = printNest(e.values, out, ++i);
else
return out;
});
return out;
}
function printObject(obj) {
var s = "{";
for (var f in obj) {
s += f + ": " + obj[f] + ", ";
}
s += "}";
return s;
}
数据筛选
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style type="text/css">
.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>
<script type="text/javascript">
var 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) {
d3.select("body").selectAll("div.h-bar")
.data(data)
.enter()
.append("div")
.attr("class", "h-bar")
.append("span");
d3.select("body").selectAll("div.h-bar")
.data(data)
.exit().remove();
d3.select("body").selectAll("div.h-bar")
.data(data)
.attr("class", "h-bar")
.style("width", function (d) {
return (d.expense * 5) + "px";}
)
.select("span")
.text(function (d) {
return d.category;
});
// 筛选
d3.select("body").selectAll("div.h-bar")
.filter(function (d, i) {
return d.category == category;
})
.classed("selected", true);
}
render(data);
function select(category) {
render(data, category);
}
</script>
<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>
</body>
</html>
图形排序
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style type="text/css">
.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>
<script type="text/javascript">
var 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) {
d3.select("body").selectAll("div.h-bar")
.data(data)
.enter().append("div")
.attr("class", "h-bar")
.append("span");
d3.select("body").selectAll("div.h-bar")
.data(data)
.exit().remove();
d3.select("body").selectAll("div.h-bar")
.data(data)
.attr("class", "h-bar")
.style("width", function (d) {
return (d.expense * 5) + "px";
})
.select("span")
.text(function (d) {
return d.category;
});
if(comparator)
d3.select("body")
.selectAll("div.h-bar")
.sort(comparator);
}
var compareByExpense = function (a, b) {
return a.expense < b.expense?-1:1;
};
var compareByCategory = function (a, b) {
return a.category < b.category?-1:1;
};
var compareByNameLength = function (a, b){
return a.category.length - b.category.length;
}
render(data);
function sort(comparator) {
render(data, comparator);
}
</script>
<div class="control-group">
<button onclick="sort(compareByExpense)">
Sort by Width
</button>
<button onclick="sort(compareByCategory)">
Sort by Category
</button>
<button onclick="sort(compareByNameLength)">
Sort by Category's Length
</button>
<button onclick="sort()">
Clear
</button>
</div>
</body>
</html>
动态更新
没啥,就是通过接口调用替换data
,这里就不写了。