d3.event is null in a ReactJS + d3JS component

2020-05-01 05:43发布

问题:

I'm using ReactJS, d3JS and ES6 to create an org chart. I can create the chart and see it. But I want to add the zoom behavior to the chart, so I used d3.behavior.zoom. I can see the method zoomed is called, but the d3.event is null.

In my ASP.NET project I made sure that I don't have any reference to d3.js other than in the systemJS configuration which many stackoverflow answers mentioned as the issue related to d3.event is null.

This is my systemJS configuration:

<script>
    System.defaultJSExtensions = true;
    System.config({
        map: {
            'rx': "https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js",
            'react': 'https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react.js',
            'react-dom': 'https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.js',
            'd3': 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.js'
        }
    });
    System.import('../app/app.js');
</script>

OrgChart component:

import React, { Component } from 'react';
import * as Rx from 'rx';
import * as d3 from 'd3';
import Person from './person';

class OrgChart extends Component {

    constructor(props) {
        super(props);

        this.state = { persons: [] };
    }

    componentWillMount() {
        var source = Rx.Observable.fromPromise($.getJSON("/js/org-persons.json"));

        source.subscribe(
            function(chart) {
                var tree = d3.layout.tree()
                    .nodeSize([110, 50])
                    .separation(function() {
                        return 1;
                    });

                var nodes = tree.nodes(chart[0]).reverse();

                var links = tree.links(nodes);

                var p = [];

                nodes.forEach(function(node) {
                    fs.push((<Person x={node.x} y={node.y}></Person>));
                }.bind(this));

                this.setState({ person: p });

            }.bind(this)
        );
    }

    componentDidMount() {
        var rootX = document.body.clientWidth / 4;
        var rootY = 300;

        var zoom = d3.behavior.zoom()
            .scaleExtent([1, 10])
            .on("zoom", this.zoomed)
            .translate([rootX, rootY]);

        d3.select("#viewport")
            .call(zoom);
    }

    zoomed() {
        d3.select("#viewport").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }

    render() {

        return (
            <svg id='fullTree' width='100%'>
                <g id='viewport'>
                    { this.state.persons }
                </g>
            </svg>
        );
    }
}

export default OrgChart;

回答1:

If you're using React, Babel or Webpack and d3.js v4, it looks like you need to import event from d3-selection in order to use d3.event

import * as d3 from 'd3';
import {event as currentEvent} from 'd3-selection';

You can now use currentEvent the same way you'd use d3.event. See https://github.com/d3/d3/issues/2733 for more information.



回答2:

If you are importing multiple d3 packages. these packages have a d3 object accessible which might clash with one another. you have to import them with different names.

import d3 from 'd3-selection'
import d3Zoom from 'd3-zoom'
import d3Scale from 'd3-scale'

and have a anonymous function for your callback

.on("zoom", () => d3.select("#viewport")
    .attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
)


回答3:

Did you try importing d3 like this instead : import d3 from 'd3';

This issue seems to occur when two occurences of d3 exist in the document.



回答4:

Quick shot, try this.

    var zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", () => d3.select("#viewport").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");)
        .translate([rootX, rootY]);

If this works, than following should resolve your issue,

var zoom = d3.behavior.zoom()
            .scaleExtent([1, 10])
            .on("zoom", this.zoomed.bind(this))
            .translate([rootX, rootY]);


回答5:

I'm not sure exactly what you're doing, but it smells like you're overthinking it. In theory, the zoom behavior changes ranges of scales.

So all you have to do, is base your visualization on two scales - one for x coordinates, one for y coordinates. Then you tell zoom to change those scales and your visualization changes automagically.

Basically, zooming in just means that x and y map wider. At zoom-level = 1 a conceptual coordinate (0.1, 0.1) maps to, say, (10, 10). When zoom level changes to 2, that same mapping becomes (20, 20). This creates the impression of zooming in.

Here's some old code of mine that uses this principle to create zooming: https://github.com/Swizec/candidate-bucket-chart/blob/169779e110c8825f4545c71ae5845ff7bad4f1f4/src/BubbleChart.jsx

The React itself is a bit outdated by now, and I haven't had time to extrapolate the zooming into a cleaner example project, but I hope it helps.

Remember to forceUpdate because React doesn't know your scales changed.