文章目录
品牌排名动态可视化 @ D3.js
任务清单
- 做出一个类似于下图所示的能根据时间更新变换的品牌动态排行榜
- 数据地址
https://gist.github.com/jrzief/70f1f8a5d066a286da3a1e699823470f
整个clone下来即可
由于多位朋友反应,这个数据集比较难下载,我把它下载好了放在了我的网盘上,大家自行下载。
在文章结尾有源代码,复制下来即可运行。
数据链接: https://pan.baidu.com/s/1VQeqk5oDTTSysPI4SHGLwg 提取码: gpve
- 要求:1.动画效果,2.各品牌的排名与数值根据年份进行修改
思路分析
- 画布初始化
- 数据预处理
- 首次数据-图元绑定,即首次data-join
- 设置时间触发器(d3.interval / setInterval)
- 编写更新函数
- 更新数据,即重新得到前12名数据
- 更新x坐标轴(因为数据值会变大,原来的坐标轴不适用了)
- 重新进行数据-图元绑定
- 年份更新
画布初始化
首先要进行画布初始化,创建初始画布svg,定义全局变量,绘制标题,副标题,坐标轴等等。
let svg = d3.select('body').select('svg');
const top_n = 12;
const margin = {
top : 80,
right : 65,
bottom : 5,
left : 20
};
let height = +(svg.attr('height'))
let width = +(svg.attr('width'))
let xAxis;
let xScale;
let yScale;
let yearText;
const barPadding = (+svg.attr('height')-margin.top-margin.bottom)/(top_n*5)
let title = svg.append('text')
.attr('class','Title')
.attr('y', 30)
.attr('x',width/2 )
.text('BrandRank')
let subTitle = svg.append('text')
.attr('y', 55 )
.attr('x', width-margin.right-70)
.attr('class', 'subTitle' )
.text("Brand value , $m")
let year = 2000;
const tickDuration = 500;//执行间隔
数据预处理
这里的数据预处理有以下几点需要注意的
注意将所有值转换为数值类型
因为d3.csv默认读进来数据都是字符串类型的,因此为了方便后续计算,要转换成数值类型
注意设置缺失值的缺省值
由于数据中有许多 NaN,集中在value字段,因此在value字段要多进行一步缺省。
注意:缺省必须在转换为数值之后
d.value = +(d.value)
d.value = isNaN(d.value) ? 0 : d.value
为每个数据设置颜色
这里比较有意思,设置颜色这步完全可以挪到data-join的时候,但是这里可以默认为某个数据绑定上一个默认的颜色,使用到的是d3.hsl()
这个接口。
d.color = d3.hsl(Math.random()*360,0.75,0.75,0.8)
这是设置完的color数据值,最后一个参数是透明度,缺省值为1。
过滤数据,仅保留当前年份的,数据切片,设置排名
-
用
data.filter()
过滤数据。然后由于绑定只需要使用到前12名数据,因此这里用到了slice()
切片函数进行了数据切片处理。let yearSlice = data.filter(d => d.year == year && !isNaN(d.value)) .sort((a,b) => b.value - a.value) .slice(0,top_n)
-
由于最终需要根据数值排名来设置矩形的位置,因此要设置一下
yearSlice
中的数据排名//此时索引就是排名了 yearSlice.forEach((d,i) => d.rank = i)
首次数据绑定
首次数据绑定我这里为了让逻辑更加清晰,封装成了一个函数render_init(yearSlice)
,而没有写在d3.csv.then()
中 ,参数是已经切分好的当前年份top12的数据。注意,render_init()
中的内容是后续需要更新的图元,由于坐标轴后续也需要更新,因此将坐标轴也放在render_init()
中
//yearSilce:切片好了的数据
const render_init = function(yearSlice){
//functionBody()
}
坐标轴
xScale = d3.scaleLinear()
.domain([0,d3.max(yearSlice,d => d.value)])
.range([margin.left , width-margin.right])
.nice()
yScale = d3.scaleLinear()
.domain([top_n, 0])
.range([height-margin.bottom, margin.top]);
xAxis = d3.axisTop(xScale)
.ticks(width > 500 ? 5:2)
.tickSize(-(height-margin.top-margin.bottom))
.tickFormat(d => d3.format(',')(d))
;
svg.append('g')
.attr('class','xAxis')
.call(xAxis)
.attr('transform',`translate(${
margin.left},${
margin.top})`)
矩形bar
//首次join rect
svg.selectAll('rect.bar')
.data(yearSlice,d => d.name)
.enter()
.append('rect')
.attr('class','bar')
.attr('x',xScale(0)+margin.left+2)
.attr('width', d => xScale(d.value)-xScale(0))
.attr('y', d => yScale(d.rank))
.attr('height', yScale(1)-yScale(0)-barPadding)
.attr('fill', d => d.color)
品牌名label
//首次join text,品牌名
svg.selectAll('text.label')
.data(yearSlice, d => name)
.enter()
.append('text')
.attr('class', 'label')
.attr('x', d => xScale(d.value)-8)
.attr('y', d =>