資料視覺化的核心是看見趨勢、概況,而不是某一個節點的細節資料,所以在圖表設計上多半不會直接在每個資料點上標示數字,但還是有其必要性,表現的方式就是以tooltip的形式呈現。
為了讓滑鼠hover的效果更強烈、明顯,我們幫方塊加上2px的邊線,與深一點的顏色。什麼是「深一點的顏色呢」?還記不記得之前有介紹過d3提供跟color有關的函式們:https://github.com/d3/d3-color ,在裡面就有提供darker()
的功能,並加入參數適當調深,我們立刻來試試看。
g
.selectAll('.block')
.data(data)
.enter()
.append('rect')
.attr('class', 'block')
.attr('x', (d) => (d.weekNum - 1) * 15 + (d.weekNum - 1) * 1 + 7.5)
.attr('y', (d) => d.day * 15 + d.day * 1 + 7.5)
.attr('fill', (d) => colorScale(0))
.attr('width', 0)
.attr('height', 0)
//只有加上這幾行
.on('mouseover', function(d) {
d3.select(this)
.attr('stroke', d3.rgb(colorScale(d.value)).darker(0.5))
.attr('stroke-width', 2);
}) // 當mouseover時加上邊框,邊框顏色為本體顏色加深0.5
.on('mouseleave', function() { //注意!這裡不能使用arrow function 因為我們要選擇的是this
d3.select(this)
.attr('stroke-width', '0');
}) // 當mouseleave時恢復原樣
.transition()
.duration(1000)
.delay((d, i) => i * 3)
.attr('fill', (d) => colorScale(d.value))
.attr('x', (d) => (d.weekNum - 1) * 15 + (d.weekNum - 1) * 1)
.attr('y', (d) => d.day * 15 + d.day * 1)
.attr('width', 15)
.attr('height', 15)
.attr('rx', 3);
與之前相同,我們直接用div
來做提示框。react不像vue一樣css寫在同一份檔案,所以在同一層的資料夾下新增一份css或scss的檔案:HeatMap.module.scss
,這邊加上module
的話就會被認為是local style,react在編譯時會幫你加上一些id,就不會有css重複命名的問題,要注意的是要記得把這份style傳給畫圖的function。
HeatMap.module.scss
.container {
position: relative;
}
.messageWrapper {
border-radius: 4px;
width: 125px;
height: 34px;
background: #ffffff;
box-shadow: 0 2px 9px 0 rgba(0, 0, 0, 0.09);
position: absolute;
display: flex;
align-items: center;
padding-left: 15px;
.circle {
border-radius: 50%;
height: 11px;
width: 11px;
}
.data {
font-size: 12px;
color: #7e7e7e;
margin-left: 10px;
}
}
HeatMap.js
import React, { useState, useEffect, useCallback } from 'react';
import shortid from 'shortid';
import drawMap from './drawMap';
import styles from './HeatMap.module.scss';
const HeatMap = (props) => {
const [id] = useState(`heatmap-${shortid.generate()}`);
const handleDrawMap = useCallback(() => {
drawMap(id, styles); //記得把styles傳進去
}, [id]);
useEffect(() => {
handleDrawMap()
}, [handleDrawMap]);
return <div id={id} className={styles.container}></div>;
};
export default React.memo(HeatMap);
drawMap.js
中先定義messageWrapper:
const messageWrapper = d3
.select(`#${id}`)
.append('div')
.attr('class', styles.messageWrapper)
.html(
`<div class="${styles.circle}"></div><div class="${styles.data}"></div>`
)
.attr('style', 'display: none;');
監聽滑鼠事件改變位置與內容:
.on('mouseover', function(d) {
d3.select(this)
.attr('stroke', d3.rgb(colorScale(d.value)).darker(0.5))
.attr('stroke-width', 2);
d3.select(`.${styles.messageWrapper} .${styles.data}`).html(
`${d.date} : ${d.value}`
); //資料內容
d3.select(`.${styles.messageWrapper} .${styles.circle}`).attr(
'style',
`background-color: ${colorScale(d.value)}`
); //圓形的顏色與所選擇的方形相同
messageWrapper.attr(
'style',
() =>
`display: flex; left: ${(d.weekNum - 1) * 15 +
(d.weekNum - 1) * 1 +
50}px; top: ${d.day * 15 + d.day * 1}px`
); // 調整位置
})
.on('mouseleave', function() {
d3.select(this).attr('stroke-width', '0');
messageWrapper.attr('style', () => 'display: none;');
})