1
0
Fork 0

Same layout for each dashboard + barchart animations

This commit is contained in:
Gregory Eremin 2015-03-12 03:18:14 +07:00
parent f04db2401f
commit 790156f28d
4 changed files with 161 additions and 113 deletions

View File

@ -115,88 +115,49 @@ var Org = React.createClass({
} }
}); });
var OrgStats = React.createClass({ var Dashboard = React.createClass({
mixins: [Router.State], mixins: [Router.State],
render: function(){ render: function(){
var org = Storage.get('org', this.getParams().org); var p = this.getParams(),
infoImage, infoTitle, infoText,
bcApi, bcItems,
sacApi, sacItems;
if (p.team) {
infoTitle = p.team;
bcApi = '/api/stat/teams/top';
bcItems = ['repo', 'user'],
sacApi = '/api/stat/teams/activity';
sacItems = ['user', 'repo'];
} else if (p.user) {
infoTitle = p.user;
bcApi = '/api/stat/users/top';
bcItems = ['repo'],
sacApi = '/api/stat/users/activity';
sacItems = ['repo'];
} else if (p.repo) {
infoTitle = p.repo;
bcApi = '/api/stat/repos/top';
bcItems = ['user', 'team'],
sacApi = '/api/stat/repos/activity';
sacItems = ['user', 'team'];
} else {
var info = Storage.get('org', p.org);
infoImage = info.avatar_url;
infoTitle = info.login;
infoText = info.descr;
bcApi = '/api/stat/orgs/top';
bcItems = ['repo', 'team', 'user'],
sacApi = '/api/stat/orgs/activity';
sacItems = ['team', 'user', 'repo'];
}
return ( return (
<section className="content"> <section className="content">
<InfoBlock key={'info-block-org-'+ this.getParams().org} <InfoBlock image={infoImage} title={infoTitle} text={infoText} />
image={org.avatar_url} <BarChart api={bcApi} params={this.getParams()} items={bcItems} />
title={org.login} <StackedAreaChart api={sacApi} params={this.getParams()} items={sacItems} />
text={org.descr} />
<BarChart key={'bar-chart-'+ this.getParams().org}
api="/api/stat/orgs/top"
params={this.getParams()}
items={["repo", "team", "user"]} />
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
api="/api/stat/orgs/activity"
params={this.getParams()}
items={["repo", "team", "user"]} />
</section>
);
}
});
var TeamStats = React.createClass({
mixins: [Router.State],
render: function(){
return (
<section className="content">
<InfoBlock key={"info-block-team-"+ this.getParams().team}
image="https://media.licdn.com/mpr/mpr/p/8/005/058/14b/0088c48.jpg"
title={this.getParams().team}
text={"The most awesome team in "+ this.getParams().org} />
<BarChart key={'bar-chart-team-'+ this.getParams().team}
api="/api/stat/teams/top"
params={this.getParams()}
items={["repo", "user"]} />
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
api="/api/stat/teams/activity"
params={this.getParams()}
items={["repo", "user"]} />
</section>
);
}
});
var UserStats = React.createClass({
mixins: [Router.State],
render: function(){
return (
<section className="content">
<InfoBlock key={'info-block-user-'+ this.getParams().user}
title={this.getParams().user} />
<BarChart key={'bar-chart-user-'+ this.getParams().user}
api="/api/stat/users/top"
params={this.getParams()}
items={["repo"]} />
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
api="/api/stat/users/activity"
params={this.getParams()}
items={["repo"]} />
</section>
);
}
});
var RepoStats = React.createClass({
mixins: [Router.State],
render: function(){
return (
<section className="content">
<InfoBlock key={'info-block-repo'+ this.getParams().repo}
title={this.getParams().repo} />
<BarChart key={'bar-chart-repo-'+ this.getParams().team}
api="/api/stat/repos/top"
params={this.getParams()}
items={["user", "team"]} />
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
api="/api/stat/repos/activity"
params={this.getParams()}
items={["user", "team"]} />
</section> </section>
); );
} }
@ -236,10 +197,10 @@ var routes = [
<Router.DefaultRoute handler={SelectOrg} /> <Router.DefaultRoute handler={SelectOrg} />
<Router.NotFoundRoute handler={NotFound} /> <Router.NotFoundRoute handler={NotFound} />
<Router.Route name="org" path=":org" handler={Org}> <Router.Route name="org" path=":org" handler={Org}>
<Router.DefaultRoute handler={OrgStats} /> <Router.DefaultRoute handler={Dashboard} />
<Router.Route name="team" path="teams/:team" handler={TeamStats} /> <Router.Route name="team" path="teams/:team" handler={Dashboard} />
<Router.Route name="user" path="users/:user" handler={UserStats} /> <Router.Route name="user" path="users/:user" handler={Dashboard} />
<Router.Route name="repo" path="repos/:repo" handler={RepoStats} /> <Router.Route name="repo" path="repos/:repo" handler={Dashboard} />
</Router.Route> </Router.Route>
</Router.Route> </Router.Route>
]; ];

View File

@ -7,13 +7,16 @@ var BarChart = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
currentApi: null,
currentParams: null,
item: this.props.items[0], item: this.props.items[0],
sort: 'commits', sort: 'commits',
rawData: [], rawData: [],
points: [], points: [],
oldPoints: [],
min: 0, min: 0,
max: 1, max: 1,
canvasWidth: 500 canvasWidth: 500,
}; };
}, },
@ -24,11 +27,17 @@ var BarChart = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this.fetchData();
this.calculateViewBoxWidth(); this.calculateViewBoxWidth();
window.addEventListener('resize', this.calculateViewBoxWidth); window.addEventListener('resize', this.calculateViewBoxWidth);
}, },
componentWillReceiveProps: function(newProps) {
this.setState({
'item': newProps.items[0],
'sort': 'commits'
}, this.fetchData);
},
handleFilter: function(thing, i) { handleFilter: function(thing, i) {
if (thing === 'item' && this.props.items[i] !== this.state.item) { if (thing === 'item' && this.props.items[i] !== this.state.item) {
this.setState({ this.setState({
@ -48,14 +57,30 @@ var BarChart = React.createClass({
}, },
fetchData: function() { fetchData: function() {
$.get(this.props.api, this.apiParams(), function(res){ if (!this.apiParams().item) {
this.setState({ return;
rawData: res }
}, this.sort); if (this.state.currentApi === this.props.api &&
this.state.currentParams === JSON.stringify(this.apiParams())) {
return;
}
console.log('-----> fetching', this.state.currentApi, this.props.api);
this.setState({
currentApi: this.props.api,
currentParams: JSON.stringify(this.apiParams())
}, function() {
$.get(this.props.api, this.apiParams(), function(res){
this.setState({
rawData: res,
oldPoints: this.state.points
}, this.sort);
}.bind(this));
}.bind(this)); }.bind(this));
}, },
sort: function() { sort: function() {
console.log('-----> sorting');
var sortFun = function(a, b) { var sortFun = function(a, b) {
return Math.abs(b[this.state.sort]) - Math.abs(a[this.state.sort]); return Math.abs(b[this.state.sort]) - Math.abs(a[this.state.sort]);
}.bind(this); }.bind(this);
@ -132,9 +157,15 @@ var BarChart = React.createClass({
y = this.y(i); y = this.y(i);
return ( return (
<Bar key={point.item} item={point.item} value={val} <Bar key={'bar-'+ i}
item={point.item}
value={val}
color={Colors2[i]} color={Colors2[i]}
x={x} y={y} offset={offset} width={width} height={height} x={x}
y={y}
offset={offset}
width={width}
height={height}
onClick={this.handleClick.bind(this, point)} /> onClick={this.handleClick.bind(this, point)} />
); );
} }
@ -143,6 +174,36 @@ var BarChart = React.createClass({
var Bar = React.createClass({ var Bar = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
getInitialState: function() {
return {lastx: 0, lastw: 0};
},
componentWillReceiveProps: function(newProps) {
console.log("New bar props!", newProps.item, newProps.x, newProps.width);
this.setState({
lastx: this.props.x,
lastw: this.props.width
}, this.animate);
},
animate: function() {
var bar = this.refs.bar.getDOMNode(),
anim = anim = document.createElementNS(SVGNS, 'animate');
if (bar.childNodes.length > 0) {
bar.removeChild(bar.childNodes[0]);
}
anim.setAttributeNS(null, 'attributeType', 'XML');
anim.setAttributeNS(null, 'attributeName', 'width');
anim.setAttributeNS(null, 'from', this.state.lastw);
anim.setAttributeNS(null, 'to', this.props.width);
anim.setAttributeNS(null, 'dur', '300ms');
anim.setAttributeNS(null, 'repeatCount', '1');
bar.appendChild(anim);
anim.beginElement();
},
render: function() { render: function() {
var val = this.props.value, var val = this.props.value,
item = this.props.item, item = this.props.item,
@ -183,13 +244,23 @@ var Bar = React.createClass({
return ( return (
<g onClick={this.props.onClick}> <g onClick={this.props.onClick}>
<rect className="bar" fill={this.props.color} <rect ref="bar"
width={width} height={this.props.height} className="bar"
x={this.props.x} y={this.props.y} rx="2" ry="2" /> fill={this.props.color}
<rect className="label_underlay" width={width}
x={labelX - labelPaddingH} y={this.props.y + labelMarginV} height={this.props.height}
height={labelOuterHeight} width={labelOuterWidth} x={this.props.x}
rx="3" ry="3" /> y={this.props.y}
rx="2"
ry="2" />
<rect
className="label_underlay"
width={labelOuterWidth}
height={labelOuterHeight}
x={labelX - labelPaddingH}
y={this.props.y + labelMarginV}
rx="3"
ry="3" />
<text className="label" x={labelX} y={labelY}>{label}</text> <text className="label" x={labelX} y={labelY}>{label}</text>
</g> </g>
); );

View File

@ -15,6 +15,13 @@ var StackedAreaChart = React.createClass({
}; };
}, },
componentWillReceiveProps: function(newProps) {
this.setState({
'item': newProps.items[0],
'sort': 'commits'
}, this.fetchData);
},
calculateViewBoxWidth: function() { calculateViewBoxWidth: function() {
this.setState({ this.setState({
canvasWidth: this.refs.svg.getDOMNode().offsetWidth canvasWidth: this.refs.svg.getDOMNode().offsetWidth
@ -77,20 +84,20 @@ var StackedAreaChart = React.createClass({
if (res[el.week] === undefined) { if (res[el.week] === undefined) {
res[el.week] = {}; res[el.week] = {};
} }
res[el.week][el.item] = el.commits; if (top.indexOf(el.item) > -1) {
res[el.week][el.item] = el.commits;
}
return res; return res;
}, {}); }, {});
var max = _.chain(weeks).keys().sort().reverse().take(15).map(function(week) {
return _.sum(_.values(weeks[week]));
})
.max()
.value();
var max = _.chain(this.state.rawData) // var max = _.max(_.map(weeks, function(items, week) {
.reduce(function(res, el) { // return _.sum(_.values(items));
if (res[el.week] === undefined) { // }));
res[el.week] = 0;
}
res[el.week] += el.commits;
return res;
}, {})
.max()
.value();
this.setState({ this.setState({
top: top, top: top,
@ -102,11 +109,12 @@ var StackedAreaChart = React.createClass({
buildPathD: function(points) { buildPathD: function(points) {
var maxWidth = this.state.canvasWidth, var maxWidth = this.state.canvasWidth,
maxHeight = this.height, maxHeight = this.height,
maxValue = this.state.max,
len = points.length; len = points.length;
var d = _.map(points, function(point, i) { var d = _.map(points, function(point, i) {
return 'L'+ Math.floor(i/len*maxWidth) +','+ (maxHeight - point); return 'L'+ Math.floor(i/len*maxWidth) +','+ Math.floor(maxHeight - point);
}); });
d.unshift('M0,'+maxHeight); d.unshift('M0,'+ maxHeight);
d.push('L'+ maxWidth +','+ maxHeight +'Z'); d.push('L'+ maxWidth +','+ maxHeight +'Z');
// for (var i = 0; i < missing; i++) { // for (var i = 0; i < missing; i++) {
@ -133,8 +141,10 @@ var StackedAreaChart = React.createClass({
}); });
var sum = 0; var sum = 0;
// console.log('----------');
var points = _.map(values, function(val) { var points = _.map(values, function(val) {
sum += Math.floor(val/max*maxHeight); sum += Math.floor(val/max*maxHeight);
// console.log(val, max, maxHeight, sum);
return sum; return sum;
}); });
@ -182,7 +192,10 @@ var StackedAreaChart = React.createClass({
<ul className="legend"> <ul className="legend">
{_.pairs(colors).map(function(pair){ {_.pairs(colors).map(function(pair){
return ( return (
<li><div className="color-dot" style={{backgroundColor: pair[1]}}></div>{pair[0]}</li> <li key={'legend-'+ pair[0]}>
<div className="color-dot" style={{backgroundColor: pair[1]}}></div>
{pair[0]}
</li>
); );
})} })}
</ul> </ul>
@ -194,7 +207,10 @@ var StackedAreaChart = React.createClass({
var StackedArea = React.createClass({ var StackedArea = React.createClass({
render: function() { render: function() {
return ( return (
<path d={this.props.path} fill={this.props.color} shapeRendering="optimizeQuality" /> <path key={'sac-area-'+ this.props.item}
d={this.props.path}
fill={this.props.color}
shapeRendering="optimizeQuality" />
); );
} }
}); });

View File

@ -148,7 +148,7 @@ order by commits desc`
const repoActivityQuery = ` const repoActivityQuery = `
select select
c.week as weel, c.week as week,
%s as item, %s as item,
sum(c.commits) as commits, sum(c.commits) as commits,
sum(c.additions) - sum(c.deletions) as delta sum(c.additions) - sum(c.deletions) as delta
@ -211,6 +211,6 @@ func StatRepoTop(p map[string]interface{}) (res []StatItem) {
func StatRepoActivity(p map[string]interface{}) (res []StatPoint) { func StatRepoActivity(p map[string]interface{}) (res []StatPoint) {
defer measure("StatRepoActivity", time.Now()) defer measure("StatRepoActivity", time.Now())
mustSelectN(&res, fmt.Sprintf(repoTopQuery, p["item"]), p) mustSelectN(&res, fmt.Sprintf(repoActivityQuery, p["item"]), p)
return return
} }