上一篇: 横向柱状图 https://blog.csdn.net/zjw_python/article/details/98209333
下一篇: 堆叠面积图 https://blog.csdn.net/zjw_python/article/details/98214359
代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540
本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/lineChart/basicLineChart
1 图表效果
2 数据
date,money
Mon,120
Tue,200
Wed,150
Thu,80
Fri,70
Sat,110
Sun,130
3 关键代码
导入数据
d3.csv('./data.csv', function(d){
return {
date: d.date,
money: +d.money
};
}).then(function(data){
....
一些样式配置参数,折线图不同于柱状图,其网格线可能包括平行于X轴和Y轴两个方向
const config = {
lineColor: chart._colors(0),
margins: {top: 80, left: 80, bottom: 50, right: 80},
textColor: 'black',
gridColor: 'gray',
ShowGridX: [],
ShowGridY: [20, 40, 60, 80, 100, 120, 140, 160 ,180, 200, 220],
title: '折线图',
pointSize: 5,
pointColor: 'white',
hoverColor: 'red',
animateDuration: 1000
}
尺度转换,与柱状图类似
/* ----------------------------尺度转换------------------------ */
chart.scaleX = d3.scaleBand()
.domain(data.map((d) => d.date))
.range([0, chart.getBodyWidth()])
chart.scaleY = d3.scaleLinear()
.domain([0, (Math.floor(d3.max(data, (d) => d.money)/10) + 1)*10])
.range([chart.getBodyHeight(), 0])
渲染线条,这里运用了d3.line
函数方便地绘制了包含一系列点地折线。值得注意的是,我们想要折线的由左向右逐渐显现的动画效果不能简单依靠属性动画了,这里先对数据点进行了线性插值,然后使用了中间帧attrTween
动画达到效果。
/* ----------------------------渲染线条------------------------ */
chart.renderLines = function(){
let lines = chart.body().selectAll('.line')
.data([data]);
lines.enter()
.append('path')
.classed('line', true)
.merge(lines)
.attr('fill', 'none')
.attr('stroke', config.lineColor)
.attr('transform', 'translate(' + chart.scaleX.bandwidth()/2 +',0)')
.transition().duration(config.animateDuration)
.attrTween('d', lineTween);
lines.exit()
.remove();
//中间帧函数
function lineTween(){
const generateLine = d3.line()
.x((d) => d[0])
.y((d) => d[1]);
const pointX = data.map((d) => chart.scaleX(d.date));
const pointY = data.map((d) => chart.scaleY(d.money));
const interpolate = getInterpolate(pointX, pointY);
const ponits = [];
const interval = 1/(pointX.length-1);
let index = 0;
return function(t){
if (t - interval > 0 && t % interval < Math.pow(10, -1.4)){ //保证线条一定经过数据点
index = Math.floor(t / interval);
ponits.push([pointX[index], pointY[index]]);
}else{
ponits.push([interpolate.x(t), interpolate.y(t)]);
}
return generateLine(ponits);
}
}
//点插值
function getInterpolate(pointX, pointY){
const domain = d3.range(0, 1, 1/(pointX.length-1));
domain.push(1);
const interpolateX = d3.scaleLinear()
.domain(domain)
.range(pointX);
const interpolateY = d3.scaleLinear()
.domain(domain)
.range(pointY);
return {
x: interpolateX,
y: interpolateY
};
}
}
接下来渲染数据点,运用circle
元素
/* ----------------------------渲染点------------------------ */
chart.renderPonits = function(){
let ponits = chart.body().selectAll('.point')
.data(data);
ponits.enter()
.append('circle')
.classed('point', true)
.merge(ponits)
.attr('cx', (d) => chart.scaleX(d.date))
.attr('cy', (d) => chart.scaleY(d.money))
.attr('r', 0)
.attr('fill', config.pointColor)
.attr('stroke', config.lineColor)
.attr('transform', 'translate(' + chart.scaleX.bandwidth()/2 +',0)')
.transition().duration(config.animateDuration)
.attr('r', config.pointSize);
}
然后渲染坐标轴、文本标签、网格线等辅助组件,与柱状图类似
/* ----------------------------渲染坐标轴------------------------ */
chart.renderX = function(){
chart.svg().insert('g','.body')
.attr('transform', 'translate(' + chart.bodyX() + ',' + (chart.bodyY() + chart.getBodyHeight()) + ')')
.attr('class', 'xAxis')
.call(d3.axisBottom(chart.scaleX));
}
chart.renderY = function(){
chart.svg().insert('g','.body')
.attr('transform', 'translate(' + chart.bodyX() + ',' + chart.bodyY() + ')')
.attr('class', 'yAxis')
.call(d3.axisLeft(chart.scaleY));
}
chart.renderAxis = function(){
chart.renderX();
chart.renderY();
}
/* ----------------------------渲染文本标签------------------------ */
chart.renderText = function(){
d3.select('.xAxis').append('text')
.attr('class', 'axisText')
.attr('x', chart.getBodyWidth())
.attr('y', 0)
.attr('fill', config.textColor)
.attr('dy', 30)
.text('日期');
d3.select('.yAxis').append('text')
.attr('class', 'axisText')
.attr('x', 0)
.attr('y', 0)
.attr('fill', config.textColor)
.attr('transform', 'rotate(-90)')
.attr('dy', -40)
.attr('text-anchor','end')
.text('每日收入(元)');
}
/* ----------------------------渲染网格线------------------------ */
chart.renderGrid = function(){
d3.selectAll('.yAxis .tick')
.each(function(d, i){
if (config.ShowGridY.indexOf(d) > -1){
d3.select(this).append('line')
.attr('class','grid')
.attr('stroke', config.gridColor)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', chart.getBodyWidth())
.attr('y2', 0);
}
});
d3.selectAll('.xAxis .tick')
.each(function(d, i){
if (config.ShowGridX.indexOf(d) > -1){
d3.select(this).append('line')
.attr('class','grid')
.attr('stroke', config.gridColor)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -chart.getBodyHeight());
}
});
}
最后绑定鼠标交互事件,这里鼠标移动到点上时,会显示数值
/* ----------------------------绑定鼠标交互事件------------------------ */
chart.addMouseOn = function(){
//防抖函数
function debounce(fn, time){
let timeId = null;
return function(){
const context = this;
const event = d3.event;
timeId && clearTimeout(timeId)
timeId = setTimeout(function(){
d3.event = event;
fn.apply(context, arguments);
}, time);
}
}
d3.selectAll('.point')
.on('mouseover', function(d){
const e = d3.event;
const position = d3.mouse(chart.svg().node());
e.target.style.cursor = 'hand'
d3.select(e.target)
.attr('fill', config.textColor);
chart.svg()
.append('text')
.classed('tip', true)
.attr('x', position[0]+5)
.attr('y', position[1])
.attr('fill', config.textColor)
.text('收入:' + d.money);
})
.on('mouseleave', function(){
const e = d3.event;
d3.select(e.target)
.attr('fill', config.pointColor);
d3.select('.tip').remove();
})
.on('mousemove', debounce(function(){
const position = d3.mouse(chart.svg().node());
d3.select('.tip')
.attr('x', position[0]+5)
.attr('y', position[1]-5);
}, 6)
);
}