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],
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 (
<section className="content">
<InfoBlock key={'info-block-org-'+ this.getParams().org}
image={org.avatar_url}
title={org.login}
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"]} />
<InfoBlock image={infoImage} title={infoTitle} text={infoText} />
<BarChart api={bcApi} params={this.getParams()} items={bcItems} />
<StackedAreaChart api={sacApi} params={this.getParams()} items={sacItems} />
</section>
);
}
@ -236,10 +197,10 @@ var routes = [
<Router.DefaultRoute handler={SelectOrg} />
<Router.NotFoundRoute handler={NotFound} />
<Router.Route name="org" path=":org" handler={Org}>
<Router.DefaultRoute handler={OrgStats} />
<Router.Route name="team" path="teams/:team" handler={TeamStats} />
<Router.Route name="user" path="users/:user" handler={UserStats} />
<Router.Route name="repo" path="repos/:repo" handler={RepoStats} />
<Router.DefaultRoute handler={Dashboard} />
<Router.Route name="team" path="teams/:team" handler={Dashboard} />
<Router.Route name="user" path="users/:user" handler={Dashboard} />
<Router.Route name="repo" path="repos/:repo" handler={Dashboard} />
</Router.Route>
</Router.Route>
];

View File

@ -7,13 +7,16 @@ var BarChart = React.createClass({
getInitialState: function() {
return {
currentApi: null,
currentParams: null,
item: this.props.items[0],
sort: 'commits',
rawData: [],
points: [],
oldPoints: [],
min: 0,
max: 1,
canvasWidth: 500
canvasWidth: 500,
};
},
@ -24,11 +27,17 @@ var BarChart = React.createClass({
},
componentDidMount: function() {
this.fetchData();
this.calculateViewBoxWidth();
window.addEventListener('resize', this.calculateViewBoxWidth);
},
componentWillReceiveProps: function(newProps) {
this.setState({
'item': newProps.items[0],
'sort': 'commits'
}, this.fetchData);
},
handleFilter: function(thing, i) {
if (thing === 'item' && this.props.items[i] !== this.state.item) {
this.setState({
@ -48,14 +57,30 @@ var BarChart = React.createClass({
},
fetchData: function() {
$.get(this.props.api, this.apiParams(), function(res){
this.setState({
rawData: res
}, this.sort);
if (!this.apiParams().item) {
return;
}
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));
},
sort: function() {
console.log('-----> sorting');
var sortFun = function(a, b) {
return Math.abs(b[this.state.sort]) - Math.abs(a[this.state.sort]);
}.bind(this);
@ -132,9 +157,15 @@ var BarChart = React.createClass({
y = this.y(i);
return (
<Bar key={point.item} item={point.item} value={val}
<Bar key={'bar-'+ i}
item={point.item}
value={val}
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)} />
);
}
@ -143,6 +174,36 @@ var BarChart = React.createClass({
var Bar = React.createClass({
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() {
var val = this.props.value,
item = this.props.item,
@ -183,13 +244,23 @@ var Bar = React.createClass({
return (
<g onClick={this.props.onClick}>
<rect className="bar" fill={this.props.color}
width={width} height={this.props.height}
x={this.props.x} y={this.props.y} rx="2" ry="2" />
<rect className="label_underlay"
x={labelX - labelPaddingH} y={this.props.y + labelMarginV}
height={labelOuterHeight} width={labelOuterWidth}
rx="3" ry="3" />
<rect ref="bar"
className="bar"
fill={this.props.color}
width={width}
height={this.props.height}
x={this.props.x}
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>
</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() {
this.setState({
canvasWidth: this.refs.svg.getDOMNode().offsetWidth
@ -77,20 +84,20 @@ var StackedAreaChart = React.createClass({
if (res[el.week] === undefined) {
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;
}, {});
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)
.reduce(function(res, el) {
if (res[el.week] === undefined) {
res[el.week] = 0;
}
res[el.week] += el.commits;
return res;
}, {})
.max()
.value();
// var max = _.max(_.map(weeks, function(items, week) {
// return _.sum(_.values(items));
// }));
this.setState({
top: top,
@ -102,11 +109,12 @@ var StackedAreaChart = React.createClass({
buildPathD: function(points) {
var maxWidth = this.state.canvasWidth,
maxHeight = this.height,
maxValue = this.state.max,
len = points.length;
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');
// for (var i = 0; i < missing; i++) {
@ -133,8 +141,10 @@ var StackedAreaChart = React.createClass({
});
var sum = 0;
// console.log('----------');
var points = _.map(values, function(val) {
sum += Math.floor(val/max*maxHeight);
// console.log(val, max, maxHeight, sum);
return sum;
});
@ -182,7 +192,10 @@ var StackedAreaChart = React.createClass({
<ul className="legend">
{_.pairs(colors).map(function(pair){
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>
@ -194,7 +207,10 @@ var StackedAreaChart = React.createClass({
var StackedArea = React.createClass({
render: function() {
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 = `
select
c.week as weel,
c.week as week,
%s as item,
sum(c.commits) as commits,
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) {
defer measure("StatRepoActivity", time.Now())
mustSelectN(&res, fmt.Sprintf(repoTopQuery, p["item"]), p)
mustSelectN(&res, fmt.Sprintf(repoActivityQuery, p["item"]), p)
return
}