UNPKG

@opitzconsulting/pie-chart

Version:
723 lines (722 loc) 77.1 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ import { Component, Input, ElementRef, Output, EventEmitter } from '@angular/core'; import * as d3 from 'd3'; /** * chart item properties * @record */ export function PieChartData() { } function PieChartData_tsickle_Closure_declarations() { /** * value of item * @type {?} */ PieChartData.prototype.value; /** * caption of item (must be unique) * @type {?} */ PieChartData.prototype.caption; /** * optional color of item (if not set, generated automatically) * @type {?|undefined} */ PieChartData.prototype.color; } /** * internal chart item properties * @record */ export function InternalPieChartData() { } function InternalPieChartData_tsickle_Closure_declarations() { /** * svg path for item * @type {?|undefined} */ InternalPieChartData.prototype.path; /** * delete flag for removing after transition * @type {?|undefined} */ InternalPieChartData.prototype.deleted; } export class PieChartComponent { /** * constructor * @param {?} element */ constructor(element) { this.element = element; /** * chart data, which should be displayed */ this.data = []; /** * chart width in pixel */ this.width = 250; /** * chart height in pixel */ this.height = 250; /** * duration of animation transition */ this.duration = 1000; /** * inner spacing in pixel, if greater than 0 it defines the radius of the empty circle in the middle */ this.innerSpacing = 0; /** * outer spacing in pixel */ this.outerSpacing = 1; /** * fired when user clicks on a chart entry */ this.chartClick = new EventEmitter(); /** * fired when user hovers a chart entry */ this.chartHover = new EventEmitter(); /** * current chart data with angle and path definitions, it will be consistent to the representation */ this.curData = []; /** * end chart data with angle and path definitions, it will representate the end state and used only for interpolation */ this.endData = []; /** * copy of last processed data, used to identify changes in ngDoCheck that Angular overlooked */ this.lastData = []; /** * Function for interrupt a running chart animation. Necessary because if transition is still active * when a new transition is started, tween factory function from previos transition will still be fired * until end of transition is reached. For entries which have a started transition the tween factory * function will be fired multiple times with different tween interpolation range! */ this.interrupt = undefined; } /** * Creates a deep copy of an variable. Do not use this function with recursive objects or * browser objects like window or document. * ToDo: should be outsourced. * @template T * @param {?} v * @return {?} */ deepCopy(v) { return JSON.parse(JSON.stringify(v)); } ; ; /** * @return {?} */ ngOnInit() { this.tooltip = /** @type {?} */ (this.element.nativeElement.querySelector('div.pie-chart-tooltip')); } /** * Fired when Angular (re-)sets data-bound properties. This function does not fire when changed data in bound objects or arrays. * Angular only checks references. * @param {?} changes * @return {?} */ ngOnChanges(changes) { // check if entries in bound data property has changed this.detectDataChange(); } ; /** * Fired during every change detection run to detect and act upon changes that Angular can't or won't detect on its own. * @return {?} */ ngDoCheck() { // check if entries in bound data property has changed this.detectDataChange(); } ; /** * Checks whether the data property has changed. This function also check whether only an item property has * changed. In case of change the chart will be rendered. * @return {?} */ detectDataChange() { // fast check: if items were added or removed let /** @type {?} */ dataChanged = (this.data.length !== this.lastData.length); // detail check: if (dataChanged === false) { // loop all items for (let /** @type {?} */ idx = 0; idx < this.data.length; ++idx) { const /** @type {?} */ a = this.data[idx]; const /** @type {?} */ b = this.lastData[idx]; // check internal item properties dataChanged = dataChanged || (a.caption !== b.caption || a.color !== b.color || a.value !== b.value); // for optimization, stop if change detected if (dataChanged) break; } } // if change detected if (dataChanged) { // render chart this.render(); // copy current data to identify changes this.lastData = this.deepCopy(this.data); } } ; /** * Generates a random color for a chart item. * @param {?} value * @return {?} */ generateRandomColor(value) { const /** @type {?} */ hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; // make sure, generated color does not exists yet in data array let /** @type {?} */ color; let /** @type {?} */ uniqueColorGenerated = false; while (uniqueColorGenerated === false) { const /** @type {?} */ h = (Math.random() + 0.618033988749895) % 1; const /** @type {?} */ s = .5; const /** @type {?} */ l = .6; let /** @type {?} */ q = l + s - l * s; let /** @type {?} */ p = 2 * l - q; const /** @type {?} */ r = hue2rgb(p, q, h + 1 / 3); const /** @type {?} */ g = hue2rgb(p, q, h); const /** @type {?} */ b = hue2rgb(p, q, h - 1 / 3); color = '#' + Math.round(r * 255).toString(16) + Math.round(g * 255).toString(16) + Math.round(b * 255).toString(16); uniqueColorGenerated = (this.data.map((d) => d.color).filter((d) => d === color).length === 0); } return color; } ; /** * generates a pie chart item definition * @param {?} item * @param {?} index * @param {?} value * @param {?} startAngle * @param {?} endAngle * @return {?} */ generatePieArcData(item, index, value, startAngle, endAngle) { // generate definition const /** @type {?} */ result = { data: item, index: index, value: value, startAngle: startAngle, endAngle: endAngle, padAngle: 0, innerRadius: this.radius - 40, outerRadius: this.radius }; // generate svg path d-attribute from definition (/** @type {?} */ (result.data)).path = this.pathGenerator(result); // return definition return result; } ; /** * Checks whether items were deleted and initiate delete transition for these items. * @return {?} */ detectDeletedEntries() { // loop current state entries this.curData.forEach((curItem, idx) => { // only check if current entry is not marked as deleted if (curItem.data.deleted !== true) { // check if entry not exists anymore const /** @type {?} */ isDeleted = (this.data.filter((item) => item.caption === curItem.data.caption).length === 0); // if entry is deleted if (isDeleted) { // mark entry in current state as deleted this.curData[idx].data.deleted = true; // mark entry in end state as deleted and set value to 0 for transtion this.endData[idx].data.deleted = true; this.endData[idx].value = 0; } } }); } ; /** * Checks whether items were inserted and initiate insert transition for these items. * @return {?} */ detectInsertedEntries() { // loop given data array this.data.forEach((item, idx) => { // check if entry is new const /** @type {?} */ isInserted = (this.curData.filter((curItem) => curItem.data.deleted !== true && curItem.data.caption === item.caption).length === 0); // if entry is new if (isInserted) { { const /** @type {?} */ d = this.generatePieArcData(this.deepCopy(item), idx, 0, -1, -1); this.curData.splice(idx, 0, d); } { const /** @type {?} */ d = this.generatePieArcData(this.deepCopy(item), idx, item.value, -1, -1); this.endData.splice(idx, 0, d); } } }); } ; /** * Checks whether items were moved and initiate transition for these items. * @return {?} */ detectMovedEntries() { // separate index in current state array let /** @type {?} */ curIndex = 0; // loop data array for (let /** @type {?} */ index = 0; index < this.data.length; ++index) { // find next index in current state array, skip items marked as deleted while (this.curData[curIndex].data.deleted) ++curIndex; // check if item is moved by comparing captions if (this.data[index].caption !== this.curData[curIndex].data.caption) { { // mark item in current state array as deleted this.curData[curIndex].data.deleted = true; // mark item in end state array as deleted and set value to 0 for transition this.endData[curIndex].data.deleted = true; this.endData[curIndex].value = 0; } { const /** @type {?} */ item = this.deepCopy(this.data[index]); const /** @type {?} */ d = this.generatePieArcData(item, -1, 0, -1, -1); this.curData.splice(curIndex, 0, d); } { const /** @type {?} */ item = this.deepCopy(this.data[index]); const /** @type {?} */ d = this.generatePieArcData(item, -1, item.value, -1, -1); this.endData.splice(curIndex, 0, d); } // because of inserting item to the array's, increment index twice ++curIndex; } ++curIndex; } } ; /** * Synchronize state arrays (curData / endData) with given items (data). * @return {?} */ syncItems() { // sync values and colors this.data.forEach((item, index) => { // find item index in state array's let /** @type {?} */ curIndex = 0; for (let /** @type {?} */ i = 0; i < this.curData.length; ++i) { if (!this.curData[i].data.deleted && this.curData[i].data.caption === item.caption) { curIndex = i; break; } } // update value in state entries this.curData[curIndex].data.value = item.value; this.endData[curIndex].data.value = item.value; // update value in end state entry for transition this.endData[curIndex].value = item.value; // update color in end state entry for transition this.endData[curIndex].data.color = item.color; }); } ; /** * will be triggerd to animate chart changes. * important! this method musst be called within a setTimeout function because of angulars * rendering cycle. * @return {?} */ animateChanges() { // get svg element reference const /** @type {?} */ svg = (/** @type {?} */ (this.element.nativeElement.querySelector('svg'))); // reference all path elements in svg element const /** @type {?} */ paths = d3.select(svg).selectAll('path'); // define interruption function to stop running animations this.interrupt = () => { // call paths interrupt method paths.interrupt(); // delete interupt definition delete this.interrupt; }; // start path animation paths .transition() .duration(this.duration) .attrTween('pie-tween-dummy', (arg0, idx, nodeList) => { // create interpolation functions to calculate step values const /** @type {?} */ iValue = d3.interpolate(this.curData[idx].value, this.endData[idx].value); const /** @type {?} */ iStartAngle = d3.interpolate(this.curData[idx].startAngle, this.endData[idx].startAngle); const /** @type {?} */ iEndAngle = d3.interpolate(this.curData[idx].endAngle, this.endData[idx].endAngle); const /** @type {?} */ iColor = d3.interpolate(this.curData[idx].data.color, this.endData[idx].data.color); // return factory function for animation steps return (t) => { // interpolate values by given transition value this.curData[idx].value = iValue(t); this.curData[idx].startAngle = iStartAngle(t); this.curData[idx].endAngle = iEndAngle(t); this.curData[idx].data.color = iColor(t); // generate new path this.curData[idx].data.path = this.pathGenerator(this.curData[idx]); // return empty string. This is only necessary for typescript compiler. Nothing should be changed here. return ''; }; }) .on('end', (arg0, idx, nodeList) => { // when transition is complete for the last item if (idx === nodeList.length - 1) { // remove as deleted marked entries this.cleanStateItems(); // Delete interupt definition, because everything has finished and nothing can be interrupted. delete this.interrupt; } }); } ; /** * Must be called after transition ends to remove entries in curData and endData which are marked * as deleted. * @return {?} */ cleanStateItems() { // clean current state array for (let /** @type {?} */ i = this.curData.length - 1; i >= 0; --i) { if (this.curData[i].data.deleted === true) { this.curData.splice(i, 1); } } // clean end state array for (let /** @type {?} */ i = this.endData.length - 1; i >= 0; --i) { if (this.endData[i].data.deleted === true) { this.endData.splice(i, 1); } } } ; /** * Checks whether all items have assigned color values and if necessary completes colors in given data array. * @return {?} */ initColors() { // loop all entries this.data.forEach((item) => { // if no color is assigned if (!item.color) { // generate random color for item item.color = this.generateRandomColor(item.value); } }); } ; /** * Returns maximal angle of current state items. * @return {?} */ getMaxAngle() { let /** @type {?} */ maxAngle = 0; this.curData.forEach((curItem) => { if (curItem.endAngle > maxAngle) { maxAngle = curItem.endAngle; } }); return maxAngle; } ; /** * Calculates angles for current and end state items. * @param {?} maxAngle last maximal angle in current state to avoid "jumping" transitions * @return {?} */ calculateAngles(maxAngle) { { // calculate sum of values const /** @type {?} */ total = this.curData.reduce((p, c) => p + c.value, 0); // loop items and calculate start and end angles, initialize rendering let /** @type {?} */ lastAngle = 0; this.curData.forEach((item, idx) => { // calculate angles by last used maximal angle. without data (total=0) simulate 0 values, so draw items in clockwise direction. const /** @type {?} */ nextAngle = lastAngle + ((maxAngle) / ((total === 0) ? 1 : total)) * item.value; item.startAngle = lastAngle; item.endAngle = nextAngle; item.index = idx; item.data.path = this.pathGenerator(item); lastAngle = nextAngle; }); } { // calculate sum of values const /** @type {?} */ total = this.endData.reduce((p, c) => p + c.value, 0); // loop items and calculate start and end angles, initialize rendering let /** @type {?} */ lastAngle = 0; this.endData.forEach((item, idx) => { // calculate angles with circumference. without data (total=0) simulate 0 values, so draw items in anti-clockwise direction. const /** @type {?} */ nextAngle = lastAngle + ((2 * Math.PI) / ((total === 0) ? 1 : total)) * item.value; item.startAngle = lastAngle; item.endAngle = nextAngle; item.index = idx; item.data.path = this.pathGenerator(item); lastAngle = nextAngle; }); } } ; /** * fired when mouse enters a pie chart path element and shows tooltip * @param {?} event * @return {?} */ overPath(event) { // get tooltip-text of path element const /** @type {?} */ txt = (/** @type {?} */ (event.target)).getAttribute('tooltip'); // show tooltip and assign text d3.select(this.tooltip) .html(txt) .style('display', 'block') .transition() .duration(250) .style('opacity', 1); // get index const /** @type {?} */ idx = parseInt((/** @type {?} */ (event.target)).getAttribute('idx'), 10); // get caption of element const /** @type {?} */ caption = this.curData[idx].data.caption; // get original data by caption const /** @type {?} */ item = this.data.filter((d) => d.caption === caption)[0]; // if data found then emit chart click event if (item) { this.chartHover.emit(item); } } ; /** * fired when mouse moves over a pie chart path element and adjusts tooltip * @param {?} event * @return {?} */ movePath(event) { // aggregate scroll positions, because event.page* properties are relative to top left corner of document let /** @type {?} */ offsetX = 0; let /** @type {?} */ offsetY = 0; let /** @type {?} */ element = (/** @type {?} */ (this.tooltip.parentElement)); while (element) { offsetX += element.scrollLeft; offsetY += element.scrollTop; element = element.parentElement; } // adjust tooltip d3.select(this.tooltip) .style('top', (event.pageY - offsetY + 10) + 'px') .style('left', (event.pageX - offsetX + 10) + 'px'); } ; /** * fired when mouse leaves a pie chart path element and hides tooltip * @param {?} event * @return {?} */ outPath(event) { // hide tooltip d3.select(this.tooltip) .transition() .duration(250) .style('opacity', 0) .on('end', () => { d3.select(this.tooltip).style('display', 'none'); }); } ; /** * fired when user clicks on a pie chart path element * @param {?} event * @return {?} */ clickPath(event) { // get index const /** @type {?} */ idx = parseInt((/** @type {?} */ (event.target)).getAttribute('idx'), 10); // get caption of element const /** @type {?} */ caption = this.curData[idx].data.caption; // get original data by caption const /** @type {?} */ item = this.data.filter((d) => d.caption === caption)[0]; // if data found then emit chart click event if (item) { this.chartClick.emit(item); } } ; /** * main rendering function * @return {?} */ render() { // interrupt possible running animations if (this.interrupt) this.interrupt(); // initialize chart colors this.initColors(); // calculate radius this.radius = Math.min(this.width, this.height) / 2; // calculate middle of chart this.center = `translate(${this.width / 2}, ${this.height / 2})`; // create path generator this.pathGenerator = d3.arc().outerRadius(this.radius - this.outerSpacing).innerRadius(this.innerSpacing); // get current maximal angle, necessary to avoid "jumping" transitions const /** @type {?} */ maxAngle = this.getMaxAngle(); // check data array for deleted entries and assign transition configuration this.detectDeletedEntries(); // check data array for inserted entries and assign transition configuration this.detectInsertedEntries(); // check data array for moved entries and assign transition configuration this.detectMovedEntries(); // synchronize data entries with current and end state entries this.syncItems(); // calculate angles for current and end state entries this.calculateAngles(maxAngle); // important! use setTimeout because angular first must exec change detection setTimeout(() => { // start change animations this.animateChanges(); }, 0); } ; } PieChartComponent.decorators = [ { type: Component, args: [{ selector: 'oc-pie-chart', template: `<div class="pie-chart-tooltip"></div> <svg [attr.width]="width" [attr.height]="height"> <g [attr.transform]="center"> <path *ngFor="let d of curData; let idx = index;" [attr.idx]="idx" [attr.fill]="d.data.color" [attr.d]="d.data.path" [attr.tooltip]="d.data.caption" (mouseover)="overPath($event)" (mousemove)="movePath($event);" (mouseout)="outPath($event)" (click)="clickPath($event)" /> </g> </svg>`, styles: [`div.pie-chart-tooltip{position:fixed;display:none;opacity:0;font:12px sans-serif;color:#fff;background-color:rgba(35,47,52,.8);padding:5px}path{opacity:.7;stroke:#fff;stroke-width:2px}path:hover{opacity:1;stroke:#e3e3e3}`] },] }, ]; /** @nocollapse */ PieChartComponent.ctorParameters = () => [ { type: ElementRef } ]; PieChartComponent.propDecorators = { data: [{ type: Input }], width: [{ type: Input }], height: [{ type: Input }], duration: [{ type: Input }], innerSpacing: [{ type: Input }], outerSpacing: [{ type: Input }], chartClick: [{ type: Output }], chartHover: [{ type: Output }] }; function PieChartComponent_tsickle_Closure_declarations() { /** * chart data, which should be displayed * @type {?} */ PieChartComponent.prototype.data; /** * chart width in pixel * @type {?} */ PieChartComponent.prototype.width; /** * chart height in pixel * @type {?} */ PieChartComponent.prototype.height; /** * duration of animation transition * @type {?} */ PieChartComponent.prototype.duration; /** * inner spacing in pixel, if greater than 0 it defines the radius of the empty circle in the middle * @type {?} */ PieChartComponent.prototype.innerSpacing; /** * outer spacing in pixel * @type {?} */ PieChartComponent.prototype.outerSpacing; /** * fired when user clicks on a chart entry * @type {?} */ PieChartComponent.prototype.chartClick; /** * fired when user hovers a chart entry * @type {?} */ PieChartComponent.prototype.chartHover; /** * pie chart radius in pixel * @type {?} */ PieChartComponent.prototype.radius; /** * transform-attribute to center chart vertical and horizontal * @type {?} */ PieChartComponent.prototype.center; /** * current chart data with angle and path definitions, it will be consistent to the representation * @type {?} */ PieChartComponent.prototype.curData; /** * end chart data with angle and path definitions, it will representate the end state and used only for interpolation * @type {?} */ PieChartComponent.prototype.endData; /** * path generator function (internal use only) * @type {?} */ PieChartComponent.prototype.pathGenerator; /** * copy of last processed data, used to identify changes in ngDoCheck that Angular overlooked * @type {?} */ PieChartComponent.prototype.lastData; /** * Function for interrupt a running chart animation. Necessary because if transition is still active * when a new transition is started, tween factory function from previos transition will still be fired * until end of transition is reached. For entries which have a started transition the tween factory * function will be fired multiple times with different tween interpolation range! * @type {?} */ PieChartComponent.prototype.interrupt; /** * reference to tooltip div element * @type {?} */ PieChartComponent.prototype.tooltip; /** @type {?} */ PieChartComponent.prototype.element; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGllLWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiJuZzovL0BvcGl0emNvbnN1bHRpbmcvcGllLWNoYXJ0LyIsInNvdXJjZXMiOlsibGliL3BpZS1jaGFydC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFzQixVQUFVLEVBQXlCLE1BQU0sRUFBRSxZQUFZLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFOUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFtQ3pCLE1BQU07Ozs7O0lBNkNKLFlBQ1U7UUFBQSxZQUFPLEdBQVAsT0FBTzs7OztvQkE1Q29CLEVBQUU7Ozs7cUJBRXRCLEdBQUc7Ozs7c0JBRUYsR0FBRzs7Ozt3QkFFRCxJQUFJOzs7OzRCQUVBLENBQUM7Ozs7NEJBRUQsQ0FBQzs7OzswQkFFMEIsSUFBSSxZQUFZLEVBQUU7Ozs7MEJBRWxCLElBQUksWUFBWSxFQUFFOzs7O3VCQU90QyxFQUFFOzs7O3VCQUVELEVBQUU7Ozs7d0JBSU0sRUFBRTs7Ozs7Ozt5QkFvUFYsU0FBUztLQWxPckM7Ozs7Ozs7OztJQVZNLFFBQVEsQ0FBSSxDQUFJO1FBQ3hCLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztLQUN0QztJQUFBLENBQUM7SUFRRSxDQUFDOzs7O0lBRUwsUUFBUTtRQUNOLElBQUksQ0FBQyxPQUFPLHFCQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLGFBQWEsQ0FBQyx1QkFBdUIsQ0FBbUIsQ0FBQSxDQUFDO0tBQ3BHOzs7Ozs7O0lBT0QsV0FBVyxDQUFDLE9BQXNCOztRQUVoQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztLQUN6QjtJQUFBLENBQUM7Ozs7O0lBS0YsU0FBUzs7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O29CQUVMLEtBQUs7cUJBRUwsS0FBSzt1QkFFTCxLQUFLOzJCQUVMLEtBQUs7MkJBRUwsS0FBSzt5QkFFTCxNQUFNO3lCQUVOLE1BQU0iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0LCBPbkNoYW5nZXMsIERvQ2hlY2ssIEVsZW1lbnRSZWYsIFNpbXBsZUNoYW5nZXMsIE9uSW5pdCwgT3V0cHV0LCBFdmVudEVtaXR0ZXIgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuXHJcbmltcG9ydCAqIGFzIGQzIGZyb20gJ2QzJztcclxuXHJcbi8qKiBjaGFydCBpdGVtIHByb3BlcnRpZXMgKi9cclxuZXhwb3J0IGludGVyZmFjZSBQaWVDaGFydERhdGEge1xyXG4gIC8qKiB2YWx1ZSBvZiBpdGVtICovXHJcbiAgdmFsdWU6IG51bWJlcjtcclxuICAvKiogY2FwdGlvbiBvZiBpdGVtIChtdXN0IGJlIHVuaXF1ZSkgKi9cclxuICBjYXB0aW9uOiBzdHJpbmc7XHJcbiAgLyoqIG9wdGlvbmFsIGNvbG9yIG9mIGl0ZW0gKGlmIG5vdCBzZXQsIGdlbmVyYXRlZCBhdXRvbWF0aWNhbGx5KSAqL1xyXG4gIGNvbG9yPzogc3RyaW5nO1xyXG59XHJcblxyXG4vKiogaW50ZXJuYWwgY2hhcnQgaXRlbSBwcm9wZXJ0aWVzICovXHJcbmV4cG9ydCBpbnRlcmZhY2UgSW50ZXJuYWxQaWVDaGFydERhdGEgZXh0ZW5kcyBQaWVDaGFydERhdGEge1xyXG4gIC8qKiBzdmcgcGF0aCBmb3IgaXRlbSAqL1xyXG4gIHBhdGg/OiBzdHJpbmc7XHJcbiAgLyoqIGRlbGV0ZSBmbGFnIGZvciByZW1vdmluZyBhZnRlciB0cmFuc2l0aW9uICovXHJcbiAgZGVsZXRlZD86IGJvb2xlYW47XHJcbn1cclxuXHJcbi8qKiBpbnRlcm5hbCB0eXBlIGZvciBvcHRpbWl6YXRpb24gKi9cclxuZXhwb3J0IHR5cGUgUGllQXJjRGF0YSA9IGQzLlBpZUFyY0RhdHVtPEludGVybmFsUGllQ2hhcnREYXRhPiAmIGQzLkRlZmF1bHRBcmNPYmplY3Q7XHJcblxyXG5AQ29tcG9uZW50KHtcclxuICBzZWxlY3RvcjogJ29jLXBpZS1jaGFydCcsXHJcbiAgdGVtcGxhdGU6IGA8ZGl2IGNsYXNzPVwicGllLWNoYXJ0LXRvb2x0aXBcIj48L2Rpdj5cclxuPHN2ZyBbYXR0ci53aWR0aF09XCJ3aWR0aFwiIFthdHRyLmhlaWdodF09XCJoZWlnaHRcIj5cclxuICAgIDxnIFthdHRyLnRyYW5zZm9ybV09XCJjZW50ZXJcIj5cclxuICAgICAgICA8cGF0aCAqbmdGb3I9XCJsZXQgZCBvZiBjdXJEYXRhOyBsZXQgaWR4ID0gaW5kZXg7XCIgW2F0dHIuaWR4XT1cImlkeFwiIFxyXG4gICAgICAgICAgICBbYXR0ci5maWxsXT1cImQuZGF0YS5jb2xvclwiIFthdHRyLmRdPVwiZC5kYXRhLnBhdGhcIiBbYXR0ci50b29sdGlwXT1cImQuZGF0YS5jYXB0aW9uXCJcclxuICAgICAgICAgICAgKG1vdXNlb3Zlcik9XCJvdmVyUGF0aCgkZXZlbnQpXCIgKG1vdXNlbW92ZSk9XCJtb3ZlUGF0aCgkZXZlbnQpO1wiIChtb3VzZW91dCk9XCJvdXRQYXRoKCRldmVudClcIiAoY2xpY2spPVwiY2xpY2tQYXRoKCRldmVudClcIiAvPlxyXG4gICAgPC9nPlxyXG48L3N2Zz5gLFxyXG4gIHN0eWxlczogW2BkaXYucGllLWNoYXJ0LXRvb2x0aXB7cG9zaXRpb246Zml4ZWQ7ZGlzcGxheTpub25lO29wYWNpdHk6MDtmb250OjEycHggc2Fucy1zZXJpZjtjb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgzNSw0Nyw1MiwuOCk7cGFkZGluZzo1cHh9cGF0aHtvcGFjaXR5Oi43O3N0cm9rZTojZmZmO3N0cm9rZS13aWR0aDoycHh9cGF0aDpob3ZlcntvcGFjaXR5OjE7c3Ryb2tlOiNlM2UzZTN9YF1cclxufSlcclxuZXhwb3J0IGNsYXNzIFBpZUNoYXJ0Q29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkNoYW5nZXMsIERvQ2hlY2sge1xyXG4gIC8qKiBjaGFydCBkYXRhLCB3aGljaCBzaG91bGQgYmUgZGlzcGxheWVkICovXHJcbiAgQElucHV0KCkgZGF0YTogQXJyYXk8UGllQ2hhcnREYXRhPiA9IFtdO1xyXG4gIC8qKiBjaGFydCB3aWR0aCBpbiBwaXhlbCAqL1xyXG4gIEBJbnB1dCgpIHdpZHRoID0gMjUwO1xyXG4gIC8qKiBjaGFydCBoZWlnaHQgaW4gcGl4ZWwgKi9cclxuICBASW5wdXQoKSBoZWlnaHQgPSAyNTA7XHJcbiAgLyoqIGR1cmF0aW9uIG9mIGFuaW1hdGlvbiB0cmFuc2l0aW9uICovXHJcbiAgQElucHV0KCkgZHVyYXRpb24gPSAxMDAwO1xyXG4gIC8qKiBpbm5lciBzcGFjaW5nIGluIHBpeGVsLCBpZiBncmVhdGVyIHRoYW4gMCBpdCBkZWZpbmVzIHRoZSByYWRpdXMgb2YgdGhlIGVtcHR5IGNpcmNsZSBpbiB0aGUgbWlkZGxlICovXHJcbiAgQElucHV0KCkgaW5uZXJTcGFjaW5nID0gMDtcclxuICAvKiogb3V0ZXIgc3BhY2luZyBpbiBwaXhlbCAqL1xyXG4gIEBJbnB1dCgpIG91dGVyU3BhY2luZyA9IDE7XHJcbiAgLyoqIGZpcmVkIHdoZW4gdXNlciBjbGlja3Mgb24gYSBjaGFydCBlbnRyeSAqL1xyXG4gIEBPdXRwdXQoKSBjaGFydENsaWNrOiBFdmVudEVtaXR0ZXI8UGllQ2hhcnREYXRhPiA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICAvKiogZmlyZWQgd2hlbiB1c2VyIGhvdmVycyBhIGNoYXJ0IGVudHJ5ICovXHJcbiAgQE91dHB1dCgpIGNoYXJ0SG92ZXI6IEV2ZW50RW1pdHRlcjx