Home  >  Q&A  >  body text

React + d3 - Why is my <g> element unresponsive to onClick listener

I'm fairly new to d3 and I know there may be some conflicts between React and d3. This may be one of them.

I have an svg that implements d3's scalable behavior. I want to make the elements inside the svg draggable. My first attempt was to put an onClick listener to log something in the console, but it didn't work. Can you explain what I'm missing here? I suspect that the d3 scaling behavior is hijacking the click event. Thanks. Here is the code:

Grid with zoom function.

import React, { useState, useEffect, useRef } from 'react'
import * as d3 from 'd3';
import Rect from './Rect';
import './d3-grid.css';

const zoomBehaviour = (width, height, gridSize, gridRef) => d3
    .zoom()
    .scaleExtent([0.5, 2])
    .translateExtent([[0, 0], [width, height]])
    .on('zoom', (event) => {
        const svg = d3.select(gridRef.current)

        svg.select('#grid-pattern')
            .attr('x', event.transform.x)
            .attr('y', event.transform.y)
            .attr('width', gridSize * event.transform.k)
            .attr('height', gridSize * event.transform.k)
            .selectAll('line')
            .attr('opacity', Math.min(event.transform.k, 1));
        d3.selectAll('g').attr('transform', event.transform);
    })


const D3Grid = ({ data, width, height }) => {
    const [shapes, setShapes] = useState(data);
    const svgGridRef = useRef();
    const content = useRef();

    const gridSize = 25;
    const gridColor = '#a4a4a4';

    useEffect(() => {
        const svg = d3.select(svgGridRef.current);
        var pattern = svg.append('pattern')
            .attr('id', 'grid-pattern')
            .attr('patternUnits', 'userSpaceOnUse')
            .attr('x', 0)
            .attr('y', 0)
            .attr('width', gridSize)
            .attr('height', gridSize);

        pattern.append('line')
            .attr('stroke', gridColor)
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', gridSize * 16)
            .attr('y2', 0);

        pattern.append('line')
            .attr('stroke', gridColor)
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', 0)
            .attr('y2', gridSize * 16);

        svg.append('rect')
            .attr('fill', 'url(#grid-pattern)')
            .attr('width', '100%')
            .attr('height', '100%');

        return () => {
            // Clean up any D3 elements or listeners
        };
    }, [])

    useEffect(() => {
        const svg = d3.select(svgGridRef.current);
        svg.call(zoomBehaviour(width, height, gridSize, svgGridRef));

        return () => {
            svg.on('.zoom', null);
        };
    }, []);

    return (
        <svg
            className='main-svg'
            ref={svgGridRef}
            viewBox={`0 0 ${width} ${height}`}
        >
            {
                shapes.map((shape, index) => (
                    <Rect 
                    key={index} 
                    id={shape.id} 
                    fill={shape.fill} 
                    rx={shape.rx} 
                    width={shape.width} 
                    height={shape.height} 
                    x={shape.posX} 
                    y={shape.posY} 
                />
                ))
            }
        </svg >
    )
}

export default D3Grid

Rectangular component. I tried g elements and rect elements. no job.

const Rect = (props) => {
    return (
        <g onClick={(event) => { console.log({ event }); }}>
            <rect {...props} onClick={(event) => { console.log({ event }); }} />
        </g>

    )
}

export default Rect

P粉504080992P粉504080992236 days ago448

reply all(1)I'll reply

  • P粉773659687

    P粉7736596872024-01-30 00:06:32

    In your code, the useEffect hook will be triggered after the Rect component is rendered, which means that the pattern and related background rectangle in useEffect will be rendered on top of the Rect component. They then prevent the event from propagating to the underlying rectangular component.

    To solve this problem, there are two options. The first is to set the mode's "pointer-events" property and related stuff to "none", and the second is to put these stuff at the bottom. This is a live demonstration. Int Screenshot Demo link

    const D3Grid = () => {
      useEffect(() => {
        const background = svg.select(".background")
        background.append("pattern");
        background.append("rect");
      });
      return (
        
        {...rects}
      );
    }

    reply
    0
  • Cancelreply