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

时间:2018-06-14 18:44:22

标签: javascript reactjs d3.js tooltip

我花了很长时间制作这段代码片段,以突出一个非常令人沮丧的问题,我已经遇到了使用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')
);
&#13;
/* 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;
}
/* ========== */
&#13;
<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>
&#13;
&#13;
&#13;

概述如下:

我有2个组件,一个容器组件和一个图组件。容器组件具有ageOrName状态,以及带有&#34; age&#34;的单选按钮。和&#34;名称&#34;更新此状态。然后容器组件将此值传递给图形组件。

接收ageOrName作为道具的图形组件使用此道具更改工具提示中的文本。但是,无效。为了绝对完整,我已经包含 2!不同的工具提示:

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

我有两个console.logs()来显示图形组件内的ageOrName prop的值,并且我已经意识到非常关注。 prop 的值是不同的取决于我是否在&#34; on.mouseover&#34;呼叫。我不明白为什么。

让我的应用程序对我的应用程序非常重要,我们非常感谢您提供的任何帮助!

编辑:几乎可以肯定会在2天内给这个问题带来好处(想要现在)。即使尽快给出答案,也会奖励。

1 个答案:

答案 0 :(得分:3)

选择名称输入后,{buttonName}仍然选择年龄的主要原因是mouseover功能已绑定到圈子在第一次drawGraph调用期间以及下次再次调用时,会出现 d3更新逻辑,该逻辑未向现有圈子添加鼠标悬停事件(基于新的{buttonName}

我想出了两种方法来解决这个问题:

  1. 只需获取鼠标悬停功能中的当前{buttonName}即可。方法如下:

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

    1. 在每次mouseover来电期间绑定drawGraph个事件:(类似这样)

      points.select('circle')
        ....
        .attr('fill', 'blue');
      
      d3.select('#mySvg').select('g.points').selectAll('circle')
         .on('mouseover', function(d) {
         .....
      
    2. 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>

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

      希望这有帮助。