import { Component, Input, Output, OnChanges, NgZone, EventEmitter, ViewChild, Renderer } from '@angular/core';
import { RoundProgressService } from './round-progress.service';
import { RoundProgressConfig } from './round-progress.config';
import { RoundProgressEase } from './round-progress.ease';
@Component({
selector: 'round-progress',
template: `
`,
host: {
'role': 'progressbar',
'[attr.aria-valuemin]': 'current',
'[attr.aria-valuemax]': 'max',
'[style.width]': "responsive ? '' : _diameter + 'px'",
'[style.height]': '_elementHeight',
'[style.padding-bottom]': '_paddingBottom',
'[class.responsive]': 'responsive'
},
styles: [
`:host {
display: block;
position: relative;
overflow: hidden;
}`,
`:host.responsive {
width: 100%;
padding-bottom: 100%;
}`,
`:host.responsive > svg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}`
]
})
export class RoundProgressComponent implements OnChanges {
private _lastAnimationId: number = 0;
constructor(
private _service: RoundProgressService,
private _easing: RoundProgressEase,
private _defaults: RoundProgressConfig,
private _ngZone: NgZone,
private _renderer: Renderer
) {}
/** Animates a change in the current value. */
private _animateChange(from: number, to: number): void {
if (typeof from !== 'number') {
from = 0;
}
to = this._clamp(to);
from = this._clamp(from);
const self = this;
const changeInValue = to - from;
const duration = self.duration;
// Avoid firing change detection for each of the animation frames.
self._ngZone.runOutsideAngular(() => {
let start = () => {
const startTime = self._service.getTimestamp();
const id = ++self._lastAnimationId;
requestAnimationFrame(function animation(){
let currentTime = Math.min(self._service.getTimestamp() - startTime, duration);
// let value = self._easing[self.animation](currentTime, from, changeInValue, duration);
let value = self._easing.getAnimateValue({
name: self.animation,
t: currentTime,
b: from,
c: changeInValue,
d: duration
});
self._setPath(value);
self.onRender.emit(value);
if (id === self._lastAnimationId && currentTime < duration) {
requestAnimationFrame(animation);
}
});
};
if (this.animationDelay > 0) {
setTimeout(start, this.animationDelay);
} else {
start();
}
});
}
/** Sets the path dimensions. */
private _setPath(value: number): void {
if (this._path) {
this._renderer.setElementAttribute(this._path.nativeElement, 'd', this._service.getArc(value,
this.max, this.radius - this.stroke / 2, this.radius, this.semicircle));
}
}
/** Clamps a value between the maximum and 0. */
private _clamp(value: number): number {
return Math.max(0, Math.min(value || 0, this.max));
}
/** Determines the SVG transforms for the node. */
getPathTransform(): string {
let diameter = this._diameter;
if (this.semicircle) {
return this.clockwise ?
`translate(0, ${diameter}) rotate(-90)` :
`translate(${diameter + ',' + diameter}) rotate(90) scale(-1, 1)`;
} else if (!this.clockwise) {
return `scale(-1, 1) translate(-${diameter} 0)`;
}
}
/** Resolves a color through the service. */
resolveColor(color: string): string {
return this._service.resolveColor(color);
}
/** Change detection callback. */
ngOnChanges(changes): void {
if (changes.current) {
this._animateChange(changes.current.previousValue, changes.current.currentValue);
} else {
this._setPath(this.current);
}
}
/** Diameter of the circle. */
get _diameter(): number {
return this.radius * 2;
}
/** The CSS height of the wrapper element. */
get _elementHeight(): string {
if (!this.responsive) {
return (this.semicircle ? this.radius : this._diameter) + 'px';
}
}
/** Viewbox for the SVG element. */
get _viewBox(): string {
let diameter = this._diameter;
return `0 0 ${diameter} ${this.semicircle ? this.radius : diameter}`;
}
/** Bottom padding for the wrapper element. */
get _paddingBottom(): string {
if (this.responsive) {
return this.semicircle ? '50%' : '100%';
}
}
@ViewChild('path') _path;
@Input() current: number;
@Input() max: number;
@Input() radius: number = this._defaults.get('radius');
@Input() animation: string = this._defaults.get('animation');
@Input() animationDelay: number = this._defaults.get('animationDelay');
@Input() duration: number = this._defaults.get('duration');
@Input() stroke: number = this._defaults.get('stroke');
@Input() color: string = this._defaults.get('color');
@Input() background: string = this._defaults.get('background');
@Input() responsive: boolean = this._defaults.get('responsive');
@Input() clockwise: boolean = this._defaults.get('clockwise');
@Input() semicircle: boolean = this._defaults.get('semicircle');
@Input() rounded: boolean = this._defaults.get('rounded');
@Output() onRender: EventEmitter = new EventEmitter();
}