var StackedAreaChart = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State, SVGChartMixin, ChartDataMixin],
canvasHeight: 350,
xAxisHeight: 20,
maxItems: 10,
maxWeeks: 30,
words: {
actions: { // Item
repo: "made to",
team: "made by the most active",
user: "made by the most active"
},
items: { // Item
repo: "repositories",
team: "teams",
user: "users"
},
whatHappened: { // Item-Target
"user-repo": "working on",
"team-repo": "working on",
"team-org": "to repositories of",
"user-org": "to repositories of",
"repo-org": "that were most actively modified by the members of",
"user-team": "working on repositories of the",
"repo-team": "that were most actively modified by the members of the",
"repo-user": "that were most actively modified by"
},
targetSuffix: { // Subject of current context
repo: "repository",
team: "team"
},
},
getInitialState: function() {
return {
item: this.props.items[0],
rawData: [],
topItems: [],
weeklyData: [],
maxCommitsPerWeek: 1
};
},
componentDidMount: function() {
this.calculateViewBoxWidth();
window.addEventListener('resize', this.calculateViewBoxWidth);
},
componentWillReceiveProps: function(newProps) {
// If new items are the same as old then don't reset current item
this.setState({
item: (_.isEqual(newProps.items, this.props.items)
? this.state.item
: newProps.items[0]),
state: 'loadingData'
}, this.fetchData);
},
shouldComponentUpdate: function(newProps, newState) {
// Don't re-render unless canvas width is calculated
if (!newState.canvasWidth) {
return false;
}
// We're working with animations here so we render only in one particular state
if (newState.state !== 'pleaseRender') {
return false;
}
return true;
},
handleFilter: function(thing, i) {
if (this.props.items[i] !== this.state.item) {
this.setState({
item: this.props.items[i],
state: 'loadingData'
}, this.fetchData);
}
},
handleClick: function(item) {
var params = {org: this.getParams().org};
params[this.state.item] = item;
this.transitionTo(this.state.item, params, this.getQuery());
},
handleFocusIn: function(i) {
var node = this.refs.container.getDOMNode();
node.className = 'sac focused item-'+ i;
},
handleFocusOut: function() {
var node = this.refs.container.getDOMNode();
node.className = 'sac';
},
handleNewData: function() {
// [week, ...]
var weeksList = _(this.state.rawData).pluck('week').uniq().sort().reverse().take(this.maxWeeks).value();
if (weeksList.length < 2) {
this.setState({
weeks: [],
state: 'pleaseRender'
});
return;
}
// {item: commits, ...}
var commitsByItem = _.reduce(this.state.rawData, function(res, el) {
if (weeksList.indexOf(el.week) === -1) {
return res;
}
if (res[el.item] === undefined) {
res[el.item] = el.commits;
} else {
res[el.item] += el.commits;
}
return res;
}, {});
// [item, ...]
var topItems = _(_.pairs(commitsByItem)) // Take [item, count] pairs from counts object
.sortBy(1).reverse() // sort them by count (descending)
.take(this.maxItems) // take first N pairs
.pluck(0) // keep only items, omit the counts
.value();
for (var i = topItems.length; i < this.maxItems; i++) {
topItems[i] = null;
};
// {week: {item: commits, ...}, ...}
var weeklyData = _.reduce(this.state.rawData, function(res, el) {
if (weeksList.indexOf(el.week) === -1) {
return res;
}
if (res[el.week] === undefined) {
res[el.week] = {};
}
if (topItems.indexOf(el.item) > -1) {
res[el.week][el.item] = el.commits;
}
return res;
}, {});
var maxCommitsPerWeek = _.max(_.map(weeksList, function(week) {
return _.sum(_.values(weeklyData[week]));
}));
this.setState({
topItems: topItems,
weeklyData: weeklyData,
maxCommitsPerWeek: maxCommitsPerWeek,
state: 'pleaseRender'
});
},
buildPathD: function(dots) {
var maxWidth = this.state.canvasWidth,
maxHeight = this.canvasHeight;
var dots = this.extendDotsWithCoordinates(dots);
var first = dots.shift(); // Don't draw a line to the first dot, it should be a move
var d = _.map(dots, function(dot){ return 'L'+ dot.x +','+ dot.y; });
d.unshift('M'+ first.x +','+ first.y); // Prepend first move
d.push('L'+ maxWidth +','+ maxHeight); // Draw a line to the bottom right corner
d.push('L0,'+ maxHeight +' Z'); // And then to a bottom left corner
return d.join(' ');
},
extendDotsWithCoordinates: function(dots) {
var maxWidth = this.state.canvasWidth,
maxHeight = this.canvasHeight,
maxValue = this.state.maxCommitsPerWeek,
len = dots.length;
return _.map(dots, function(dot, i) {
dot.x = i/(len-1)*maxWidth;
dot.y = maxHeight - dot.norm*maxHeight*0.96;
return dot;
});
},
render: function() {
var renderArea = function(pair, i) {
var item = pair[0], path = pair[1];
return (