Intractable stacked area chart
This commit is contained in:
parent
58a9713bf2
commit
299589b9c3
|
@ -75,20 +75,14 @@ var StackedAreaChart = React.createClass({
|
||||||
node.className = 'sachart-container focused item-'+ i;
|
node.className = 'sachart-container focused item-'+ i;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFocusOut: function(i) {
|
handleFocusOut: function() {
|
||||||
var node = this.refs.container.getDOMNode();
|
var node = this.refs.container.getDOMNode();
|
||||||
node.className = 'sachart-container';
|
node.className = 'sachart-container';
|
||||||
},
|
},
|
||||||
|
|
||||||
handleNewData: function() {
|
handleNewData: function() {
|
||||||
// Group commits by items
|
// Group commits by items
|
||||||
var weeksList = _.chain(this.state.rawData)
|
var weeksList = _(this.state.rawData).pluck('week').uniq().sort().reverse().take(this.maxWeeks).value();
|
||||||
.pluck('week')
|
|
||||||
.uniq()
|
|
||||||
.sort()
|
|
||||||
.reverse()
|
|
||||||
.take(this.maxWeeks)
|
|
||||||
.value();
|
|
||||||
|
|
||||||
var counts = _.reduce(this.state.rawData, function(res, el) {
|
var counts = _.reduce(this.state.rawData, function(res, el) {
|
||||||
if (weeksList.indexOf(el.week) === -1) {
|
if (weeksList.indexOf(el.week) === -1) {
|
||||||
|
@ -103,7 +97,7 @@ var StackedAreaChart = React.createClass({
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// Extract top items from
|
// Extract top items from
|
||||||
var top = _.chain(_.pairs(counts)) // Take [item, count] pairs from counts object
|
var top = _(_.pairs(counts)) // Take [item, count] pairs from counts object
|
||||||
.sortBy(1).reverse() // sort them by count (descending)
|
.sortBy(1).reverse() // sort them by count (descending)
|
||||||
.take(this.numElements) // take first N pairs
|
.take(this.numElements) // take first N pairs
|
||||||
.pluck(0) // keep only items, omit the counts
|
.pluck(0) // keep only items, omit the counts
|
||||||
|
@ -125,9 +119,7 @@ var StackedAreaChart = React.createClass({
|
||||||
return res;
|
return res;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
var max = _.max(_.map(weeksList, function(week) {
|
var max = _.max(_.map(weeksList, function(week){ return _.sum(_.values(weeks[week])); }));
|
||||||
return _.sum(_.values(weeks[week]));
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
top: top,
|
top: top,
|
||||||
|
@ -141,11 +133,12 @@ var StackedAreaChart = React.createClass({
|
||||||
var maxWidth = this.state.canvasWidth,
|
var maxWidth = this.state.canvasWidth,
|
||||||
maxHeight = this.height;
|
maxHeight = this.height;
|
||||||
|
|
||||||
var d = _.map(this.buildDots(points), function(dot) {
|
var dots = this.buildDots(points);
|
||||||
return 'L'+ dot[0] +','+ dot[1];
|
var first = dots.shift();
|
||||||
});
|
var d = _.map(dots, function(dot){ return 'L'+ dot.x +','+ dot.y; });
|
||||||
d.unshift('M0,'+ maxHeight);
|
d.unshift('M'+ first.x +','+ first.y);
|
||||||
d.push('L'+ maxWidth +','+ maxHeight +'Z');
|
d.push('L'+ maxWidth +','+ maxHeight);
|
||||||
|
d.push('L0,'+ maxHeight +' Z');
|
||||||
|
|
||||||
return d.join(' ');
|
return d.join(' ');
|
||||||
},
|
},
|
||||||
|
@ -157,7 +150,21 @@ var StackedAreaChart = React.createClass({
|
||||||
len = points.length;
|
len = points.length;
|
||||||
|
|
||||||
return _.map(points, function(point, i) {
|
return _.map(points, function(point, i) {
|
||||||
return [Math.floor(i/(len-1)*maxWidth), Math.floor(maxHeight - point)];
|
point.x = i/(len-1)*maxWidth;
|
||||||
|
point.y = maxHeight - point.point;
|
||||||
|
|
||||||
|
if (point.x < 10) { // Radius
|
||||||
|
point.x = 10
|
||||||
|
} else if (point.x > maxWidth - 10) {
|
||||||
|
point.x = maxWidth - 10;
|
||||||
|
}
|
||||||
|
if (point.y < 10) {
|
||||||
|
point.y = 10;
|
||||||
|
} else if (point.y > maxHeight - 10) {
|
||||||
|
point.y = maxHeight - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return point;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -167,7 +174,8 @@ var StackedAreaChart = React.createClass({
|
||||||
top = this.state.top,
|
top = this.state.top,
|
||||||
max = this.state.max;
|
max = this.state.max;
|
||||||
|
|
||||||
var points = _.chain(this.state.weeks)
|
// [week, [{val, point}, ...]]
|
||||||
|
var points = _(this.state.weeks)
|
||||||
.map(function(items, week) {
|
.map(function(items, week) {
|
||||||
var values = _.map(top, function(item) {
|
var values = _.map(top, function(item) {
|
||||||
return items[item] || 0;
|
return items[item] || 0;
|
||||||
|
@ -175,8 +183,11 @@ var StackedAreaChart = React.createClass({
|
||||||
|
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
var points = _.map(values, function(val) {
|
var points = _.map(values, function(val) {
|
||||||
sum += Math.floor(val/max*maxHeight*0.96);
|
sum += val/max*maxHeight*0.96;
|
||||||
return sum;
|
return {
|
||||||
|
val: val,
|
||||||
|
point: sum
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return [week, points];
|
return [week, points];
|
||||||
|
@ -187,6 +198,7 @@ var StackedAreaChart = React.createClass({
|
||||||
.reverse()
|
.reverse()
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
|
// [item, [{val, point}, ...]]
|
||||||
var paths = _.map(top, function(item, i) {
|
var paths = _.map(top, function(item, i) {
|
||||||
var itemPoints = _.map(points, function(pair) {
|
var itemPoints = _.map(points, function(pair) {
|
||||||
return pair[1][i];
|
return pair[1][i];
|
||||||
|
@ -194,62 +206,63 @@ var StackedAreaChart = React.createClass({
|
||||||
return[item, itemPoints];
|
return[item, itemPoints];
|
||||||
});
|
});
|
||||||
|
|
||||||
var colors = {};
|
|
||||||
var areas = _.map(paths, function(pair, i) {
|
var areas = _.map(paths, function(pair, i) {
|
||||||
var item = pair[0], path = pair[1];
|
var item = pair[0],
|
||||||
if (item !== null) {
|
path = pair[1];
|
||||||
colors[item] = Colors[i];
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<StackedArea key={'area-'+ i}
|
<StackedArea key={'area-'+ i}
|
||||||
item={item} i={i}
|
item={item} i={i}
|
||||||
d={roundPathCorners(this.buildPathD(path), 3)}
|
d={this.buildPathD(path)}
|
||||||
color={Colors[i]}
|
color={Colors[i]}
|
||||||
onMouseOver={this.handleFocusIn.bind(this, i)}
|
onMouseOver={this.handleFocusIn.bind(this, i)} />
|
||||||
onMouseOut={this.handleFocusOut.bind(this, i)} />
|
|
||||||
);
|
);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
var dots = _.map(paths, function(pair, i) {
|
|
||||||
var item = pair[0], path = pair[1];
|
|
||||||
var dots = this.buildDots(path);
|
|
||||||
var lastY = 0;
|
|
||||||
var renderDot = function(dot, j) {
|
|
||||||
if (lastY === dot[1]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
lastY = dot[1];
|
|
||||||
return (
|
|
||||||
<Dot key={'dot-'+ i +'-'+ j}
|
|
||||||
item={item} i={i}
|
|
||||||
value={100}
|
|
||||||
x={dot[0]}
|
|
||||||
y={dot[1]} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return dots.map(renderDot);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
var words = this.words,
|
var words = this.words,
|
||||||
who = this.getParams().repo || this.getParams().team || this.getParams().user || this.getParams().org;
|
who = this.getParams().repo ||
|
||||||
|
this.getParams().team ||
|
||||||
|
this.getParams().user ||
|
||||||
|
this.getParams().org;
|
||||||
|
|
||||||
var params = Object.keys(this.getParams());
|
var params = Object.keys(this.getParams());
|
||||||
params.splice(params.indexOf('org'), 1);
|
params.splice(params.indexOf('org'), 1);
|
||||||
var subject = params[0];
|
var subject = params[0];
|
||||||
|
|
||||||
var renderLegend = function(pair, i){
|
var renderDot = function(item, i, dot, j) {
|
||||||
|
if (dot.val === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<li key={'legend-'+ pair[0]}
|
<Dot key={'dot-'+ i +'-'+ j}
|
||||||
|
item={item} i={i}
|
||||||
|
value={dot.val}
|
||||||
|
x={dot.x}
|
||||||
|
y={dot.y}
|
||||||
|
onMouseOver={this.handleFocusIn.bind(this, i)} />
|
||||||
|
);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var renderedDots = _.map(paths, function(pair, i) {
|
||||||
|
var item = pair[0], path = pair[1];
|
||||||
|
var dots = this.buildDots(path);
|
||||||
|
return dots.map(renderDot.bind(this, item, i));
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
var renderLegend = function(item, i){
|
||||||
|
return (
|
||||||
|
<li key={'legend-'+ item}
|
||||||
className={'label label-'+ i}
|
className={'label label-'+ i}
|
||||||
onMouseOver={this.handleFocusIn.bind(this, i)}
|
onMouseOver={this.handleFocusIn.bind(this, i)}
|
||||||
onMouseOut={this.handleFocusOut.bind(this, i)}>
|
onMouseOut={this.handleFocusOut.bind(this, i)}>
|
||||||
<div className="color-dot" style={{backgroundColor: pair[1]}}></div>
|
<div className="color-dot" style={{backgroundColor: Colors[i]}}></div>
|
||||||
{pair[0]}
|
{item}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
|
var legend = _(paths).pluck(0).filter(function(el){ return el !== null; }).value();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref="container" className="sachart-container">
|
<div ref="container" className="sachart-container">
|
||||||
<div className="whatsgoingon">
|
<div className="whatsgoingon">
|
||||||
|
@ -267,13 +280,14 @@ var StackedAreaChart = React.createClass({
|
||||||
onChange={this.handleFilter.bind(this, 'item')} />
|
onChange={this.handleFilter.bind(this, 'item')} />
|
||||||
</div>
|
</div>
|
||||||
<svg ref="svg" className="sachart" key="sachart-svg"
|
<svg ref="svg" className="sachart" key="sachart-svg"
|
||||||
|
onMouseOut={this.handleFocusOut}
|
||||||
width="100%" height={maxHeight}
|
width="100%" height={maxHeight}
|
||||||
viewBox={"0 0 "+ (this.state.canvasWidth || 0) + " "+ maxHeight}>
|
viewBox={"0 0 "+ (this.state.canvasWidth || 0) + " "+ maxHeight}>
|
||||||
<g ref="areas">{areas.reverse()}</g>
|
<g ref="areas">{areas.reverse()}</g>
|
||||||
<g ref="dots">{dots}</g>
|
<g ref="dots">{renderedDots}</g>
|
||||||
</svg>
|
</svg>
|
||||||
<ul className="legend">
|
<ul className="legend">
|
||||||
{_.pairs(colors).map(renderLegend)}
|
{legend.map(renderLegend)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -305,7 +319,6 @@ var StackedArea = React.createClass({
|
||||||
d={this.state.lastd || this.props.d}
|
d={this.state.lastd || this.props.d}
|
||||||
fill={this.props.color}
|
fill={this.props.color}
|
||||||
onMouseOver={this.props.onMouseOver}
|
onMouseOver={this.props.onMouseOver}
|
||||||
onMouseOut={this.props.onMouseOut}
|
|
||||||
shapeRendering="optimizeQuality" />
|
shapeRendering="optimizeQuality" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -314,7 +327,7 @@ var StackedArea = React.createClass({
|
||||||
var Dot = React.createClass({
|
var Dot = React.createClass({
|
||||||
mixins: [ChartAnimationMixin],
|
mixins: [ChartAnimationMixin],
|
||||||
|
|
||||||
radius: 12,
|
radius: 10,
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {};
|
return {};
|
||||||
|
@ -333,15 +346,18 @@ var Dot = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<g className={'dot dot-'+ this.props.i}>
|
<g className={'dot dot-'+ this.props.i} onMouseOver={this.props.onMouseOver}>
|
||||||
<circle ref="dot"
|
<circle ref="dot"
|
||||||
cx={this.props.x}
|
cx={this.props.x}
|
||||||
cy={this.state.lastY || this.props.y}
|
cy={this.state.lastY || this.props.y}
|
||||||
r={this.radius}
|
r={this.radius}
|
||||||
fill="rgba(255, 255, 255, .9)" />
|
fill="#fff"
|
||||||
|
stroke="#f0f0f0"
|
||||||
|
strokeWidth="1" />
|
||||||
<text ref="value"
|
<text ref="value"
|
||||||
x={this.props.x-8}
|
x={this.props.x}
|
||||||
y={(this.state.lastY || this.props.y)+4}>
|
y={this.props.y+4}
|
||||||
|
textAnchor="middle">
|
||||||
{this.props.value}
|
{this.props.value}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
@ -62,23 +62,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sachart-container.focused .path {
|
.sachart-container.focused .path {
|
||||||
opacity: 0.03;
|
fill: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sachart-container.focused .label {
|
.sachart-container.focused .label {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sachart-container.item-0 .path-0, .sachart-container.item-0 .label-0 { opacity: 1; }
|
.sachart-container.item-0 .path-0 { fill: #EA8369; }
|
||||||
.sachart-container.item-1 .path-1, .sachart-container.item-1 .label-1 { opacity: 1; }
|
.sachart-container.item-1 .path-1 { fill: #FCCD67; }
|
||||||
.sachart-container.item-2 .path-2, .sachart-container.item-2 .label-2 { opacity: 1; }
|
.sachart-container.item-2 .path-2 { fill: #B5A9F2; }
|
||||||
.sachart-container.item-3 .path-3, .sachart-container.item-3 .label-3 { opacity: 1; }
|
.sachart-container.item-3 .path-3 { fill: #9DD7F2; }
|
||||||
.sachart-container.item-4 .path-4, .sachart-container.item-4 .label-4 { opacity: 1; }
|
.sachart-container.item-4 .path-4 { fill: #FFA3A4; }
|
||||||
.sachart-container.item-5 .path-5, .sachart-container.item-5 .label-5 { opacity: 1; }
|
.sachart-container.item-5 .path-5 { fill: #A8EAA8; }
|
||||||
.sachart-container.item-6 .path-6, .sachart-container.item-6 .label-6 { opacity: 1; }
|
.sachart-container.item-6 .path-6 { fill: #F9D08B; }
|
||||||
.sachart-container.item-7 .path-7, .sachart-container.item-7 .label-7 { opacity: 1; }
|
.sachart-container.item-7 .path-7 { fill: #6CCECB; }
|
||||||
.sachart-container.item-8 .path-8, .sachart-container.item-8 .label-8 { opacity: 1; }
|
.sachart-container.item-8 .path-8 { fill: #F9E559; }
|
||||||
.sachart-container.item-9 .path-9, .sachart-container.item-9 .label-9 { opacity: 1; }
|
.sachart-container.item-9 .path-9 { fill: #FFBAD2; }
|
||||||
.sachart-container.item-0 .dot-0 { display: inline; }
|
.sachart-container.item-0 .dot-0 { display: inline; }
|
||||||
.sachart-container.item-1 .dot-1 { display: inline; }
|
.sachart-container.item-1 .dot-1 { display: inline; }
|
||||||
.sachart-container.item-2 .dot-2 { display: inline; }
|
.sachart-container.item-2 .dot-2 { display: inline; }
|
||||||
|
@ -89,6 +89,16 @@
|
||||||
.sachart-container.item-7 .dot-7 { display: inline; }
|
.sachart-container.item-7 .dot-7 { display: inline; }
|
||||||
.sachart-container.item-8 .dot-8 { display: inline; }
|
.sachart-container.item-8 .dot-8 { display: inline; }
|
||||||
.sachart-container.item-9 .dot-9 { display: inline; }
|
.sachart-container.item-9 .dot-9 { display: inline; }
|
||||||
|
.sachart-container.item-0 .label-0 { opacity: 1; }
|
||||||
|
.sachart-container.item-1 .label-1 { opacity: 1; }
|
||||||
|
.sachart-container.item-2 .label-2 { opacity: 1; }
|
||||||
|
.sachart-container.item-3 .label-3 { opacity: 1; }
|
||||||
|
.sachart-container.item-4 .label-4 { opacity: 1; }
|
||||||
|
.sachart-container.item-5 .label-5 { opacity: 1; }
|
||||||
|
.sachart-container.item-6 .label-6 { opacity: 1; }
|
||||||
|
.sachart-container.item-7 .label-7 { opacity: 1; }
|
||||||
|
.sachart-container.item-8 .label-8 { opacity: 1; }
|
||||||
|
.sachart-container.item-9 .label-9 { opacity: 1; }
|
||||||
|
|
||||||
|
|
||||||
.selector {
|
.selector {
|
||||||
|
|
Loading…
Reference in New Issue