UNPKG

68.3 kBJavaScriptView Raw
1import { Component, EventEmitter, HostListener, Input, Output, ViewChild } from '@angular/core';
2import moment from 'moment';
3import { easeLinear, event, max, scaleBand, scaleLinear, scaleTime, select, timeDays, timeHours, timeMonths, timeSecond, timeYears } from 'd3/index';
4import * as d3 from 'd3/index';
5
6// Import dependencies
7var CalendarHeatmap = /** @class */ (function () {
8 function CalendarHeatmap() {
9 this.color = '#ff4500';
10 this.overview = 'global';
11 this.handler = new EventEmitter();
12 this.gutter = 5;
13 this.item_gutter = 1;
14 this.width = 1000;
15 this.height = 200;
16 this.item_size = 10;
17 this.label_padding = 40;
18 this.max_block_height = 20;
19 this.transition_duration = 500;
20 this.in_transition = false;
21 this.tooltip_width = 250;
22 this.tooltip_padding = 15;
23 this.history = ['global'];
24 this.selected = {};
25 }
26 /**
27 * Check if data is available
28 * @return {?}
29 */
30 CalendarHeatmap.prototype.ngOnChanges = function () {
31 if (!this.data) {
32 return;
33 }
34 // Update data summaries
35 this.updateDataSummary();
36 // Draw the chart
37 this.drawChart();
38 };
39
40 /**
41 * Get hold of the root element and append our svg
42 * @return {?}
43 */
44 CalendarHeatmap.prototype.ngAfterViewInit = function () {
45 var /** @type {?} */ element = this.element.nativeElement;
46 // Initialize svg element
47 this.svg = select(element)
48 .append('svg')
49 .attr('class', 'svg');
50 // Initialize main svg elements
51 this.items = this.svg.append('g');
52 this.labels = this.svg.append('g');
53 this.buttons = this.svg.append('g');
54 // Add tooltip to the same element as main svg
55 this.tooltip = select(element).append('div')
56 .attr('class', 'heatmap-tooltip')
57 .style('opacity', 0);
58 // Calculate chart dimensions
59 this.calculateDimensions();
60 // Draw the chart
61 this.drawChart();
62 };
63
64 /**
65 * Utility function to get number of complete weeks in a year
66 * @return {?}
67 */
68 CalendarHeatmap.prototype.getNumberOfWeeks = function () {
69 var /** @type {?} */ dayIndex = Math.round((+moment() - +moment().subtract(1, 'year').startOf('week')) / 86400000);
70 var /** @type {?} */ colIndex = Math.trunc(dayIndex / 7);
71 var /** @type {?} */ numWeeks = colIndex + 1;
72 return numWeeks;
73 };
74
75 /**
76 * Utility funciton to calculate chart dimensions
77 * @return {?}
78 */
79 CalendarHeatmap.prototype.calculateDimensions = function () {
80 var /** @type {?} */ element = this.element.nativeElement;
81 this.width = element.clientWidth < 1000 ? 1000 : element.clientWidth;
82 this.item_size = ((this.width - this.label_padding) / this.getNumberOfWeeks() - this.gutter);
83 this.height = this.label_padding + 7 * (this.item_size + this.gutter);
84 this.svg.attr('width', this.width).attr('height', this.height);
85 };
86
87 /**
88 * Recalculate dimensions on window resize events
89 * @param {?} event
90 * @return {?}
91 */
92 CalendarHeatmap.prototype.onResize = function (event$$1) {
93 this.calculateDimensions();
94 if (!!this.data && !!this.data[0]['summary']) {
95 this.drawChart();
96 }
97 };
98
99 /**
100 * Helper function to check for data summary
101 * @return {?}
102 */
103 CalendarHeatmap.prototype.updateDataSummary = function () {
104 // Get daily summary if that was not provided
105 if (!this.data[0]['summary']) {
106 this.data.map(function (d) {
107 var /** @type {?} */ summary = d['details'].reduce(function (uniques, project) {
108 if (!uniques[project.name]) {
109 uniques[project.name] = {
110 'value': project.value
111 };
112 }
113 else {
114 uniques[project.name].value += project.value;
115 }
116 return uniques;
117 }, {});
118 var /** @type {?} */ unsorted_summary = Object.keys(summary).map(function (key) {
119 return {
120 'name': key,
121 'value': summary[key].value
122 };
123 });
124 d['summary'] = unsorted_summary.sort(function (a, b) {
125 return b.value - a.value;
126 });
127 return d;
128 });
129 }
130 };
131 /**
132 * Draw the chart based on the current overview type
133 * @return {?}
134 */
135 CalendarHeatmap.prototype.drawChart = function () {
136 if (!this.svg || !this.data) {
137 return;
138 }
139 if (this.overview === 'global') {
140 this.drawGlobalOverview();
141 }
142 else if (this.overview === 'year') {
143 this.drawYearOverview();
144 }
145 else if (this.overview === 'month') {
146 this.drawMonthOverview();
147 }
148 else if (this.overview === 'week') {
149 this.drawWeekOverview();
150 }
151 else if (this.overview === 'day') {
152 this.drawDayOverview();
153 }
154 };
155
156 /**
157 * Draw global overview (multiple years)
158 * @return {?}
159 */
160 CalendarHeatmap.prototype.drawGlobalOverview = function () {
161 var _this = this;
162 // Add current overview to the history
163 if (this.history[this.history.length - 1] !== this.overview) {
164 this.history.push(this.overview);
165 }
166 // Define start and end of the dataset
167 var /** @type {?} */ start = moment(this.data[0]['date']).startOf('year');
168 var /** @type {?} */ end = moment(this.data[this.data.length - 1]['date']).endOf('year');
169 // Define array of years and total values
170 var /** @type {?} */ data = this.data;
171 var /** @type {?} */ year_data = timeYears(start, end).map(function (d) {
172 var /** @type {?} */ date = moment(d);
173 return {
174 'date': date,
175 'total': data.reduce(function (prev, current) {
176 if (moment(current.date).year() === date.year()) {
177 prev += current.total;
178 }
179 return prev;
180 }, 0),
181 'summary': function () {
182 var /** @type {?} */ summary = data.reduce(function (summary, d) {
183 if (moment(d.date).year() === date.year()) {
184 for (var /** @type {?} */ i = 0; i < d.summary.length; i++) {
185 if (!summary[d.summary[i].name]) {
186 summary[d.summary[i].name] = {
187 'value': d.summary[i].value,
188 };
189 }
190 else {
191 summary[d.summary[i].name].value += d.summary[i].value;
192 }
193 }
194 }
195 return summary;
196 }, {});
197 var /** @type {?} */ unsorted_summary = Object.keys(summary).map(function (key) {
198 return {
199 'name': key,
200 'value': summary[key].value
201 };
202 });
203 return unsorted_summary.sort(function (a, b) {
204 return b.value - a.value;
205 });
206 }(),
207 };
208 });
209 // Calculate max value of all the years in the dataset
210 var /** @type {?} */ max_value = max(year_data, function (d) {
211 return d.total;
212 });
213 // Define year labels and axis
214 var /** @type {?} */ year_labels = timeYears(start, end).map(function (d) {
215 return moment(d);
216 });
217 var /** @type {?} */ yearScale = scaleBand()
218 .rangeRound([0, this.width])
219 .padding(0.05)
220 .domain(year_labels.map(function (d) {
221 return d.year();
222 }));
223 // Add month data items to the overview
224 this.items.selectAll('.item-block-year').remove();
225 var /** @type {?} */ item_block = this.items.selectAll('.item-block-year')
226 .data(year_data)
227 .enter()
228 .append('rect')
229 .attr('class', 'item item-block-year')
230 .attr('width', function () {
231 return (_this.width - _this.label_padding) / year_labels.length - _this.gutter * 5;
232 })
233 .attr('height', function () {
234 return _this.height - _this.label_padding;
235 })
236 .attr('transform', function (d) {
237 return 'translate(' + yearScale(d.date.year()) + ',' + _this.tooltip_padding * 2 + ')';
238 })
239 .attr('fill', function (d) {
240 var /** @type {?} */ color = scaleLinear()
241 .range(['#ffffff', _this.color || '#ff4500'])
242 .domain([-0.15 * max_value, max_value]);
243 return color(d.total) || '#ff4500';
244 })
245 .on('click', function (d) {
246 if (_this.in_transition) {
247 return;
248 }
249 // Set in_transition flag
250 _this.in_transition = true;
251 // Set selected date to the one clicked on
252 _this.selected = d;
253 // Hide tooltip
254 _this.hideTooltip();
255 // Remove all month overview related items and labels
256 _this.removeGlobalOverview();
257 // Redraw the chart
258 _this.overview = 'year';
259 _this.drawChart();
260 })
261 .style('opacity', 0)
262 .on('mouseover', function (d) {
263 if (_this.in_transition) {
264 return;
265 }
266 // Construct tooltip
267 var /** @type {?} */ tooltip_html = '';
268 tooltip_html += '<div><span><strong>Total time tracked:</strong></span>';
269 var /** @type {?} */ sec = parseInt(d.total, 10);
270 var /** @type {?} */ days = Math.floor(sec / 86400);
271 if (days > 0) {
272 tooltip_html += '<span>' + (days === 1 ? '1 day' : days + ' days') + '</span></div>';
273 }
274 var /** @type {?} */ hours = Math.floor((sec - (days * 86400)) / 3600);
275 if (hours > 0) {
276 if (days > 0) {
277 tooltip_html += '<div><span></span><span>' + (hours === 1 ? '1 hour' : hours + ' hours') + '</span></div>';
278 }
279 else {
280 tooltip_html += '<span>' + (hours === 1 ? '1 hour' : hours + ' hours') + '</span></div>';
281 }
282 }
283 var /** @type {?} */ minutes = Math.floor((sec - (days * 86400) - (hours * 3600)) / 60);
284 if (minutes > 0) {
285 if (days > 0 || hours > 0) {
286 tooltip_html += '<div><span></span><span>' + (minutes === 1 ? '1 minute' : minutes + ' minutes') + '</span></div>';
287 }
288 else {
289 tooltip_html += '<span>' + (minutes === 1 ? '1 minute' : minutes + ' minutes') + '</span></div>';
290 }
291 }
292 tooltip_html += '<br />';
293 // Add summary to the tooltip
294 if (d.summary.length <= 5) {
295 for (var /** @type {?} */ i = 0; i < d.summary.length; i++) {
296 tooltip_html += '<div><span><strong>' + d.summary[i].name + '</strong></span>';
297 tooltip_html += '<span>' + _this.formatTime(d.summary[i].value) + '</span></div>';
298 }
299
300 }
301 else {
302 for (var /** @type {?} */ i = 0; i < 5; i++) {
303 tooltip_html += '<div><span><strong>' + d.summary[i].name + '</strong></span>';
304 tooltip_html += '<span>' + _this.formatTime(d.summary[i].value) + '</span></div>';
305 }
306
307 tooltip_html += '<br />';
308 var /** @type {?} */ other_projects_sum = 0;
309 for (var /** @type {?} */ i = 5; i < d.summary.length; i++) {
310 other_projects_sum = +d.summary[i].value;
311 }
312
313 tooltip_html += '<div><span><strong>Other:</strong></span>';
314 tooltip_html += '<span>' + _this.formatTime(other_projects_sum) + '</span></div>';
315 }
316 // Calculate tooltip position
317 var /** @type {?} */ x = yearScale(d.date.year()) + _this.tooltip_padding * 2;
318 while (_this.width - x < (_this.tooltip_width + _this.tooltip_padding * 5)) {
319 x -= 10;
320 }
321 var /** @type {?} */ y = _this.tooltip_padding * 3;
322 // Show tooltip
323 _this.tooltip.html(tooltip_html)
324 .style('left', x + 'px')
325 .style('top', y + 'px')
326 .transition()
327 .duration(_this.transition_duration / 2)
328 .ease(easeLinear)
329 .style('opacity', 1);
330 })
331 .on('mouseout', function () {
332 if (_this.in_transition) {
333 return;
334 }
335 _this.hideTooltip();
336 })
337 .transition()
338 .delay(function (d, i) {
339 return _this.transition_duration * (i + 1) / 10;
340 })
341 .duration(function () {
342 return _this.transition_duration;
343 })
344 .ease(easeLinear)
345 .style('opacity', 1)
346 .call(function (transition, callback) {
347 if (transition.empty()) {
348 callback();
349 }
350 var /** @type {?} */ n = 0;
351 transition
352 .each(function () { ++n; })
353 .on('end', function () {
354 if (!--n) {
355 callback.apply(this, arguments);
356 }
357 });
358 }, function () {
359 _this.in_transition = false;
360 });
361 // Add year labels
362 this.labels.selectAll('.label-year').remove();
363 this.labels.selectAll('.label-year')
364 .data(year_labels)
365 .enter()
366 .append('text')
367 .attr('class', 'label label-year')
368 .attr('font-size', function () {
369 return Math.floor(_this.label_padding / 3) + 'px';
370 })
371 .text(function (d) {
372 return d.year();
373 })
374 .attr('x', function (d) {
375 return yearScale(d.year());
376 })
377 .attr('y', this.label_padding / 2)
378 .on('mouseenter', function (year_label) {
379 if (_this.in_transition) {
380 return;
381 }
382 _this.items.selectAll('.item-block-year')
383 .transition()
384 .duration(_this.transition_duration)
385 .ease(easeLinear)
386 .style('opacity', function (d) {
387 return (moment(d.date).year() === year_label.year()) ? 1 : 0.1;
388 });
389 })
390 .on('mouseout', function () {
391 if (_this.in_transition) {
392 return;
393 }
394 _this.items.selectAll('.item-block-year')
395 .transition()
396 .duration(_this.transition_duration)
397 .ease(easeLinear)
398 .style('opacity', 1);
399 })
400 .on('click', function (d) {
401 if (_this.in_transition) {
402 return;
403 }
404 // Set in_transition flag
405 _this.in_transition = true;
406 // Set selected month to the one clicked on
407 _this.selected = d;
408 // Hide tooltip
409 _this.hideTooltip();
410 // Remove all year overview related items and labels
411 _this.removeGlobalOverview();
412 // Redraw the chart
413 _this.overview = 'year';
414 _this.drawChart();
415 });
416 };
417
418 /**
419 * Draw year overview
420 * @return {?}
421 */
422 CalendarHeatmap.prototype.drawYearOverview = function () {
423 var _this = this;
424 // Add current overview to the history
425 if (this.history[this.history.length - 1] !== this.overview) {
426 this.history.push(this.overview);
427 }
428 // Define start and end date of the selected year
429 var /** @type {?} */ start_of_year = moment(this.selected['date']).startOf('year');
430 var /** @type {?} */ end_of_year = moment(this.selected['date']).endOf('year');
431 // Filter data down to the selected year
432 var /** @type {?} */ year_data = this.data.filter(function (d) {
433 return start_of_year <= moment(d.date) && moment(d.date) < end_of_year;
434 });
435 // Calculate max value of the year data
436 var /** @type {?} */ max_value = max(year_data, function (d) {
437 return d.total;
438 });
439 var /** @type {?} */ color = scaleLinear()
440 .range(['#ffffff', this.color])
441 .domain([-0.15 * max_value, max_value]);
442 this.items.selectAll('.item-circle').remove();
443 this.items.selectAll('.item-circle')
444 .data(year_data)
445 .enter()
446 .append('rect')
447 .attr('class', 'item item-circle')
448 .style('opacity', 0)
449 .attr('x', function (d) {
450 return _this.calcItemX(d, start_of_year) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
451 })
452 .attr('y', function (d) {
453 return _this.calcItemY(d) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
454 })
455 .attr('rx', function (d) {
456 return _this.calcItemSize(d, max_value);
457 })
458 .attr('ry', function (d) {
459 return _this.calcItemSize(d, max_value);
460 })
461 .attr('width', function (d) {
462 return _this.calcItemSize(d, max_value);
463 })
464 .attr('height', function (d) {
465 return _this.calcItemSize(d, max_value);
466 })
467 .attr('fill', function (d) {
468 return (d.total > 0) ? color(d.total) : 'transparent';
469 })
470 .on('click', function (d) {
471 if (_this.in_transition) {
472 return;
473 }
474 // Don't transition if there is no data to show
475 if (d.total === 0) {
476 return;
477 }
478 _this.in_transition = true;
479 // Set selected date to the one clicked on
480 _this.selected = d;
481 // Hide tooltip
482 _this.hideTooltip();
483 // Remove all year overview related items and labels
484 _this.removeYearOverview();
485 // Redraw the chart
486 _this.overview = 'day';
487 _this.drawChart();
488 })
489 .on('mouseover', function (d) {
490 if (_this.in_transition) {
491 return;
492 }
493 // Pulsating animation
494 var /** @type {?} */ circle = select(event.currentTarget);
495 var /** @type {?} */ repeat = function () {
496 circle.transition()
497 .duration(_this.transition_duration)
498 .ease(easeLinear)
499 .attr('x', function (d) {
500 return _this.calcItemX(d, start_of_year) - (_this.item_size * 1.1 - _this.item_size) / 2;
501 })
502 .attr('y', function (d) {
503 return _this.calcItemY(d) - (_this.item_size * 1.1 - _this.item_size) / 2;
504 })
505 .attr('width', _this.item_size * 1.1)
506 .attr('height', _this.item_size * 1.1)
507 .transition()
508 .duration(_this.transition_duration)
509 .ease(easeLinear)
510 .attr('x', function (d) {
511 return _this.calcItemX(d, start_of_year) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
512 })
513 .attr('y', function (d) {
514 return _this.calcItemY(d) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
515 })
516 .attr('width', function (d) {
517 return _this.calcItemSize(d, max_value);
518 })
519 .attr('height', function (d) {
520 return _this.calcItemSize(d, max_value);
521 })
522 .on('end', repeat);
523 };
524 repeat();
525 // Construct tooltip
526 var /** @type {?} */ tooltip_html = '';
527 tooltip_html += '<div class="header"><strong>' + (d.total ? _this.formatTime(d.total) : 'No time') + ' tracked</strong></div>';
528 tooltip_html += '<div>on ' + moment(d.date).format('dddd, MMM Do YYYY') + '</div><br>';
529 // Add summary to the tooltip
530 d.summary.map(function (d) {
531 tooltip_html += '<div><span><strong>' + d.name + '</strong></span>';
532 tooltip_html += '<span>' + _this.formatTime(d.value) + '</span></div>';
533 });
534 // Calculate tooltip position
535 var /** @type {?} */ x = _this.calcItemX(d, start_of_year) + _this.item_size;
536 if (_this.width - x < (_this.tooltip_width + _this.tooltip_padding * 3)) {
537 x -= _this.tooltip_width + _this.tooltip_padding * 2;
538 }
539 var /** @type {?} */ y = _this.calcItemY(d) + _this.item_size;
540 // Show tooltip
541 _this.tooltip.html(tooltip_html)
542 .style('left', x + 'px')
543 .style('top', y + 'px')
544 .transition()
545 .duration(_this.transition_duration / 2)
546 .ease(easeLinear)
547 .style('opacity', 1);
548 })
549 .on('mouseout', function () {
550 if (_this.in_transition) {
551 return;
552 }
553 // Set circle radius back to what it's supposed to be
554 select(event.currentTarget).transition()
555 .duration(_this.transition_duration / 2)
556 .ease(easeLinear)
557 .attr('x', function (d) {
558 return _this.calcItemX(d, start_of_year) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
559 })
560 .attr('y', function (d) {
561 return _this.calcItemY(d) + (_this.item_size - _this.calcItemSize(d, max_value)) / 2;
562 })
563 .attr('width', function (d) {
564 return _this.calcItemSize(d, max_value);
565 })
566 .attr('height', function (d) {
567 return _this.calcItemSize(d, max_value);
568 });
569 // Hide tooltip
570 _this.hideTooltip();
571 })
572 .transition()
573 .delay(function () {
574 return (Math.cos(Math.PI * Math.random()) + 1) * _this.transition_duration;
575 })
576 .duration(function () {
577 return _this.transition_duration;
578 })
579 .ease(easeLinear)
580 .style('opacity', 1)
581 .call(function (transition, callback) {
582 if (transition.empty()) {
583 callback();
584 }
585 var /** @type {?} */ n = 0;
586 transition
587 .each(function () { ++n; })
588 .on('end', function () {
589 if (!--n) {
590 callback.apply(this, arguments);
591 }
592 });
593 }, function () {
594 _this.in_transition = false;
595 });
596 // Add month labels
597 var /** @type {?} */ month_labels = timeMonths(start_of_year.toDate(), end_of_year.toDate());
598 var /** @type {?} */ monthScale = scaleLinear()
599 .range([0, this.width])
600 .domain([0, month_labels.length]);
601 this.labels.selectAll('.label-month').remove();
602 this.labels.selectAll('.label-month')
603 .data(month_labels)
604 .enter()
605 .append('text')
606 .attr('class', 'label label-month')
607 .attr('font-size', function () {
608 return Math.floor(_this.label_padding / 3) + 'px';
609 })
610 .text(function (d) {
611 return d.toLocaleDateString('en-us', { month: 'short' });
612 })
613 .attr('x', function (d, i) {
614 return monthScale(i) + (monthScale(i) - monthScale(i - 1)) / 2;
615 })
616 .attr('y', this.label_padding / 2)
617 .on('mouseenter', function (d) {
618 if (_this.in_transition) {
619 return;
620 }
621 var /** @type {?} */ selected_month = moment(d);
622 _this.items.selectAll('.item-circle')
623 .transition()
624 .duration(_this.transition_duration)
625 .ease(easeLinear)
626 .style('opacity', function (d) {
627 return moment(d.date).isSame(selected_month, 'month') ? 1 : 0.1;
628 });
629 })
630 .on('mouseout', function () {
631 if (_this.in_transition) {
632 return;
633 }
634 _this.items.selectAll('.item-circle')
635 .transition()
636 .duration(_this.transition_duration)
637 .ease(easeLinear)
638 .style('opacity', 1);
639 })
640 .on('click', function (d) {
641 if (_this.in_transition) {
642 return;
643 }
644 // Check month data
645 var /** @type {?} */ month_data = _this.data.filter(function (e) {
646 return moment(d).startOf('month') <= moment(e.date) && moment(e.date) < moment(d).endOf('month');
647 });
648 // Don't transition if there is no data to show
649 if (!month_data.length) {
650 return;
651 }
652 // Set selected month to the one clicked on
653 _this.selected = { date: d };
654 _this.in_transition = true;
655 // Hide tooltip
656 _this.hideTooltip();
657 // Remove all year overview related items and labels
658 _this.removeYearOverview();
659 // Redraw the chart
660 _this.overview = 'month';
661 _this.drawChart();
662 });
663 // Add day labels
664 var /** @type {?} */ day_labels = timeDays(moment().startOf('week').toDate(), moment().endOf('week').toDate());
665 var /** @type {?} */ dayScale = scaleBand()
666 .rangeRound([this.label_padding, this.height])
667 .domain(day_labels.map(function (d) {
668 return moment(d).weekday().toString();
669 }));
670 this.labels.selectAll('.label-day').remove();
671 this.labels.selectAll('.label-day')
672 .data(day_labels)
673 .enter()
674 .append('text')
675 .attr('class', 'label label-day')
676 .attr('x', this.label_padding / 3)
677 .attr('y', function (d, i) {
678 return dayScale((i).toString()) + dayScale.bandwidth() / 1.75;
679 })
680 .style('text-anchor', 'left')
681 .attr('font-size', function () {
682 return Math.floor(_this.label_padding / 3) + 'px';
683 })
684 .text(function (d) {
685 return moment(d).format('dddd')[0];
686 })
687 .on('mouseenter', function (d) {
688 if (_this.in_transition) {
689 return;
690 }
691 var /** @type {?} */ selected_day = moment(d);
692 _this.items.selectAll('.item-circle')
693 .transition()
694 .duration(_this.transition_duration)
695 .ease(easeLinear)
696 .style('opacity', function (d) {
697 return (moment(d.date).day() === selected_day.day()) ? 1 : 0.1;
698 });
699 })
700 .on('mouseout', function () {
701 if (_this.in_transition) {
702 return;
703 }
704 _this.items.selectAll('.item-circle')
705 .transition()
706 .duration(_this.transition_duration)
707 .ease(easeLinear)
708 .style('opacity', 1);
709 });
710 // Add button to switch back to previous overview
711 this.drawButton();
712 };
713
714 /**
715 * Draw month overview
716 * @return {?}
717 */
718 CalendarHeatmap.prototype.drawMonthOverview = function () {
719 var _this = this;
720 // Add current overview to the history
721 if (this.history[this.history.length - 1] !== this.overview) {
722 this.history.push(this.overview);
723 }
724 // Define beginning and end of the month
725 var /** @type {?} */ start_of_month = moment(this.selected['date']).startOf('month');
726 var /** @type {?} */ end_of_month = moment(this.selected['date']).endOf('month');
727 // Filter data down to the selected month
728 var /** @type {?} */ month_data = this.data.filter(function (d) {
729 return start_of_month <= moment(d.date) && moment(d.date) < end_of_month;
730 });
731 var /** @type {?} */ max_value = max(month_data, function (d) {
732 return max(d.summary, function (d) {
733 return +d.value;
734 });
735 });
736 // Define day labels and axis
737 var /** @type {?} */ day_labels = timeDays(moment().startOf('week').toDate(), moment().endOf('week').toDate());
738 var /** @type {?} */ dayScale = scaleBand()
739 .rangeRound([this.label_padding, this.height])
740 .domain(day_labels.map(function (d) {
741 return moment(d).weekday().toString();
742 }));
743 // Define week labels and axis
744 var /** @type {?} */ week_labels = [start_of_month.clone()];
745 while (start_of_month.week() !== end_of_month.week()) {
746 week_labels.push(start_of_month.add(1, 'week').clone());
747 }
748 var /** @type {?} */ weekScale = scaleBand()
749 .rangeRound([this.label_padding, this.width])
750 .padding(0.05)
751 .domain(week_labels.map(function (weekday) {
752 return weekday.week().toString();
753 }));
754 // Add month data items to the overview
755 this.items.selectAll('.item-block-month').remove();
756 var /** @type {?} */ item_block = this.items.selectAll('.item-block-month')
757 .data(month_data)
758 .enter()
759 .append('g')
760 .attr('class', 'item item-block-month')
761 .attr('width', function () {
762 return (_this.width - _this.label_padding) / week_labels.length - _this.gutter * 5;
763 })
764 .attr('height', function () {
765 return Math.min(dayScale.bandwidth(), _this.max_block_height);
766 })
767 .attr('transform', function (d) {
768 return 'translate(' + weekScale(moment(d.date).week().toString()) + ',' + ((dayScale(moment(d.date).weekday().toString()) + dayScale.bandwidth() / 1.75) - 15) + ')';
769 })
770 .attr('total', function (d) {
771 return d.total;
772 })
773 .attr('date', function (d) {
774 return d.date;
775 })
776 .attr('offset', 0)
777 .on('click', function (d) {
778 if (_this.in_transition) {
779 return;
780 }
781 // Don't transition if there is no data to show
782 if (d.total === 0) {
783 return;
784 }
785 _this.in_transition = true;
786 // Set selected date to the one clicked on
787 _this.selected = d;
788 // Hide tooltip
789 _this.hideTooltip();
790 // Remove all month overview related items and labels
791 _this.removeMonthOverview();
792 // Redraw the chart
793 _this.overview = 'day';
794 _this.drawChart();
795 });
796 var /** @type {?} */ item_width = (this.width - this.label_padding) / week_labels.length - this.gutter * 5;
797 var /** @type {?} */ itemScale = scaleLinear()
798 .rangeRound([0, item_width]);
799 var /** @type {?} */ item_gutter = this.item_gutter;
800 item_block.selectAll('.item-block-rect')
801 .data(function (d) {
802 return d.summary;
803 })
804 .enter()
805 .append('rect')
806 .attr('class', 'item item-block-rect')
807 .attr('x', function (d) {
808 var /** @type {?} */ total = parseInt(select(this.parentNode).attr('total'));
809 var /** @type {?} */ offset = parseInt(select(this.parentNode).attr('offset'));
810 itemScale.domain([0, total]);
811 select(this.parentNode).attr('offset', offset + itemScale(d.value));
812 return offset;
813 })
814 .attr('width', function (d) {
815 var /** @type {?} */ total = parseInt(select(this.parentNode).attr('total'));
816 itemScale.domain([0, total]);
817 return Math.max((itemScale(d.value) - item_gutter), 1);
818 })
819 .attr('height', function () {
820 return Math.min(dayScale.bandwidth(), _this.max_block_height);
821 })
822 .attr('fill', function (d) {
823 var /** @type {?} */ color = scaleLinear()
824 .range(['#ffffff', _this.color])
825 .domain([-0.15 * max_value, max_value]);
826 return color(d.value) || '#ff4500';
827 })
828 .style('opacity', 0)
829 .on('mouseover', function (d) {
830 if (_this.in_transition) {
831 return;
832 }
833 // Get date from the parent node
834 var /** @type {?} */ date = new Date(select(event.currentTarget.parentNode).attr('date'));
835 // Construct tooltip
836 var /** @type {?} */ tooltip_html = '';
837 tooltip_html += '<div class="header"><strong>' + d.name + '</strong></div><br>';
838 tooltip_html += '<div><strong>' + (d.value ? _this.formatTime(d.value) : 'No time') + ' tracked</strong></div>';
839 tooltip_html += '<div>on ' + moment(date).format('dddd, MMM Do YYYY') + '</div>';
840 // Calculate tooltip position
841 var /** @type {?} */ x = weekScale(moment(date).week().toString()) + _this.tooltip_padding;
842 while (_this.width - x < (_this.tooltip_width + _this.tooltip_padding * 3)) {
843 x -= 10;
844 }
845 var /** @type {?} */ y = dayScale(moment(date).weekday().toString()) + _this.tooltip_padding * 2;
846 // Show tooltip
847 _this.tooltip.html(tooltip_html)
848 .style('left', x + 'px')
849 .style('top', y + 'px')
850 .transition()
851 .duration(_this.transition_duration / 2)
852 .ease(easeLinear)
853 .style('opacity', 1);
854 })
855 .on('mouseout', function () {
856 if (_this.in_transition) {
857 return;
858 }
859 _this.hideTooltip();
860 })
861 .transition()
862 .delay(function () {
863 return (Math.cos(Math.PI * Math.random()) + 1) * _this.transition_duration;
864 })
865 .duration(function () {
866 return _this.transition_duration;
867 })
868 .ease(easeLinear)
869 .style('opacity', 1)
870 .call(function (transition, callback) {
871 if (transition.empty()) {
872 callback();
873 }
874 var /** @type {?} */ n = 0;
875 transition
876 .each(function () { ++n; })
877 .on('end', function () {
878 if (!--n) {
879 callback.apply(this, arguments);
880 }
881 });
882 }, function () {
883 _this.in_transition = false;
884 });
885 // Add week labels
886 this.labels.selectAll('.label-week').remove();
887 this.labels.selectAll('.label-week')
888 .data(week_labels)
889 .enter()
890 .append('text')
891 .attr('class', 'label label-week')
892 .attr('font-size', function () {
893 return Math.floor(_this.label_padding / 3) + 'px';
894 })
895 .text(function (d) {
896 return 'Week ' + d.week();
897 })
898 .attr('x', function (d) {
899 return weekScale(d.week());
900 })
901 .attr('y', this.label_padding / 2)
902 .on('mouseenter', function (weekday) {
903 if (_this.in_transition) {
904 return;
905 }
906 _this.items.selectAll('.item-block-month')
907 .transition()
908 .duration(_this.transition_duration)
909 .ease(easeLinear)
910 .style('opacity', function (d) {
911 return (moment(d.date).week() === weekday.week()) ? 1 : 0.1;
912 });
913 })
914 .on('mouseout', function () {
915 if (_this.in_transition) {
916 return;
917 }
918 _this.items.selectAll('.item-block-month')
919 .transition()
920 .duration(_this.transition_duration)
921 .ease(easeLinear)
922 .style('opacity', 1);
923 })
924 .on('click', function (d) {
925 if (_this.in_transition) {
926 return;
927 }
928 // Check week data
929 var /** @type {?} */ week_data = _this.data.filter(function (e) {
930 return d.startOf('week') <= moment(e.date) && moment(e.date) < d.endOf('week');
931 });
932 // Don't transition if there is no data to show
933 if (!week_data.length) {
934 return;
935 }
936 _this.in_transition = true;
937 // Set selected month to the one clicked on
938 _this.selected = { date: d };
939 // Hide tooltip
940 _this.hideTooltip();
941 // Remove all year overview related items and labels
942 _this.removeMonthOverview();
943 // Redraw the chart
944 _this.overview = 'week';
945 _this.drawChart();
946 });
947 // Add day labels
948 this.labels.selectAll('.label-day').remove();
949 this.labels.selectAll('.label-day')
950 .data(day_labels)
951 .enter()
952 .append('text')
953 .attr('class', 'label label-day')
954 .attr('x', this.label_padding / 3)
955 .attr('y', function (d, i) {
956 return dayScale(i) + dayScale.bandwidth() / 1.75;
957 })
958 .style('text-anchor', 'left')
959 .attr('font-size', function () {
960 return Math.floor(_this.label_padding / 3) + 'px';
961 })
962 .text(function (d) {
963 return moment(d).format('dddd')[0];
964 })
965 .on('mouseenter', function (d) {
966 if (_this.in_transition) {
967 return;
968 }
969 var /** @type {?} */ selected_day = moment(d);
970 _this.items.selectAll('.item-block-month')
971 .transition()
972 .duration(_this.transition_duration)
973 .ease(easeLinear)
974 .style('opacity', function (d) {
975 return (moment(d.date).day() === selected_day.day()) ? 1 : 0.1;
976 });
977 })
978 .on('mouseout', function () {
979 if (_this.in_transition) {
980 return;
981 }
982 _this.items.selectAll('.item-block-month')
983 .transition()
984 .duration(_this.transition_duration)
985 .ease(easeLinear)
986 .style('opacity', 1);
987 });
988 // Add button to switch back to previous overview
989 this.drawButton();
990 };
991
992 /**
993 * Draw week overview
994 * @return {?}
995 */
996 CalendarHeatmap.prototype.drawWeekOverview = function () {
997 var _this = this;
998 // Add current overview to the history
999 if (this.history[this.history.length - 1] !== this.overview) {
1000 this.history.push(this.overview);
1001 }
1002 // Define beginning and end of the week
1003 var /** @type {?} */ start_of_week = moment(this.selected['date']).startOf('week');
1004 var /** @type {?} */ end_of_week = moment(this.selected['date']).endOf('week');
1005 // Filter data down to the selected week
1006 var /** @type {?} */ week_data = this.data.filter(function (d) {
1007 return start_of_week <= moment(d.date) && moment(d.date) < end_of_week;
1008 });
1009 var /** @type {?} */ max_value = max(week_data, function (d) {
1010 return max(d.summary, function (d) {
1011 return +d.value;
1012 });
1013 });
1014 // Define day labels and axis
1015 var /** @type {?} */ day_labels = timeDays(moment().startOf('week').toDate(), moment().endOf('week').toDate());
1016 var /** @type {?} */ dayScale = scaleBand()
1017 .rangeRound([this.label_padding, this.height])
1018 .domain(day_labels.map(function (d) {
1019 return moment(d).weekday().toString();
1020 }));
1021 // Define week labels and axis
1022 var /** @type {?} */ week_labels = [start_of_week];
1023 var /** @type {?} */ weekScale = scaleBand()
1024 .rangeRound([this.label_padding, this.width])
1025 .padding(0.01)
1026 .domain(week_labels.map(function (weekday) {
1027 return weekday.week();
1028 }));
1029 // Add week data items to the overview
1030 this.items.selectAll('.item-block-week').remove();
1031 var /** @type {?} */ item_block = this.items.selectAll('.item-block-week')
1032 .data(week_data)
1033 .enter()
1034 .append('g')
1035 .attr('class', 'item item-block-week')
1036 .attr('width', function () {
1037 return (_this.width - _this.label_padding) / week_labels.length - _this.gutter * 5;
1038 })
1039 .attr('height', function () {
1040 return Math.min(dayScale.bandwidth(), _this.max_block_height);
1041 })
1042 .attr('transform', function (d) {
1043 return 'translate(' + weekScale(moment(d.date).week().toString()) + ',' + ((dayScale(moment(d.date).weekday().toString()) + dayScale.bandwidth() / 1.75) - 15) + ')';
1044 })
1045 .attr('total', function (d) {
1046 return d.total;
1047 })
1048 .attr('date', function (d) {
1049 return d.date;
1050 })
1051 .attr('offset', 0)
1052 .on('click', function (d) {
1053 if (_this.in_transition) {
1054 return;
1055 }
1056 // Don't transition if there is no data to show
1057 if (d.total === 0) {
1058 return;
1059 }
1060 _this.in_transition = true;
1061 // Set selected date to the one clicked on
1062 _this.selected = d;
1063 // Hide tooltip
1064 _this.hideTooltip();
1065 // Remove all week overview related items and labels
1066 _this.removeWeekOverview();
1067 // Redraw the chart
1068 _this.overview = 'day';
1069 _this.drawChart();
1070 });
1071 var /** @type {?} */ item_width = (this.width - this.label_padding) / week_labels.length - this.gutter * 5;
1072 var /** @type {?} */ itemScale = scaleLinear()
1073 .rangeRound([0, item_width]);
1074 var /** @type {?} */ item_gutter = this.item_gutter;
1075 item_block.selectAll('.item-block-rect')
1076 .data(function (d) {
1077 return d.summary;
1078 })
1079 .enter()
1080 .append('rect')
1081 .attr('class', 'item item-block-rect')
1082 .attr('x', function (d) {
1083 var /** @type {?} */ total = parseInt(select(this.parentNode).attr('total'));
1084 var /** @type {?} */ offset = parseInt(select(this.parentNode).attr('offset'));
1085 itemScale.domain([0, total]);
1086 select(this.parentNode).attr('offset', offset + itemScale(d.value));
1087 return offset;
1088 })
1089 .attr('width', function (d) {
1090 var /** @type {?} */ total = parseInt(select(this.parentNode).attr('total'));
1091 itemScale.domain([0, total]);
1092 return Math.max((itemScale(d.value) - item_gutter), 1);
1093 })
1094 .attr('height', function () {
1095 return Math.min(dayScale.bandwidth(), _this.max_block_height);
1096 })
1097 .attr('fill', function (d) {
1098 var /** @type {?} */ color = scaleLinear()
1099 .range(['#ffffff', _this.color])
1100 .domain([-0.15 * max_value, max_value]);
1101 return color(d.value) || '#ff4500';
1102 })
1103 .style('opacity', 0)
1104 .on('mouseover', function (d) {
1105 if (_this.in_transition) {
1106 return;
1107 }
1108 // Get date from the parent node
1109 var /** @type {?} */ date = new Date(select(event.currentTarget.parentNode).attr('date'));
1110 // Construct tooltip
1111 var /** @type {?} */ tooltip_html = '';
1112 tooltip_html += '<div class="header"><strong>' + d.name + '</strong></div><br>';
1113 tooltip_html += '<div><strong>' + (d.value ? _this.formatTime(d.value) : 'No time') + ' tracked</strong></div>';
1114 tooltip_html += '<div>on ' + moment(date).format('dddd, MMM Do YYYY') + '</div>';
1115 // Calculate tooltip position
1116 var /** @type {?} */ total = parseInt(select(event.currentTarget.parentNode).attr('total'));
1117 itemScale.domain([0, total]);
1118 var /** @type {?} */ x = parseInt(select(event.currentTarget).attr('x')) + itemScale(d.value) / 4 + _this.tooltip_width / 4;
1119 while (_this.width - x < (_this.tooltip_width + _this.tooltip_padding * 3)) {
1120 x -= 10;
1121 }
1122 var /** @type {?} */ y = dayScale(moment(date).weekday().toString()) + _this.tooltip_padding * 1.5;
1123 // Show tooltip
1124 _this.tooltip.html(tooltip_html)
1125 .style('left', x + 'px')
1126 .style('top', y + 'px')
1127 .transition()
1128 .duration(_this.transition_duration / 2)
1129 .ease(easeLinear)
1130 .style('opacity', 1);
1131 })
1132 .on('mouseout', function () {
1133 if (_this.in_transition) {
1134 return;
1135 }
1136 _this.hideTooltip();
1137 })
1138 .transition()
1139 .delay(function () {
1140 return (Math.cos(Math.PI * Math.random()) + 1) * _this.transition_duration;
1141 })
1142 .duration(function () {
1143 return _this.transition_duration;
1144 })
1145 .ease(easeLinear)
1146 .style('opacity', 1)
1147 .call(function (transition, callback) {
1148 if (transition.empty()) {
1149 callback();
1150 }
1151 var /** @type {?} */ n = 0;
1152 transition
1153 .each(function () { ++n; })
1154 .on('end', function () {
1155 if (!--n) {
1156 callback.apply(this, arguments);
1157 }
1158 });
1159 }, function () {
1160 _this.in_transition = false;
1161 });
1162 // Add week labels
1163 this.labels.selectAll('.label-week').remove();
1164 this.labels.selectAll('.label-week')
1165 .data(week_labels)
1166 .enter()
1167 .append('text')
1168 .attr('class', 'label label-week')
1169 .attr('font-size', function () {
1170 return Math.floor(_this.label_padding / 3) + 'px';
1171 })
1172 .text(function (d) {
1173 return 'Week ' + d.week();
1174 })
1175 .attr('x', function (d) {
1176 return weekScale(d.week());
1177 })
1178 .attr('y', this.label_padding / 2)
1179 .on('mouseenter', function (weekday) {
1180 if (_this.in_transition) {
1181 return;
1182 }
1183 _this.items.selectAll('.item-block-week')
1184 .transition()
1185 .duration(_this.transition_duration)
1186 .ease(easeLinear)
1187 .style('opacity', function (d) {
1188 return (moment(d.date).week() === weekday.week()) ? 1 : 0.1;
1189 });
1190 })
1191 .on('mouseout', function () {
1192 if (_this.in_transition) {
1193 return;
1194 }
1195 _this.items.selectAll('.item-block-week')
1196 .transition()
1197 .duration(_this.transition_duration)
1198 .ease(easeLinear)
1199 .style('opacity', 1);
1200 });
1201 // Add day labels
1202 this.labels.selectAll('.label-day').remove();
1203 this.labels.selectAll('.label-day')
1204 .data(day_labels)
1205 .enter()
1206 .append('text')
1207 .attr('class', 'label label-day')
1208 .attr('x', this.label_padding / 3)
1209 .attr('y', function (d, i) {
1210 return dayScale((i).toString()) + dayScale.bandwidth() / 1.75;
1211 })
1212 .style('text-anchor', 'left')
1213 .attr('font-size', function () {
1214 return Math.floor(_this.label_padding / 3) + 'px';
1215 })
1216 .text(function (d) {
1217 return moment(d).format('dddd')[0];
1218 })
1219 .on('mouseenter', function (d) {
1220 if (_this.in_transition) {
1221 return;
1222 }
1223 var /** @type {?} */ selected_day = moment(d);
1224 _this.items.selectAll('.item-block-week')
1225 .transition()
1226 .duration(_this.transition_duration)
1227 .ease(easeLinear)
1228 .style('opacity', function (d) {
1229 return (moment(d.date).day() === selected_day.day()) ? 1 : 0.1;
1230 });
1231 })
1232 .on('mouseout', function () {
1233 if (_this.in_transition) {
1234 return;
1235 }
1236 _this.items.selectAll('.item-block-week')
1237 .transition()
1238 .duration(_this.transition_duration)
1239 .ease(easeLinear)
1240 .style('opacity', 1);
1241 });
1242 // Add button to switch back to previous overview
1243 this.drawButton();
1244 };
1245
1246 /**
1247 * Draw day overview
1248 * @return {?}
1249 */
1250 CalendarHeatmap.prototype.drawDayOverview = function () {
1251 var _this = this;
1252 // Add current overview to the history
1253 if (this.history[this.history.length - 1] !== this.overview) {
1254 this.history.push(this.overview);
1255 }
1256 // Initialize selected date to today if it was not set
1257 if (!Object.keys(this.selected).length) {
1258 this.selected = this.data[this.data.length - 1];
1259 }
1260 var /** @type {?} */ project_labels = this.selected['summary'].map(function (project) {
1261 return project.name;
1262 });
1263 var /** @type {?} */ projectScale = scaleBand()
1264 .rangeRound([this.label_padding, this.height])
1265 .domain(project_labels);
1266 var /** @type {?} */ itemScale = scaleTime()
1267 .range([this.label_padding * 2, this.width])
1268 .domain([moment(this.selected['date']).startOf('day'), moment(this.selected['date']).endOf('day')]);
1269 this.items.selectAll('.item-block').remove();
1270 this.items.selectAll('.item-block')
1271 .data(this.selected['details'])
1272 .enter()
1273 .append('rect')
1274 .attr('class', 'item item-block')
1275 .attr('x', function (d) {
1276 return itemScale(moment(d.date));
1277 })
1278 .attr('y', function (d) {
1279 return (projectScale(d.name) + projectScale.bandwidth() / 2) - 15;
1280 })
1281 .attr('width', function (d) {
1282 var /** @type {?} */ end = itemScale(timeSecond.offset(moment(d.date).toDate(), d.value));
1283 return Math.max((end - itemScale(moment(d.date))), 1);
1284 })
1285 .attr('height', function () {
1286 return Math.min(projectScale.bandwidth(), _this.max_block_height);
1287 })
1288 .attr('fill', function () {
1289 return _this.color;
1290 })
1291 .style('opacity', 0)
1292 .on('mouseover', function (d) {
1293 if (_this.in_transition) {
1294 return;
1295 }
1296 // Construct tooltip
1297 var /** @type {?} */ tooltip_html = '';
1298 tooltip_html += '<div class="header"><strong>' + d.name + '</strong><div><br>';
1299 tooltip_html += '<div><strong>' + (d.value ? _this.formatTime(d.value) : 'No time') + ' tracked</strong></div>';
1300 tooltip_html += '<div>on ' + moment(d.date).format('dddd, MMM Do YYYY HH:mm') + '</div>';
1301 // Calculate tooltip position
1302 var /** @type {?} */ x = d.value * 100 / (60 * 60 * 24) + itemScale(moment(d.date));
1303 while (_this.width - x < (_this.tooltip_width + _this.tooltip_padding * 3)) {
1304 x -= 10;
1305 }
1306 var /** @type {?} */ y = projectScale(d.name) + projectScale.bandwidth() / 2 + _this.tooltip_padding / 2;
1307 // Show tooltip
1308 _this.tooltip.html(tooltip_html)
1309 .style('left', x + 'px')
1310 .style('top', y + 'px')
1311 .transition()
1312 .duration(_this.transition_duration / 2)
1313 .ease(easeLinear)
1314 .style('opacity', 1);
1315 })
1316 .on('mouseout', function () {
1317 if (_this.in_transition) {
1318 return;
1319 }
1320 _this.hideTooltip();
1321 })
1322 .on('click', function (d) {
1323 if (_this.handler) {
1324 _this.handler.emit(d);
1325 }
1326 })
1327 .transition()
1328 .delay(function () {
1329 return (Math.cos(Math.PI * Math.random()) + 1) * _this.transition_duration;
1330 })
1331 .duration(function () {
1332 return _this.transition_duration;
1333 })
1334 .ease(easeLinear)
1335 .style('opacity', 0.5)
1336 .call(function (transition, callback) {
1337 if (transition.empty()) {
1338 callback();
1339 }
1340 var /** @type {?} */ n = 0;
1341 transition
1342 .each(function () { ++n; })
1343 .on('end', function () {
1344 if (!--n) {
1345 callback.apply(this, arguments);
1346 }
1347 });
1348 }, function () {
1349 _this.in_transition = false;
1350 });
1351 // Add time labels
1352 var /** @type {?} */ timeLabels = timeHours(moment(this.selected['date']).startOf('day').toDate(), moment(this.selected['date']).endOf('day').toDate());
1353 var /** @type {?} */ timeScale = scaleTime()
1354 .range([this.label_padding * 2, this.width])
1355 .domain([0, timeLabels.length]);
1356 this.labels.selectAll('.label-time').remove();
1357 this.labels.selectAll('.label-time')
1358 .data(timeLabels)
1359 .enter()
1360 .append('text')
1361 .attr('class', 'label label-time')
1362 .attr('font-size', function () {
1363 return Math.floor(_this.label_padding / 3) + 'px';
1364 })
1365 .text(function (d) {
1366 return moment(d).format('HH:mm');
1367 })
1368 .attr('x', function (d, i) {
1369 return timeScale(i);
1370 })
1371 .attr('y', this.label_padding / 2)
1372 .on('mouseenter', function (d) {
1373 if (_this.in_transition) {
1374 return;
1375 }
1376 var /** @type {?} */ selected = itemScale(moment(d));
1377 _this.items.selectAll('.item-block')
1378 .transition()
1379 .duration(_this.transition_duration)
1380 .ease(easeLinear)
1381 .style('opacity', function (d) {
1382 var /** @type {?} */ start = itemScale(moment(d.date));
1383 var /** @type {?} */ end = itemScale(moment(d.date).add(d.value, 'seconds'));
1384 return (selected >= start && selected <= end) ? 1 : 0.1;
1385 });
1386 })
1387 .on('mouseout', function () {
1388 if (_this.in_transition) {
1389 return;
1390 }
1391 _this.items.selectAll('.item-block')
1392 .transition()
1393 .duration(_this.transition_duration)
1394 .ease(easeLinear)
1395 .style('opacity', 0.5);
1396 });
1397 // Add project labels
1398 var /** @type {?} */ label_padding = this.label_padding;
1399 this.labels.selectAll('.label-project').remove();
1400 this.labels.selectAll('.label-project')
1401 .data(project_labels)
1402 .enter()
1403 .append('text')
1404 .attr('class', 'label label-project')
1405 .attr('x', this.gutter)
1406 .attr('y', function (d) {
1407 return projectScale(d) + projectScale.bandwidth() / 2;
1408 })
1409 .attr('min-height', function () {
1410 return projectScale.bandwidth();
1411 })
1412 .style('text-anchor', 'left')
1413 .attr('font-size', function () {
1414 return Math.floor(_this.label_padding / 3) + 'px';
1415 })
1416 .text(function (d) {
1417 return d;
1418 })
1419 .each(function (d, i) {
1420 var /** @type {?} */ obj = select(this), /** @type {?} */ text_length = obj.node().getComputedTextLength(), /** @type {?} */ text = obj.text();
1421 while (text_length > (label_padding * 1.5) && text.length > 0) {
1422 text = text.slice(0, -1);
1423 obj.text(text + '...');
1424 text_length = obj.node().getComputedTextLength();
1425 }
1426 })
1427 .on('mouseenter', function (project) {
1428 if (_this.in_transition) {
1429 return;
1430 }
1431 _this.items.selectAll('.item-block')
1432 .transition()
1433 .duration(_this.transition_duration)
1434 .ease(easeLinear)
1435 .style('opacity', function (d) {
1436 return (d.name === project) ? 1 : 0.1;
1437 });
1438 })
1439 .on('mouseout', function () {
1440 if (_this.in_transition) {
1441 return;
1442 }
1443 _this.items.selectAll('.item-block')
1444 .transition()
1445 .duration(_this.transition_duration)
1446 .ease(easeLinear)
1447 .style('opacity', 0.5);
1448 });
1449 // Add button to switch back to previous overview
1450 this.drawButton();
1451 };
1452
1453 /**
1454 * Helper function to calculate item position on the x-axis
1455 * @param {?} d object
1456 * @param {?} start_of_year
1457 * @return {?}
1458 */
1459 CalendarHeatmap.prototype.calcItemX = function (d, start_of_year) {
1460 var /** @type {?} */ date = moment(d.date);
1461 var /** @type {?} */ dayIndex = Math.round((+date - +moment(start_of_year).startOf('week')) / 86400000);
1462 var /** @type {?} */ colIndex = Math.trunc(dayIndex / 7);
1463 return colIndex * (this.item_size + this.gutter) + this.label_padding;
1464 };
1465
1466 /**
1467 * Helper function to calculate item position on the y-axis
1468 * @param {?} d object
1469 * @return {?}
1470 */
1471 CalendarHeatmap.prototype.calcItemY = function (d) {
1472 return this.label_padding + moment(d.date).weekday() * (this.item_size + this.gutter);
1473 };
1474
1475 /**
1476 * Helper function to calculate item size
1477 * @param {?} d object
1478 * @param {?} max number
1479 * @return {?}
1480 */
1481 CalendarHeatmap.prototype.calcItemSize = function (d, max$$1) {
1482 if (max$$1 <= 0) {
1483 return this.item_size;
1484 }
1485 return this.item_size * 0.75 + (this.item_size * d.total / max$$1) * 0.25;
1486 };
1487
1488 /**
1489 * Draw the button for navigation purposes
1490 * @return {?}
1491 */
1492 CalendarHeatmap.prototype.drawButton = function () {
1493 var _this = this;
1494 this.buttons.selectAll('.button').remove();
1495 var /** @type {?} */ button = this.buttons.append('g')
1496 .attr('class', 'button button-back')
1497 .style('opacity', 0)
1498 .on('click', function () {
1499 if (_this.in_transition) {
1500 return;
1501 }
1502 // Set transition boolean
1503 _this.in_transition = true;
1504 // Clean the canvas from whichever overview type was on
1505 if (_this.overview === 'year') {
1506 _this.removeYearOverview();
1507 }
1508 else if (_this.overview === 'month') {
1509 _this.removeMonthOverview();
1510 }
1511 else if (_this.overview === 'week') {
1512 _this.removeWeekOverview();
1513 }
1514 else if (_this.overview === 'day') {
1515 _this.removeDayOverview();
1516 }
1517 // Redraw the chart
1518 _this.history.pop();
1519 _this.overview = _this.history.pop();
1520 _this.drawChart();
1521 });
1522 button.append('circle')
1523 .attr('cx', this.label_padding / 2.25)
1524 .attr('cy', this.label_padding / 2.5)
1525 .attr('r', this.item_size / 2);
1526 button.append('text')
1527 .attr('x', this.label_padding / 2.25)
1528 .attr('y', this.label_padding / 2.5)
1529 .attr('dy', function () {
1530 return Math.floor(_this.width / 100) / 3;
1531 })
1532 .attr('font-size', function () {
1533 return Math.floor(_this.label_padding / 3) + 'px';
1534 })
1535 .html('&#x2190;');
1536 button.transition()
1537 .duration(this.transition_duration)
1538 .ease(easeLinear)
1539 .style('opacity', 1);
1540 };
1541
1542 /**
1543 * Transition and remove items and labels related to global overview
1544 * @return {?}
1545 */
1546 CalendarHeatmap.prototype.removeGlobalOverview = function () {
1547 this.items.selectAll('.item-block-year')
1548 .transition()
1549 .duration(this.transition_duration)
1550 .ease(easeLinear)
1551 .style('opacity', 0)
1552 .remove();
1553 this.labels.selectAll('.label-year').remove();
1554 };
1555
1556 /**
1557 * Transition and remove items and labels related to year overview
1558 * @return {?}
1559 */
1560 CalendarHeatmap.prototype.removeYearOverview = function () {
1561 this.items.selectAll('.item-circle')
1562 .transition()
1563 .duration(this.transition_duration)
1564 .ease(easeLinear)
1565 .style('opacity', 0)
1566 .remove();
1567 this.labels.selectAll('.label-day').remove();
1568 this.labels.selectAll('.label-month').remove();
1569 this.hideBackButton();
1570 };
1571
1572 /**
1573 * Transition and remove items and labels related to month overview
1574 * @return {?}
1575 */
1576 CalendarHeatmap.prototype.removeMonthOverview = function () {
1577 var _this = this;
1578 this.items.selectAll('.item-block-month').selectAll('.item-block-rect')
1579 .transition()
1580 .duration(this.transition_duration)
1581 .ease(easeLinear)
1582 .style('opacity', 0)
1583 .attr('x', function (d, i) {
1584 return (i % 2 === 0) ? -_this.width / 3 : _this.width / 3;
1585 })
1586 .remove();
1587 this.labels.selectAll('.label-day').remove();
1588 this.labels.selectAll('.label-week').remove();
1589 this.hideBackButton();
1590 };
1591
1592 /**
1593 * Transition and remove items and labels related to week overview
1594 * @return {?}
1595 */
1596 CalendarHeatmap.prototype.removeWeekOverview = function () {
1597 var _this = this;
1598 this.items.selectAll('.item-block-week').selectAll('.item-block-rect')
1599 .transition()
1600 .duration(this.transition_duration)
1601 .ease(easeLinear)
1602 .style('opacity', 0)
1603 .attr('x', function (d, i) {
1604 return (i % 2 === 0) ? -_this.width / 3 : _this.width / 3;
1605 })
1606 .remove();
1607 this.labels.selectAll('.label-day').remove();
1608 this.labels.selectAll('.label-week').remove();
1609 this.hideBackButton();
1610 };
1611
1612 /**
1613 * Transition and remove items and labels related to daily overview
1614 * @return {?}
1615 */
1616 CalendarHeatmap.prototype.removeDayOverview = function () {
1617 var _this = this;
1618 this.items.selectAll('.item-block')
1619 .transition()
1620 .duration(this.transition_duration)
1621 .ease(easeLinear)
1622 .style('opacity', 0)
1623 .attr('x', function (d, i) {
1624 return (i % 2 === 0) ? -_this.width / 3 : _this.width / 3;
1625 })
1626 .remove();
1627 this.labels.selectAll('.label-time').remove();
1628 this.labels.selectAll('.label-project').remove();
1629 this.hideBackButton();
1630 };
1631
1632 /**
1633 * Helper function to hide the tooltip
1634 * @return {?}
1635 */
1636 CalendarHeatmap.prototype.hideTooltip = function () {
1637 this.tooltip.transition()
1638 .duration(this.transition_duration / 2)
1639 .ease(easeLinear)
1640 .style('opacity', 0);
1641 };
1642
1643 /**
1644 * Helper function to hide the back button
1645 * @return {?}
1646 */
1647 CalendarHeatmap.prototype.hideBackButton = function () {
1648 this.buttons.selectAll('.button')
1649 .transition()
1650 .duration(this.transition_duration)
1651 .ease(easeLinear)
1652 .style('opacity', 0)
1653 .remove();
1654 };
1655
1656 /**
1657 * Helper function to convert seconds to a human readable format
1658 * @param {?} seconds Integer
1659 * @return {?}
1660 */
1661 CalendarHeatmap.prototype.formatTime = function (seconds) {
1662 var /** @type {?} */ hours = Math.floor(seconds / 3600);
1663 var /** @type {?} */ minutes = Math.floor((seconds - (hours * 3600)) / 60);
1664 var /** @type {?} */ time = '';
1665 if (hours > 0) {
1666 time += hours === 1 ? '1 hour ' : hours + ' hours ';
1667 }
1668 if (minutes > 0) {
1669 time += minutes === 1 ? '1 minute' : minutes + ' minutes';
1670 }
1671 if (hours === 0 && minutes === 0) {
1672 time = Math.round(seconds) + ' seconds';
1673 }
1674 return time;
1675 };
1676
1677 CalendarHeatmap.decorators = [
1678 { type: Component, args: [{
1679 selector: 'calendar-heatmap',
1680 template: "<div #root></div>",
1681 styles: ["\n :host {\n user-select: none;\n -ms-user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n }\n :host >>> .item {\n cursor: pointer;\n }\n :host >>> .label {\n cursor: pointer;\n fill: rgb(170, 170, 170);\n font-family: Helvetica, arial, 'Open Sans', sans-serif;\n }\n :host >>> .button {\n cursor: pointer;\n fill: transparent;\n stroke-width: 2;\n stroke: rgb(170, 170, 170);\n }\n :host >>> .button text {\n stroke-width: 1;\n text-anchor: middle;\n fill: rgb(170, 170, 170);\n }\n :host >>> .heatmap-tooltip {\n pointer-events: none;\n position: absolute;\n z-index: 9999;\n width: 250px;\n max-width: 250px;\n overflow: hidden;\n padding: 15px;\n font-size: 12px;\n line-height: 14px;\n color: rgb(51, 51, 51);\n font-family: Helvetica, arial, 'Open Sans', sans-serif;\n background: rgba(255, 255, 255, 0.75);\n }\n :host >>> .heatmap-tooltip .header strong {\n display: inline-block;\n width: 250px;\n }\n :host >>> .heatmap-tooltip span {\n display: inline-block;\n width: 50%;\n padding-right: 10px;\n box-sizing: border-box;\n }\n :host >>> .heatmap-tooltip span,\n :host >>> .heatmap-tooltip .header strong {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n "],
1682 },] },
1683 ];
1684 /**
1685 * @nocollapse
1686 */
1687 CalendarHeatmap.ctorParameters = function () { return []; };
1688 CalendarHeatmap.propDecorators = {
1689 'element': [{ type: ViewChild, args: ['root',] },],
1690 'data': [{ type: Input },],
1691 'color': [{ type: Input },],
1692 'overview': [{ type: Input },],
1693 'handler': [{ type: Output },],
1694 'onResize': [{ type: HostListener, args: ['window:resize', ['$event'],] },],
1695 };
1696 return CalendarHeatmap;
1697}());
1698
1699export { CalendarHeatmap };