前言:本篇主要是d3.js里面的datajoin
本质上是将数据与图元进行绑定
例如:
为什么要使用data-join?
selection.attr('attrbuteName',value)
selection.attr('attrbuteName',(d,i) = >{...})
data(dataArray)
(1)dataArray在保证是一个数组的前提下可以是任何形式
(2)先考虑数据和图元数目相同的情况:
Updata
:图元和数据条目相同
Enter
:数据的条目多于图元甚至没有图元,常用于第一次绑定数据
Exit
:数据的条目少于图元甚至没有数据,常用于结束可视化
有图元有数据
const p = maingroup.selectAll('.dataourve').data(data).attr(...).attr(...)
.merge
(enterSelection).attr(…).attr(…)
有数据没图元,d3.jd会自动搞清楚哪些数据是新增的,根据新增的数据生成相应的图元。
生成图元的占位,占位内容需要编程者自行添加(append)
const p = maingroup.selectAll('.class').data(data).enter().append('').attr(...)
enter本质上生成指向父节点的指针,而append操作相当于在父节点后添加指针数量的图元并将其余多出的数据绑定。
有图元没有数据,d3.js会自动“搞清楚”哪些图元是不绑定数据的
const p= maingroup.selectAll('.class').data(data).exit().remove()
.data(...).join(...)
默认enter和updata的执行形式相同
默认exit是删除(remove)节点
默认data-join形式简洁但不灵活
必须需要设置enter数据的初始图元属性,updata会每次重新设置初始值,从而导致动画出现“奇怪”的效果
仍支持“定制”:
.join(enter => enter.append('text').attr('fill','green').text(d=>d),
updata =>(),exit=>())
Updata经常与d3,js的动画一起使用
.transition().duration();
d3.selectAll('rect').data(data2,d=>d.name)
.transition().duration(3000).attr('width',d=>xScale(d.age))
//duration(...)中为毫秒
.transition(...)
经过调用后,后续的链式调用会变成数值上的渐变,渐变的时间由.duration(...)
决定.data(data,keyFunction)
keyFunction的返回值通常是一个字符串(string)
keyFunction的定义根据数据,比如keyFunction = d = >d.name
selection.data(data,d=>d.name);
d3.selectAll('rect').data(data2,d=>d.name).attr('width',d=>xScale(d.age))
常见的是CSV数据。第一行为属性列表,后续每一行对应一条数据,CSV本质上是纯文本,区别于EXCEL的格式
d3.csv('path/to/data.csv').then(data=>{...})
Promise
’对象(Promise对象用于执行异步操作).then(...)
的参数为一个函数,参数为.csv(...)
的返回值d3.csv(...)
会正常向服务器请求数据,在请求并处理好之后,将结果扔给.then(...)
中的回调函数数据要从服务器端获取,还涉及到跨域的问题。目前bug还没完全找出来
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用d3的data-join散点图title>
<script src='https://d3js.org/d3.v5.min.js'>script>
head>
<body style="text-align: center;">
<svg width="1650" height="920" id="mainsvg" class="svgs" style="background-color: #ffffff;">svg>
<script>
const svg = d3.select('#mainsvg');
const width = +svg.attr('width');
const height = +svg.attr('height');
const margin = {
top:100,right:120,bottom:100,left:120};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const xValue = d=>{
return Math.log(d['确诊人数'] +1)};
const yValue = d=>{
return Math.log([d['新增确诊'] +1 ])};
const rValue = d=>{
return Math.log(d['感染率']*500)*0.8};
const keyHint = '地区';
let xScale,yScale;
let maxX,maxY;
let datas;
let aduration = 1000;
let metapop;
const xAxisLabel = '累计确诊人数(对数)';
const yAxisLabel = '新增人数(对数)';
var color = {
"武汉":"#ff1c12",
"黄石": "#de5991",
"十堰": "#759AA0",
"荆州": "#E69D87",
"宜昌": "#be3259",
"襄阳": "#EA7E53",
"鄂州": "#EEDD78",
"荆门": "#9359b1",
"孝感": "#47c0d4",
"黄冈": "#F49F42",
"咸宁": "#AA312C",
"恩施州": "#B35E45",
"随州": "#4B8E6F",
"仙桃": "#ff8603",
"天门": "#ffde1d",
"潜江": "#1e9d95",
"神农架": "#7289AB"
}
const renderinit = function(data,seq){
xScale = d3.scaleLinear()
.domain(d3.extent(data,xValue))
.range([0,innerWeight])
.nice();
yScale = d3.scaleLinear()
.domain(d3.extent(data,yValue).reverse())
.range([0,innerHeight])
.nice();
maxX = xScale(d3.max(data,xValue));
maxY = yScale(d3.max(data,yValue));
//添加组
const g = svg.append('g')
.attr('transform',`translate(${
margin.left},${
margin.top})`)
.attr('id','maingroup');
const yAxis = d3.axisLeft(yScale)
.tickSize(-innerWidth)
.tickPadding(10);
const xAxis = d3.axisBottom(xScale)
.tickSize(-innerHeight)
.tickPadding(10);
let yAxisGroup = g.append('g').call(yAxis)
.attr('id','yaxis');
yAxisGroup.append('text')
.attr('font-size','2em')
.attr('transform',`rotate(-90)`)
.attr('x',-innerHeight/2)
.attr('y',-60)
.attr('fill','#333333')
.text(yAxisLabel)
.attr('text-anchor','middle')
yAxisGroup.selectAll('.domain').remove();
let xAxisGroup = g.append('g').call(xAxis)
.attr('transform', `translate(${
0}, ${
innerHeight})`)
.attr('id', 'xaxis');
xAxisGroup.append('text')
.attr('font-size', '2em')
.attr('y', 60)
.attr('x', innerWidth / 2)
.attr('fill', '#333333')
.text(xAxisLabel);
xAxisGroup.selectAll('.domain').remove();
var legend_color = [
"#ff1c12",
"#de5991",
"#759AA0",
"#E69D87",
"#be3259",
"#EA7E53",
"#EEDD78",
"#9359b1",
"#47c0d4",
"#F49F42",
"#AA312C",
"#B35E45",
"#4B8E6F",
"#ff8603",
"#ffde1d",
"#1e9d95",
"#7289AB"
]
var legend_name = ["武汉市",
"黄石市",
"十堰市",
"荆州市",
"宜昌市",
"襄阳市",
"鄂州市",
"荆门市",
"孝感市",
"黄冈市",
"咸宁市",
"恩施州",
"随州市",
"仙桃市",
"天门市",
"潜江市",
"神农架",
];
//下面是复制粘贴的
var legend = d3.select('#maingroup').selectAll(".legend")
.data(legend_name)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + (innerWidth + 10) + "," + (i * 25 + 300) + ")"; });
// draw legend colored rectangles
legend.append("rect")
.data(legend_name)
.attr("x", 0)
.attr("y", 0)
.attr("width", 30)
.attr("height", 20)
.style("fill", function (d,i) {
return legend_color[i];});
// draw legend text
legend.append("text")
.data(legend_name)
.attr('class', 'legend_text')
.attr("x", 40)
.attr("y", 9)
.attr("dy", ".5em")
.style("text-anchor", "start")
.text(function (d,i) {
return legend_name[i];});
};
const renderupdate = function(seq){
const g = d3.select('#maingroup');
time = seq[0]['日期'];
g.selectAll('.date_text').remove();
g.append("text")
.data(['seq'])
.attr('class', 'date_text')
.attr("x", innerWidth / 4 + 30)
.attr("y", innerHeight / 10 - 20)
.attr("dy", ".5em")
.style("text-anchor", "end")
.attr("fill", "#504f4f")
.attr('font-size', '6em')
.attr('font-weight', 'bold')
.text(time);
circleupdates = g.selectAll('circle').data(seq, d => d[keyHint]);
circleenter = circleupdates.enter().append('circle')
.attr('cy', (datum) => {
return yScale(yValue(datum)) })
.attr('cx', (datum) => {
return xScale(xValue(datum)) }) // use xSacle to re-scale data space (domain) and return the rescaled population;
.attr('r', datum => rValue(datum))
.attr('fill', function(d,i) {
return color[d[keyHint]] })
.attr('opacity', .8);
circleupdates.merge(circleenter).transition().ease(d3.easeLinear).duration(aduration)
.attr('cy', (datum) => {
return yScale(yValue(datum)) })
.attr('cx', (datum) => {
return xScale(xValue(datum)) }) // use xSacle to re-scale data space (domain) and return the rescaled population;
.attr('r', datum => rValue(datum));
textupdates = g.selectAll('.province_text').data(seq);
textenter = textupdates.enter().append('text')
.attr("class", "province_text")
.attr("x", (datum) => {
return xScale(xValue(datum)); })
.attr("y", (datum) => {
return yScale(yValue(datum)); })
.attr("dy", "1em")
.style("text-anchor", "middle")
.attr("fill", "#333333")
//.attr('opacity', 0)
.text(function (d,i) {
return d[keyHint];
});
textupdates.merge(textenter).transition().ease(d3.easeLinear).duration(aduration)
.attr('x', (datum) => {
return xScale(xValue(datum)); })
.attr('y', (datum) => {
return yScale(yValue(datum)); });
};
d3.csv('hubeipop.csv').then(data => {
data.forEach(datum => {
datum['人口(万人)'] = +(datum['人口(万人)']);
})
metapop = data;
});
d3.csv('hubei_day14.csv').then(function(data){
data = data.filter(datum => {
return datum[keyHint] !== '总计'});
data.forEach(datum => {
// pre-process the data;
datum['确诊人数'] = +(datum['确诊人数']);
datum['治愈人数'] = +(datum['治愈人数']);
datum['死亡人数'] = +(datum['死亡人数']);
datum['新增确诊'] = +(datum['新增确诊']);
if(datum['新增确诊'] < 0){
datum['新增确诊'] = 0;
}
datum['感染率'] = datum['确诊人数'] /
(metapop.find(x => x[keyHint] === datum[keyHint])['人口(万人)']);
});
// remove duplicated items;
alldates = Array.from(new Set(data.map( datum => datum['日期'])));
// make sure dates are listed according to real time order;
alldates = alldates.sort(function(a,b){
return new Date(b.date) - new Date(a.date);
});
dates = alldates;
// re-arrange the data sequentially;
sequential = [];
alldates.forEach(datum => {
sequential.push([]);
});
data.forEach(datum => {
sequential[alldates.indexOf(datum['日期'])].push(datum);
});
renderinit(data, sequential[0]);
// set the animation interval;
let c = 0;
intervalId = setInterval(function(){
if(c >= alldates.length){
console.log('time to close this animation');
clearInterval(intervalId);
}else{
renderupdate(sequential[c]);
c = c + 1;
}
}, aduration);
});
script>
body>
html>