首页 > 解决方案 > 在 D3 中 - 同步两组单选按钮并更新反应状态

问题描述

首先让我分享一个代码示例,该示例将非常清楚且轻松地突出我当前遇到的问题:

class App extends React.Component {
  constructor(props) { 
    super(props);
    
    this.state = {
      oppDiv: 'none',
      oppTeam: 'none'
    }
    
    this.chartProps = {
      myLightGrey: '#EEE',
      myMidGrey: '#999',
      myDarkGrey: '#333',
    }
  }
  
  updateButtonColors(button, parent, self) {
    const { myLightGrey, myDarkGrey } = self.chartProps;
    parent.selectAll("rect")
      .attr("fill", myLightGrey)
    parent.selectAll("text")
      .attr("fill", myDarkGrey)


    button.select("rect")
      .attr("fill", myDarkGrey)
    button.select("text")
      .attr("fill", myLightGrey)
  }
  
  grabTeamInfo() {
    var data = [{"teamid":"ARI","division":"NL West"},{"teamid":"ATL","division":"NL East"},{"teamid":"BAL","division":"AL East"},{"teamid":"BOS","division":"AL East"},{"teamid":"CHC","division":"NL Central"},{"teamid":"CWS","division":"AL Central"},{"teamid":"CIN","division":"NL Central"},{"teamid":"CLE","division":"AL Central"},{"teamid":"COL","division":"NL West"},{"teamid":"DET","division":"AL Central"},{"teamid":"HOU","division":"AL West"},{"teamid":"KC","division":"AL Central"},{"teamid":"LAA","division":"AL West"},{"teamid":"LAD","division":"NL West"},{"teamid":"MIA","division":"NL East"},{"teamid":"MIL","division":"NL Central"},{"teamid":"MIN","division":"AL Central"},{"teamid":"NYM","division":"NL East"},{"teamid":"NYY","division":"AL East"},{"teamid":"OAK","division":"AL West"},{"teamid":"PHI","division":"NL East"},{"teamid":"PIT","division":"NL Central"},{"teamid":"STL","division":"NL Central"},{"teamid":"SD","division":"NL West"},{"teamid":"SF","division":"NL West"},{"teamid":"SEA","division":"AL West"},{"teamid":"TB","division":"AL East"},{"teamid":"TEX","division":"AL West"},{"teamid":"TOR","division":"AL East"},{"teamid":"WAS","division":"NL East"}];
  
    return data;
  }
  
  drawOppDivision() {
    
    const teamInfo = this.grabTeamInfo();
    const { myLightGrey, myMidGrey, myDarkGrey } = this.chartProps;
    const { updateButtonColors } = this;
    const divs = ["NL East", "NL Central", "NL West", "AL East", "AL Central", "AL West"];
    d3.select('g.oppDivision')
      .attr("transform", "translate(" + 585 + "," + 135 + ")")
        
    // Draw Button Group For 6 Divisions
    // ==================================
    const oppDivision = d3.select('g.oppDivision')
      .selectAll('.divisions')
      .data(divs)
      .enter()
      .append("g")
        .attr("class", "divisions")
        .attr("cursor", "pointer")
      
    oppDivision.append("rect")
      .attr("x", (d,i) => (i % 3)*67)
      .attr("y", (d,i) => i > 2 ? 27 : 0)
      .attr("rx", 4).attr("ry", 4)
      .attr("width", 65).attr("height", 25)
      .attr("stroke", "#BBB")
      .attr("fill", "#EEE")
    
    oppDivision.append("text")
      .attr("x", (d,i) => 32 + (i % 3)*67)
      .attr("y", (d,i) => i > 2 ? 15 + 27 : 15 + 0)
      .style("font-size", "0.7em")
      .style("text-anchor", "middle")
      .style("font-weight", "700")
      .text(d => d)
    
    const self = this;
    oppDivision
      .on("click", function(d,i) {
        updateButtonColors(d3.select(this), d3.select(this.parentNode), self)
        self.setState({oppDiv: divs[i]})
      })
      .on("mouseover", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myMidGrey); // lol almost here keep trying
        }
      })
      .on("mouseout", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myLightGrey);
        }
      });
    
    // Draw Title
    d3.select('g.oppDivision').append("text")
      .attr("x", -1).attr("y", -15)
      .style("font-size", '1.25em')
      .style("font-weight", '700')
      .style("fill", myDarkGrey)
      .text("Opposing Div / Team") 
  }
  drawOppTeam() {
    
    // Draw Button Group For 5 Teams In Selected Division
    // ====================================================
    // make an object with (team, division, abbrev) keys?
    const teamInfo = this.grabTeamInfo();
    const { myLightGrey, myMidGrey, myDarkGrey } = this.chartProps;
    const { updateButtonColors } = this;
    const { oppDiv } = this.state;
    
    const oppTeamList = teamInfo
      .filter(team => team.division == oppDiv)
      .map(team => team.teamid)
    
    // d3.select('g.oppTeam').selectAll('*').remove() 
    
    d3.select('g.oppTeam')
      .attr("transform", "translate(" + 585 + "," + 135 + ")")

    const oppTeam = d3.select('g.oppTeam')
      .selectAll('.oppteams')
      .data(oppTeamList)
    
    oppTeam
      .enter()
      .append("g")
        .attr("class", "oppteams")
        .attr("cursor", "pointer")

    oppTeam.append("rect")
      .attr("x", (d,i) => i * 40)
      .attr("y", 65)
      .attr("rx", 4).attr("ry", 4)
      .attr("width", 38).attr("height", 20)
      .attr("stroke", myMidGrey)
      .attr("fill", myLightGrey)    
    
    oppTeam.append("text")
      .attr("x", (d,i) => (i * 40)+20)
      .attr("y", 79)
      .style("font-size", "0.7em") 
      .style("font-weight", "700")
      .style("text-anchor", "middle")
      .text(d => d)
    
    oppTeam   // not wanting to work like it should (need D3 GUP)
      .exit()
      .remove()
    


    
    const self = this;
    oppTeam
      .on("click", function(d,i) {
        updateButtonColors(d3.select(this), d3.select(this.parentNode), self)
        self.setState({oppTeam: oppTeamList[i]})
      })
      .on("mouseover", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myMidGrey); // lol almost here keep trying
        }
      })
      .on("mouseout", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myLightGrey);
        }
      });
    // ======
  }
  
  componentDidUpdate() {
    this.drawOppTeam()
  }
  componentDidMount() {
    d3.select('#my-button-svg')
			.attr('width', '100%')
			.attr('height', '100%')
			.attr('viewBox', "0 0 " + (800) + " " + 600)  
			.attr('preserveAspectRatio', "xMaxYMax")  
  
    this.drawOppDivision();
  }
  
  render() {
    return(
      <div>
        <svg id='my-button-svg'>
          <g className="oppDivision" />
          <g className="oppTeam" />
        </svg>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<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>


<div id='root'>
  Come On Work!
</div>

我正在努力让 2 个 D3 无线电按钮组在这里携手合作。应该发生的是:

我目前面临的问题是:

我认为这是 React 中简洁、干净的单选按钮代码,在按钮组之间具有简单而有用的交互性(尤其是在团队和部门之间,我需要一个棒球应用程序)。修复按钮无法正常工作的原因(并知道我的代码有什么问题),将对我有很大帮助!在此先感谢您的帮助。

标签: reactjsd3.js

解决方案


快速修复它,现在似乎对我有用,让我知道它是否是你要找的!

顺便说一句,从长远来看,我建议完全在 React 中(componentDidUpdate()负责更新样式)或完全在 d3 中维护状态(这意味着您只渲染 SVG 并且 React 之后不会触摸它,除法按钮单击处理程序调用drawOppTeam()而不是componentDidUpdate())。我认为这个问题很棘手的原因是状态处理是在两个库之间共享的。

class App extends React.Component {
  constructor(props) { 
    super(props);
    
    this.state = {
      oppDiv: 'none',
      oppTeam: 'none'
    }
    
    this.chartProps = {
      myLightGrey: '#EEE',
      myMidGrey: '#999',
      myDarkGrey: '#333',
    }
  }
  
  updateButtonColors(button, parent, self) {
    const { myLightGrey, myDarkGrey } = self.chartProps;
    parent.selectAll("rect")
      .attr("fill", myLightGrey)
    parent.selectAll("text")
      .attr("fill", myDarkGrey)


    button.select("rect")
      .attr("fill", myDarkGrey)
    button.select("text")
      .attr("fill", myLightGrey)
  }
  
  grabTeamInfo() {
    var data = [{"teamid":"ARI","division":"NL West"},{"teamid":"ATL","division":"NL East"},{"teamid":"BAL","division":"AL East"},{"teamid":"BOS","division":"AL East"},{"teamid":"CHC","division":"NL Central"},{"teamid":"CWS","division":"AL Central"},{"teamid":"CIN","division":"NL Central"},{"teamid":"CLE","division":"AL Central"},{"teamid":"COL","division":"NL West"},{"teamid":"DET","division":"AL Central"},{"teamid":"HOU","division":"AL West"},{"teamid":"KC","division":"AL Central"},{"teamid":"LAA","division":"AL West"},{"teamid":"LAD","division":"NL West"},{"teamid":"MIA","division":"NL East"},{"teamid":"MIL","division":"NL Central"},{"teamid":"MIN","division":"AL Central"},{"teamid":"NYM","division":"NL East"},{"teamid":"NYY","division":"AL East"},{"teamid":"OAK","division":"AL West"},{"teamid":"PHI","division":"NL East"},{"teamid":"PIT","division":"NL Central"},{"teamid":"STL","division":"NL Central"},{"teamid":"SD","division":"NL West"},{"teamid":"SF","division":"NL West"},{"teamid":"SEA","division":"AL West"},{"teamid":"TB","division":"AL East"},{"teamid":"TEX","division":"AL West"},{"teamid":"TOR","division":"AL East"},{"teamid":"WAS","division":"NL East"}];
  
    return data;
  }
  
  drawOppDivision() {
    
    const teamInfo = this.grabTeamInfo();
    const { myLightGrey, myMidGrey, myDarkGrey } = this.chartProps;
    const { updateButtonColors } = this;
    const divs = ["NL East", "NL Central", "NL West", "AL East", "AL Central", "AL West"];
    d3.select('g.oppDivision')
      .attr("transform", "translate(" + 585 + "," + 135 + ")")
        
    // Draw Button Group For 6 Divisions
    // ==================================
    const oppDivision = d3.select('g.oppDivision')
      .selectAll('.divisions')
      .data(divs)
      .enter()
      .append("g")
        .attr("class", "divisions")
        .attr("cursor", "pointer")
      
    oppDivision.append("rect")
      .attr("x", (d,i) => (i % 3)*67)
      .attr("y", (d,i) => i > 2 ? 27 : 0)
      .attr("rx", 4).attr("ry", 4)
      .attr("width", 65).attr("height", 25)
      .attr("stroke", "#BBB")
      .attr("fill", "#EEE")
    
    oppDivision.append("text")
      .attr("x", (d,i) => 32 + (i % 3)*67)
      .attr("y", (d,i) => i > 2 ? 15 + 27 : 15 + 0)
      .style("font-size", "0.7em")
      .style("text-anchor", "middle")
      .style("font-weight", "700")
      .text(d => d)
    
    const self = this;
    oppDivision
      .on("click", function(d,i) {
        updateButtonColors(d3.select(this), d3.select(this.parentNode), self)
        self.setState({oppDiv: divs[i]})
      })
      .on("mouseover", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myMidGrey); // lol almost here keep trying
        }
      })
      .on("mouseout", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myLightGrey);
        }
      });
    
    // Draw Title
    d3.select('g.oppDivision').append("text")
      .attr("x", -1).attr("y", -15)
      .style("font-size", '1.25em')
      .style("font-weight", '700')
      .style("fill", myDarkGrey)
      .text("Opposing Div / Team") 
  }
  drawOppTeam() {
    
    // Draw Button Group For 5 Teams In Selected Division
    // ====================================================
    // make an object with (team, division, abbrev) keys?
    const teamInfo = this.grabTeamInfo();
    const { myLightGrey, myMidGrey, myDarkGrey } = this.chartProps;
    const { updateButtonColors } = this;
    const { oppDiv } = this.state;
    
    const oppTeamList = teamInfo
      .filter(team => team.division == oppDiv)
      .map(team => team.teamid)
    
    d3.select('g.oppTeam').selectAll('*').remove() 
    
    d3.select('g.oppTeam')
      .attr("transform", "translate(" + 585 + "," + 135 + ")")
      .attr("cursor", "pointer")

    const oppTeam = d3.select('g.oppTeam')
      .selectAll('.oppteams')
      .data(oppTeamList)
    

    const teams = oppTeam
      .enter()
      .append("g")
        .attr("class", "oppteams")
        

    teams.append("rect")
      .attr("x", (d,i) => i * 40)
      .attr("y", 65)
      .attr("rx", 4).attr("ry", 4)
      .attr("width", 38).attr("height", 20)
      .attr("stroke", (d) => this.state.oppTeam === d ? myLightGrey : myMidGrey)
      .attr("fill", (d) => this.state.oppTeam === d ? myDarkGrey : myLightGrey)    
    
    teams.append("text")
      .attr("x", (d,i) => (i * 40)+20)
      .attr("y", 79)
      .attr("fill", (d) => this.state.oppTeam === d ? myLightGrey : myDarkGrey)
      .style("font-size", "0.7em") 
      .style("font-weight", "700")
      .style("text-anchor", "middle")
      .text(d => d)
    
    //oppTeam   // not wanting to work like it should (need D3 GUP)
      //.exit()
      //.remove()
    


    
    const self = this;
    teams
      .on("click", function(d,i) {
        updateButtonColors(d3.select(this), d3.select(this.parentNode), self)
        self.setState({oppTeam: oppTeamList[i]})
      })
      .on("mouseover", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myMidGrey); // lol almost here keep trying
        }
      })
      .on("mouseout", function() {
        if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
          d3.select(this)
            .select("rect")
            .attr("fill", myLightGrey);
        }
      });
    // ======
  }
  
  componentDidUpdate() {
    this.drawOppTeam()
  }
  componentDidMount() {
    d3.select('#my-button-svg')
			.attr('width', '100%')
			.attr('height', '100%')
			.attr('viewBox', "0 0 " + (800) + " " + 600)  
			.attr('preserveAspectRatio', "xMaxYMax")  
  
    this.drawOppDivision();
  }
  
  render() {
    return(
      <div>
        <svg id='my-button-svg'>
          <g className="oppDivision" />
          <g className="oppTeam" />
        </svg>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<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>


<div id='root'>
  Come On Work!
</div>


推荐阅读