共计 6071 个字符,预计需要花费 16 分钟才能阅读完成。
公司我的项目要求做动静条形图,也就是 bar chart race
,原本想从网上找个 demo 察觉没有适合的,就本人写了一个。动静条形图能够很好地比照多个数据随之间变动的趋势。该 demo 是基于 d3
的 v6
版本,因为仅应用了一些最根底的 api
,所以在之前版本应该也能够跑起来。本文章仅提供一种实现的思路,不对 api
和“进入、更新、退出”模式作具体解说,所以在浏览之前最初对它们有一个最根本的理解。
首先须要找一个适合的数据源,如果切实找不到就去我的 GitHub 上复制就好了,我也是从别的网站复制过去的。
初始化变量
const width = 1200, height = 600, margin = {top: 20, bottom: 0, left: 50, right: 80};
const chartWidth = width - (margin.left + margin.right), chartHeight = height - (margin.top + margin.bottom);
const data = [];
const count = 10;
const duration = 500;
const barPadding = 20;
const barHeight = (chartHeight - (barPadding * count)) / count;
const getDate = () => dataOri[0][dateIndex];
let dateIndex = 0;
let date = getDate();
let dataSlice = [];
let chart = null, scale = null, axis = null, svg = null, dateTitle = null;
首先设定长、宽、外边距,和图表尺寸。data
寄存格式化后的数据。因为图表不可能把数据源中所有的行都显示进去,所以这里只取前 10
个。每隔 10
秒切换一纵列。柱间距为 20
。用图表高度减去柱间距乘以柱数量再除以柱数量得每个柱的宽。定义一个函数来获取以后列表头,这里就是日期,赋给 date
。定义 dataSlice
来寄存以后日期下的所有数据。最初定义 chart
寄存图表实例,scale
寄存比例尺,axis
寄存坐标轴,svg
寄存画布,dateTitle
寄存以后列表头。
const createSvg = () => svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height);
创立一个 svg
设置宽高并 append
到事后写好的 container
中。
格式化数据
function randomRgbColor() {const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r},${g},${b})`;
}
先申明一个创立随机色彩的函数,用来给条形上色。
咱们的数据源是这种模式的,须要对其进行简略的格式化,成为一个一个条目
const formatData = () => {dataOri[0].forEach((date, index) => {if (index > 0) {dataOri.forEach((row, rowIndex) => {if (rowIndex > 0) {
data.push({name: row[0],
value: Number(row[index]),
lastValue: index > 1 ? Number(row[index - 1]) : 0,
date: date,
color: randomRgbColor()});
}
});
}
});
}
两层循环,第一层循环列,第二层循环行,存入行表头,数据,上一列的数据,如果没有就写 0
,列表头,和一个随机的色彩,用作给条形图上色,至于 lastValue
的用处之后用到了会具体解释。
格式化后的数据如图所示:
const sliceData = () =>
dataSlice = data.filter(d => d.date === date).sort((a, b) => b.value - a.value).slice(0, count);
筛选出当天的数据,顺叙排列并取前 10
个
创立坐标轴
const createScale = () =>
scale = d3.scaleLinear().domain([0, d3.max(dataSlice, d => d.value)]).range([0, chartWidth]);
首先把比例尺创立进去,定义域是 0
到当天的最大值,值域是 0
到图表宽度,对 d3.js
的 api
不相熟的同学能够去官网补习一下或者自行百度,罕用的根本就那么几个。
坐标轴的最终成果如下图所示:
须要对坐标轴进行简略的配置
const renderAxis = () => {createScale();
axis = d3.axisTop().scale(scale).ticks(5).tickPadding(10).tickSize(0);
svg.append('g')
.classed('axis', true)
.style('transform', `translate3d(${margin.left}px, ${margin.top}px, 0)`)
.call(axis);
}
调用之前定义的比例尺函数创立比例尺,而后设置顶部的坐标轴,ticks
设置 5
个刻度(这个办法比拟有意思,尽管设置了5
,然而不肯定真的是 5
,可能比 5
多也可能比 5
少),tickPadding
设置刻度与数值之间的间距,tickSize
设置刻度线长度,这里不让它显示。设置实现之后 append
到图表中,程度位移,让出边距的地位。
创立参考线
这条竖线就是参考线,从坐标轴刻度延长进去,贯通整个图表。
const renderAxisLine = () => {d3.selectAll('g.axis g.tick').select('line.grid-line').remove();
d3.selectAll('g.axis g.tick').append('line')
.classed('grid-line', true)
.attr('stroke', 'black')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', chartHeight);
}
因为随着数据的变动,参考线是一直变动的,该函数会被重复调用,所以要在一开始革除上一组数据的参考线。而后在坐标轴每一个有刻度线的地位都 append
一条线进去,x1
和 y1
是该条线绝对于父元素的左端点,x2
和 y2
是右端点,因为要贯通整个图表,所以右端点的 y
坐标设置为 chartHeight
。
创立列表头
图表右下键这个日期,也就是列的表头
const renderDateTitle = () => {dateTitle = svg.append('text')
.classed('date-title', true)
.text(date)
.attr('x', chartWidth - margin.top)
.attr('y', chartHeight - margin.left)
.attr('fill', 'rgb(128, 128, 128)')
.attr('font-size', 40)
.attr('text-anchor', 'end')
}
位移至右下角,设置色彩。这里重点说一下 text-anchor
,次要使用在 svg
中 <text>
标签的一个属性,设置文本的对其形式,设置为 end
示意文本字符串的开端即以后文本的初始地位。
创立图表主体
const createChart = () => {chart = svg.append('g')
.classed('chart', true)
.style('transform', `translate3d(${margin.left}px, ${margin.top}px, 0)`);
}
创立一个容器寄存多个条形,并移到正地方。
const renderChart = () => {// 进入、更新、退出模式}
该函数寄存进入更新退出模式的代码,如果对进入、更新、退出模式不太理解的同学还是倡议先去自行了解一下。
const bars = chart.selectAll('g.bar').data(dataSlice, (d) => d.name);
let barsEnter;
barsEnter = bars.enter()
.append('g')
.classed('bar', true)
.style('transform', (d, i) => `translate3d(0, ${calTranslateY(i)}px, 0)`);
dateIndex > 1 && barsEnter
.transition().duration(this.duration)
.style('transform', (d, i) => `translate3d(0, ${calTranslateY(i, 'end')}px, 0)`);
barsEnter.append('rect')
.style('width', d => scale(d.value))
.style('height', barHeight + 'px')
.style('fill', d => d.color);
barsEnter.append('text')
.classed('label', true)
.text(d => d.name)
.attr('x', '-5')
.attr('y', barPadding)
.attr('font-size', 14)
.style('text-anchor', 'end');
barsEnter.append('text')
.classed('value', true)
.text(d => d.value)
.attr('x', d => scale(d.value) + 10)
.attr('y', barPadding);
将图形与 dataSlice
绑定,barsEnter
代表的是绑定了数据的图形,设置它的宽,高和色彩,条形左侧的 y
轴,这里对应的国家的名字,还有右侧数值标注。这里用到了一个工具函数:
const calTranslateY = (i, end) => {if (dateIndex === 1 || end) {return (barHeight + barPadding) * i + (barPadding / 2);
} else {return (barHeight + barPadding) * (count + 1);
}
}
当数据为第一列或者传入 end
的时候条形的纵轴地位在排序所在的地位,否则都放在图表里面,期待进入。
bars.transition().duration(duration).ease(d3.easeLinear)
.style('transform', function (d, i) {return 'translate3d(0,' + calTranslateY(i, 'end') + 'px, 0)';
})
.select('rect')
.style('width', function (d) {return scale(d.value) + 'px';
});
bars
.select('text.value')
.transition().duration(duration).ease(d3.easeLinear)
.attr('x', function (d) {return scale(d.value) + 10;
})
.tween('text', function (d) {
const textDom = this;
const i = d3.interpolateRound(d.lastValue, d.value);
return (t) => textDom.textContent = i(t);
});
更新模式,第一个办法链目标是条形依照程序排序,并且依据数值设定宽度。第一个办法链是设定右面标注的数值,并且自定义了一个数值过渡,让数值的增长没有那么僵硬,这里用到了一开始格式化数据的时候设置的 lastValue
。
bars.exit()
.transition().duration(duration).ease(d3.easeLinear)
.style('transform', function (d, i) {return 'translate3d(0,' + calTranslateY(i) + 'px, 0)';
})
.style('width', function (d) {return scale(d.value) + 'px';
})
.remove();
退出模式,将退出后的条形移到屏幕外并删除。
调用办法
const init = () => {createSvg(); // 创立一个 svg
formatData(); // 格式化数据
sliceData(); // 截取当天数据
renderAxis(); // 渲染坐标轴
renderAxisLine(); // 渲染批示线
renderDateTitle(); // 渲染日期
createChart(); // 创立图表
renderChart(); // 渲染图表
createTicker(); // 创立定时器}
init();
顺次调用一开始申明的那些办法,还有最初一个 createTicker
办法没有申明
function createTicker() {const ticker = d3.interval(() => {if (dateIndex < dataOri[0].length - 1) {
dateIndex++;
date = getDate();
dateTitle.text(date);
sliceData();
updateAxis();
renderAxisLine();
renderChart();} else {ticker.stop();
}
}, duration);
}
创立了一个定时器,每隔 duration
设定的事件进行切换,更新坐标轴、辅助线、图表等,这里用到了 updateAxis
办法。
const updateAxis = () => {createScale();
axis.scale().domain([0, d3.max(dataSlice, d => d.value)]);
svg.select('g.axis')
.transition().duration(duration).ease(d3.easeLinear)
.call(axis);
d3.selectAll('g.axis g.tick text').attr('font-size', 14);
}
该办法用于当数据扭转是更新坐标轴。
总结
动静条形图的所有性能都曾经开发完了,关上页面就能够看到动画成果了。残缺代码能够我的 GitHub 中下载。其实该图表算是 d3
比拟入门的成果,把握了进入、更新、退出模式和过渡之后就能够开发进去了。本文提供的思路也并非该图表实现的最优解,如有更好的实现办法欢送留言探讨。