Color shading with D3 comparative histogram - reactjs
Edit: due to sizing, probably easiest to view the snippet full screen. Also, use buttons to see what each bars actual color is, which may be helpful.
I am working on an interactive, comparative histogram in D3, however I am struggling with setting the colors appropriately. See below for what I have so far.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedName: 'none'
}
this.chartProps = {
myLightGrey: '#EEE',
myMidGrey: '#888',
myDarkGrey: '#333',
chartWidth: 800, // Dont Change These, For Viewbox
chartHeight: 480, // Dont Change These, For Viewbox
padding: 80,
margin: {top: 20, right: 20, bottom: 30, left: 40},
logoRadius: 800 / 36,
svgID: "nbaStatHistograms"
};
}
// Helper Functions
generateStatData() {
var data = [{"Pos":"PG","teamAbb":"GSW","PTS":37,"REB":6,"AST":3,"STL":0,"BLK":0,"TOV":2,"X3PM":6,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":29,"REB":13,"AST":3,"STL":1,"BLK":3,"TOV":6,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":14,"REB":2,"AST":1,"STL":0,"BLK":1,"TOV":0,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":29,"REB":2,"AST":8,"STL":4,"BLK":0,"TOV":6,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":8,"AST":6,"STL":0,"BLK":1,"TOV":2,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":21,"REB":4,"AST":3,"STL":0,"BLK":1,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":30,"REB":4,"AST":5,"STL":3,"BLK":1,"TOV":2,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":29,"REB":4,"AST":5,"STL":1,"BLK":0,"TOV":1,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":22,"REB":8,"AST":5,"STL":1,"BLK":1,"TOV":4,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":20,"REB":5,"AST":8,"STL":2,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":31,"REB":11,"AST":6,"STL":1,"BLK":2,"TOV":4,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":18,"REB":2,"AST":3,"STL":0,"BLK":2,"TOV":1,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":27,"REB":6,"AST":8,"STL":1,"BLK":0,"TOV":5,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":28,"REB":6,"AST":4,"STL":0,"BLK":2,"TOV":3,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":29,"REB":2,"AST":0,"STL":2,"BLK":0,"TOV":4,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":31,"REB":5,"AST":6,"STL":2,"BLK":1,"TOV":1,"X3PM":7,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":19,"REB":7,"AST":4,"STL":0,"BLK":0,"TOV":1,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":15,"REB":5,"AST":6,"STL":0,"BLK":0,"TOV":3,"X3PM":1,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":21,"REB":8,"AST":4,"STL":3,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":24,"REB":8,"AST":5,"STL":0,"BLK":3,"TOV":2,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":27,"REB":3,"AST":2,"STL":0,"BLK":1,"TOV":1,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":22,"REB":0,"AST":11,"STL":2,"BLK":0,"TOV":3,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":7,"AST":7,"STL":0,"BLK":2,"TOV":4,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":15,"REB":2,"AST":2,"STL":1,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":16,"REB":5,"AST":4,"STL":2,"BLK":0,"TOV":1,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":21,"REB":8,"AST":6,"STL":1,"BLK":3,"TOV":1,"X3PM":1,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":13,"REB":6,"AST":2,"STL":1,"BLK":2,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":22,"REB":8,"AST":8,"STL":2,"BLK":1,"TOV":5,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":28,"REB":5,"AST":3,"STL":1,"BLK":0,"TOV":1,"X3PM":6,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":22,"REB":4,"AST":9,"STL":1,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":29,"REB":5,"AST":4,"STL":2,"BLK":2,"TOV":4,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":23,"REB":1,"AST":2,"STL":1,"BLK":0,"TOV":1,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":21,"REB":7,"AST":8,"STL":0,"BLK":0,"TOV":2,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":15,"REB":4,"AST":5,"STL":0,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":9,"REB":6,"AST":5,"STL":4,"BLK":0,"TOV":4,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":24,"REB":3,"AST":3,"STL":1,"BLK":2,"TOV":3,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":13,"REB":7,"AST":3,"STL":0,"BLK":1,"TOV":1,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":35,"REB":5,"AST":5,"STL":0,"BLK":1,"TOV":2,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":27,"REB":5,"AST":3,"STL":2,"BLK":2,"TOV":4,"X3PM":1,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":17,"REB":4,"AST":1,"STL":0,"BLK":0,"TOV":1,"X3PM":1,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":39,"REB":11,"AST":7,"STL":3,"BLK":0,"TOV":7,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":23,"REB":10,"AST":1,"STL":2,"BLK":1,"TOV":2,"X3PM":1,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":24,"REB":5,"AST":6,"STL":1,"BLK":0,"TOV":6,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":21,"REB":5,"AST":2,"STL":2,"BLK":0,"TOV":4,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":9,"REB":4,"AST":2,"STL":0,"BLK":1,"TOV":1,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":33,"REB":7,"AST":4,"STL":2,"BLK":0,"TOV":2,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":29,"REB":4,"AST":1,"STL":1,"BLK":1,"TOV":4,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":27,"REB":4,"AST":6,"STL":1,"BLK":0,"TOV":1,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":24,"REB":4,"AST":2,"STL":2,"BLK":2,"TOV":0,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SG","teamAbb":"GSW","PTS":21,"REB":5,"AST":2,"STL":1,"BLK":1,"TOV":1,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":28,"REB":5,"AST":7,"STL":0,"BLK":0,"TOV":5,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":29,"REB":7,"AST":5,"STL":1,"BLK":1,"TOV":6,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":20,"REB":3,"AST":2,"STL":0,"BLK":0,"TOV":0,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":23,"REB":6,"AST":10,"STL":3,"BLK":0,"TOV":4,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":2,"AST":7,"STL":0,"BLK":4,"TOV":1,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":27,"REB":3,"AST":5,"STL":0,"BLK":0,"TOV":0,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":30,"REB":2,"AST":3,"STL":2,"BLK":0,"TOV":0,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":24,"REB":3,"AST":7,"STL":1,"BLK":0,"TOV":4,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":19,"REB":5,"AST":4,"STL":0,"BLK":0,"TOV":1,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":31,"REB":5,"AST":11,"STL":1,"BLK":0,"TOV":2,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":19,"REB":7,"AST":5,"STL":2,"BLK":1,"TOV":7,"X3PM":1,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":22,"REB":3,"AST":1,"STL":1,"BLK":0,"TOV":0,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":35,"REB":11,"AST":10,"STL":0,"BLK":2,"TOV":3,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":22,"REB":4,"AST":3,"STL":0,"BLK":0,"TOV":3,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":36,"REB":10,"AST":7,"STL":1,"BLK":5,"TOV":2,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":21,"REB":4,"AST":5,"STL":1,"BLK":0,"TOV":3,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":28,"REB":9,"AST":5,"STL":0,"BLK":3,"TOV":4,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":24,"REB":3,"AST":3,"STL":1,"BLK":0,"TOV":4,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":36,"REB":11,"AST":7,"STL":0,"BLK":2,"TOV":5,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":25,"REB":5,"AST":0,"STL":0,"BLK":0,"TOV":4,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":36,"REB":11,"AST":8,"STL":1,"BLK":3,"TOV":2,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":17,"REB":10,"AST":2,"STL":0,"BLK":0,"TOV":2,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":22,"REB":8,"AST":2,"STL":0,"BLK":2,"TOV":4,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":29,"REB":2,"AST":5,"STL":2,"BLK":0,"TOV":3,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":33,"REB":7,"AST":7,"STL":2,"BLK":4,"TOV":1,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":16,"REB":1,"AST":1,"STL":1,"BLK":2,"TOV":2,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":18,"REB":6,"AST":1,"STL":2,"BLK":2,"TOV":3,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":15,"REB":5,"AST":4,"STL":3,"BLK":0,"TOV":3,"X3PM":1,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":7,"AST":3,"STL":2,"BLK":5,"TOV":4,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":24,"REB":7,"AST":2,"STL":0,"BLK":0,"TOV":0,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":21,"REB":6,"AST":4,"STL":0,"BLK":3,"TOV":2,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":15,"REB":0,"AST":1,"STL":0,"BLK":1,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":27,"REB":4,"AST":6,"STL":0,"BLK":0,"TOV":3,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":24,"REB":3,"AST":2,"STL":0,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":38,"REB":4,"AST":3,"STL":0,"BLK":0,"TOV":0,"X3PM":10,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":20,"REB":5,"AST":9,"STL":1,"BLK":1,"TOV":1,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":21,"REB":0,"AST":5,"STL":0,"BLK":0,"TOV":3,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":32,"REB":5,"AST":8,"STL":1,"BLK":0,"TOV":3,"X3PM":6,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":12,"AST":6,"STL":0,"BLK":4,"TOV":2,"X3PM":1,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":25,"REB":5,"AST":4,"STL":2,"BLK":1,"TOV":1,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":29,"REB":9,"AST":5,"STL":1,"BLK":0,"TOV":3,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":28,"REB":3,"AST":4,"STL":0,"BLK":0,"TOV":1,"X3PM":6,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":45,"REB":6,"AST":3,"STL":3,"BLK":0,"TOV":2,"X3PM":8,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":10,"REB":4,"AST":3,"STL":1,"BLK":1,"TOV":2,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":32,"REB":4,"AST":9,"STL":2,"BLK":0,"TOV":6,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SG","teamAbb":"GSW","PTS":19,"REB":5,"AST":1,"STL":0,"BLK":2,"TOV":0,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"SF","teamAbb":"GSW","PTS":40,"REB":4,"AST":4,"STL":1,"BLK":1,"TOV":5,"X3PM":6,"fullName":"Kevin-Durant"},{"Pos":"SF","teamAbb":"GSW","PTS":26,"REB":6,"AST":6,"STL":2,"BLK":0,"TOV":3,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":12,"REB":5,"AST":2,"STL":0,"BLK":0,"TOV":2,"X3PM":0,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":24,"REB":6,"AST":9,"STL":1,"BLK":0,"TOV":1,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":25,"REB":6,"AST":5,"STL":0,"BLK":1,"TOV":1,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":26,"REB":3,"AST":0,"STL":2,"BLK":0,"TOV":0,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":23,"REB":4,"AST":8,"STL":2,"BLK":0,"TOV":3,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":32,"REB":5,"AST":8,"STL":3,"BLK":1,"TOV":3,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":17,"REB":4,"AST":2,"STL":1,"BLK":1,"TOV":2,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":30,"REB":9,"AST":4,"STL":1,"BLK":0,"TOV":6,"X3PM":6,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":19,"REB":8,"AST":7,"STL":1,"BLK":0,"TOV":0,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":38,"REB":4,"AST":2,"STL":0,"BLK":1,"TOV":3,"X3PM":7,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":19,"REB":1,"AST":8,"STL":2,"BLK":0,"TOV":6,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":26,"REB":7,"AST":5,"STL":0,"BLK":2,"TOV":4,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":8,"REB":1,"AST":1,"STL":2,"BLK":0,"TOV":1,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":32,"REB":6,"AST":7,"STL":0,"BLK":0,"TOV":1,"X3PM":8,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":14,"REB":4,"AST":14,"STL":2,"BLK":2,"TOV":5,"X3PM":2,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":9,"REB":2,"AST":4,"STL":0,"BLK":0,"TOV":0,"X3PM":0,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":25,"REB":5,"AST":9,"STL":3,"BLK":0,"TOV":2,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":28,"REB":10,"AST":11,"STL":2,"BLK":3,"TOV":4,"X3PM":6,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":25,"REB":5,"AST":3,"STL":1,"BLK":1,"TOV":3,"X3PM":7,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":49,"REB":4,"AST":5,"STL":2,"BLK":0,"TOV":1,"X3PM":8,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":20,"REB":9,"AST":2,"STL":1,"BLK":1,"TOV":2,"X3PM":1,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":7,"REB":4,"AST":3,"STL":1,"BLK":2,"TOV":1,"X3PM":0,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":14,"REB":2,"AST":5,"STL":0,"BLK":0,"TOV":3,"X3PM":1,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":17,"REB":2,"AST":3,"STL":1,"BLK":0,"TOV":0,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":27,"REB":6,"AST":3,"STL":2,"BLK":0,"TOV":3,"X3PM":3,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":23,"REB":5,"AST":6,"STL":2,"BLK":0,"TOV":6,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":33,"REB":4,"AST":6,"STL":0,"BLK":0,"TOV":5,"X3PM":6,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":20,"REB":6,"AST":3,"STL":0,"BLK":0,"TOV":1,"X3PM":4,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":24,"REB":6,"AST":4,"STL":2,"BLK":0,"TOV":5,"X3PM":5,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":31,"REB":6,"AST":7,"STL":0,"BLK":2,"TOV":1,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":16,"REB":1,"AST":5,"STL":2,"BLK":0,"TOV":0,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":21,"REB":5,"AST":5,"STL":1,"BLK":1,"TOV":3,"X3PM":2,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":33,"REB":6,"AST":1,"STL":1,"BLK":1,"TOV":5,"X3PM":3,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":12,"REB":4,"AST":2,"STL":1,"BLK":0,"TOV":3,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":20,"REB":7,"AST":8,"STL":0,"BLK":0,"TOV":4,"X3PM":4,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":24,"REB":9,"AST":4,"STL":1,"BLK":2,"TOV":1,"X3PM":4,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":18,"REB":5,"AST":4,"STL":0,"BLK":0,"TOV":3,"X3PM":2,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":17,"REB":4,"AST":8,"STL":2,"BLK":0,"TOV":2,"X3PM":3,"fullName":"Stephen-Curry"},{"Pos":"SF","teamAbb":"GSW","PTS":10,"REB":6,"AST":6,"STL":0,"BLK":2,"TOV":1,"X3PM":0,"fullName":"Kevin-Durant"},{"Pos":"SG","teamAbb":"GSW","PTS":25,"REB":3,"AST":0,"STL":0,"BLK":0,"TOV":3,"X3PM":5,"fullName":"Klay-Thompson"},{"Pos":"PG","teamAbb":"GSW","PTS":22,"REB":9,"AST":7,"STL":0,"BLK":1,"TOV":3,"X3PM":3,"fullName":"Stephen-Curry"}];
return data;
}
round(number, precision) {
var factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
updateButtonColors(button, parent, self) {
const {myLightGrey, myMidGrey, 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)
}
xScaleFunc(self) {
const {chartWidth, margin } = self.chartProps;
const statData = self.generateStatData();
// looks to be working correctly here
return d3.scaleLinear()
.domain(d3.extent(statData, stat => stat.AST)).nice()
.range([margin.left, chartWidth - margin.right]);
}
yScaleFunc(self, maxY) {
const { chartHeight, margin } = self.chartProps;
const statData = self.generateStatData();
return d3.scaleLinear()
.domain([0, maxY]).nice()
.range([chartHeight - margin.bottom, margin.top])
}
// Draw Graph Functions
drawHistogram() {
// 0. Setup | Scales and Other Constants
// =======================================
const { chartHeight, chartWidth, margin, myDarkGrey } = this.chartProps;
const { xScaleFunc, yScaleFunc } = this;
const xScale = xScaleFunc(this);
const statData = this.generateStatData()
const histogram = d3.histogram()
.domain(xScale.domain())
.thresholds(xScale.ticks(10))
// ======
// 1. Draw The Histogram For Each Guy
// ===================================
const histogramBars = d3.select('g.histogramBars');
const allPlayers = [... new Set(statData.map(row => row.fullName))]
// loop to find max Y for yScale
let maxY = 0;
for(var i = 0; i < allPlayers.length; i++) {
const playerStats = statData
.filter(row => row.fullName == allPlayers[i])
.map(row => row.AST)
const playerBinnedData = histogram(playerStats)
let thisYmax = d3.max(playerBinnedData, d => d.length)
maxY = (thisYmax > maxY) ? thisYmax : maxY;
}
const yScale = yScaleFunc(this, maxY);
// loop to create histogram for each player
const colors = ['yellow', 'red', 'blue'];
for(var i = 0; i < allPlayers.length; i++) {
const playerStats = statData
.filter(row => row.fullName == allPlayers[i])
.map(row => row.AST)
const playerBinnedData = histogram(playerStats)
const playerBars = histogramBars
.append("g")
.selectAll(`.rect-${allPlayers[i]}`)
.data(playerBinnedData)
.enter()
.append("rect")
.attr("class", `rect-${allPlayers[i]}`)
.attr("x", d => xScale(d.x0) + 1)
.attr("width", d => Math.max(0, xScale(d.x1) - xScale(d.x0) - 1))
.attr("y", d => yScale(d.length))
.attr("height", d => yScale(0) - yScale(d.length))
.attr("fill", colors[i])
.attr("stroke", myDarkGrey)
.attr("stroke-width", 2)
.attr("opacity", 0.5);
}
const xAxis = g => g
.attr("transform", `translate(0,${chartHeight - margin.bottom})`)
.call(d3.axisBottom(xScale).tickSizeOuter(0))
.call(g => g.append("text")
.attr("x", chartWidth - margin.right)
.attr("y", -4)
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text('X Axis Name Goes Here'))
histogramBars.append("g")
.call(xAxis);
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 4)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text('# of Games'))
histogramBars.append("g")
.call(yAxis);
}
drawNameHoverButtons() {
// Setup Constants
// ==================
const { myLightGrey, myMidGrey, myDarkGrey } = this.chartProps;
const { updateButtonColors } = this;
const bWidth= 75;
const bHeight= 25;
const bSpace= 5;
const statData = this.generateStatData()
const allPlayers = [... new Set(statData.map(row => row.fullName))]
const nameHoverButtons = d3.select('g.nameHoverButtons')
// ====
// Add buttons
// ==================
const self = this;
const buttonGroup = nameHoverButtons.selectAll("g.nameButton")
.data(allPlayers)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
buttonGroup.append("rect")
.attr("class","buttonRect")
.attr("width",bWidth)
.attr("height",bHeight)
.attr("x", (d,i) => 500 + (bWidth+bSpace)*i)
.attr("y", 0)
.attr("rx", 4) // rx and ry give the buttons rounded corners
.attr("ry", 4)
.attr("fill", myLightGrey)
.attr("stroke", myDarkGrey)
.attr("stroke-width", "0.1em")
// adding text to each toggle button group
buttonGroup.append("text")
.attr("class","buttonText")
.attr("font-family", "arial")
.attr("font-size", "0.6em")
.attr("x", (d,i) => 500 + (bWidth+bSpace)*i + bWidth/2)
.attr("y", 10)
.attr("text-anchor","middle")
.attr("dominant-baseline","central")
.attr("fill", '#222')
.text(d => d)
buttonGroup
.on("click", function(d,i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode), self)
self.setState({selectedName: allPlayers[i]})
})
.on("mouseover", function(d, i) {
if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
d3.select(this)
.select("rect")
.attr("fill", myMidGrey); // lol almost here keep trying
}
d3.selectAll(`g.histogramBars rect`)
.attr('opacity', 0.1)
d3.selectAll(`.rect-${allPlayers[i]}`)
.attr('z-index', 3)
.attr('opacity', 1)
})
.on("mouseout", function(d, i) {
if (d3.select(this).select("rect").attr("fill") != myDarkGrey) {
d3.select(this)
.select("rect")
.attr("fill", myLightGrey);
}
d3.selectAll(`g.histogramBars rect`)
.attr('opacity', 0.5)
d3.selectAll(`.rect-${allPlayers[i]}`)
.attr('z-index', 1)
.attr('opacity', 0.5)
})
}
drawStatButtons() {
}
// When component mounts, get
componentDidMount() {
const { chartHeight, chartWidth, svgID } = this.chartProps;
d3.select(`#${svgID}`)
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', "0 0 " + chartWidth + " " + chartHeight)
.attr('preserveAspectRatio', "xMaxYMax");
this.drawHistogram()
this.drawNameHoverButtons()
}
render() {
const { svgID } = this.chartProps;
return (
<div ref="scatter">
<svg id={svgID}>
<g className="histogramBars" />
<g className="nameHoverButtons" />
<g className="points" />
</svg>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<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://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.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'>
FUCKING WORK
</div>
My issue I think is a simple one, but hard to describe typing, but I will try.
The 3 main colors used in this graph are red, blue and yellow. However, if you look in column 3, you will see blue, darker blue, and purple. This is because of the order of my for loop, where the histogram for yellow is plotted first, then the histogram for red, then the histogram for blue bars. Since the blue bars are plotted last, the color with by far the most emphasis is blue, even though I've used an opacity of 0.5.
For example, if you were to change the line in my code where I create the color array, by reordering the colors, you'll see a new emphasis placed on whichever color is plotted last.
Instead of blue, dark blue, purple, I would like to see the yellow, red and blue in each bar column. If it is the case for a particular histogram column that yellow has the lowest y value (say y=3), then red (y=5), then blue (y=8), I would like the color of emphasis for the bottom part of the bar (0-3) to be yellow, for the middle part of the bar (3-5) to be red, and for the top part of the bar to be blue (5-8).
Please let me know if I can elaborate more on what I'm looking for. Otherwise, appreciate in advance any help I can get with this!
Related
D3 + React Tooltip Not Showing
I built a basic heatmap in html using d3, and the tooltips show properly. I've got the same code working in React, and the heatmap is working, and the mouseover, mousemove, and mouseleave functions are working (and the data is printing into the console accurately), but no matter what i try, I can't get the tooltip to actually show up. Any help would be appreciated Below is my code- import rd3 from 'react-d3-library'; import * as d3 from 'd3'; import React from 'react'; import data2 from '../data/hmap_current_year.csv'; const node = document.createElement('div'); // set the dimensions and margins of the graph const margin = { top: 30, right: 60, bottom: 29, left: 60 }, width = 1400 - margin.left - margin.right, height = 630 - margin.top - margin.bottom; // append the svg object to the body of the page const svg = d3 .select(node) .append("svg") //add mobile-friendly .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) // .attr("width", width + margin.left + margin.right) // .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); d3.csv(data2).then(function (data) { const var0 = data.map(function (d0) { return { month: d3.timeParse("%Y-%m-%d")(d0.time).getMonth(), day: d3.timeParse("%Y-%m-%d")(d0.time).getDate(), value: d0.tmed } }) const myGroups = Array.from({ length: 31 }, (_, i) => i + 1) const myMonths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] // Build X scales and axis: const x = d3.scaleBand() .range([0, width]) .domain(myGroups) .padding(0.01); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)) .call(g => g.select(".domain").remove()); svg.append("g") .call(d3.axisTop(x)) .call(g => g.select(".domain").remove()); // Build X scales and axis: const y = d3.scaleBand() .range([0, height]) .domain(myMonths) .padding(0.01); svg.append("g") .call(d3.axisLeft(y)) .call(g => g.select(".domain").remove()); svg.append("g") .call(d3.axisRight(y)) .attr("transform", `translate(${width}, 0)`) .call(g => g.select(".domain").remove()); // Build color scale const myColor = d3.scaleQuantile() .domain([0, 5, 20, 40, 60, 80, 95, 100]) .range([ "#08306B", "#2171B5", "#6BAED6", "#FFFFFF", "#FCBBA1", "#FA6A4A", "#CB181D" ]); // create a tooltip const tooltip = d3.select(node) .append("div") .style("opacity", 0) .attr("class", "tooltip") .style("background-color", "white") .style("border", "solid") .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") .style("margin-bottom", '51px') // Three function that change the tooltip when user hover / move / leave a cell const mouseover = function (event, d) { tooltip .style("opacity", 1) d3.select(this) .style("stroke", "black") .style("opacity", 1) } const mousemove = function (event, d) { tooltip .html("The exact value of<br>this cell is: " + d.value) //accessing values correctly console.log(d.value, 'HELLO') .style("left", (event.x) / 2 + "px") .style("top", (event.y) / 2 + "px") }; const mouseleave = function (event, d) { tooltip .style("opacity", 0) d3.select(this) .style("stroke", "none") .style("opacity", 1) } svg.selectAll() .data(var0) .join("rect") .attr("x", function (d) { return x(d.day) }) .attr("y", function (d) { return y(myMonths[d.month]) }) .attr("width", x.bandwidth()) .attr("height", y.bandwidth()) .style("fill", function (d) { return myColor(d.value) }) .style("stroke", "none") .on("mouseover", mouseover) .on("mousemove", mousemove) .on("mouseleave", mouseleave) }); const RD3Component = rd3.Component; class HeatmapInteractive extends React.Component { constructor(props) { super(props); this.state = {d3: ''} } componentDidMount() { this.setState({d3: node}); } render() { return ( <div> <RD3Component data={this.state.d3}/> </div> ) } } export default HeatmapInteractive;
here you have done perfectly for defining the tooltip initially const tooltip = d3.select(node) .append("div") .style("opacity", 0) .attr("class", "tooltip") .style("background-color", "white") .style("border", "solid") .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") .style("margin-bottom", '51px') but while hovering you dont have to select any node by using d3.select(this) in mouseover and mouseleave state you've written this const mouseover = function (event, d) { tooltip .style("opacity", 1) d3.select(this) .style("stroke", "black") .style("opacity", 1) } you can simply remove the d3.select(node) from chaining Insted of doing like that you can simple change only opacity in mouseover const mouseover = function(event, d) { tooltip.style("opacity", 1) } if it's mouseleave just reverse const mouseleave = function(event, d) { tooltip.style("opacity", 0) } if it's a mousemove then you have to add html and postion properties for dynamic float of tooltip according to the mouse values like Note one more thing dont use console in middle of chaining that may break the chaining, if you have tested with it then have to remove otherwise it won't work const mousemove = function (event, d) { tooltip .html("The exact value of<br>this cell is: " + d.value) .style("opacity", 1) .style("left", (event.x) + 20 + "px") .style("top", (event.y) + 20 + "px") }; i hope this answer will get clarifies you
D3 chart is duplicating on React.js component
This is on a react application, what might be going wrong? A new svg is appended everytime, instead of the one that exists updating... I'm not sure what other details I should add, I already included all the info that you may need to help me, please let me know if there's anything else I should add. Thank you. This is my component, I'm passing the data through props. export const FourDirectionsTimeChart = ({data}) => { useEffect(() => { const width = document.getElementById("container").clientWidth; const height = document.getElementById("container").clientHeight; const R = (width + height) / 8; const CX = width / 2; const CY = height / 2; const smallR = R * 0.1; const circleColor = "bisque"; const itemColor = "#3F4200"; let svg = d3.select("#container") .append("svg") .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewBox", `0 0 ${width} ${height}`) let mainCircle = svg.append("circle") .attr("fill", circleColor) .attr("r", R) .attr("cx", CX) .attr("cy", CY); let centerCircle = svg.append("circle") .attr("fill", "white") .attr("r", smallR) .attr("cx", CX) .attr("cy", CY); const timePercentage = (time) => { const percentage = (time * 100 / 23) / 100; return percentage; }; function timeToRadius(time) { return (smallR + timePercentage(time) * (R - smallR)) } // Times concentric circles --- for (let i = 0; i <= 23; i += 4) { svg.append("circle") .attr("fill", "none") .attr("cx", CX) .attr("cy", CY) .attr("stroke-dasharray", "4 20") .attr("stroke", "gray") .attr("stroke-width", 1) .attr("r", timeToRadius(i)) } // Cardinal points --- const textTime = 25; const fontSize = R * 0.25; svg.append("text") .attr("dx", getPosition(0, textTime).x) .attr("dy", getPosition(0, textTime).y) .text("N") .attr("fill", "black") .attr("font-size", fontSize) .attr("text-anchor", "middle") .style("font-family", "serif") svg.append("text") .attr("dx", getPosition(180, textTime).x) .attr("dy", getPosition(180, textTime).y) .text("S") .attr("fill", "black") .attr("font-size", fontSize) .attr("text-anchor", "middle") .style("font-family", "serif") .attr("alignment-baseline", "hanging") svg.append("text") .attr("dx", getPosition(-90, textTime).x) .attr("dy", getPosition(-90, textTime).y) .text("E") .attr("fill", "black") .attr("font-size", fontSize) .attr("text-anchor", "start") .attr("alignment-baseline", "middle") .style("font-family", "serif"); svg.append("text") .attr("dx", getPosition(90, textTime).x) .attr("dy", getPosition(90, textTime).y) .text("O") .attr("fill", "black") .attr("font-size", fontSize) .attr("text-anchor", "end") .attr("alignment-baseline", "middle") .style("font-family", "serif") // Ships positions --- function getPosition(degrees, time) { const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2; let x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX; let y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY; return { x, y }; } // Data mapping --- let parsedData = []; (() => { data?.forEach((x) => { parsedData.push({ id: x.Patente, course: x.Rumbo, speed: x.Velocidad, time: new Date(x.Fecha_gps).getHours() }) }); let position; parsedData.map(d => { position = getPosition(d.course, d.time); svg.append("circle") .attr("fill", () => { if (d.speed == 0) return "brown"; else return "brown"; }) .attr("r", R * 0.015) .attr("cx", position.x) .attr("cy", position.y) .attr("opacity", 0.4); }); })(); }, [data]); return ( <div id="container" style={{ width: '100%', height: '100%' }}></div> ) }
What do you think the line let svg = d3.select("#container").append("svg") does? It selects the element with ID "container" and appends a new SVG element to it. You can essentially do one of two things: Select and remove all existing SVG elements in the container and then draw the chart from scratch on update: d3.select("#container svg").remove(), then d3.select("#container").append("svg"); Split the initialization (append svg, set size, draw axes) logic from the logic that might need to be run multiple times (update the values) and make sure to call only the second part on update. This is more complex, but also efficient, especially in an already DOM-heavy ecosystem like React. I've taken your code and added an example of how I would implement the second way below: Note that to access R, smallR, etc, I've had to divide the initialization logic over two useEffect hooks. const { useEffect, useState } = React; const FourDirectionsTimeChart = () => { const [data, setData] = useState([]); const [constants, setConstants] = useState({}); // Initialization logic, run only once useEffect(() => { const width = document.getElementById("container").clientWidth; const height = document.getElementById("container").clientHeight; const R = (width + height) / 8; const CX = width / 2; const CY = height / 2; const smallR = R * 0.1; setConstants({ R, CX, CY, smallR }); const circleColor = "bisque"; const itemColor = "#3F4200"; const svg = d3.select("#container") .append("svg") .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewBox", `0 0 ${width} ${height}`); const mainCircle = svg.append("circle") .attr('id', 'mainCircle') .attr("fill", circleColor); const centerCircle = svg.append("circle") .attr('id', 'innerCircle') .attr("fill", "white"); // Times concentric circles --- for (let i = 0; i <= 23; i += 4) { svg.append("circle") .attr("class", "concentric") .datum(i) .attr("fill", "none") .attr("stroke-dasharray", "4 20") .attr("stroke", "gray") .attr("stroke-width", 1); } // Cardinal points --- svg.append("text") .attr("id", "N") .text("N") .attr("fill", "black") .attr("text-anchor", "middle") .style("font-family", "serif") svg.append("text") .attr("id", "S") .text("S") .attr("fill", "black") .attr("text-anchor", "middle") .style("font-family", "serif") .attr("alignment-baseline", "hanging") svg.append("text") .attr("id", "E") .text("E") .attr("fill", "black") .attr("text-anchor", "start") .attr("alignment-baseline", "middle") .style("font-family", "serif"); svg.append("text") .attr("id", "O") .text("O") .attr("fill", "black") .attr("text-anchor", "end") .attr("alignment-baseline", "middle") .style("font-family", "serif") }, []); // Drawing logic, run whenever `constants` changes (which is only once) useEffect(() => { const { R, smallR, CX, CY } = constants; const textTime = 25; const fontSize = R * 0.25; const svg = d3.select("#container svg"); const mainCircle = svg.select("#mainCircle") .attr("r", R) .attr("cx", CX) .attr("cy", CY); const centerCircle = svg.select("#innerCircle") .attr("r", smallR) .attr("cx", CX) .attr("cy", CY); // Times concentric circles --- function timeToRadius(time) { return (smallR + timePercentage(time) * (R - smallR)); } svg.selectAll(".concentric") .attr("cx", CX) .attr("cy", CY) .attr("r", d => timeToRadius(d)) // Cardinal points --- svg.select("text#N") .attr("font-size", fontSize) .attr("dx", getPosition(0, textTime).x) .attr("dy", getPosition(0, textTime).y); svg.select("text#S") .attr("font-size", fontSize) .attr("dx", getPosition(180, textTime).x) .attr("dy", getPosition(180, textTime).y); svg.select("text#E") .attr("font-size", fontSize) .attr("dx", getPosition(-90, textTime).x) .attr("dy", getPosition(-90, textTime).y); svg.select("text#O") .attr("font-size", fontSize) .attr("dx", getPosition(90, textTime).x) .attr("dy", getPosition(90, textTime).y); }, [constants]); const timePercentage = (time) => { const percentage = (time * 100 / 23) / 100; return percentage; }; function timeToRadius(time) { const { smallR, R } = constants; return (smallR + timePercentage(time) * (R - smallR)); } const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2; function getPosition(degrees, time) { const { smallR, R, CX, CY } = constants; const x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX; const y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY; return { x, y }; } useEffect(() => { const svg = d3.select('#container svg'); // Data mapping --- let position; const points = svg.selectAll('.point').data(data); // Remove old points points.exit().remove(); // Append and set constants only for new points points .enter() .append('circle') .attr('class', 'point') .attr("r", 15) .attr("opacity", 0.4) // Then, take both the new and the updated points .merge(points) .attr("cx", d => getPosition(d.course, d.time).x) .attr("cy", d => getPosition(d.course, d.time).y) .attr("fill", d => d.speed === 0 ? "brown" : "red"); }, [data]); useEffect(() => { let i = 0; setInterval(() => { setData(i % 2 === 0 ? data1 : data2); i++; }, 2000); }, []); return ( < div id = "container" style = { { width: '100%', height: '100%' } } > < /div> ) } const data1 = [{ id: 100, course: 35, speed: 0, time: 5 }]; const data2 = [{ id: 101, course: 80, speed: 25, time: 12 }, { id: 102, course: 256, speed: 0, time: 15 }]; ReactDOM.render( < FourDirectionsTimeChart / > , document.getElementById('root') ) <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script> <div id="root" style="width: 500px; height: 500px"></div>
x-axis horizontal line and y-axis labels are not displaying in D3 Grouped bar chart
I am creating grouped bar chart using D3 V5 in react.I am able to display y axis but not ticks and text.but in case of x-axis it's completely invisible. i have added d3.min.js to index.html file, but nothing works. any help is appreciated here I am attaching my code DrawChart = (data) => { var w = 450, h = 300, p = 100; var x0 = d3.scaleBand().range([0, w]).padding(0.4); var x1 = d3.scaleBand(); var y = d3.scaleLinear().range([h, 0]); var color = d3.scaleOrdinal().range(["#a85db3", "#95f578"]); const svg = d3.select("div#predicative") .append("svg").attr("width", w).attr("height", h) .attr("padding", p).style("margin-left", 30) .style("margin-top", 20).style("margin-bottom", 10); var ageNames = d3.keys(data[0]).filter(function (key) { return key !== "dept"; }); data.forEach(function (d) { d.ages = ageNames.map(function (name) { return { name: name, value: +d[name] }; }); }); x0.domain(data.map(function (d) { return d.dept; })); x1.domain(ageNames).rangeRound([0, x0.bandwidth()]); y.domain([0, (d3.max(data, function (d) { return d3.max(d.ages, function (d) { return d.value; }); })) + 10]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + h + ")") .call(d3.axisBottom(x0) .tickSize(-w, 0, 0) .tickFormat('')); svg.append("g") .attr("class", "y axis") .call(d3.axisLeft(y)) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Time");
svg:not(:root){ overflow: visible; }
d3js get data from multidimensional array
I'm working on a d3js donut chart and I'm trying to feed data in from a multidimensional array: fiddle Output of topHoldersArray: { "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166.54621424","percent":"30.9953%"}, "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"}, "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"} } With this array I get the error: Uncaught TypeError: Cannot read property 'startAngle' of undefined How can I feed this array into the graph? It seems the issue is that it's a multidimensional array but I"m not sure how to access it for the data points Here's a snippet illustrating the problem: var topHoldersArray = [ { "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166","percent":"30.9953%"}, "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"}, "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"} } ]; var data = topHoldersArray; var text = ""; var width = 260; var height = 260; var thickness = 40; var duration = 750; var radius = Math.min(width, height) / 2; var color = d3.scaleOrdinal(d3.schemeCategory20); var svg = d3.select("#topHoldersChart") .append('svg') .attr('class', 'pie') .attr('width', width) .attr('height', height); var g = svg.append('g') .attr('transform', 'translate(' + (width/2) + ',' + (height/2) + ')'); var arc = d3.arc() .innerRadius(radius - thickness) .outerRadius(radius); var pie = d3.pie() .value(function(d) { return d.amount; }) .sort(null); var path = g.selectAll('path') .data(pie(data)) .enter() .append("g") .on("mouseover", function(d) { let g = d3.select(this) .style("cursor", "pointer") .style("fill", "black") .append("g") .attr("class", "text-group"); g.append("text") .attr("class", "name-text") .text(`${d.data.address}`) .attr('text-anchor', 'middle') .attr('dy', '-1.2em'); g.append("text") .attr("class", "value-text") .text(`${d.data.amount}`) .attr('text-anchor', 'middle') .attr('dy', '.6em'); }) .on("mouseout", function(d) { d3.select(this) .style("cursor", "none") .style("fill", color(this._current)) .select(".text-group").remove(); }) .append('path') .attr('d', arc) .attr('fill', (d,i) => color(i)) .on("mouseover", function(d) { d3.select(this) .style("cursor", "pointer") .style("fill", "black"); }) .on("mouseout", function(d) { d3.select(this) .style("cursor", "none") .style("fill", color(this._current)); }) .each(function(d, i) { this._current = i; }); g.append('text') .attr('text-anchor', 'middle') .attr('dy', '.35em') .text(text); .pie { margin: 20px; } .pie text { font-family: "Verdana"; fill: #888; } .pie .name-text{ font-size: 1em; } .pie .value-text{ font-size: 3em; } <div class="token-chart"> <h6>Top Holders</h6> <div class="chart" id="topHoldersChart"></div> </div> <script src="https://d3js.org/d3.v4.min.js"></script>
Let's look at your d3.pie layout: var pie = d3.pie() .value(function(d) { return d.amount; }) .sort(null); When we feed data to this (pie(data)), pie is expecting an array. But you are providing an object: { "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166.54621424","percent":"30.9953%"}, "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"}, "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"} } We need to convert this to an array to feed it to d3.pie(). For this we could use d3.entries() (though there are other ways to achieve this too). d3.entries() takes an object, say: { a: value1, b: value2 } And converts it to an array: [ { key: "a", value: value1 }, {key: "b", value: value2 } ] The values are now within a property called value. This requires us to look up the amount at d.value.amount. For example: var topHoldersArray = { "1":{"address":"0xd35a2d8c651f3eba4f0a044db961b5b0ccf68a2d","amount":"309953166","percent":"30.9953%"}, "2":{"address":"0xe17c20292b2f1b0ff887dc32a73c259fae25f03b","amount":"200000001","percent":"20.0000%"}, "3":{"address":"0x0000000000000000000000000000000000000000","amount":"129336426","percent":"12.9336%"} }; var data = d3.entries(topHoldersArray); var text = ""; var width = 260; var height = 260; var thickness = 40; var duration = 750; var radius = Math.min(width, height) / 2; var color = d3.scaleOrdinal(d3.schemeCategory20); var svg = d3.select("#topHoldersChart") .append('svg') .attr('class', 'pie') .attr('width', width) .attr('height', height); var g = svg.append('g') .attr('transform', 'translate(' + (width/2) + ',' + (height/2) + ')'); var arc = d3.arc() .innerRadius(radius - thickness) .outerRadius(radius); var pie = d3.pie() .value(function(d) { return d.value.amount; }) .sort(null); var path = g.selectAll('path') .data(pie(data)) .enter() .append("g") .on("mouseover", function(d) { let g = d3.select(this) .style("cursor", "pointer") .style("fill", "black") .append("g") .attr("class", "text-group"); g.append("text") .attr("class", "name-text") .text(`${d.data.value.address}`) .attr('text-anchor', 'middle') .attr('dy', '-1.2em') g.append("text") .attr("class", "value-text") .text(`${d.data.value.amount}`) .attr('text-anchor', 'middle') .attr('dy', '.6em') }) .on("mouseout", function(d) { d3.select(this) .style("cursor", "none") .style("fill", color(this._current)) .select(".text-group").remove(); }) .append('path') .attr('d', arc) .attr('fill', (d,i) => color(i)) .on("mouseover", function(d) { d3.select(this) .style("cursor", "pointer") .style("fill", "black"); }) .on("mouseout", function(d) { d3.select(this) .style("cursor", "none") .style("fill", color(this._current)); }) .each(function(d, i) { this._current = i; }); g.append('text') .attr('text-anchor', 'middle') .attr('dy', '.35em') .text(text); .pie { margin: 20px; } .pie text { font-family: "Verdana"; fill: #888; } .pie .name-text{ font-size: 1em; } .pie .value-text{ font-size: 3em; } <div class="token-chart"> <h6>Top Holders</h6> <div class="chart" id="topHoldersChart"></div> </div> <script src="https://d3js.org/d3.v4.min.js"></script>
In D3 - sync'ing up two sets of radio buttons and updating React State
First let me share an example of the code that will very clearly and easily highlight the issue I'm currently having: 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> I am struggling with having the 2 D3 radio buttun groups working hand in hand with one another here. What should happen is: clicking on any of the 6 divisions will display the (correct) list of 5 teams in that baseball division, and will highlight the division. clicking on a new division will display 5 new teams, and highlight the new division. clicking on a team will highlight the team. The current issue I am facing is that: The list of teams does not appear until the 2nd time I click on a division name, The team names do not highlight. I think this is neat, clean radio button code in React, with a simple and useful interactivity between button groups (especially here with teams and divisions, which I need for a baseball app). Fixing why the buttons arent working correctly (and knowing what is wrong with my code), will help me a ton! Thanks in advance for the help.
Quickly fixed it, seems to be working for me now, let me know if it's what you're looking for! By the way in the long term I would recommend maintaining state either totally in React (where componentDidUpdate() is responsible for making updating the styling) or totally in d3 (meaning you just render the SVG and React doesn't touch it after, the division button click handler calls drawOppTeam() instead of componentDidUpdate()). I think the reason this issue was tricky was that state handling was shared between the two libraries. 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>