Lauris BH
6 years ago
committed by
Jonas Franz
18 changed files with 258 additions and 384 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,27 +0,0 @@
|
||||
text.month-name, |
||||
text.calendar-heatmap-legend-text, |
||||
text.day-initial { |
||||
font-size: 10px; |
||||
fill: inherit; |
||||
font-family: Helvetica, arial, 'Open Sans', sans-serif; |
||||
} |
||||
rect.day-cell:hover { |
||||
stroke: #555555; |
||||
stroke-width: 1px; |
||||
} |
||||
.day-cell-tooltip { |
||||
position: absolute; |
||||
z-index: 9999; |
||||
padding: 5px 9px; |
||||
color: #bbbbbb; |
||||
font-size: 12px; |
||||
background: rgba(0, 0, 0, 0.85); |
||||
border-radius: 3px; |
||||
text-align: center; |
||||
} |
||||
.day-cell-tooltip > span { |
||||
font-family: Helvetica, arial, 'Open Sans', sans-serif |
||||
} |
||||
.calendar-heatmap { |
||||
box-sizing: initial; |
||||
} |
@ -1,311 +0,0 @@
|
||||
// https://github.com/DKirwan/calendar-heatmap
|
||||
|
||||
function calendarHeatmap() { |
||||
// defaults
|
||||
var width = 750; |
||||
var height = 110; |
||||
var legendWidth = 150; |
||||
var selector = 'body'; |
||||
var SQUARE_LENGTH = 11; |
||||
var SQUARE_PADDING = 2; |
||||
var MONTH_LABEL_PADDING = 6; |
||||
var now = moment().endOf('day').toDate(); |
||||
var yearAgo = moment().startOf('day').subtract(1, 'year').toDate(); |
||||
var startDate = null; |
||||
var counterMap= {}; |
||||
var data = []; |
||||
var max = null; |
||||
var colorRange = ['#D8E6E7', '#218380']; |
||||
var tooltipEnabled = true; |
||||
var tooltipUnit = 'contribution'; |
||||
var legendEnabled = true; |
||||
var onClick = null; |
||||
var weekStart = 1; //0 for Sunday, 1 for Monday
|
||||
var locale = { |
||||
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], |
||||
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], |
||||
No: 'No', |
||||
on: 'on', |
||||
Less: 'Less', |
||||
More: 'More' |
||||
}; |
||||
var v = Number(d3.version.split('.')[0]); |
||||
|
||||
// setters and getters
|
||||
chart.data = function (value) { |
||||
if (!arguments.length) { return data; } |
||||
data = value; |
||||
|
||||
counterMap= {}; |
||||
|
||||
data.forEach(function (element, index) { |
||||
var key= moment(element.date).format( 'YYYY-MM-DD' ); |
||||
var counter= counterMap[key] || 0; |
||||
counterMap[key]= counter + element.count; |
||||
}); |
||||
|
||||
return chart; |
||||
}; |
||||
|
||||
chart.max = function (value) { |
||||
if (!arguments.length) { return max; } |
||||
max = value; |
||||
return chart; |
||||
}; |
||||
|
||||
chart.selector = function (value) { |
||||
if (!arguments.length) { return selector; } |
||||
selector = value; |
||||
return chart; |
||||
}; |
||||
|
||||
chart.startDate = function (value) { |
||||
if (!arguments.length) { return startDate; } |
||||
yearAgo = value; |
||||
now = moment(value).endOf('day').add(1, 'year').toDate(); |
||||
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.locale = function (value) { |
||||
if (!arguments.length) { return locale; } |
||||
locale = value; |
||||
return chart; |
||||
}; |
||||
|
||||
function chart() { |
||||
|
||||
d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists
|
||||
|
||||
var dateRange = ((d3.time && d3.time.days) || d3.timeDays)(yearAgo, now); // generates an array of date objects within the specified range
|
||||
var monthRange = ((d3.time && d3.time.months) || d3.timeMonths)(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
|
||||
var firstDate = moment(dateRange[0]); |
||||
if (chart.data().length == 0) { |
||||
max = 0; |
||||
} else if (max === null) { |
||||
max = d3.max(chart.data(), function (d) { return d.count; }); // max data value
|
||||
} |
||||
|
||||
// color range
|
||||
var color = ((d3.scale && d3.scale.linear) || d3.scaleLinear)() |
||||
.range(chart.colorRange()) |
||||
.domain([0, max]); |
||||
|
||||
var tooltip; |
||||
var dayRects; |
||||
|
||||
drawChart(); |
||||
|
||||
function drawChart() { |
||||
var svg = d3.select(chart.selector()) |
||||
.style('position', 'relative') |
||||
.append('svg') |
||||
.attr('width', width) |
||||
.attr('class', 'calendar-heatmap') |
||||
.attr('height', height) |
||||
.style('padding', '36px'); |
||||
|
||||
dayRects = svg.selectAll('.day-cell') |
||||
.data(dateRange); // array of days for the last yr
|
||||
|
||||
var enterSelection = dayRects.enter().append('rect') |
||||
.attr('class', 'day-cell') |
||||
.attr('width', SQUARE_LENGTH) |
||||
.attr('height', SQUARE_LENGTH) |
||||
.attr('fill', function(d) { return color(countForDate(d)); }) |
||||
.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 + formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING); |
||||
}); |
||||
|
||||
if (typeof onClick === 'function') { |
||||
(v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('click', function(d) { |
||||
var count = countForDate(d); |
||||
onClick({ date: d, count: count}); |
||||
}); |
||||
} |
||||
|
||||
if (chart.tooltipEnabled()) { |
||||
(v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('mouseover', function(d, i) { |
||||
tooltip = d3.select(chart.selector()) |
||||
.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 formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 2 + 'px'; |
||||
}); |
||||
}) |
||||
.on('mouseout', function (d, i) { |
||||
tooltip.remove(); |
||||
}); |
||||
} |
||||
|
||||
if (chart.legendEnabled()) { |
||||
var colorRange = [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) * 13; }) |
||||
.attr('y', height + SQUARE_PADDING) |
||||
.attr('fill', function (d) { return d; }); |
||||
|
||||
legendGroup.append('text') |
||||
.attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-less') |
||||
.attr('x', width - legendWidth - 13) |
||||
.attr('y', height + SQUARE_LENGTH) |
||||
.text(locale.Less); |
||||
|
||||
legendGroup.append('text') |
||||
.attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-more') |
||||
.attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13) |
||||
.attr('y', height + SQUARE_LENGTH) |
||||
.text(locale.More); |
||||
} |
||||
|
||||
dayRects.exit().remove(); |
||||
var monthLabels = svg.selectAll('.month') |
||||
.data(monthRange) |
||||
.enter().append('text') |
||||
.attr('class', 'month-name') |
||||
.text(function (d) { |
||||
return locale.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'); |
||||
}); |
||||
|
||||
return Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING); |
||||
}) |
||||
.attr('y', 0); // fix these to the top
|
||||
|
||||
locale.days.forEach(function (day, index) { |
||||
index = formatWeekday(index); |
||||
if (index % 2) { |
||||
svg.append('text') |
||||
.attr('class', 'day-initial') |
||||
.attr('transform', 'translate(-8,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')') |
||||
.style('text-anchor', 'middle') |
||||
.attr('dy', '2') |
||||
.text(day); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function pluralizedTooltipUnit (count) { |
||||
if ('string' === typeof tooltipUnit) { |
||||
return (tooltipUnit + (count === 1 ? '' : 's')); |
||||
} |
||||
for (var i in tooltipUnit) { |
||||
var _rule = tooltipUnit[i]; |
||||
var _min = _rule.min; |
||||
var _max = _rule.max || _rule.min; |
||||
_max = _max === 'Infinity' ? Infinity : _max; |
||||
if (count >= _min && count <= _max) { |
||||
return _rule.unit; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function tooltipHTMLForDate(d) { |
||||
var dateStr = moment(d).format('ddd, MMM Do YYYY'); |
||||
var count = countForDate(d); |
||||
return '<span><strong>' + (count ? count : locale.No) + ' ' + pluralizedTooltipUnit(count) + '</strong> ' + locale.on + ' ' + dateStr + '</span>'; |
||||
} |
||||
|
||||
function countForDate(d) { |
||||
var key= moment(d).format( 'YYYY-MM-DD' ); |
||||
return counterMap[key] || 0; |
||||
} |
||||
|
||||
function formatWeekday(weekDay) { |
||||
if (weekStart === 1) { |
||||
if (weekDay === 0) { |
||||
return 6; |
||||
} else { |
||||
return weekDay - 1; |
||||
} |
||||
} |
||||
return weekDay; |
||||
} |
||||
|
||||
var daysOfChart = chart.data().map(function (day) { |
||||
return day.date.toDateString(); |
||||
}); |
||||
|
||||
} |
||||
|
||||
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 */ |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,112 @@
|
||||
|
||||
svg.vch__wrapper[data-v-a9cfea66] { |
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
||||
line-height: 10px; |
||||
} |
||||
svg.vch__wrapper .vch__months__labels__wrapper text.vch__month__label[data-v-a9cfea66] { |
||||
font-size: 10px; |
||||
} |
||||
svg.vch__wrapper .vch__days__labels__wrapper text.vch__day__label[data-v-a9cfea66], |
||||
svg.vch__wrapper .vch__legend__wrapper text[data-v-a9cfea66] { |
||||
font-size: 9px; |
||||
} |
||||
svg.vch__wrapper .vch__months__labels__wrapper text.vch__month__label[data-v-a9cfea66], |
||||
svg.vch__wrapper .vch__days__labels__wrapper text.vch__day__label[data-v-a9cfea66], |
||||
svg.vch__wrapper .vch__legend__wrapper text[data-v-a9cfea66] { |
||||
fill: #767676; |
||||
} |
||||
svg.vch__wrapper rect.vch__day__square[data-v-a9cfea66]:hover { |
||||
stroke: #555; |
||||
stroke-width: 1px; |
||||
} |
||||
svg.vch__wrapper rect.vch__day__square[data-v-a9cfea66]:focus { |
||||
outline: none; |
||||
} |
||||
|
||||
.vue-tooltip-theme.tooltip { |
||||
display: block !important; |
||||
z-index: 10000; |
||||
} |
||||
.vue-tooltip-theme.tooltip .tooltip-inner { |
||||
background: rgba(0, 0, 0, .7); |
||||
border-radius: 3px; |
||||
color: #ebedf0; |
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
||||
font-size: 12px; |
||||
line-height: 16px; |
||||
padding: 14px 10px; |
||||
} |
||||
.vue-tooltip-theme.tooltip .tooltip-inner b { |
||||
color: white; |
||||
} |
||||
.vue-tooltip-theme.tooltip .tooltip-arrow { |
||||
width: 0; |
||||
height: 0; |
||||
border-style: solid; |
||||
position: absolute; |
||||
margin: 5px; |
||||
border-color: black; |
||||
z-index: 1; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="top"] { |
||||
margin-bottom: 5px; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="top"] .tooltip-arrow { |
||||
border-width: 5px 5px 0 5px; |
||||
border-left-color: transparent !important; |
||||
border-right-color: transparent !important; |
||||
border-bottom-color: transparent !important; |
||||
bottom: -5px; |
||||
left: calc(50% - 5px); |
||||
margin-top: 0; |
||||
margin-bottom: 0; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="bottom"] { |
||||
margin-top: 5px; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="bottom"] .tooltip-arrow { |
||||
border-width: 0 5px 5px 5px; |
||||
border-left-color: transparent !important; |
||||
border-right-color: transparent !important; |
||||
border-top-color: transparent !important; |
||||
top: -5px; |
||||
left: calc(50% - 5px); |
||||
margin-top: 0; |
||||
margin-bottom: 0; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="right"] { |
||||
margin-left: 5px; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="right"] .tooltip-arrow { |
||||
border-width: 5px 5px 5px 0; |
||||
border-left-color: transparent !important; |
||||
border-top-color: transparent !important; |
||||
border-bottom-color: transparent !important; |
||||
left: -5px; |
||||
top: calc(50% - 5px); |
||||
margin-left: 0; |
||||
margin-right: 0; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="left"] { |
||||
margin-right: 5px; |
||||
} |
||||
.vue-tooltip-theme.tooltip[x-placement^="left"] .tooltip-arrow { |
||||
border-width: 5px 0 5px 5px; |
||||
border-top-color: transparent !important; |
||||
border-right-color: transparent !important; |
||||
border-bottom-color: transparent !important; |
||||
right: -5px; |
||||
top: calc(50% - 5px); |
||||
margin-left: 0; |
||||
margin-right: 0; |
||||
} |
||||
.vue-tooltip-theme.tooltip[aria-hidden='true'] { |
||||
visibility: hidden; |
||||
opacity: 0; |
||||
transition: opacity .15s, visibility .15s; |
||||
} |
||||
.vue-tooltip-theme.tooltip[aria-hidden='false'] { |
||||
visibility: visible; |
||||
opacity: 1; |
||||
transition: opacity .15s; |
||||
} |
Loading…
Reference in new issue