简体   繁体   English

D3工具提示未在React组件中正确更新

[英]D3 tooltip not updating correctly in a React Component

I spent quite a while making this code snippet to highlight a very frustrating issue I've run into using d3 and react together, in particular with trying to dynamically update tooltips based on component state. 我花了很长时间制作这段代码片段,以突出一个非常令人沮丧的问题,我遇到了使用d3并一起做出反应,特别是尝试根据组件状态动态更新工具提示。

First, the code snippet, which I've tried my best to keep short and complete (for brevity, you can skip over the css. just note the radio button group, and the mouseover in the graph component): 首先,代码片段,我尽力保持简短和完整(为了简洁起见,您可以跳过css。只需注意单选按钮组,鼠标悬停在图形组件中):

 class GraphComponent extends React.Component { constructor(props) { super(props); } // Lifecycle Components drawGraph() { const { buttonName } = this.props; const myData = [ {x:25, y:30, name:"tommy", age:"24"}, {x:108, y:82, name:"joey", age:"21"}, {x:92, y:107, name:"nicky", age:"23"}, {x:185, y:50, name:"peter", age:"27"}, {x:65, y:80, name:"mickie", age:"4"}, {x:165, y:80, name:"gregie", age:"14"}, {x:154, y:10, name:"tammie", age:"24"}, {x:102, y:42, name:"benny", age:"29"} ] // var myD3tip = d3Tip() var myD3tip = d3.tip() .attr('class', 'd3-tip') .offset([-20,0]) .html(d => `<p>D3 Tip: ${buttonName}: ${d[buttonName]}</p>`) console.log('buttonName outside mouseover', buttonName) const divToolTip = d3.select('div#myTooltip') const points = d3.select('#mySvg').select('g.points') points.call(myD3tip) points .selectAll('circle') .data(myData) .enter() .append('circle') .attr('cx', d => dx) .attr('cy', d => dy) .attr('r', 8) .attr('fill', 'blue') .on('mouseover', function(d) { myD3tip.show(d) console.log('buttonName in mouseover', buttonName) divToolTip .style('opacity', 1) .style('left', d3.mouse(this)[0] + 'px') .style('top', (d3.mouse(this)[1]) + 'px') .html(`<p>Div Tip: ${buttonName}: ${d[buttonName]}</p>`) }) .on('mouseout', function(d) { myD3tip.hide(d) divToolTip .style('opacity', 0) }) } componentDidUpdate() { this.drawGraph() } componentDidMount() { this.drawGraph() } render() { return ( <svg id="mySvg"> <g className="points" /> </svg> ) } } class GraphContainer extends React.Component { constructor(props) { super(props); this.state = { ageOrName: "age" } this.handleChange = this.handleChange.bind(this); } handleChange = (event) => { this.setState({ ageOrName: event.target.value }) } render() { const { ageOrName } = this.state; const ageOrNameOptions = ["age", "name"]; const ageOrNameButtons = <form> <div> {ageOrNameOptions.map((d, i) => { return ( <label key={i+d}> <input type={"radio"} value={ageOrNameOptions[i]} checked={ageOrName === ageOrNameOptions[i]} onChange={this.handleChange} /> <span>{ageOrNameOptions[i]}</span> </label> ) })} </div> </form>; return ( <div> {ageOrNameButtons} <GraphComponent buttonName={ageOrName} /> </div> ) } } ReactDOM.render( <GraphContainer />, document.getElementById('root') ); 
 /* Div ToolTip */ #myTooltip { opacity: 0; position: absolute; pointer-events: none; background-color: lightblue; line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* ========== */ /* D3 ToolTip */ /* ========== */ .d3-tip { line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* Creates a small triangle extender for the tooltip */ .d3-tip:after { box-sizing: border-box; display: inline; font-size: 10px; width: 100%; line-height: 1; color: #444; content: "\\25BC"; position: absolute; text-align: center; } /* Style northward tooltips differently */ .d3-tip.n:after { margin: -1px 0 0 0; top: 100%; left: 0; } /* ========== */ 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.8.0-alpha.1/d3-tip.js"></script> <div id="root"> <svg id="mySvg"> </div> <div id="myTooltip"></div> 

The overview is this: 概述如下:

I have 2 components, a container component and a graph component. 我有2个组件,一个容器组件和一个图形组件。 The container component has ageOrName state, and a radio button with "age" and "name" that updates this state. 容器组件具有ageOrName状态,以及具有“age”和“name”的单选按钮,用于更新此状态。 The container component then passes this value into the graph component. 然后容器组件将此值传递给图形组件。

The graph component, receiving ageOrName as a prop, uses this prop to change the text in the tooltip. 接收ageOrName作为prop的图形组件使用此prop来更改工具提示中的文本。 However, its not working . 但是, 它不起作用 For absolute completeness, I've included 2! 为了绝对完整,我已经包含了2个! different tooltips: 不同的工具提示:

  • a tooltip created with the d3-tip library 使用d3-tip库创建的工具提示
  • a custom tooltip, created from a div on the page 自定义工具提示,从页面上的div创建

I have two console.logs() to display the value of the ageOrName prop inside of the graph component, and I've realized something very concerning . 我有两个console.logs()来显示图形组件内的ageOrName prop的值,我已经意识到一些非常令人担忧的事情。 The value of the prop is different depending on whether I am inside of the "on.mouseover" call. 根据我是否在“on.mouseover”调用中,prop的值是不同的 I have no idea why. 我不知道为什么。

Getting this to work is important for an app of mine, and any help at all with this is greatly appreciated! 让这个工作对我的应用程序很重要,非常感谢任何帮助!

Edit: Will almost certainly bounty this question in 2 days (would like to now). 编辑:几乎肯定会在2天内给出这个问题(现在想)。 Will award even if an answer is provided sooner. 即使提供更快的答案,也会奖励。

Main reason for the {buttonName} still picking up age EVEN after choosing the name input is that the mouseover function is bound to the circles during the first drawGraph call and the next time it's called again, there occurs the d3 update logic which doesn't add a new mouseover event to the existing circles (based on the new {buttonName} . {buttonName}在选择名称输入后仍然接收年龄的主要原因是在第一次drawGraph调用期间mouseover功能被绑定到圆圈,并且下次再次调用时,会出现d3更新逻辑将新的鼠标悬停事件添加到现有圈子(基于新的{buttonName}

I figured out 2 approaches to resolve this: 我想出了两种方法来解决这个问题:

  1. Just get the current {buttonName} in the mouseover function itself. 只需获取鼠标悬停功能本身的当前{buttonName} Here's how: 这是如何做:

     .on('mouseover', function(d) { var { buttonName } = that.props; myD3tip.html(d => `<p>D3 Tip: ${buttonName}: ${d[buttonName]}</p>`).show(d); 

 class GraphComponent extends React.Component { constructor(props) { super(props); } // Lifecycle Components drawGraph() { var that = this; const { buttonName } = this.props; const myData = [ {x:25, y:30, name:"tommy", age:"24"}, {x:108, y:82, name:"joey", age:"21"}, {x:92, y:107, name:"nicky", age:"23"}, {x:185, y:50, name:"peter", age:"27"}, {x:65, y:80, name:"mickie", age:"4"}, {x:165, y:80, name:"gregie", age:"14"}, {x:154, y:10, name:"tammie", age:"24"}, {x:102, y:42, name:"benny", age:"29"} ] // var myD3tip = d3Tip() var myD3tip = d3.tip() .attr('class', 'd3-tip') .offset([-20,0]) .html(d => `<p>D3 Tip: ${buttonName}: ${d[buttonName]}</p>`) const divToolTip = d3.select('div#myTooltip') const points = d3.select('#mySvg').select('g.points') points.call(myD3tip) points .selectAll('circle') .data(myData) .enter() .append('circle') .attr('cx', d => dx) .attr('cy', d => dy) .attr('r', 8) .attr('fill', 'blue'); d3.select('#mySvg').select('g.points').selectAll('circle') .on('mouseover', function(d) { var { buttonName } = that.props; myD3tip.html(d => `<p>D3 Tip: ${buttonName}: ${d[buttonName]}</p>`).show(d); divToolTip .style('opacity', 1) .style('left', d3.mouse(this)[0] + 'px') .style('top', (d3.mouse(this)[1]) + 'px') .html(`<p>Div Tip: ${buttonName}: ${d[buttonName]}</p>`) }) .on('mouseout', function(d) { myD3tip.hide(d) divToolTip .style('opacity', 0) }) } componentDidUpdate() { this.drawGraph() } componentDidMount() { this.drawGraph() } render() { return ( <svg id="mySvg"> <g className="points" /> </svg> ) } } class GraphContainer extends React.Component { constructor(props) { super(props); this.state = { ageOrName: "age" } this.handleChange = this.handleChange.bind(this); } handleChange = (event) => { this.setState({ ageOrName: event.target.value }) } render() { const { ageOrName } = this.state; const ageOrNameOptions = ["age", "name"]; const ageOrNameButtons = <form> <div> {ageOrNameOptions.map((d, i) => { return ( <label key={i+d}> <input type={"radio"} value={ageOrNameOptions[i]} checked={ageOrName === ageOrNameOptions[i]} onChange={this.handleChange} /> <span>{ageOrNameOptions[i]}</span> </label> ) })} </div> </form>; return ( <div> {ageOrNameButtons} <GraphComponent buttonName={ageOrName} /> </div> ) } } ReactDOM.render( <GraphContainer />, document.getElementById('root') ); 
 /* Div ToolTip */ #myTooltip { opacity: 0; position: absolute; pointer-events: none; background-color: lightblue; line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* ========== */ /* D3 ToolTip */ /* ========== */ .d3-tip { line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* Creates a small triangle extender for the tooltip */ .d3-tip:after { box-sizing: border-box; display: inline; font-size: 10px; width: 100%; line-height: 1; color: #444; content: "\\25BC"; position: absolute; text-align: center; } /* Style northward tooltips differently */ .d3-tip.n:after { margin: -1px 0 0 0; top: 100%; left: 0; } /* ========== */ 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.8.0-alpha.1/d3-tip.js"></script> <div id="root"> <svg id="mySvg"></svg> </div> <div id="myTooltip"></div> 

  1. Bind the mouseover event during every drawGraph call: (something like this) 在每个drawGraph调用期间绑定mouseover事件:(类似这样)

     points.select('circle') .... .attr('fill', 'blue'); d3.select('#mySvg').select('g.points').selectAll('circle') .on('mouseover', function(d) { ..... 

 class GraphComponent extends React.Component { constructor(props) { super(props); } // Lifecycle Components drawGraph() { const { buttonName } = this.props; const myData = [ {x:25, y:30, name:"tommy", age:"24"}, {x:108, y:82, name:"joey", age:"21"}, {x:92, y:107, name:"nicky", age:"23"}, {x:185, y:50, name:"peter", age:"27"}, {x:65, y:80, name:"mickie", age:"4"}, {x:165, y:80, name:"gregie", age:"14"}, {x:154, y:10, name:"tammie", age:"24"}, {x:102, y:42, name:"benny", age:"29"} ] // var myD3tip = d3Tip() var myD3tip = d3.tip() .attr('class', 'd3-tip') .offset([-20,0]) .html(d => `<p>D3 Tip: ${buttonName}: ${d[buttonName]}</p>`); const divToolTip = d3.select('div#myTooltip') const points = d3.select('#mySvg').select('g.points') points.call(myD3tip) points .selectAll('circle') .data(myData) .enter() .append('circle') .attr('cx', d => dx) .attr('cy', d => dy) .attr('r', 8) .attr('fill', 'blue'); d3.select('#mySvg').select('g.points').selectAll('circle') .on('mouseover', function(d) { myD3tip.show(d) divToolTip .style('opacity', 1) .style('left', d3.mouse(this)[0] + 'px') .style('top', (d3.mouse(this)[1]) + 'px') .html(`<p>Div Tip: ${buttonName}: ${d[buttonName]}</p>`) }) .on('mouseout', function(d) { myD3tip.hide(d) divToolTip .style('opacity', 0) }) } componentDidUpdate() { this.drawGraph() } componentDidMount() { this.drawGraph() } render() { return ( <svg id="mySvg"> <g className="points" /> </svg> ) } } class GraphContainer extends React.Component { constructor(props) { super(props); this.state = { ageOrName: "age" } this.handleChange = this.handleChange.bind(this); } handleChange = (event) => { this.setState({ ageOrName: event.target.value }) } render() { const { ageOrName } = this.state; const ageOrNameOptions = ["age", "name"]; const ageOrNameButtons = <form> <div> {ageOrNameOptions.map((d, i) => { return ( <label key={i+d}> <input type={"radio"} value={ageOrNameOptions[i]} checked={ageOrName === ageOrNameOptions[i]} onChange={this.handleChange} /> <span>{ageOrNameOptions[i]}</span> </label> ) })} </div> </form>; return ( <div> {ageOrNameButtons} <GraphComponent buttonName={ageOrName} /> </div> ) } } ReactDOM.render( <GraphContainer />, document.getElementById('root') ); 
 /* Div ToolTip */ #myTooltip { opacity: 0; position: absolute; pointer-events: none; background-color: lightblue; line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* ========== */ /* D3 ToolTip */ /* ========== */ .d3-tip { line-height: 0.50; font-weight: bold; padding: 8px 8px; padding-bottom: 0px; border-radius: 10px; border: 2px solid #444; font-size: 0.9em; } /* Creates a small triangle extender for the tooltip */ .d3-tip:after { box-sizing: border-box; display: inline; font-size: 10px; width: 100%; line-height: 1; color: #444; content: "\\25BC"; position: absolute; text-align: center; } /* Style northward tooltips differently */ .d3-tip.n:after { margin: -1px 0 0 0; top: 100%; left: 0; } /* ========== */ 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.8.0-alpha.1/d3-tip.js"></script> <div id="root"> <svg id="mySvg"></svg> </div> <div id="myTooltip"></div> 

(I'd personally choose the first approach) (我个人选择第一种方法)

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM