diff --git a/app/scripts/src/charts/stacked_area_chart.jsx b/app/scripts/src/charts/stacked_area_chart.jsx index 270b8d8..ab4eaa9 100644 --- a/app/scripts/src/charts/stacked_area_chart.jsx +++ b/app/scripts/src/charts/stacked_area_chart.jsx @@ -1,11 +1,12 @@ var StackedAreaChart = React.createClass({ mixins: [ReactRouter.Navigation, ReactRouter.State, SVGChartMixin, ChartDataMixin], - numElements: 10, - maxWeeks: 30, - height: 350, + canvasHeight: 350, xAxisHeight: 20, + maxItems: 10, + maxWeeks: 30, + words: { items: { repo: 'repositories', @@ -27,9 +28,9 @@ var StackedAreaChart = React.createClass({ return { item: this.props.items[0], rawData: [], - top: [], - weeks: [], - max: 1 + topItems: [], + weeklyData: [], + maxCommitsPerWeek: 1 }; }, @@ -39,19 +40,22 @@ var StackedAreaChart = React.createClass({ }, 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: 'newProps' + state: 'loadingData' }, this.fetchData); }, shouldComponentUpdate: function(newProps, newState) { + // Don't re-render unless canvas width is calculated if (!newState.canvasWidth) { return false; } - if (newState.state !== 'newPoints') { + // We're working with animations here so we render only in one particular state + if (newState.state !== 'pleaseRender') { return false; } return true; @@ -61,7 +65,7 @@ var StackedAreaChart = React.createClass({ if (this.props.items[i] !== this.state.item) { this.setState({ item: this.props.items[i], - state: 'newProps' + state: 'loadingData' }, this.fetchData); } }, @@ -83,17 +87,18 @@ var StackedAreaChart = React.createClass({ }, handleNewData: function() { - // Group commits by items + // [week, ...] var weeksList = _(this.state.rawData).pluck('week').uniq().sort().reverse().take(this.maxWeeks).value(); if (weeksList.length < 2) { this.setState({ weeks: [], - state: 'newPoints' + state: 'pleaseRender' }); return; } - var counts = _.reduce(this.state.rawData, function(res, el) { + // {item: commits, ...} + var commitsByItem = _.reduce(this.state.rawData, function(res, el) { if (weeksList.indexOf(el.week) === -1) { return res; } @@ -105,108 +110,72 @@ var StackedAreaChart = React.createClass({ return res; }, {}); - // Extract top items from - var top = _(_.pairs(counts)) // Take [item, count] pairs from counts object + // [item, ...] + var topItems = _(_.pairs(commitsByItem)) // Take [item, count] pairs from counts object .sortBy(1).reverse() // sort them by count (descending) - .take(this.numElements) // take first N pairs + .take(this.maxItems) // take first N pairs .pluck(0) // keep only items, omit the counts .value(); - for (var i = top.length; i < this.numElements; i++) { - top[i] = null; + for (var i = topItems.length; i < this.maxItems; i++) { + topItems[i] = null; }; - var weeks = _.reduce(this.state.rawData, function(res, el) { + // {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 (top.indexOf(el.item) > -1) { + if (topItems.indexOf(el.item) > -1) { res[el.week][el.item] = el.commits; } return res; }, {}); - var max = _.max(_.map(weeksList, function(week){ return _.sum(_.values(weeks[week])); })); + var maxCommitsPerWeek = _.max(_.map(weeksList, function(week) { + return _.sum(_.values(weeklyData[week])); + })); this.setState({ - top: top, - weeks: weeks, - max: max, - state: 'newPoints' + topItems: topItems, + weeklyData: weeklyData, + maxCommitsPerWeek: maxCommitsPerWeek, + state: 'pleaseRender' }); }, - buildPathD: function(points) { + buildPathD: function(dots) { var maxWidth = this.state.canvasWidth, - maxHeight = this.height; + maxHeight = this.canvasHeight; - var dots = this.buildDots(points); - var first = dots.shift(); + 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); - d.push('L'+ maxWidth +','+ maxHeight); - d.push('L0,'+ maxHeight +' Z'); + 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(' '); }, - buildDots: function(points) { + extendDotsWithCoordinates: function(dots) { var maxWidth = this.state.canvasWidth, - maxHeight = this.height, - maxValue = this.state.max, - len = points.length; + maxHeight = this.canvasHeight, + maxValue = this.state.maxCommitsPerWeek, + len = dots.length; - return _.map(points, function(point, i) { - point.x = i/(len-1)*maxWidth; - point.y = maxHeight - point.point; - return point; + 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 maxWidth = this.state.canvasWidth, - maxHeight = this.height, - top = this.state.top, - max = this.state.max; - - // [week, [{val, point}, ...]] - var points = _(this.state.weeks) - .map(function(items, week) { - var values = _.map(top, function(item) { - return items[item] || 0; - }); - - var sum = 0; - var points = _.map(values, function(val) { - sum += val/max*maxHeight*0.96; - return { - val: val, - point: sum - }; - }); - - return [parseInt(week, 10), points]; - }) - .sort(0) - .reverse() - .take(this.maxWeeks) - .reverse() - .value(); - - // [item, [{val, point}, ...]] - var paths = _.map(top, function(item, i) { - var itemPoints = _.map(points, function(pair) { - return pair[1][i]; - }); - return[item, itemPoints]; - }); - - var areas = _.map(paths, function(pair, i) { - var item = pair[0], - path = pair[1]; - + var renderArea = function(pair, i) { + var item = pair[0], path = pair[1]; return ( ); - }.bind(this)); - - var words = this.words, - who = this.getParams().repo || - this.getParams().team || - this.getParams().user || - this.getParams().org; - - var params = Object.keys(this.getParams()); - params.splice(params.indexOf('org'), 1); - var subject = params[0]; + }.bind(this); var renderDot = function(item, i, dot, j) { if (dot.val === 0) { @@ -232,7 +191,7 @@ var StackedAreaChart = React.createClass({ } var maxWidth = this.state.canvasWidth, - maxHeight = this.height, + maxHeight = this.canvasHeight, radius = 10, x = dot.x, y = dot.y; @@ -258,12 +217,6 @@ var StackedAreaChart = React.createClass({ ); }.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 (
  • @@ -298,15 +304,15 @@ var StackedAreaChart = React.createClass({ - {areas.reverse()} + {dotsByItem.map(renderArea).reverse()} {renderedDots}