上一篇: 多符号散点图 https://blog.csdn.net/zjw_python/article/details/98483989
下一篇: 雷达图 https://blog.csdn.net/zjw_python/article/details/98486655
代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540
本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/scatterChart/bubbleChart
x,y,size
24,12,64
39,20,14
78,15,67
15,80,34
36,70,14
7,11,64
36,13,34
34,89,74
9,95,6
72,35,25
26,65,80
16,36,10
99,65,45
46,78,65
85,42,55
32,14,20
43,12,31
73,95,8
8,6,16
81,62,11
导入数据
d3.csv('./data.csv', function(d){
return {
x: +d.x,
y: +d.y,
r: +d.size
};
}).then(function(data){
.....
一些样式参数配置
const config = {
margins: {top: 80, left: 80, bottom: 50, right: 80},
textColor: 'black',
gridColor: 'gray',
ShowGridX: [10, 20, 30, 40, 50, 60, 70 ,80, 90, 100],
ShowGridY: [10, 20, 30, 40, 50, 60, 70 ,80, 90, 100],
title: '气泡图',
pointMaxSize: 20,
hoverColor: 'white',
animateDuration: 1000,
pointCenterColor: 'white',
pointEdgeColor: chart._colors(0)
}
尺度转换,气泡图相比基础散点图,新增了一个数据点的大小作为新的维度
/* ----------------------------尺度转换------------------------ */
chart.scaleX = d3.scaleLinear()
.domain([0, Math.ceil(d3.max(data, (d) => d.x)/10)*10])
.range([0, chart.getBodyWidth()]);
chart.scaleY = d3.scaleLinear()
.domain([0, Math.ceil(d3.max(data, (d) => d.y)/10)*10])
.range([chart.getBodyHeight(), 0]);
chart.scaleSize = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.r)])
.range([0, config.pointMaxSize]);
为了使数据点的形状美观,我们定义了一个径向渐变
/* ----------------------------定义颜色径向渐变------------------------ */
chart.defRadialGrad = function(){
const radialGrad = chart.svg()
.append('defs')
.append('radialGradient')
.attr('id', 'bubble-fill')
.attr('cx', 0.4)
.attr('cy', 0.4);
radialGrad.append('stop')
.attr('offset', '0%')
.attr('stop-color', config.pointCenterColor);
radialGrad.append('stop')
.attr('offset', '100%')
.attr('stop-color', config.pointEdgeColor);
}
渲染数据点,应用镜像渐变,步骤简单,对数据点的属性r
应用过渡动画
/* ----------------------------渲染数据点------------------------ */
chart.renderPoints = function(){
let points = chart.body().selectAll('.point')
.data(data);
points.enter()
.append('circle')
.classed('point', true)
.merge(points)
.attr('cx', (d) => chart.scaleX(d.x))
.attr('cy', (d) => chart.scaleY(d.y))
.attr('r', 0)
.attr('fill', 'url(#bubble-fill)')
.transition().duration(config.animateDuration)
.attr('r', (d) => chart.scaleSize(d.r));
points.exit()
.remove();
}
接着使渲染坐标轴、文本标签和网格线等常规步骤
/* ----------------------------渲染坐标轴------------------------ */
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('X');
d3.select('.yAxis').append('text')
.attr('class', 'axisText')
.attr('x', 0)
.attr('y', 0)
.attr('fill', config.textColor)
.attr('dx', '-30')
.attr('dy', '10')
.text('Y');
}
/* ----------------------------渲染网格线------------------------ */
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('r', chart.scaleSize(d.r) + 5);
chart.svg()
.append('text')
.classed('tip', true)
.attr('x', position[0]+5)
.attr('y', position[1])
.attr('fill', config.textColor)
.text('x: ' + d.x + ', y: ' + d.y);
})
.on('mouseleave', function(d){
const e = d3.event;
d3.select(e.target)
.attr('r', chart.scaleSize(d.r));
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)
);
}