public/js/controller-3219-2.js
d3.tip = require('./vendor/d3-tip');
var outstandingCommits;
var chartData = {};
var start = moment(new Date(2016, 0, 1));
var end = moment(new Date(2017, 0, 1));
var day = start;
while (day <= end) {
var startDateObj = day.toDate();
var startDateStr = day.toISOString();
chartData[startDateStr] = {
date: startDateObj,
count: 0
};
day = day.clone().add(1, 'd');
}
viz.data.getRepoCommits(handleCommits);
function handleCommits(data) {
outstandingCommits = data.length;
// console.log(data);
data.forEach(function(d) {
viz.data.getCommitDetails(d, handleCommitDetail);
});
}
function handleCommitDetail(commitDetail) {
// console.log(commitDetail);
var commitDate = viz.util.parseDate(commitDetail['commit']['author']['date']);
// console.log(commitDate);
addToChartData(commitDate);
outstandingCommits--;
if (outstandingCommits === 0) {
// console.log('all completed');
// console.log(chartData);
initChart();
}
}
function getTooltipContent(d) {
return moment(d.date).calendar() + ' - ' + d.count + ' ' + (d.count === 1 ? 'commit' : 'commits');
}
function initChart() {
var arr = Object.keys(chartData).map(function(key) { return chartData[key]; });
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-5, 0])
.html(function(d) {
return getTooltipContent(d);
});
var chart1 = calendarHeatmap()
.data(arr)
.dateRange(d3.time.days(new Date(2016, 0, 1), new Date(2017, 0, 1)))
.selector('#container')
.colorRange(['#D8E6E7', '#218380'])
.tooltipEnabled(true)
.onMouseOver(tip.show)
.onMouseOut(tip.hide);
chart1(); // render the chart
d3.select('#container').select('svg').call(tip);
}
function addToChartData(date) {
var startDate = moment(date).startOf('date');
var startDateObj = startDate.toDate();
var startDateStr = startDate.toISOString();
// console.log(startDateObj);
// console.log(startDateStr);
if (chartData.hasOwnProperty(startDateStr)) {
chartData[startDateStr].count++;
} else {
chartData[startDateStr] = {
date: startDateObj,
count: 1
};
}
}
function calendarHeatmap() {
// defaults
var width = 750;
var height = 110;
var legendWidth = 125;
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// var days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var selector = 'body';
var SQUARE_LENGTH = 13;
var SQUARE_PADDING = 1;
var MONTH_LABEL_PADDING = 6;
var now = moment().endOf('day').toDate();
var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
var data = [];
var colorRange = ['#bcd9d8', '#218380'];
var tooltipEnabled = true;
var tooltipUnit = 'commit';
var legendEnabled = true;
var onClick = null;
var onMouseOver = null;
var onMouseOut = null;
var dateRange;
var blankColor = '#fbfcfc';
var zeroColor = '#fbfcfc';
var futureColor = '#f1f2f3';
// setters and getters
chart.dateRange = function(value) {
if (!arguments.length) { return dateRange; }
dateRange = value;
return chart;
};
chart.width = function(value) {
if (!arguments.length) { return width; }
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) { return height; }
height = value;
return chart;
};
chart.data = function (value) {
if (!arguments.length) { return data; }
data = value;
return chart;
};
chart.selector = function (value) {
if (!arguments.length) { return selector; }
selector = value;
return chart;
};
chart.colorRange = function (value) {
if (!arguments.length) { return colorRange; }
colorRange = value;
return chart;
};
chart.tooltipEnabled = function (value) {
if (!arguments.length) { return tooltipEnabled; }
tooltipEnabled = value;
return chart;
};
chart.tooltipUnit = function (value) {
if (!arguments.length) { return tooltipUnit; }
tooltipUnit = value;
return chart;
};
chart.legendEnabled = function (value) {
if (!arguments.length) { return legendEnabled; }
legendEnabled = value;
return chart;
};
chart.onClick = function (value) {
if (!arguments.length) { return onClick; }
onClick = value;
return chart;
};
chart.onMouseOver = function (value) {
if (!arguments.length) { return onMouseOver; }
onMouseOver = value;
return chart;
};
chart.onMouseOut = function (value) {
if (!arguments.length) { return onMouseOut; }
onMouseOut = value;
return chart;
};
function chart() {
d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists
if(!chart.dateRange()) {
dateRange = d3.time.days(yearAgo, now); // generates an array of date objects within the specified range
}
// remove data outside the date range
var start = dateRange[0];
var end = dateRange[dateRange.length - 1];
data = data.filter(function(d) {
return (d.date > start || d.date.getTime() === start.getTime()) &&
(d.date < end || d.date.getTime() === end.getTime());
});
var monthRange = d3.time.months(moment(dateRange[0]).startOf('month').toDate(), dateRange[dateRange.length - 1]); // it ignores the first month if the 1st date is after the start of the month
var firstDate = moment(dateRange[0]);
var max = d3.max(chart.data(), function (d) { return d.count; }); // max data value
// handle exception when data is empty
if(!max) {
max = 10;
}
// color range
var color = d3.scale.linear()
.range(chart.colorRange())
.domain([0, max]);
var tooltip;
var dayRects;
drawChart();
function drawChart() {
var svg = d3.select(chart.selector())
.append('svg')
.attr('width', width)
.attr('class', 'calendar-heatmap')
.attr('height', height)
.style('padding', '18px 36px');
dayRects = svg.selectAll('.day-cell')
.data(dateRange); // array of days for the last yr
dayRects.enter().append('rect')
.attr('class', 'day-cell')
.attr('width', SQUARE_LENGTH)
.attr('height', SQUARE_LENGTH)
.attr('fill', blankColor)
.attr('x', function (d, i) {
var cellDate = moment(d);
var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
return result * (SQUARE_LENGTH + SQUARE_PADDING);
})
.attr('y', function (d, i) { return MONTH_LABEL_PADDING + d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING); });
if (typeof onClick === 'function') {
dayRects.on('click', function (d) {
onClick(dataForDate(d));
});
}
if (typeof onMouseOver === 'function' && typeof onMouseOut === 'function') {
dayRects.on('mouseover', function (d) {
onMouseOver.call(this, dataForDate(d));
})
.on('mouseout', function (d) {
onMouseOut.call(this, dataForDate(d));
});
} else if (chart.tooltipEnabled()) {
dayRects.on('mouseover', function (d, i) {
tooltip = d3.select('#container')
.append('div')
.attr('class', 'day-cell-tooltip')
.html(tooltipHTMLForDate(d))
.style('left', function() { return Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
.style('top', function() { return d.getDay() * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 3 + 'px'; });
})
.on('mouseout', function (d, i) {
tooltip.remove();
});
}
if (chart.legendEnabled()) {
var colorRange = [zeroColor, color(0)];
for (var i = 3; i > 0; i--) {
colorRange.push(color(max / i));
}
var legendGroup = svg.append('g');
legendGroup.selectAll('.calendar-heatmap-legend')
.data(colorRange)
.enter()
.append('rect')
.attr('class', 'calendar-heatmap-legend')
.attr('width', SQUARE_LENGTH)
.attr('height', SQUARE_LENGTH)
.attr('x', function (d, i) { return (width - legendWidth) + (i + 1) * (SQUARE_LENGTH + 2); })
.attr('y', height + SQUARE_PADDING)
.attr('fill', function (d) { return d; });
legendGroup.append('text')
.attr('class', 'calendar-heatmap-legend-text')
.attr('x', width - legendWidth - 13)
.attr('y', height + SQUARE_LENGTH)
.text('Less');
legendGroup.append('text')
.attr('class', 'calendar-heatmap-legend-text')
.attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * (SQUARE_LENGTH + 2))
.attr('y', height + SQUARE_LENGTH)
.text('More');
}
var daysOfChart = chart.data().map(function(day){
return day.date.toDateString();
});
// color future dates with no data using light color and prevent user interactions on them
dayRects.filter(function(d) {
return d > now && daysOfChart.indexOf(d.toDateString()) <= -1;
})
.attr('fill', futureColor)
.on('mouseover', null)
.on('mouseout', null)
.classed('future', true)
.style('pointer-events', 'none');
dayRects.filter(function(d) {
return daysOfChart.indexOf(d.toDateString()) > -1;
})
.attr('fill', function(d, i) {
return chart.data()[i].count ? color(chart.data()[i].count) : zeroColor;
});
dayRects.exit().remove();
var monthLabels = svg.selectAll('.month')
.data(monthRange)
.enter().append('text')
.attr('class', 'month-name')
.style()
.text(function (d) {
return months[d.getMonth()];
})
.attr('x', function (d, i) {
var matchIndex = 0;
dateRange.find(function (element, index) {
matchIndex = index;
return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
});
// center month label in month box
var startPos = (matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
return startPos + (SQUARE_LENGTH + SQUARE_PADDING) * 2.5;
})
.attr('y', 0); // fix these to the top
days.forEach(function (day, index) {
svg.append('text')
.attr('class', 'day-initial')
.attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
.style('text-anchor', 'end')
.attr('dy', '2')
.text(day);
});
// month border
var year = dateRange[0].getFullYear();
svg.append('g')
.attr('transform', 'translate(-1,' + (MONTH_LABEL_PADDING-1) + ')')
.selectAll('.monthpath')
.data(d3.time.months(new Date(year, 0, 1), new Date(year + 1, 0, 1)))
.enter().append('path')
.attr('class', 'monthpath')
.attr('d', monthPath);
}
function tooltipHTMLForDate(d) {
var dateStr = moment(d).format('ddd, MMM Do YYYY');
var data = dataForDate(d);
var count = data.count;
return '<span><strong>' + (count ? count : 'No') + ' ' + tooltipUnit + (count === 1 ? '' : 's') + '</strong> on ' + dateStr + '</span>';
}
function dataForDate(d) {
var match = chart.data().find(function (element, index) {
return moment(element.date).isSame(d, 'day');
});
if (match) {
return match;
}
return {
date: d,
count: 0
};
}
// https://bl.ocks.org/mbostock/4063318 MAGIC
function monthPath(t0) {
var cellSize = SQUARE_LENGTH + SQUARE_PADDING;
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(), w0 = d3.time.weekOfYear(t0),
d1 = t1.getDay(), w1 = d3.time.weekOfYear(t1);
return 'M' + (w0 + 1) * cellSize + ',' + d0 * cellSize +
'H' + w0 * cellSize + 'V' + 7 * cellSize +
'H' + w1 * cellSize + 'V' + (d1 + 1) * cellSize +
'H' + (w1 + 1) * cellSize + 'V' + 0 +
'H' + (w0 + 1) * cellSize + 'Z';
}
}
return chart;
}
// polyfill for Array.find() method
/* jshint ignore:start */
if (!Array.prototype.find) {
Array.prototype.find = function (predicate) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
/* jshint ignore:end */