import { Component, OnInit, ViewChild, ElementRef, Input, OnChanges, AfterViewInit, EventEmitter, Output } from '@angular/core'; import stackedBarChart from 'britecharts/dist/umd/stackedBar.min.js'; import Legend from 'britecharts/dist/umd/legend.min.js'; import tooltip from 'britecharts/dist/umd/tooltip.min.js'; import * as d3Selection from 'd3-selection'; import * as d3 from 'd3'; import moment from 'moment'; // import { LocalStorage } from '@services/services.local-storage'; @Component({ selector: 'onguard-stacked-bar-chart', templateUrl: './stacked-bar-chart.component.html', styleUrls: ['./stacked-bar-chart.component.scss'] }) export class StackedBarChartComponent implements OnInit, AfterViewInit { // litle hack to get proper element for tooltip private static i = 0; private stackedBarUpdateRef; public tooltipDataPoint: any; public tooltipShow = false; public lockTooltip = false; @Input() tooltipType = 0; @Output() viewCalls: EventEmitter = new EventEmitter(); public viewCallsProvided = false; @ViewChild('stackedBarChartElement') stackedBarChartElementRef: ElementRef; private _data: any; private colorScheme: string[] = ['#FF8D06', '#BB54E5', '#00A2EF', '#6645AD']; // Default is used for Trunk Traffic Report @Input() isHorizontal = false; @Input() sortByField = null; @Input() generateTooltipFunction: (dataPoint: any) => any = null; @Input() valueLabel = 'count'; @Input() betweenBarsPadding = 0.1; @Input() customAxis = false; @Input() xAxisLabel = 'x-Axis'; @Input() yAxisLabel = 'y-Axis'; @Input() hasPercentage = false; @Input() labelsNumberFormat = '.0'; @Input() margin = { left: 100, top: 40, right: 20, bottom: 70 }; @Input() maxTopCharts = 10000; @Input() height = 400; @Input() hasLegend = false; @Input() chartName = 'unnamed'; private stackedBar; private legend; private width: number; private overTooltip = false; private _count: number = StackedBarChartComponent.i++; @Input() set data(data: any) { if (!data || data.length < 1) { return; } data.forEach(d => { if (d.callCount < 0) { console.warn('No data to display'); } }); this._data = data; if (this.stackedBarUpdateRef) { if (this.width) { this.stackedBarUpdateRef(this._data); } else { // need to resize this.redraw(); } } else if (this.colorScheme) { this.redraw(); } // update custom axis labels this.customAxisLabel(); } @Input() set colorSchema(colors: string[]) { if (this.stackedBar) { this.stackedBar.colorSchema(colors); this.stackedBarUpdateRef(this._data); } this.colorScheme = colors; this.customAxisLabel(); } constructor() { } redraw(): void { // var container = d3Selection.select(this.stackedBarChartElementRef.nativeElement); // container.selectAll('*').remove(); // this.initializeChart(); // container.selectAll("*").remove(); // this.initializeChart(); } ngOnInit(): void { this.viewCallsProvided = this.viewCalls.observers.length > 0; } ngAfterViewInit(): void { this.initializeChart(); this.customAxisLabel(); } private customAxisLabel(): void { if (!this.customAxis) { return; } // Customize x axis const ticks = d3.selectAll('.stacked-bar-chart-uniq-id-' + this._count + ' .x.axis .tick'); const current = { year: 0, month: 0, day: 0 }; ticks.each((d, i, nodes) => { // get references to d3 node element and the date text it contains const node = d3.select(nodes[i]); const date = moment(node.select('text').text()); // clear existing label node.selectAll('*').remove(); // Handle updated year, month and day displays let format = '[]'; let rotation = 0; let anchor = 'inherit'; if (date.year() !== current.year) { format = 'MMM. DD'; rotation = 45; anchor = 'start'; } else if (date.month() !== current.month) { format = 'MMM. DD'; rotation = 45; anchor = 'start'; } else if (date.day() !== current.day) { format = 'DD'; } else { return; } current.year = date.year(); current.month = date.month(); current.day = date.day(); // add the label node.append('text') .attr('fill', '#000') .attr('y', 15) .attr('dy', '0.71em') .text(date.format(format)) .attr('text-anchor', anchor) .attr('transform', 'rotate(' + rotation + ')'); // add a tick mark node.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', 1) .attr('height', 5) .attr('fill', '#D1D5DE'); }); // Add the x-axis title const xAxis = d3.select('.stacked-bar-chart-uniq-id-' + this._count + ' .x.axis'); xAxis.append('text') .attr('x', this.width / 2) .attr('y', 35) .attr('text-anchor', 'middle') .attr('class', 'x-axis-label-text') .attr('fill', '#ACAFB6') .text(this.xAxisLabel); } public mouseOverTooltip(over: boolean): void { if (over) { this.tooltipShow = true; this.overTooltip = true; } else { this.tooltipShow = false; this.overTooltip = false; } } private initializeChart(): void { const container = d3Selection.select(this.stackedBarChartElementRef.nativeElement); const chartTooltip = tooltip(); this.stackedBar = stackedBarChart(); const containerWidth = container.node() ? container.node().getBoundingClientRect().width : false; this.width = containerWidth; // need to define self so that it's accessible from the .on('custom events' handlers) // If we don't then 'this' in the handler's scope refers to the svg component if (containerWidth) { let gridDirection = 'horizontal'; if (this.isHorizontal) { gridDirection = 'vertical'; } let height = this.height; if (this.hasLegend) { this.legend = new Legend(); this.legend .width(this.width) .height(this.height / 9) .isHorizontal(true) .colorSchema(this.colorScheme); // account for the height used onlegend height = this.height * 8 / 9; } this.stackedBar .isHorizontal(this.isHorizontal) .width(containerWidth) .height(height) .isAnimated(false) .stackLabel('stack') // .labelsNumberFormat(this.labelsNumberFormat) .hasPercentage(this.hasPercentage) // .xAxisLabel(this.xAxisLabel) .yAxisLabel(this.yAxisLabel) .colorSchema(this.colorScheme) .nameLabel('name') .valueLabel(this.valueLabel) .margin(this.margin) // .nameLabelFormat('') .betweenBarsPadding(this.betweenBarsPadding) .grid(gridDirection) // .percentageAxisToMaxRatio(1.8) .on('customMouseOver', (x) => { if (x > 0) { this.tooltipShow = true; } }) .on('customMouseMove', (dataPoint, topicColorMap, x, y) => { // only show tooltip if hovering over bars. Not labels if (this.lockTooltip) { return; } if (x > 0) { this.tooltipShow = true; // use the callback to create tooltip if defined if (this.generateTooltipFunction) { this.tooltipDataPoint = this.generateTooltipFunction(dataPoint); // set the type if defined if (this.tooltipDataPoint.type) { this.tooltipType = this.tooltipDataPoint.type; } } else if (moment(dataPoint.key).isValid()) { this.tooltipType = 0; this.tooltipDataPoint = { title: 'Call types count', displayDate: moment(dataPoint.key).format('YYYY-MM-DD hh:mm A'), date: dataPoint.key, trunk: this.chartName, items: [ { name: 'inbound', value: dataPoint.inbound }, { name: 'outbound', value: dataPoint.outbound }, { name: 'internal', value: dataPoint.internal }, { name: 'tromboned', value: dataPoint.tromboned }, { name: 'total', value: dataPoint.total } ] }; // 5 Busiest trunks } } else { if (!this.overTooltip) { this.tooltipShow = false; } } }) .on('customClick', () => { this.lockTooltip = !this.lockTooltip; }) .on('customMouseOut', () => { if (!this.overTooltip && !this.lockTooltip) { this.tooltipShow = false; } }); this.stackedBarUpdateRef = valuesList => { const data = Object.values(valuesList); if (this.sortByField) { data.sort(function (a, b) { return moment(a[this.sortByField]).isBefore(moment(b[this.sortByField])) ? -1 : 1; }); } container.datum(data).call(this.stackedBar); if (this.hasLegend) { const legendItems = [ { name: 'Average' }, { name: 'Peak' }, { name: 'Available' } ]; container.datum(legendItems).call(this.legend); } const xLabelContainer = d3Selection.select('.stacked-bar-chart-uniq-id-' + this._count + ' .x-axis-group'); const yLabelContainer = d3Selection.select('.stacked-bar-chart-uniq-id-' + this.count + ' .y-axis-group'); // Add Count axis label to the if (this.isHorizontal) { // THIS WORKS TO ADD LABELS xLabelContainer.append('text') .attr('transform', 'translate(10 ,' + (this.height - (this.margin.top + this.margin.bottom - 40)) + ')') .style('text-anchor', 'middle') .text('Count'); } else { } }; } else { console.log('something went wrong...'); this.stackedBarUpdateRef = data => { console.log('something went wrong', data); }; } if (this._data) { this.stackedBarUpdateRef(this._data); } } public demo(): void { const data: any = [ { 'callType': 'inbound', 'stack': 'Direct1', 'count': 1, 'date': '2011-01-05' }, { 'callType': 'inbound', 'stack': 'Direct1', 'count': 2, 'date': '2011-01-06' }, { 'callType': 'inbound', 'stack': 'Direct1', 'count': 3, 'date': '2011-01-07' }, { 'callType': 'internal', 'stack': 'Direct2', 'count': 1, 'date': '2011-01-05' }, { 'callType': 'internal', 'stacks': 'Direct2', 'count': 2, 'date': '2011-01-06' } ]; setTimeout(() => { this.stackedBarUpdateRef(data); }, 1000); } get count(): number { return this._count; } }