前两天都是展现Data而已,今天来试做看看互动&换资料的动态!
折线图也是满常见的样式,
这次以非洲的通膨为主题,主要功能为
1.可以有三个国家资料做比较
2.点击把其中一个国家隐藏
老样子先看成果:
主要流程如下:
- 基本绘制:svg定位、x & y轴
- 资料读取&整理
- 绘制线段、点、label & legend
- 资料互动:监听legend点击
- 资料更新的监听:监听selection change
- 资料更新:线段等的位置移动
参考
更换data的动态是参考这个:Connected scatter plot with dropdown to select group,
隐藏线段是参考这个: Connected scatter plot with interactive legend
//1. 基本绘制
const margin = {top: 10, right: 100, bottom: 30, left: 30},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//1. 基本绘制: append the svg to the body of the page
const svg = d3.select("#my_data")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",`translate(${margin.left},${margin.top})`);
// 1. 基本绘制 add X-axis
const x = d3.scaleLinear()
.domain([2012, 2020])
.range([0, width]);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")));//.tickFormat(d3.format("d"))是把数字变成字串
//1. 基本绘制 dd Y-axis
const y = d3.scaleLinear()
.domain( [-10,50])
.range([height, 0 ]);//上面是起点,要把-10往下推,就让他y变高高
svg.append("g")
.call(d3.axisLeft(y));
//2. 资料读取&整理
let countriesData = []
let time = []
for(i = 2012; i <=2020; i++){
time.push(i);
}
//2.资料读取&整理:开始读资料
d3.csv('https://raw.githubusercontent.com/africadatahub/adh-africa-inflation/main/data/africa_inflation_data.csv')
.then(data=>{
//2. 资料准备:create countries
countriesData = data.map(d=>d.country);
//2. 资料准备:加入下拉选单
d3.select('#countrySelect')
.selectAll('option')
.data(countriesData)
.join('option')
.attr('value', d=> d)
.text(d=> d)
//2. 资料准备:先有个初始资料
let allGroup = ['Burundi','Chad','Gabon'];
//2. 资料准备:将拿到的资料整理成好读版
//整理成这样的阵列:[{name: 国家名称, values: [{2012: 0.18}, {2013: 0.25}...]}
//,{name: 国家, ...}]
let dataReady = allGroup.map(groupName => {
return {
name: groupName,
values: data.reduce((pre,data)=>{
if(data.country == groupName){
result = time.map(year=>{
return {time: year, value: data[year]}
})
}
return result
}, [])
}
})
//2. 资料准备: colorScale 把国家配对成一个一个颜色
let myColor = d3.scaleOrdinal().domain(countriesData).range(d3.schemeSet2);
//3. 绘制:Line Maker
const line = d3.line()
.x(d=>x(+d.time))
.y(d=>y(+d.value));
//3. 绘制:drawlines
let lines = svg.selectAll('myLines')
.data(dataReady)
.join("path")
.attr("class", d => d.name.replace(/\s*/g,""))
//class name用国家名称,但有些国家名称有空格,所以把空格拔掉
.attr("d", d => line(d.values))
.attr("stroke", d => myColor(d.name))
.style("stroke-width", 4)
.style("fill", "none");
//3. 绘制:add points Group
let pointsGroup = svg.selectAll('myDots')
.data(dataReady)
.join('g')
.style('fill', d=>myColor(d.name))
.attr('class',d=>d.name.replace(/\s*/g,""))
//3. 绘制:add points
let points = pointsGroup.selectAll('myPoints')
.data(d=>d.values)
.join('circle')
.attr('cx', d=>x(d.time))
.attr('cy', d=>y(d.value))
.attr('stroke','white')
.attr('r',5);
//3. 绘制:Add labels
let label = svg.selectAll()
.data(dataReady)
.join('g')
.append('text')
.attr('class',d=>d.name.replace(/\s*/g,""))
.datum(d=>{return {name: d.name, value: d.values[d.values.length -1]}})
.attr('transform', d =>`translate( ${x(d.value.time) + 10} ,${y(d.value.value)} )`)
.text(d=>d.name)
.style('height', '30px')
.style('fill',d=>myColor(d.name))
.style("font-size", 15);
//3. 绘制:加上图例
myLegend = svg.selectAll('myLegend')
.data(dataReady)
.join('g')
.append('text')
.attr('x', 30)
.attr('y', (d,i)=> 10 + i*30)
.text(d=>d.name)
.style('fill',d=>myColor(d.name))
.style('font-size',15)
//4.资料互动:Interactive: hide line
//监听图标的点击
myLegend.on('click',function(e,d){
currentOpacity = d3.selectAll('.'+d.name.replace(/\s*/g,"")).style('opacity');
d3.selectAll('.'+d.name.replace(/\s*/g,"")).style('opacity',currentOpacity == 1 ? 0 : 1);
})
//5.资料change data: 监听器
d3.select('#countrySelect').on('change', function(){
//用d3 的nodes()拿到被点击的节点
let selected = d3.selectAll('#countrySelect>option:checked').nodes();
//固定只取三个
if(selected.length != 3) return;
allGroup = [];
d3.selectAll('#countrySelect>option:checked')
.each((item)=>{
allGroup.push(item)
});
//有变动时我就呼叫update罗,请看下一小节
update();
})
//6. 资料更新
function update(){
dataReady = allGroup.map(groupName => {
return {
name: groupName,
values: data.reduce((pre,data)=>{
if(data.country == groupName){
result = time.map(year=>{
return {time: year, value: data[year]}
})
}
return result
}, [])
}
});
lines.data(dataReady)
.join()
.transition()
.duration(1000)
.attr("class", d => d.name.replace(/\s*/g,""))
.attr("d", d => line(d.values))
.attr("stroke", d => myColor(d.name))
.style("stroke-width", 4)
.style("fill", "none");
pointsGroup.data(dataReady)
.join()
.transition()
.duration(1000)
.style('fill', d=>myColor(d.name))
.attr('class',d=>d.name.replace(/\s*/g,""))
points.data(d=>d.values)
.join()
.transition()
.duration(1000)
.attr('cx', d=>x(d.time))
.attr('cy', d=>y(d.value))
.attr('stroke','white')
.attr('r',5);
label.data(dataReady)
.join()
.attr('class',d=>d.name.replace(/\s*/g,""))
.datum(d=>{return {name: d.name, value: d.values[d.values.length -1]}})
.transition()
.duration(1000)
.attr('transform', d =>`translate( ${x(d.value.time) + 10} ,${y(d.value.value)} )`)
.text(d=>d.name)
.style('height', '30px')
.style('fill',d=>myColor(d.name))
.style("font-size", 15);
myLegend.data(dataReady)
.join()
.transition()
.duration(1000)
.attr('y',(d,i)=> 10 + i*30)
.attr('x', 30)
.text(d=>d.name)
.style('fill',d=>myColor(d.name))
.style('font-size',15);
}
})
今天的code在这里
当然还有太多细节可以调整,譬如说资料标签重叠的地方,以及option多选的介面,还有一次强迫选3条...
等之後有时间再回头来!QQ
有任何错误或想法欢迎批评指教!
<<: TypeScript | namespace 心得纪录
各位早安阿~ 不知不觉间已经来到铁人赛第十天,也就是过完1/3了呢,想想还真是快。只不过今天就开学了...
预设已经帮我们建立一个 Text 元件"Hello world",且会看到Aut...
PWM-脉冲宽度调变 我相信很多人在使用Arduion的时候还是不清楚PWM到底在干嘛? PWM是一...
在谈论完书中提的组织 domain 的三种模式 : Transaction Script Domai...
本文目标 学习基本的排程演算法 阅读原始码以理解排程器的实际运作 常见的排程演算法 FCFS (Fi...