import { Component, Input, HostBinding, ViewChild, ElementRef, OnDestroy, OnChanges, SimpleChanges, NgZone, AfterViewInit, TemplateRef, OnInit, HostListener, ViewEncapsulation } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/debounceTime';
@Component({
selector: 'water-wave',
template: `
{{_title}}
{{percent}}%
`,
styleUrls: [ './water-wave.less' ],
encapsulation: ViewEncapsulation.Emulated,
// tslint:disable-next-line:use-host-property-decorator
host: {
'[style.transform]': `'scale(' + radio + ')'`
}
})
export class WaterWaveComponent implements OnDestroy, OnChanges, AfterViewInit, OnInit {
// region: fields
_title = '';
_titleTpl: TemplateRef;
@Input()
set title(value: string | TemplateRef) {
if (value instanceof TemplateRef)
this._titleTpl = value;
else
this._title = value;
}
@Input() color = '#1890FF';
@Input() height = 160;
@Input() percent: number;
// endregion
@ViewChild('container') node: ElementRef;
chart: any;
initFlag = false;
timer: any;
radio = 1;
constructor(private el: ElementRef, private zone: NgZone) { }
renderChart() {
const data = this.percent / 100;
if (!data) return;
const self = this;
this.zone.runOutsideAngular(() => {
const canvas = this.node.nativeElement as HTMLCanvasElement;
const ctx = canvas.getContext('2d');
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const radius = canvasWidth / 2;
const lineWidth = 2;
const cR = radius - (lineWidth);
ctx.beginPath();
ctx.lineWidth = lineWidth * 2;
const axisLength = canvasWidth - (lineWidth);
const unit = axisLength / 8;
const range = 0.2; // 振幅
let currRange = range;
const xOffset = lineWidth;
let sp = 0; // 周期偏移量
let currData = 0;
const waveupsp = 0.005; // 水波上涨速度
let arcStack = [];
const bR = radius - (lineWidth);
const circleOffset = -(Math.PI / 2);
let circleLock = true;
for (let i = circleOffset; i < circleOffset + (2 * Math.PI); i += 1 / (8 * Math.PI)) {
arcStack.push([
radius + (bR * Math.cos(i)),
radius + (bR * Math.sin(i)),
]);
}
const cStartPoint = arcStack.shift();
ctx.strokeStyle = this.color;
ctx.moveTo(cStartPoint[0], cStartPoint[1]);
function drawSin() {
ctx.beginPath();
ctx.save();
const sinStack = [];
for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
const x = sp + ((xOffset + i) / unit);
const y = Math.sin(x) * currRange;
const dx = i;
const dy = ((2 * cR * (1 - currData)) + (radius - cR)) - (unit * y);
ctx.lineTo(dx, dy);
sinStack.push([dx, dy]);
}
const startPoint = sinStack.shift();
ctx.lineTo(xOffset + axisLength, canvasHeight);
ctx.lineTo(xOffset, canvasHeight);
ctx.lineTo(startPoint[0], startPoint[1]);
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(1, '#1890FF');
ctx.fillStyle = gradient;
ctx.fill();
ctx.restore();
}
function render() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
if (circleLock) {
if (arcStack.length) {
const temp = arcStack.shift();
ctx.lineTo(temp[0], temp[1]);
ctx.stroke();
} else {
circleLock = false;
ctx.lineTo(cStartPoint[0], cStartPoint[1]);
ctx.stroke();
arcStack = null;
ctx.globalCompositeOperation = 'destination-over';
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.arc(radius, radius, bR, 0, 2 * Math.PI, true);
ctx.beginPath();
ctx.save();
ctx.arc(radius, radius, radius - (3 * lineWidth), 0, 2 * Math.PI, true);
ctx.restore();
ctx.clip();
ctx.fillStyle = '#1890FF';
}
} else {
if (data >= 0.85) {
if (currRange > range / 4) {
const t = range * 0.01;
currRange -= t;
}
} else if (data <= 0.1) {
if (currRange < range * 1.5) {
const t = range * 0.01;
currRange += t;
}
} else {
if (currRange <= range) {
const t = range * 0.01;
currRange += t;
}
if (currRange >= range) {
const t = range * 0.01;
currRange -= t;
}
}
if ((data - currData) > 0) {
currData += waveupsp;
}
if ((data - currData) < 0) {
currData -= waveupsp;
}
sp += 0.07;
drawSin();
}
self.timer = requestAnimationFrame(render);
}
render();
});
}
uninstall() {
if (this.chart) {
this.zone.runOutsideAngular(() => {
this.chart.destroy();
});
this.chart = null;
}
}
ngAfterViewInit(): void {
this.initFlag = true;
this.resize();
}
ngOnInit(): void {
this.installResizeEvent();
}
ngOnChanges(changes: SimpleChanges): void {
if (this.initFlag)
this.renderChart();
}
ngOnDestroy(): void {
if (this.timer) cancelAnimationFrame(this.timer);
this.uninstallResizeEvent();
this.uninstall();
}
// region: resize
private autoHideXLabels = false;
private scroll$: Subscription = null;
private installResizeEvent() {
if (this.scroll$) return;
this.scroll$ = Observable.fromEvent(window, 'resize')
.debounceTime(500)
.subscribe(() => this.resize());
}
private uninstallResizeEvent() {
if (this.scroll$) this.scroll$.unsubscribe();
}
resize() {
const { offsetWidth } = this.el.nativeElement;
this.radio = offsetWidth < this.height ? offsetWidth / this.height : 1;
if (!this.chart) this.renderChart();
}
// endregion
}