This is a six part series, taking you through stages of designing and creating reusable visualizations with d3.js
All visualizations have the same functionality, showcase the individual points with a bar chart and sum up the selected bars.
Part 6: This is showcasing the power of combining react.js (& flux) with d3.js and how to use them together creating an abstraction between the visualization (the bar chart) and the rest of the application.
This new example was created to compare React to the other concepts. The best part of working with react.js is that it really requires you to build applications without global state.
Cheers,
Miles @milr0c
xxxxxxxxxx
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="https://littlesparkvt.com/flatstrap/assets/css/bootstrap.css"/>
<link rel="stylesheet" type="text/css" href="style.css"/>
<script src="react.min.js"></script>
<script src="JSXTransformer.js"></script>
<script src="es6-promise.min.js"></script>
<script src="EventEmitter.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="src.js"></script>
<script src="flux.js"></script>
</head>
<body>
<script type="text/jsx">
/** @jsx React.DOM */
// Models (Stores in Flux terminology)
var ChartStore = (function() {
var _data = randomize(),
_sum = 0;
var ChartStore = function() {};
ChartStore.prototype = new EventEmitter();
ChartStore.prototype.getData = function() {
return _data;
};
ChartStore.prototype.getSum = function() {
return _sum;
};
ChartStore.prototype.emitChange = function(eventType) {
this.emit(eventType);
};
ChartStore.prototype.addChangeListener = function(eventType, callback) {
this.on(eventType, callback);
};
ChartStore.prototype.removeChangeListener = function(eventType, callback) {
this.removeListener(eventType, callback);
};
var exports = new ChartStore(); // a bit of a hack
function updateData() {
_data = randomize();
_sum = 0;
}
function updateSum(extent, x) {
_sum = _data.filter(function(d) {
return extent[0] <= x(d.x) && x(d.x) + x.rangeBand() <= extent[1];
})
.reduce(function(a, b) {
return a + b.y;
}, 0);
}
AppDispatcher.register(function(payload) {
var action = payload.action;
if (action.actionType === 'update') {
updateData();
exports.emitChange('range');
} else if (action.actionType === 'range') {
updateSum(action.extent, action.x);
} else {
return true;
}
// A bit convoluted, basically the Stores events and the actions are congruent
exports.emitChange(action.actionType);
return true; // No errors. Needed by promise in Dispatcher.
});
return exports;
})();
// you cannot add methods to a React Class that are 'objects' (chainable closures)
// function () {
// return method.apply(component, arguments);
// }
// figure out scope
var bar = charts.bar();
var RxBarChart = React.createClass({
render: function() {
return (
<div></div>
);
},
getInitialState: function() {
return { data: ChartStore.getData() };
},
componentDidMount: function() {
// bind events
ChartStore.addChangeListener('update', this._updateData);
this._bindBrush();
this.d3Render();
},
shouldComponentUpdate: function() {
return false;
},
_bindBrush: function() {
bar
.on('brush', this._updateRange)
.on('brushend', this._updateRange);
},
_updateData: function() {
// you have to manage re-rendering
this.setState({ data: ChartStore.getData() }, this.d3Render);
},
_updateRange: function() {
ChartActions.range(d3.event.target.extent(), bar.x());
},
d3Render: function() {
// properties mirroring
bar.width(this.props.width || 300)
.height(this.props.height || 300)
// rendering
d3.select(this.getDOMNode())
.datum(this.state.data)
.call(bar);
}
});
var SumTotal = React.createClass({
render: function() {
return (
<div className="span2">
TOTAL: {this.state.sum}
</div>
);
},
getInitialState: function() {
return {
sum: ChartStore.getSum()
};
},
componentDidMount: function() {
ChartStore.addChangeListener('range', this._onChange);
},
componentWillUnmount: function() {
ChartStore.removeChangeListener('range', this._onChange);
},
_onChange: function() {
this.setState({ sum: ChartStore.getSum() });
}
})
var app = React.createClass({
render: function() {
return (
<div className="app">
<div className="row">
<div className="span2">
<button className="btn btn-success" onClick={this._onUpdateClick}>update</button>
</div>
<SumTotal />
</div>
<RxBarChart width={230*4} height={120*4-40} />
</div>
);
},
getInitialState: function() {
return {
data: this.props.data || [],
sum: 0,
resetBrush: false
};
},
_onUpdateClick: function() {
ChartActions.update();
},
});
React.renderComponent(
app({data: randomize() }),
document.body
);
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js