2015-03-13 12:08:13 +00:00
|
|
|
var BarChart = React.createClass({
|
|
|
|
mixins: [ReactRouter.Navigation, ReactRouter.State, SVGChartMixin, ChartDataMixin],
|
|
|
|
|
|
|
|
sorts: ['commits', 'delta'],
|
|
|
|
numElements: 15,
|
|
|
|
barHeight: 30,
|
|
|
|
barMargin: 5,
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
|
|
|
item: this.props.items[0],
|
|
|
|
sort: 'commits',
|
|
|
|
rawData: [],
|
|
|
|
points: [],
|
|
|
|
min: 0,
|
|
|
|
max: 1
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
this.calculateViewBoxWidth();
|
|
|
|
window.addEventListener('resize', this.calculateViewBoxWidth);
|
|
|
|
this.componentWillReceiveProps(this.props);
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillReceiveProps: function(newProps) {
|
|
|
|
this.setState({
|
|
|
|
item: newProps.items[0],
|
|
|
|
state: 'newProps'
|
|
|
|
}, this.fetchData);
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldComponentUpdate: function(newProps, newState) {
|
|
|
|
if (!newState.canvasWidth) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (newState.state !== 'newPoints') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
handleFilter: function(thing, i) {
|
|
|
|
if (thing === 'item' && this.props.items[i] !== this.state.item) {
|
|
|
|
this.setState({
|
|
|
|
item: this.props.items[i],
|
|
|
|
state: 'newProps'
|
|
|
|
}, this.fetchData);
|
|
|
|
} else if (thing === 'sort' && this.sorts[i] !== this.state.sort) {
|
|
|
|
this.setState({
|
|
|
|
sort: this.sorts[i],
|
|
|
|
state: 'newProps'
|
|
|
|
}, this.handleNewData);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleClick: function(point) {
|
|
|
|
var params = {org: this.getParams().org};
|
|
|
|
params[this.state.item] = point.item;
|
2015-03-15 12:10:02 +00:00
|
|
|
this.transitionTo(this.state.item, params, this.getQuery());
|
2015-03-13 12:08:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleNewData: function() {
|
|
|
|
var min = 0, max = 1;
|
|
|
|
var points = _.chain(this.state.rawData)
|
|
|
|
.sort(function(a, b) {
|
|
|
|
return Math.abs(b[this.state.sort]) - Math.abs(a[this.state.sort]);
|
|
|
|
}.bind(this))
|
|
|
|
.take(this.numElements)
|
|
|
|
.value();
|
|
|
|
for (var i = points.length; i < this.numElements; i++) {
|
|
|
|
var point = {};
|
|
|
|
point[this.state.sort] = 0;
|
|
|
|
points.push(point);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
points: points,
|
|
|
|
min: _.min(points, this.state.sort)[this.state.sort],
|
|
|
|
max: _.max(points, this.state.sort)[this.state.sort],
|
|
|
|
state: 'newPoints'
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
height: function() {
|
|
|
|
if (this.state.points.length === 0) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return this.y(this.state.points.length) - this.barMargin;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
y: function(i) {
|
|
|
|
return i*(this.barHeight + this.barMargin);
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
2015-03-14 10:13:28 +00:00
|
|
|
var words = {
|
|
|
|
items: {
|
|
|
|
repo: 'repositories',
|
|
|
|
team: 'teams',
|
|
|
|
user: 'contributors'
|
|
|
|
},
|
|
|
|
item: {
|
|
|
|
repo: 'repository',
|
2015-03-14 18:59:52 +00:00
|
|
|
team: 'team'
|
2015-03-14 10:13:28 +00:00
|
|
|
},
|
|
|
|
actions: {
|
|
|
|
repo: 'which were the most attended by',
|
|
|
|
team: 'which were the most active working on',
|
|
|
|
user: 'which were the most active working on'
|
|
|
|
}
|
|
|
|
},
|
2015-03-14 15:54:27 +00:00
|
|
|
who = this.getParams().repo || this.getParams().team || this.getParams().user || this.getParams().org;
|
|
|
|
|
|
|
|
var params = Object.keys(this.getParams());
|
2015-03-14 10:13:28 +00:00
|
|
|
params.splice(params.indexOf('org'), 1);
|
2015-03-14 15:54:27 +00:00
|
|
|
var subject = params[0];
|
|
|
|
|
2015-03-13 12:08:13 +00:00
|
|
|
return (
|
|
|
|
<div className="barchart-container">
|
2015-03-14 10:13:28 +00:00
|
|
|
<div className="whatsgoingon">
|
2015-03-16 11:20:32 +00:00
|
|
|
This bar chart represents <em>{words.items[this.state.item]}</em> {words.actions[this.state.item]} <em>{who}</em> {words.item[subject]} <WeekIntervalSelector />
|
2015-03-14 10:13:28 +00:00
|
|
|
</div>
|
2015-03-13 12:08:13 +00:00
|
|
|
<div className="filters">
|
|
|
|
<Selector thing="sort"
|
2015-03-16 08:36:15 +00:00
|
|
|
title="Show"
|
2015-03-13 12:08:13 +00:00
|
|
|
items={this.sorts}
|
|
|
|
value={this.state.sort}
|
|
|
|
onChange={this.handleFilter.bind(this, 'sort')} />
|
2015-03-16 08:36:15 +00:00
|
|
|
<Selector thing="item"
|
|
|
|
title="Grouped by"
|
|
|
|
items={this.props.items}
|
|
|
|
value={this.state.item}
|
|
|
|
onChange={this.handleFilter.bind(this, 'item')} />
|
2015-03-13 12:08:13 +00:00
|
|
|
</div>
|
|
|
|
<svg ref="svg" className="barchart" key="barchart-svg"
|
|
|
|
width="100%" height={this.height()}
|
|
|
|
viewBox={"0 0 "+ (this.state.canvasWidth || 0) + " "+ this.height()}>
|
|
|
|
{this.state.points.map(this.renderBar)}
|
|
|
|
</svg>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
renderBar: function(point, i) {
|
|
|
|
var maxWidth = this.state.canvasWidth,
|
|
|
|
val = point[this.state.sort],
|
|
|
|
min = this.state.min,
|
|
|
|
max = this.state.max,
|
|
|
|
max2 = (min < 0 ? max - min : max),
|
|
|
|
width = Math.floor(Math.abs(val)/max2*maxWidth),
|
|
|
|
height = this.barHeight,
|
|
|
|
offset = (min < 0 ? -min : 0)/max2*maxWidth,
|
|
|
|
x = (min >= 0 ? 0 : offset - (val >= 0 ? 0 : width)),
|
|
|
|
y = this.y(i);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Bar key={'bar-'+ i}
|
|
|
|
item={point.item}
|
|
|
|
value={val}
|
2015-03-16 07:30:06 +00:00
|
|
|
color={Colors[i]}
|
2015-03-13 12:08:13 +00:00
|
|
|
x={x}
|
|
|
|
y={y}
|
|
|
|
offset={offset}
|
|
|
|
width={width}
|
|
|
|
height={height}
|
|
|
|
max={maxWidth}
|
|
|
|
onClick={this.handleClick.bind(this, point)} />
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var Bar = React.createClass({
|
|
|
|
mixins: [ReactRouter.Navigation, ChartAnimationMixin],
|
|
|
|
|
|
|
|
height: 30,
|
|
|
|
labelPaddingH: 5, // Label horizontal padding
|
|
|
|
labelPaddingV: 2, // Label vertical padding
|
|
|
|
labelMarginV: 5, // Same as padding
|
|
|
|
labelHeight: 16, // Text size
|
|
|
|
labelOuterHeight: 20, // labelHeight + 2*labelPaddingV,
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
|
|
|
labelX: 0,
|
2015-03-16 07:43:20 +00:00
|
|
|
lastLabelX: this.labelPaddingH
|
2015-03-13 12:08:13 +00:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
this.calculateLabelPosition();
|
|
|
|
},
|
|
|
|
|
|
|
|
componentWillReceiveProps: function(newProps) {
|
|
|
|
if (_.isEqual(this.props, newProps)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
lastBarX: (this.props.x !== undefined ? this.props.x : newProps.x),
|
|
|
|
lastBarWidth: (this.props.width !== undefined ? this.props.width : newProps.width),
|
|
|
|
lastLabelX: this.state.labelX
|
|
|
|
}, this.calculateLabelPosition);
|
|
|
|
},
|
|
|
|
|
|
|
|
calculateLabelPosition: function() {
|
|
|
|
var val = this.props.value,
|
|
|
|
offset = this.props.offset,
|
2015-03-14 08:53:53 +00:00
|
|
|
label = this.props.item + ': ' + numberFormat(val),
|
2015-03-13 12:08:13 +00:00
|
|
|
labelWidth = textWidth(label),
|
2015-03-16 07:43:20 +00:00
|
|
|
labelOffsetWidth = labelWidth + 2*this.labelPaddingH,
|
2015-03-14 08:19:09 +00:00
|
|
|
labelX;
|
2015-03-13 12:08:13 +00:00
|
|
|
|
|
|
|
if (offset === 0) {
|
2015-03-16 07:43:20 +00:00
|
|
|
labelX = this.labelPaddingH;
|
2015-03-13 12:08:13 +00:00
|
|
|
} else {
|
|
|
|
if (val < 0) {
|
|
|
|
if (offset >= labelOffsetWidth) {
|
2015-03-16 07:43:20 +00:00
|
|
|
labelX = offset - labelOffsetWidth + this.labelPaddingH;
|
2015-03-13 12:08:13 +00:00
|
|
|
} else {
|
2015-03-16 07:43:20 +00:00
|
|
|
labelX = offset + this.labelPaddingH;
|
2015-03-13 12:08:13 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (offset + labelOffsetWidth <= this.props.max) {
|
2015-03-16 07:43:20 +00:00
|
|
|
labelX = offset + this.labelPaddingH;
|
2015-03-13 12:08:13 +00:00
|
|
|
} else {
|
2015-03-16 07:43:20 +00:00
|
|
|
labelX = offset - labelOffsetWidth + this.labelPaddingH;
|
2015-03-13 12:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
2015-03-14 16:19:25 +00:00
|
|
|
labelX: labelX,
|
|
|
|
barWidth: (this.props.item && this.props.width < 5 ? 5 : this.props.width)
|
2015-03-13 12:08:13 +00:00
|
|
|
}, this.animateAll);
|
|
|
|
},
|
|
|
|
|
|
|
|
animateAll: function() {
|
2015-03-16 07:43:20 +00:00
|
|
|
this.clearAnimations(this.refs.bar);
|
|
|
|
this.clearAnimations(this.refs.label);
|
|
|
|
this.animate(this.refs.bar, 'width', this.state.lastBarWidth, this.state.barWidth);
|
|
|
|
this.animate(this.refs.bar, 'x', this.state.lastBarX, this.props.x);
|
|
|
|
this.animate(this.refs.label, 'x', this.state.lastLabelX, this.state.labelX);
|
2015-03-13 12:08:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
2015-03-14 08:53:53 +00:00
|
|
|
var label = this.props.item ? (this.props.item + ': ' + numberFormat(this.props.value)) : '',
|
2015-03-13 12:08:13 +00:00
|
|
|
labelWidth = textWidth(label),
|
2015-03-14 08:19:09 +00:00
|
|
|
labelOuterWidth = (labelWidth == 0 ? 0 : labelWidth + 2*this.labelPaddingH),
|
|
|
|
barX = (this.state.lastBarX && this.state.lastBarX !== this.props.x
|
|
|
|
? this.state.lastBarX
|
|
|
|
: this.props.x),
|
2015-03-14 16:19:25 +00:00
|
|
|
barWidth = (this.state.lastBarWidth && this.state.lastBarWidth !== this.state.barWidth
|
2015-03-14 08:19:09 +00:00
|
|
|
? this.state.lastBarWidth
|
2015-03-14 16:19:25 +00:00
|
|
|
: this.state.barWidth);
|
2015-03-13 12:08:13 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<g onClick={this.props.onClick}>
|
2015-03-16 07:43:20 +00:00
|
|
|
<rect ref="bar" className="bar"
|
2015-03-13 12:08:13 +00:00
|
|
|
fill={this.props.color}
|
2015-03-14 08:19:09 +00:00
|
|
|
width={barWidth}
|
2015-03-13 12:08:13 +00:00
|
|
|
height={this.props.height}
|
2015-03-14 08:19:09 +00:00
|
|
|
x={barX}
|
2015-03-13 12:08:13 +00:00
|
|
|
y={this.props.y}
|
|
|
|
rx="2"
|
|
|
|
ry="2" />
|
2015-03-16 07:43:20 +00:00
|
|
|
<text ref="label" className="label"
|
2015-03-14 08:53:53 +00:00
|
|
|
x={this.state.labelX}
|
2015-03-16 07:43:20 +00:00
|
|
|
y={this.props.y + this.labelMarginV + this.labelHeight}>
|
|
|
|
{label}
|
2015-03-14 08:53:53 +00:00
|
|
|
</text>
|
2015-03-13 12:08:13 +00:00
|
|
|
</g>
|
|
|
|
);
|
|
|
|
}
|
2015-03-16 07:43:20 +00:00
|
|
|
});
|