D3.js学习笔记:绘制热点图
介绍
热力图用矩阵表示数据关系,用颜色表示数值。例如:
《Day / Hour Heatmap》
上图受下面项目的启发而绘制。
《House Hunting All Day, Every Day》
需求
显示每个项目每小时的数据。用颜色表示数据大小。横坐标为小时,纵坐标为项目。
实现
输入数据
图形为矩阵形式,可以考虑用矩阵作为输入数据。但考虑到以下两点:
- 每行都是同一事物在不同时间段的数据,应该将这些数据放到一起便于交互查询。
- d3的数据机制最适合处理数组,比矩阵更简单直观。
输入数据形如二维数组形式(还有其他附属变量),在数据处理阶段时,将二维数组变为一维数组。 api返回数据:
{
"row": [
{
"values": [
{ "key": 0, "value": 645 },
{ "key": 1, "value": 567 }
/* ... */
]
},
{
"values": [
{ "key": 0, "value": 355 },
{ "key": 2, "value": 4 },
/* ... */
]
}
}
与上一篇博文类似,需要将数据扩展到0-24小时范围上。具体方法不再描述,详情请参看上一篇博文。
数据处理
将二维数组转变为一维数据
var heat_map_data = new_value;
var grid_data = [];
var row_name = [];
heat_map_data.row.forEach(function(row_element, row_index, row_array){
var row = row_element;
row.chart_values.forEach(function(element, index, array){
grid_data.push({
row: row_index,
key: element.key,
value: element.value
})
});
row_name.push(row.name);
});
矩阵网格
横坐标
横坐标是小时
var time_count=24;
var time_range = d3.range(0, time_count);
var x_scale = d3.scale.linear().domain([0,time_count]).range([0, grid_svg_attr.width]);
纵坐标
纵坐标是每个项目,不需要单独设置。
颜色
与上一篇博文同样,使用d3.scale.quantile
var max_value = d3.max(grid_data, function(d){return d.value; });
var color_range = colorbrewer.YlOrRd[6];
var color_scale = d3.scale.quantile().domain([0, max_value]).range(color_range);
绘制
var heat_map_rect = grid_group.selectAll('.heat-map-rect').data(grid_data)
.enter().append('rect')
.attr('height', rect_attr.height)
.attr('width', rect_attr.width)
.attr('class', 'heat-map-rect bordered')
.attr('x', function(d){ return x_scale(d.key); })
.attr('y', function(d){ return d.row*cell_size; })
.attr('rx', rect_attr.rx)
.attr('ry', rect_attr.ry)
.style("fill", function(d,i) {
if(d.value == 0)
return '#eee';
else
return color_scale(d.value);
})
动画
加一点儿颜色过渡动画,没有例二那么花哨。d3做这种简单的过渡动画很方便。
要用默认颜色填充,修改上面的fill属性。
var heat_map_rect = grid_group.selectAll('.heat-map-rect').data(grid_data)
.enter().append('rect')
.attr('height', rect_attr.height)
.attr('width', rect_attr.width)
.attr('class', 'heat-map-rect bordered')
.attr('x', function(d){ return x_scale(d.key); })
.attr('y', function(d){ return d.row*cell_size; })
.attr('rx', rect_attr.rx)
.attr('ry', rect_attr.ry)
.attr('fill', '#eee');
使用d3.transition
加入动画,包括delay
延迟效果(但延迟不明显,有待后续改进)。
heat_map_rect.transition()
.delay(function(d,i){
var cur_row = d.row;
var cur_row_pos = cur_row / row_count;
var cur_col = i - d.row*time_count;
var cur_col_pos = cur_col / time_count;
if(cur_row_pos < cur_col_pos )
return cur_col_pos/time_count*5000;
else
return cur_row_pos/time_count*5000;
})
.duration(1000)
.style("fill", function(d,i) {
if(d.value == 0)
return '#eee';
else
return color_scale(d.value);
});
文字标签
纵坐标标签为项目名
var row_label = grid_group.selectAll('row-label').data(row_name)
.enter().append('text')
.attr('x', 0)
.attr('y', function(d,i){ return i*cell_size; })
.text(function(d){ return d;})
.style('text-anchor', 'end')
.attr('transform', 'translate(-6,'+cell_size/1.5+')');
横坐标标签为小时
var time_label = grid_group.selectAll('.time-label').data(time_range)
.enter().append('text')
.attr('class', 'time-label')
.attr('x', function(d,i){ return x_scale(i); })
.attr('y', 0)
.text(function(d){
if(d<12)
return d+'a';
else
return d+'p';
})
.style("text-anchor", "middle")
.attr('transform', 'translate('+cell_size/2+',-6)');
交互
仿照例二,鼠标指向某矩阵网格时,高亮该网格边框,并在下面绘制相应项目的条形图,高亮条形图中对应的当前数值。条形图默认显示第一行的数据,第一行数据被看成是总体数值。
创建条形图
svg中rect坐标为左上角点,y轴正方向竖直向下,而一般条形图y轴正方向为竖直向上,所以条形图中rect的纵坐标为最大高度减去rect的高度。
var bar_data_max = d3.max(bar_data,function(d){return d.value});
var bar_y_scale = d3.scale.linear().domain([0, bar_data_max]).range([0,bar_chart_attr.max_bar_height]);
条形图
var bar = bar_chart_group.selectAll('.heat-map-bar').data(bar_data)
.enter()
.append('rect')
.attr('class', 'heat-map-bar')
.attr('x', function(d,i){ return i*bar_chart_attr.item_width; })
.attr('y', function(d,i){ return bar_chart_attr.max_bar_height - bar_y_scale(d.value) })
.attr('width', bar_chart_attr.bar_width)
.attr('height', function(d,i){ return bar_y_scale(d.value) })
.attr('fill', function(d,i){ return '#ddd' });
添加标签与上面相同。
更新条形图
条形图展示某一行的数据,改变行号时需要更新条形图。重新设置条形图的data,并设置变化的属性,包括y坐标、height高度和fill填充色。当然,数据最大值也发生改变,需要重新计算比例尺。
var bar_data_max = d3.max(bar_data,function(d){return d.value});
var bar_y_scale = d3.scale.linear().domain([0, bar_data_max]).range([0,bar_chart_attr.max_bar_height]);
var bar = bar_chart_group.selectAll('.heat-map-bar').data(bar_data)
.transition().duration(1000)
.attr('y', function(d,i){ return bar_chart_attr.max_bar_height - bar_y_scale(d.value) })
.attr('height', function(d,i){ return bar_y_scale(d.value) })
.attr('fill', function(d,i){ return '#ddd' });
鼠标事件相应
当鼠标指向(mouseover)网格时,如果当前行改变则更新条形图,然后高亮条形图中对应小时的数据。
当鼠标移出(mouseout)网格时,同样检测当前行是否改变(多余?),恢复默认颜色。
用css来表示高亮效果。
rect.bordered{
stroke: #E6E6E6;
stroke-width:2px;
}
rect.bordered.hover{
stroke: black;
stroke-width:2px;
}
添加对mouseover和mouseout的事件监听。使用classed
设置类。
heat_map_rect.on('mouseover', function(d, i){
d3.select(this).classed("hover", true);
if(current_bar_chart_row != d.row) {
update_bar_chart(d.row);
current_bar_chart_row = d.row;
}
var child_index = i - d.row*time_count+1;
bar_chart_group.selectAll('.heat-map-bar').filter(":nth-child("+child_index+")")
.classed('hover', true);
})
.on('mouseout', function(d, i){
d3.select(this).classed("hover", false);
if(current_bar_chart_row!=0){
current_bar_chart_row = 0;
update_bar_chart(current_bar_chart_row);
}
var child_index = i - d.row*time_count+1;
bar_chart_group.selectAll('.heat-map-bar').filter(":nth-child("+child_index+")")
.classed('hover', false);
});