我想实现的热力图的样式时这样的:
我的数据结构为:
const data ={
'2021.06': [
{ date: '2021-06-01',value: 20},
{ date: '2021-06-02',value: 20}
......
]
......
}
它的html结构是这样的:
其中,svg中包含左侧的星期几的标识,包含每一月rect的组g元素,class为date-list为热力图下面的时间。
这个图的实现可以分为三个部分:
- 左侧星期的显示。
- 热力图的显示
- 热力图下方的时间的显示。
第一点和第三点都比较好实现,就不说了,重点说一下第二点:热力图的显示。
热力图的显示也可以分为三点:
-
一个月的rect的组g的显示位置。
-
一个g中的每一个rect的显示位置。
-
rect的填色。
具体的实现过程如下:
1、处理数据
将数据处理成这样:
const list = [
['2021.06', [
{ date: '2021-06-01',value: 20},
{ date: '2021-06-02',value: 20},
......
]
......
]
2、组g位置的显示
- 添加svg元素
关于svg的宽度和高度,我们可以通过react-container-dimensions包来获取,设为svg的width和height,id为attack-protrait-heat-map的元素就是我们自己创建的svg元素。
const svg = d3.select(`#attack-protrait-heat-map`).attr('width', width).attr('height', height);
-
添加左侧星期显示
svg.append('g') .attr('transform', 'translate(0, 10)') .selectAll('text') .data(d3.range(7)) .join('text') .attr('x', 0) .attr('y', i => (i + 0.5) * cellSize) .attr('dy', '0.32em') .attr('font-size', '12px') .attr('fill', '#999') .text(i => '一二三四五六日'[i]);
这一步是通过向svg中添加一个g用来分组,并向g中添加7个text,每个text的x值时相同的,y值为(i + 0.5) * cellSize,cellSize为热力图小方块的大小。
-
添加g元素
const yearsEle = svg
.selectAll('g')
.data(years)
.join('g')
.attr('class', (d, i) =>year-item year-item-${i}) .attr('len', d => d?.[1]?.length)
.attr('index', (d, i) => i);
这里向svg中添加g元素,每一个g都是用来放一个月的rect元素的。index是为了之后定位每一个rect的位置。
-
向每一个g元素中添加一个月的rect元素(添加小方块)
yearsEle .selectAll('.year-item') // 选中所有的class为year-item的g元素 .append('rect') // 向g元素中添加rect元素 .data(([date, values]) => { svg.selectAll('.month-item').remove(); // 为了防止每次渲染都会添加rect元素,造成rect元素要来越多,所以先移除所有的rect // date为list中 2021.06等月份 // values为数据,如:[{date: '2021-06-01', value: 20}, {date: '2021-06-02', value: 20}] return values; }) .join('rect') .attr('class', 'month-item') .attr('width', cellSize) .attr('height', cellSize) .attr('x', function (d) { // 每一个小方块x值的计算过程: // 获取到父元素g的index值。 // date.getDay()获取到date为一周的第几天,为0~6,我们要实现的小方块rect从上到下排列顺序为从周一开始 // 通过date.getDay() === 0 ? 7 : date.getDay()获取到1~7的值 // 7-day获取到date还差几天这周结束。 // new Date(date).getDate() + (7 - day) 思路看补充1 // Math.ceil(total / 7)获取到rect应该为这个月的第几周,也就是在图中这一月的第几列 // 第几周 * rect的宽度 + 父元素g的偏移量(index*110)+30,也就是rect的x位置 const date = new Date(d.date.replace(/-/g, '/')); const index = Number(this.parentNode.getAttribute('index')); // 如果想要从上到下从周日开始,就不用这一步处理,只要将侧边显示周几改为周日开始 const day = date.getDay() === 0 ? 7 : date.getDay(); const total = new Date(date).getDate() + (7 - day); return Math.ceil(total / 7) * cellSize + index * 110 + 30; }) .attr('y', d => { // 每一个小方块y值计算的过程: // 获取到date为这一周的第几天,再乘以小方块的宽度即可。 const date = new Date(d.date.replace(/-/g, '/')); const day = date.getDay() === 0 ? 7 : date.getDay(); return day * cellSize - 5; }) .attr('fill', (d = {}) => { // 为小方块添加颜色 const { value = 0, compensatory } = d; return compensatory ? '#f7f7f7' : colorList[value]; })