ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

javascript-D3工具提示无法在React组件中正确更新

2019-11-08 23:43:09  阅读:202  来源: 互联网

标签:reactjs d3-js tooltip javascript


我花了相当长的时间编写此代码片段,以突出显示我在使用d3时遇到的一个非常令人沮丧的问题,并且会共同做出反应,特别是尝试根据组件状态动态更新工具提示时.

首先,我尝试尽力使代码片段简短完整(为简洁起见,您可以跳过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 => d.x)
      .attr('cy', d => d.y)
      .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>

概述是这样的:

我有2个组件,一个容器组件和一个图形组件.容器组件具有ageOrName状态,以及带有“ age”和“ name”的单选按钮,用于更新此状态.然后,容器组件将此值传递到图形组件中.

接受ageOrName作为道具的图形组件使用该道具来更改工具提示中的文本.但是,它不起作用.为了绝对的完整性,我列出了2个!不同的工具提示:

>使用d3-tip库创建的工具提示
>从页面上的div创建的自定义工具提示

我有两个console.logs()来在图形组件内部显示ageOrName prop的值,并且我已经意识到一些非常重要的问题.根据我是否在“ on.mouseover”调用之内,道具的价值有所不同.我不知道为什么.

对于我的应用程序来说,使它正常工作非常重要,对此,我们将提供任何帮助!

编辑:几乎可以肯定会在2天之内悬赏这个问题(现在想).即使会尽快提供答案也会奖励.

解决方法:

即使选择了名称输入后,{buttonName}仍会恢复使用年龄的主要原因是,在第一次drawGraph调用期间鼠标悬停功能已绑定到圆上,下次再次调用时,发生了d3更新逻辑,该逻辑没有将新的mouseover事件添加到现有圈子(基于新的{buttonName}.

我想出了两种解决方法:

>只需在mouseover函数本身中获取当前的{buttonName}.这是如何做:

.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 => d.x)
      .attr('cy', d => d.y)
      .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>

>在每个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 => d.x)
      .attr('cy', d => d.y)
      .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>

(我个人选择第一种方法)

希望这可以帮助.

标签:reactjs,d3-js,tooltip,javascript
来源: https://codeday.me/bug/20191108/2010433.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有