如何检测 D3 中条形图上指针下方最近的矩形?
How to detect closest rect below a pointer on a bar chart in D3?
我使用 D3 创建了一个条形图,但我希望当我的指针位于矩形上方时检测该矩形并更改其颜色,例如:
因为我的指针在右起第三个矩形的上方,所以会选择那个。有办法实现吗?
这是我当前的代码:
const width = 620;
const height = 280;
const svg = d3.selectAll(".canvas")
.append('svg')
.style('display', 'block')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio','xMinYMin')
const margin = {top:50, bottom:50, left: 50, right: 50}
const graphWidth = width - margin.right - margin.right
const graphHeight = height - margin.bottom - margin.top
const graph = svg.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${margin.left},${margin.top})`)
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0, ${graphHeight})`)
const yAxisGroup = graph.append('g')
d3.csv('./SixContinentFirst.csv').then(data => {
africaData = data.map(obj => {
return {infected: +(obj.Africa || '0'), date: obj.Dates}
})
console.log(africaData)
const y = d3.scaleLinear()
.domain([0, d3.max(africaData, data => data.infected)])
.range([graphHeight,0])
const x = d3.scaleBand()
.domain(africaData.map(item => item.date))
.range([0,graphWidth])
.paddingInner(0.2)
.paddingOuter(0.2)
const rects = graph.selectAll('rect')
.data(africaData)
rects.enter()
.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight - y(d.infected))
.attr('fill', 'orange')
.attr('x', (d) => x(d.date))
.attr('y', d => y(d.infected))
.attr('rx', 8)
.attr('ry', 8)
// .on('mousemove', (d, i) => {
// console.log("Hover")
// })
const xAxis = d3.axisBottom(x)
.tickFormat((d,i) => i % 6 === 0 ? d : '')
let formatter = Intl.NumberFormat('en', { notation: 'compact' });
const yAxis = d3.axisRight(y)
.ticks(3)
.tickFormat(d => formatter.format(+d))
xAxisGroup.call(xAxis)
yAxisGroup.call(yAxis)
.attr('transform', `translate(${graphWidth}, 0)`)
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10")
xAxisGroup
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10")
})
当我将“mousemove”事件添加到“rects”元素时,它只会检测到我直接悬停在 rect 上的时间,但不会检测到我在其上方的时间。
一种方法是绘制两个 rect
- 一个填满整个 graphHeight
并且没有填充(确保将 pointer-events
设置为 all
)然后画出你原来的矩形。这个 'background' rect
然后可以响应鼠标事件,你可以 select 'foreground' rect 并更改 属性 等。我用过 id
s基于索引方便selection.
为了完成这项工作,你需要为每对 rect
s(前景和背景)包含一个,然后你可以根据是否鼠标在矩形上或其上:
const rects = graph.selectAll('rect')
.data(africaData)
.enter()
.append("g")
.attr("class", "rect-container");
// background rect
rects.append('rect')
// set properties and events
.on('mouseover', function(d, i) {
// handle event
})
.on('mouseout', function(d, i) {
// handle event
})
// foreground rect
rects.append('rect')
// set properties and events
.on('mouseover', function(d, i) {
// handle event
})
.on('mouseout', function(d, i) {
// handle event
})
根据您的原始代码见下文,但有一些虚拟数据:
const width = 620;
const height = 280;
const svg = d3.selectAll(".canvas")
.append('svg')
.style('display', 'block')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio','xMinYMin');
const margin = {top:20, bottom:20, left: 20, right: 20}
const graphWidth = width - margin.right - margin.right;
const graphHeight = height - margin.bottom - margin.top;
const graph = svg.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${margin.left},${margin.top})`);
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0, ${graphHeight})`);
const yAxisGroup = graph.append('g');
// data for this example
const data = [
{ Africa: 7, Dates: "Jan 2022" },
{ Africa: 1, Dates: "Feb 2022" },
{ Africa: 0, Dates: "Mar 2022" },
{ Africa: 11, Dates: "Apr 2022" },
{ Africa: 7, Dates: "May 2022" },
{ Africa: 7, Dates: "Jun 2022" },
{ Africa: 16, Dates: "Jul 2022" },
{ Africa: 2, Dates: "Aug 2022" },
{ Africa: 8, Dates: "Sep 2022" },
{ Africa: 3, Dates: "Oct 2022" },
{ Africa: 15, Dates: "Nov 2022" },
{ Africa: 12, Dates: "Dec 2022" },
];
// render viz
render(data);
//d3.csv('./SixContinentFirst.csv').then(data => {
function render(data) {
africaData = data.map(obj => {
return {infected: +(obj.Africa || '0'), date: obj.Dates}
});
//console.log(africaData);
const y = d3.scaleLinear()
.domain([0, d3.max(africaData, data => data.infected)])
.range([graphHeight,0]);
const x = d3.scaleBand()
.domain(africaData.map(item => item.date))
.range([0,graphWidth])
.paddingInner(0.2)
.paddingOuter(0.2);
const rects = graph.selectAll('rect')
.data(africaData)
.enter()
.append("g")
.attr("class", "rect-container");
// background rect
rects.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight)
.attr('fill', 'none')
.attr('x', (d) => x(d.date))
.attr('y', d => 0)
.attr('pointer-events', 'all')
.on('mouseover', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'red')
})
.on('mouseout', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'orange')
})
// foreground rect
rects.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight - y(d.infected))
.attr('fill', 'orange')
.attr('id', (d, i) => `bar_${i}`)
.attr('class', "foreground-rect")
.attr('x', (d) => x(d.date))
.attr('y', d => y(d.infected))
.attr('rx', 8)
.attr('ry', 8)
.attr('pointer-events', 'all')
.on('mouseover', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'green')
})
.on('mouseout', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'orange')
})
const xAxis = d3.axisBottom(x)
.tickFormat((d,i) => i % 6 === 0 ? d : '');
let formatter = Intl.NumberFormat('en', { notation: 'compact' });
const yAxis = d3.axisRight(y)
.ticks(3)
.tickFormat(d => formatter.format(+d));
xAxisGroup.call(xAxis);
yAxisGroup.call(yAxis)
.attr('transform', `translate(${graphWidth}, 0)`)
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10");
xAxisGroup
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10");
}
//)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="canvas"></div>
我使用 D3 创建了一个条形图,但我希望当我的指针位于矩形上方时检测该矩形并更改其颜色,例如:
因为我的指针在右起第三个矩形的上方,所以会选择那个。有办法实现吗?
这是我当前的代码:
const width = 620;
const height = 280;
const svg = d3.selectAll(".canvas")
.append('svg')
.style('display', 'block')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio','xMinYMin')
const margin = {top:50, bottom:50, left: 50, right: 50}
const graphWidth = width - margin.right - margin.right
const graphHeight = height - margin.bottom - margin.top
const graph = svg.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${margin.left},${margin.top})`)
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0, ${graphHeight})`)
const yAxisGroup = graph.append('g')
d3.csv('./SixContinentFirst.csv').then(data => {
africaData = data.map(obj => {
return {infected: +(obj.Africa || '0'), date: obj.Dates}
})
console.log(africaData)
const y = d3.scaleLinear()
.domain([0, d3.max(africaData, data => data.infected)])
.range([graphHeight,0])
const x = d3.scaleBand()
.domain(africaData.map(item => item.date))
.range([0,graphWidth])
.paddingInner(0.2)
.paddingOuter(0.2)
const rects = graph.selectAll('rect')
.data(africaData)
rects.enter()
.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight - y(d.infected))
.attr('fill', 'orange')
.attr('x', (d) => x(d.date))
.attr('y', d => y(d.infected))
.attr('rx', 8)
.attr('ry', 8)
// .on('mousemove', (d, i) => {
// console.log("Hover")
// })
const xAxis = d3.axisBottom(x)
.tickFormat((d,i) => i % 6 === 0 ? d : '')
let formatter = Intl.NumberFormat('en', { notation: 'compact' });
const yAxis = d3.axisRight(y)
.ticks(3)
.tickFormat(d => formatter.format(+d))
xAxisGroup.call(xAxis)
yAxisGroup.call(yAxis)
.attr('transform', `translate(${graphWidth}, 0)`)
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10")
xAxisGroup
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10")
})
当我将“mousemove”事件添加到“rects”元素时,它只会检测到我直接悬停在 rect 上的时间,但不会检测到我在其上方的时间。
一种方法是绘制两个 rect
- 一个填满整个 graphHeight
并且没有填充(确保将 pointer-events
设置为 all
)然后画出你原来的矩形。这个 'background' rect
然后可以响应鼠标事件,你可以 select 'foreground' rect 并更改 属性 等。我用过 id
s基于索引方便selection.
为了完成这项工作,你需要为每对 rect
s(前景和背景)包含一个,然后你可以根据是否鼠标在矩形上或其上:
const rects = graph.selectAll('rect')
.data(africaData)
.enter()
.append("g")
.attr("class", "rect-container");
// background rect
rects.append('rect')
// set properties and events
.on('mouseover', function(d, i) {
// handle event
})
.on('mouseout', function(d, i) {
// handle event
})
// foreground rect
rects.append('rect')
// set properties and events
.on('mouseover', function(d, i) {
// handle event
})
.on('mouseout', function(d, i) {
// handle event
})
根据您的原始代码见下文,但有一些虚拟数据:
const width = 620;
const height = 280;
const svg = d3.selectAll(".canvas")
.append('svg')
.style('display', 'block')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio','xMinYMin');
const margin = {top:20, bottom:20, left: 20, right: 20}
const graphWidth = width - margin.right - margin.right;
const graphHeight = height - margin.bottom - margin.top;
const graph = svg.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${margin.left},${margin.top})`);
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0, ${graphHeight})`);
const yAxisGroup = graph.append('g');
// data for this example
const data = [
{ Africa: 7, Dates: "Jan 2022" },
{ Africa: 1, Dates: "Feb 2022" },
{ Africa: 0, Dates: "Mar 2022" },
{ Africa: 11, Dates: "Apr 2022" },
{ Africa: 7, Dates: "May 2022" },
{ Africa: 7, Dates: "Jun 2022" },
{ Africa: 16, Dates: "Jul 2022" },
{ Africa: 2, Dates: "Aug 2022" },
{ Africa: 8, Dates: "Sep 2022" },
{ Africa: 3, Dates: "Oct 2022" },
{ Africa: 15, Dates: "Nov 2022" },
{ Africa: 12, Dates: "Dec 2022" },
];
// render viz
render(data);
//d3.csv('./SixContinentFirst.csv').then(data => {
function render(data) {
africaData = data.map(obj => {
return {infected: +(obj.Africa || '0'), date: obj.Dates}
});
//console.log(africaData);
const y = d3.scaleLinear()
.domain([0, d3.max(africaData, data => data.infected)])
.range([graphHeight,0]);
const x = d3.scaleBand()
.domain(africaData.map(item => item.date))
.range([0,graphWidth])
.paddingInner(0.2)
.paddingOuter(0.2);
const rects = graph.selectAll('rect')
.data(africaData)
.enter()
.append("g")
.attr("class", "rect-container");
// background rect
rects.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight)
.attr('fill', 'none')
.attr('x', (d) => x(d.date))
.attr('y', d => 0)
.attr('pointer-events', 'all')
.on('mouseover', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'red')
})
.on('mouseout', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'orange')
})
// foreground rect
rects.append('rect')
.attr('width', x.bandwidth)
.attr('height', d => graphHeight - y(d.infected))
.attr('fill', 'orange')
.attr('id', (d, i) => `bar_${i}`)
.attr('class', "foreground-rect")
.attr('x', (d) => x(d.date))
.attr('y', d => y(d.infected))
.attr('rx', 8)
.attr('ry', 8)
.attr('pointer-events', 'all')
.on('mouseover', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'green')
})
.on('mouseout', function(d, i) {
d3.select(`#bar_${i}`).attr('fill', 'orange')
})
const xAxis = d3.axisBottom(x)
.tickFormat((d,i) => i % 6 === 0 ? d : '');
let formatter = Intl.NumberFormat('en', { notation: 'compact' });
const yAxis = d3.axisRight(y)
.ticks(3)
.tickFormat(d => formatter.format(+d));
xAxisGroup.call(xAxis);
yAxisGroup.call(yAxis)
.attr('transform', `translate(${graphWidth}, 0)`)
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10");
xAxisGroup
.call(g => g.select('.domain').remove())
.call(g => g.selectAll('line').remove())
.selectAll('text')
.attr("font-size", "10");
}
//)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="canvas"></div>