217 lines
5.5 KiB
TypeScript
217 lines
5.5 KiB
TypeScript
import { ElementRef, HostListener, Output, EventEmitter, OnInit, Input, OnChanges, SimpleChanges, Directive } from '@angular/core';
|
|
import * as Selection from 'd3-selection';
|
|
import * as Shape from 'd3-shape';
|
|
import * as Random from 'd3-random';
|
|
import * as Drag from 'd3-drag';
|
|
|
|
import { Config } from './../models/config.model';
|
|
import { Point } from './../models/point.model';
|
|
import { Param } from './../models/param.model';
|
|
|
|
@Directive({
|
|
selector: '[appCanvas]'
|
|
})
|
|
export class CanvasDirective implements OnInit, OnChanges {
|
|
private canvas: any;
|
|
private defs: any;
|
|
private gradient: any;
|
|
private svg: any;
|
|
private expandedPoints: Point[];
|
|
private drag: Point;
|
|
private dragging: any;
|
|
public config: Config;
|
|
|
|
@Input()
|
|
private param: Param;
|
|
|
|
@Output()
|
|
private emitConfig: EventEmitter<Config>;
|
|
|
|
@HostListener('window:resize', ['$event'])
|
|
private onResize(event) {
|
|
this.updateConfig();
|
|
this.initSvg();
|
|
this.resetPoints();
|
|
this.resetLines();
|
|
this.render();
|
|
}
|
|
|
|
constructor(
|
|
private el: ElementRef
|
|
) {
|
|
this.canvas = el.nativeElement;
|
|
this.emitConfig = new EventEmitter();
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
console.log(changes.param);
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.init();
|
|
}
|
|
|
|
private init() {
|
|
this.updateConfig();
|
|
this.initSvg();
|
|
this.render();
|
|
}
|
|
|
|
private initSvg() {
|
|
if (!this.svg) {
|
|
this.svg = Selection.select(this.canvas).append('svg');
|
|
this.defs = this.svg.append('defs');
|
|
this.gradient = this.defs.append('linearGradient')
|
|
.attr('id', 'gradient');
|
|
this.gradient.append('stop')
|
|
.attr('stop-color', this.param.colors.end)
|
|
.attr('offset', '0%');
|
|
this.gradient.append('stop')
|
|
.attr('stop-color', this.param.colors.start)
|
|
.attr('offset', '100%');
|
|
}
|
|
this.svg
|
|
.attr('width', this.config.width)
|
|
.attr('height', this.config.height);
|
|
}
|
|
|
|
private render() {
|
|
this.drawPoints(this.getExpandedPoints);
|
|
this.drawLine(this.getExpandedPoints);
|
|
}
|
|
|
|
private resetPoints(): void {
|
|
Selection.selectAll('circle').remove();
|
|
}
|
|
|
|
private drawPoints(points: Point[]) {
|
|
const that = this;
|
|
const group = this.svg.append('g');
|
|
|
|
points.forEach(point => {
|
|
group.append('circle')
|
|
.attr('r', point.color ? 20 : 10)
|
|
.attr('cx', point.x)
|
|
.attr('cy', point.y)
|
|
.attr('fill', point.color ? point.color : 'lightgray')
|
|
.attr('stroke-width', !point.color ? 2 : 0)
|
|
.attr('stroke', !point.color ? 'gray' : '')
|
|
.call(Drag.drag()
|
|
.on('drag', function() {
|
|
Selection.select(this)
|
|
.attr('cx', Selection.event.x)
|
|
.attr('cy', Selection.event.y);
|
|
})
|
|
.on('end', () => {
|
|
// this.render();
|
|
this.drawLine(this.getExpandedPoints);
|
|
console.log('end');
|
|
delete this.drag;
|
|
}));
|
|
});
|
|
}
|
|
|
|
private drawLine(points: Point[]) {
|
|
return this.svg.append('path')
|
|
.attr('d', Shape.line()
|
|
.x(p => p.x)
|
|
.y(p => p.y)
|
|
.curve(Shape.curveBasis)(points))
|
|
.attr('stroke', 'url(#gradient)')
|
|
.attr('stroke-width', this.param.stroke ? this.param.stroke.width : 4)
|
|
.attr('fill', 'none');
|
|
}
|
|
|
|
private resetLines(): void {
|
|
Selection.selectAll('path').remove();
|
|
}
|
|
|
|
private expandPoints(points: Point[]) {
|
|
const newPoints: Point[] = [];
|
|
const matrix = {
|
|
min: {
|
|
x: points.reduce((a, b) => a.x < b.x ? a : b).x,
|
|
y: points.reduce((a, b) => a.y < b.y ? a : b).y,
|
|
},
|
|
max: {
|
|
x: points.reduce((a, b) => a.x > b.x ? a : b).x,
|
|
y: points.reduce((a, b) => a.y > b.y ? a : b).y,
|
|
}
|
|
};
|
|
|
|
points.splice(1, 0, {
|
|
x: this.config.end.x,
|
|
y: this.config.height - this.config.height * this.param.margin.y
|
|
});
|
|
points.splice(1, 0, {
|
|
x: this.config.start.x,
|
|
y: this.config.height * this.param.margin.y
|
|
});
|
|
|
|
for (let i = 0; i < this.param.points; i++) {
|
|
points.splice(2, 0, this.generateRandomPoint(matrix));
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
private get getExpandedPoints() {
|
|
const circles = Selection.selectAll('circle');
|
|
const points = [];
|
|
const that = this;
|
|
|
|
let point = null;
|
|
|
|
if (!circles.size()) {
|
|
return this.expandPoints([
|
|
this.config.start,
|
|
this.config.end
|
|
]);
|
|
}
|
|
|
|
circles.each(function() {
|
|
point = Selection.select(this);
|
|
points.push({
|
|
x: point.attr('cx'),
|
|
y: point.attr('cy'),
|
|
color: (that.param.colors.start === point.attr('fill') || that.param.colors.end === point.attr('fill')) ? point.attr('fill') : '',
|
|
});
|
|
});
|
|
|
|
return points;
|
|
}
|
|
|
|
private calcDelta(a: Point, b: Point) {
|
|
return Math.pow(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2), 0.5);
|
|
}
|
|
|
|
private generateRandomPoint(matrix) {
|
|
return {
|
|
x: Random.randomUniform(matrix.min.x, matrix.max.x)(),
|
|
y: Random.randomUniform(matrix.min.y, matrix.max.y)()
|
|
};
|
|
}
|
|
|
|
private updateConfig(): void {
|
|
const margin = this.canvas.clientWidth * this.param.margin.x;
|
|
|
|
this.config = {
|
|
width: this.canvas.clientWidth,
|
|
height: this.canvas.clientHeight,
|
|
start: {
|
|
x: this.canvas.clientWidth - margin,
|
|
y: 0,
|
|
color: this.param.colors.start
|
|
},
|
|
end: {
|
|
x: 0 + margin,
|
|
y: this.canvas.clientHeight,
|
|
color: this.param.colors.end
|
|
},
|
|
drag: this.drag
|
|
};
|
|
// Emit Canvas Config to parent Component
|
|
this.emitConfig.next(this.config);
|
|
}
|
|
}
|