如何在D3中的条形图上检测到下方的最接近的矩形?

发布于 2025-01-24 14:30:49 字数 2796 浏览 3 评论 0原文

我使用D3创建了一个条形图,但是我希望当我的指针高于RECT以上以检测到矩形并更改其颜色时:

“在此处输入映像说明”

因为我的指针从右边的第三个级别高于右边,那将被选中。有没有办法实现这一目标?

这是我当前的代码:

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上时才能检测到,而不是当我高出它时。

I created a bar chart using D3, but I want that when my pointer is above a rect to detect that rect and change its color for example:

enter image description here

Because my pointer is above this third rect from the right, that one would be selected. Is there a way to achive this?

Here is my current code:

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")
    
})

When I add "mousemove" event to "rects" element it only detects when I am directly hovering on rect but not when I am above it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

又怨 2025-01-31 14:30:49

一种方法是绘制两个rect s-一种填充整个GraphHeight且没有填充的方法(确保将Pointer -Evests设置为全部),然后绘制您的原始rect。此“背景” rect可以响应鼠标事件,您可以选择“前景” rect并更改属性等。促进选择。

为了完成这项工作,您需要为每对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>

One approach is to draw two rects - one that fills up the entire graphHeight and has no fill (making sure to set pointer-events to all) and then draw your original rect. This 'background' rect can then respond to mouse events and you can select the 'foreground' rect and change a property etc. I've used ids based on index to facilitate the selection.

To make this work you need a contained for each pair of rects (foreground and background) and then you can handle the events for mouse events differently (or the same if you like) depending if the mouse is over the rect or on it:

  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
    })

See below based on your original code, but with some dummy data:

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文