UNPKG

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