我正在尝试使用 React 和 D3.js 构建密度图。
我希望用户能够将鼠标悬停在图表的任何部分上,并看到显示有关该特定点的附加信息的工具提示,但我不确定如何将工具提示添加到路径元素。我找不到任何相关的例子。有人做过类似的事情吗?
代码:https://playcode.io/1640953
import {useMemo, useRef } from "react";
import * as d3 from "d3";
import AxisBottom from "./axisBottom";
import AxisLeft from "./axisTop";
import importedData from "./data"
const MARGIN = { top: 50, right: 30, bottom: 50, left: 50 };
export const DensityChart = ({ width = 700, height = 400}) => {
const boundsWidth = width - MARGIN.right - MARGIN.left;
const boundsHeight = height - MARGIN.top - MARGIN.bottom;
const refs = useRef({
dataMin: Math.min(...importedData),
dataMax: Math.max(...importedData),
domain: [Math.min(...importedData), Math.max(...importedData)]
})
const xScale = useMemo(() => {
try {
const result = d3.scaleLinear().domain([refs.current.dataMin, refs.current.dataMax]).range([10, boundsWidth - 10]);
return result
} catch (error) {
console.error("Error creating xScale:", error);
return d3.scaleLinear().domain([0, 1]).range([10, boundsWidth - 10]);
}
}, [importedData, width]);
// Compute kernel density estimation
const density = useMemo(() => {
const kde = kernelDensityEstimator(kernelEpanechnikov(7), xScale.ticks(40));
return kde(importedData);
}, [xScale, importedData]);
const yScale = useMemo(() => {
try {
const max = Math.max(...density.map((d) => d[1]));
const result = d3.scaleLinear().range([boundsHeight, 0]).domain([0, max])
return result;
} catch (error) {
console.error("Error creating yScale:", error);
return
}
}, [importedData, height, density]);
const path = useMemo(() => {
try {
const lineGenerator = d3.line().x((d) => {
return xScale(d[0])
})
.y((d) => {
return yScale(d[1])
})
.curve(d3.curveBasis);
return lineGenerator(density);
} catch (error) {
console.error("Error creating path:", error);
return "";
}
}, [density, xScale, yScale]);
return (
<section>
<svg height="500" width="700">
<g
width={boundsWidth}
height={boundsHeight}
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
>
<path
d={path}
fill="blue"
opacity={0.4}
stroke="black"
strokeWidth={1}
strokeLinejoin="round"
/>
<g transform={`translate(0, ${boundsHeight})`}>
<AxisLeft yScale={yScale} pixelsPerTick={40} />
</g>
<g transform={`translate(0, ${boundsHeight})`}>
<AxisBottom xScale={xScale} pixelsPerTick={40} />
</g>
</g>
</svg>
</section>
);
}
export default DensityChart;
// Function to compute density
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - +v )})];
});
};
}
function kernelEpanechnikov(bandwidth) {
return function(v) {
const result = Math.abs(v /= bandwidth) <= 1 ? 0.75 * (1 - v * v) / bandwidth : 0;
return result
};
}
实际上你的问题与 d3 没有任何关系。用普通的javascript就可以解决。 SVG 路径通常仍然尊重指针事件(只要它们具有显式定义的填充颜色或指针事件)
例如使用 css 使路径部分改变颜色:
path {
fill:blue;
stroke:black;
}
path:hover{
fill:red;
stroke:blue;
}
<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<path id="svg_3" d="m200,114l-114,95l26,97l99,20c0,0 19,-67 19,-68c0,-1 -1,-5 4,-8c5,-3 39,-10 40,-10c1,0 13,-2 14,-9c1,-7 -4,-36 -8,-40c-4,-4 -23,-15 -27,-17c-4,-2 -24,-16 -24,-23c0,-7 -1,-15 -1,-21c0,-6 -6,-19 -7,-19c-1,0 -21,3 -21,3z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
<path id="svg_4" d="m244,101c0,0 14,55 16,56c2,1 22,11 24,13c2,2 23,17 24,18c1,1 1,28 -1,31c-2,3 -3,25 -9,30c-6,5 -32,14 -35,14c-3,0 -8,5 -10,8c-2,3 -10,37 -10,37c0,0 7,10 16,15c9,5 53,12 59,12c6,0 30,0 40,-8c10,-8 34,-7 35,-31c1,-24 1,-48 1,-65c0,-17 -13,-61 -15,-66c-2,-5 -21,-21 -21,-23c0,-2 34,-20 44,-15c10,5 29,24 33,28c4,4 10,20 16,5c6,-15 28,-31 -1,-46c-29,-15 -25,-24 -55,-25c-30,-1 -42,-5 -53,-5c-11,0 -46,-2 -52,1c-6,3 -46,16 -46,16z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
<path id="svg_5" d="m428,180c0,0 -3,52 -4,53c-1,1 -2,27 -2,31c0,4 -8,29 -11,34c-3,5 -15,36 -21,38c-6,2 -77,18 -81,18c-4,0 -68,0 -68,7c0,7 -1,18 8,23c9,5 23,9 45,14c22,5 97,12 111,6c14,-6 44,-20 55,-30c11,-10 28,-28 37,-42c9,-14 14,-15 23,-40c9,-25 16,-109 12,-114c-4,-5 -32,-12 -45,-9c-13,3 -59,11 -59,11z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
</g>
</svg>
您可以用同样的方式添加 javascript 事件:
Array
.from(document.querySelectorAll("path"))
.forEach((el) => {
el.onmouseover = () => console.log("Mouse Over " + el.id);
})
path {
fill:blue;
stroke:black;
}
<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg">
<g>
<path id="svg_3" d="m200,114l-114,95l26,97l99,20c0,0 19,-67 19,-68c0,-1 -1,-5 4,-8c5,-3 39,-10 40,-10c1,0 13,-2 14,-9c1,-7 -4,-36 -8,-40c-4,-4 -23,-15 -27,-17c-4,-2 -24,-16 -24,-23c0,-7 -1,-15 -1,-21c0,-6 -6,-19 -7,-19c-1,0 -21,3 -21,3z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
<path id="svg_4" d="m244,101c0,0 14,55 16,56c2,1 22,11 24,13c2,2 23,17 24,18c1,1 1,28 -1,31c-2,3 -3,25 -9,30c-6,5 -32,14 -35,14c-3,0 -8,5 -10,8c-2,3 -10,37 -10,37c0,0 7,10 16,15c9,5 53,12 59,12c6,0 30,0 40,-8c10,-8 34,-7 35,-31c1,-24 1,-48 1,-65c0,-17 -13,-61 -15,-66c-2,-5 -21,-21 -21,-23c0,-2 34,-20 44,-15c10,5 29,24 33,28c4,4 10,20 16,5c6,-15 28,-31 -1,-46c-29,-15 -25,-24 -55,-25c-30,-1 -42,-5 -53,-5c-11,0 -46,-2 -52,1c-6,3 -46,16 -46,16z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
<path id="svg_5" d="m428,180c0,0 -3,52 -4,53c-1,1 -2,27 -2,31c0,4 -8,29 -11,34c-3,5 -15,36 -21,38c-6,2 -77,18 -81,18c-4,0 -68,0 -68,7c0,7 -1,18 8,23c9,5 23,9 45,14c22,5 97,12 111,6c14,-6 44,-20 55,-30c11,-10 28,-28 37,-42c9,-14 14,-15 23,-40c9,-25 16,-109 12,-114c-4,-5 -32,-12 -45,-9c-13,3 -59,11 -59,11z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="none"/>
</g>
</svg>
因此,您可以使用简单的 css 来显示工具提示,甚至可以使用 javascript 来显示更多自定义工具提示行为。
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
top: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
<div class="tooltip">Hover over me
<span class="tooltiptext">Tooltip text</span>
</div>