react-d3-tree icon indicating copy to clipboard operation
react-d3-tree copied to clipboard

Mouseout and mouseover events generated while dragging tree

Open murphyke opened this issue 7 years ago • 3 comments

Great component, btw.

Are you reporting a bug, or opening a feature request?

Reporting a bug, or at least behavior that I did not expect.

What is the actual behavior/output?

If you depress the mouse on a tree node and then drag the tree to translate it, sometimes mouseout and mouseover events are generated when dragging quickly. Sometimes there is even a final mouseover event that comes after any translation events seen by the onUpdate callback.

If the user code is trying to distinguish between a click and a drag (a drag performed by grabbing a node normally generates a click event at the end, followed by a mouseout event), the above behavior makes it difficult (impossible?) to tell conclusively.

What is the behavior/output you expect?

Actually, I'd expect the onClick callback to fire only on true clicks rather than drags. But failing that, it would be nice for mouseout and mouseover events to not be generated while dragging quickly.

I found the following code, which distinguishes between clicks and drags when using d3:

https://bl.ocks.org/mbostock/a84aeb78fea81e1ad806

Can you consistently reproduce the issue/create a reproduction case (e.g. on https://codesandbox.io)?

I haven't yet.

What version of react-d3-tree are you using?

1.10.6

murphyke avatar Jul 26 '18 19:07 murphyke

Hey, I know this is from a while ago, but I'm having this issue as well. I've tried adding a custom drag handler to accompany the zoom handler as defined in the package, but I can't get it to work. When you drag after clicking a node, the node collapses, and I just want to prevent this. You ever make progress on this?

sunny-kim-92 avatar Oct 29 '19 18:10 sunny-kim-92

Wow, that's a long time ago. I don't think I made much progress on it; that project didn't make it out of proof-of-concept. I will provide code snippets of what I did, because I no longer understand them. That's the best I can do, since I'm not working on that anymore.

After all this time, I would be surprised if there is not a way of fixing or working around this problem, but good luck.

 constructor(props) {
    super(props);
    this.state = {
      // Default zoom factor
      zoom: 1,
      // `translateX` and `TRANSLATE_Y` determine where to display the tree relative to the tree's
      // container. The values are actually dynamically determined after the
      // component has been rendered once.
      translateX: 0,
      // `resetCount` is used as the `key` value applied to the tree, which allows the tree's position
      // to be reset by the user (by changing `resetCount`). This is needed because the Tree component
      // is not a controlled component; its `translate` and `zoom` props set its initial state, but thereafter,
      // the Tree manipulates its own display parameters.
      resetCount: 0,
    }
    // The SVG is reset when the window is resized, but only after the resize events subside.
    this.resizeDebounceTimeout = null;
    // treeNodeMouseOverTime and treeTranslateOrZoomTime are used to distinguish a tree drag event from a click.
    // When a tree click event occurs, a check is made to see if the tree was translated or zoomed since the
    // mouseover; if so, the click is aborted. The tree's `onUpdate` callback is used to detect translation and zoom
    // events; these are distinguished from node-specific tree events by the fact that `arg.node` is `null` (where
    // `arg` is the argument to the `onUpdate` callback).
    // TODO: this can break when the drag is very fast. See https://github.com/bkrem/react-d3-tree/issues/124
    this.treeNodeMouseOverTime = null;
    this.treeTranslateOrZoomTime = null;
  }

  // Handle a click on a node (or node label) by opening the relevant thingy details.
  clickHandler = (nodeData, event) => {
    if (this.treeTranslateOrZoomTime && this.treeNodeMouseOverTime < this.treeTranslateOrZoomTime) {
      // Make sure that if the user subsequently clicks the current node without mousing off it,
      // the click will be honored.
      this.treeTranslateOrZoomTime = null;
      return;
    }
    this.props.actions.openThingyDetail(nodeData.name, nodeData.attributes.version);
  }

render() {
    // ...
        <Tree data={treeData}
              orientation="vertical"
              pathFunc="elbow"
              translate={{
                x: this.state.translateX,
                y: History.TRANSLATE_Y,
              }}
              collapsible={false}
              nodeSize={{
                x: History.NODE_WIDTH,
                y: History.NODE_HEIGHT,
              }}
              allowForeignObjects={true}
              nodeLabelComponent={{
                render: <NodeLabel className={styles.nodeLabel}/>,
                foreignObjectWrapper: {
                  y: 2,
                  x: 5,
                }
              }}
              zoom={this.state.zoom}
              scaleExtent={{
                min: 0.1,
                max: 2,
              }}
              key={this.state.resetCount}
              onClick={this.clickHandler}
              onMouseOver={() => this.treeNodeMouseOverTime = Date.now()}
              onMouseOut={() => this.treeNodeMouseOverTime = null}
              onUpdate={(obj) => obj.node === null && (this.treeTranslateOrZoomTime = Date.now())}
        />
}

murphyke avatar Oct 29 '19 18:10 murphyke

Thank you! Your ideas make sense, thanks for all the comments.

sunny-kim-92 avatar Oct 29 '19 18:10 sunny-kim-92