diff --git a/package.json b/package.json
index ce92a21..f3df2d0 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
],
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "ng serve --aot -o",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
diff --git a/src/app/app.component.html b/src/app/app.component.html
index a651ac9..dfccfcb 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -99,8 +99,8 @@
Animation
-
-
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 17b03dc..ceed6a5 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,3 @@
-import { AnimationService } from './services/animation.service';
/**
* Copyright (C) 2018 Michael Czechowski
* This program is free software; you can redistribute it and/or modify it
@@ -29,6 +28,8 @@ import { Config } from './models/config.model';
import { CanvasService } from './services/canvas.service';
import { HistoryService } from './services/history.service';
import { Graph } from './models/graph.model';
+import { GraphService } from './services/graph.service';
+import { AnimationService } from './services/animation.service';
@Component({
selector: 'app-root',
@@ -49,6 +50,7 @@ export class AppComponent implements OnInit {
constructor(
private canvasService: CanvasService,
private historyService: HistoryService,
+ private graphService: GraphService,
) {
moment.locale('de');
@@ -100,4 +102,14 @@ export class AppComponent implements OnInit {
this.configForm.reset({...history.config});
this.restoredHistory = history;
}
+
+ public startAnimation() {
+ this.animationActive = true;
+ this.graphService.startAnimation();
+ }
+
+ public stopAnimation() {
+ this.animationActive = false;
+ this.graphService.stopAnimation();
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 7307278..1d87154 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -25,7 +25,8 @@ import { GuillocheDirective } from './directives/guilloche.directive';
import { CanvasService } from './services/canvas.service';
import { HistoryService } from './services/history.service';
import { AnimationService } from './services/animation.service';
-import { ArithmeticService } from './services/arithmetic.service';
+import { MathService } from './services/math.service';
+import { GraphService } from './services/graph.service';
@NgModule({
declarations: [
@@ -43,7 +44,8 @@ import { ArithmeticService } from './services/arithmetic.service';
CanvasService,
HistoryService,
AnimationService,
- ArithmeticService,
+ MathService,
+ GraphService,
],
bootstrap: [AppComponent]
})
diff --git a/src/app/components/graphs.component.html b/src/app/components/graphs.component.html
index 37a7bbd..389ac28 100644
--- a/src/app/components/graphs.component.html
+++ b/src/app/components/graphs.component.html
@@ -1,3 +1,5 @@
diff --git a/src/app/components/graphs.component.ts b/src/app/components/graphs.component.ts
index 59e6d26..195fd39 100644
--- a/src/app/components/graphs.component.ts
+++ b/src/app/components/graphs.component.ts
@@ -25,10 +25,11 @@ import { environment as env } from '../../environments/environment';
import { CanvasService } from './../services/canvas.service';
import { HistoryService } from './../services/history.service';
import { AnimationService } from '../services/animation.service';
-import { ArithmeticService } from '../services/arithmetic.service';
+import { MathService } from '../services/math.service';
import { GuillocheDirective } from './../directives/guilloche.directive';
import { Graph } from '../models/graph.model';
import { Point } from '../models/point.model';
+import { GraphService } from '../services/graph.service';
@Component({
selector: 'app-graphs',
@@ -48,7 +49,6 @@ export class GraphsComponent implements OnChanges, OnInit {
private hash: string;
private animation: Observable;
private timer: Observable;
- private animationSteps: any;
@Input() config: any;
@Input() restoredHistory: any;
@@ -66,7 +66,8 @@ export class GraphsComponent implements OnChanges, OnInit {
private canvasService: CanvasService,
private historyService: HistoryService,
private animationService: AnimationService,
- private arithmetics: ArithmeticService
+ private math: MathService,
+ private graphService: GraphService
) {
this.genLoadedAllGraphs = this.countLoadedGraphs();
this.timer = interval(500);
@@ -90,19 +91,6 @@ export class GraphsComponent implements OnChanges, OnInit {
this.graphs = this.restoredHistory.graphs;
this.hash = this.restoredHistory.hash;
}
-
- if (changes.animationActive) {
- if (this.animationActive) {
- this.animationSteps = setInterval(() => this.animateGraph(), 50);
- } else {
- if (this.animationSteps) {
- clearInterval(this.animationSteps);
- }
- }
- }
- }
- private animateGraph() {
- this.graphs = this.animationService.animate(this.graphs);
}
private saveHistory() {
@@ -110,6 +98,10 @@ export class GraphsComponent implements OnChanges, OnInit {
this.historyService.save(this.graphs, this.config);
}
+ private saveGraph() {
+ this.graphService.set(this.graphs);
+ }
+
private updateGraphs(): void {
const genShiftStart = this.shiftPoint(this.matrix.start, this.config.vectors.start);
const genShiftEnd = this.shiftPoint(this.matrix.end, this.config.vectors.end);
@@ -130,6 +122,7 @@ export class GraphsComponent implements OnChanges, OnInit {
this.graphs = curveList.map(curve => this.adjustGraph(curve));
this.hash = this.historyService.hash(this.graphs);
this.saveHistory();
+ this.saveGraph();
}
private adjustGraph(curve) {
@@ -147,7 +140,7 @@ export class GraphsComponent implements OnChanges, OnInit {
const generatedPoints = [];
for (let i = 0; i < this.config.nodes; i++) {
- generatedPoints.push(this.arithmetics.randomPoint(this.matrix, this.config.overlap));
+ generatedPoints.push(this.math.randomPoint(this.matrix, this.config.overlap));
}
return generatedPoints;
@@ -165,13 +158,13 @@ export class GraphsComponent implements OnChanges, OnInit {
private updateMatrix() {
const totalArea = Math.abs(this.canvas.clientWidth * this.canvas.clientHeight);
- const totalCenter = this.arithmetics.centerPoint(this.canvas.clientWidth, this.canvas.clientHeight);
+ const totalCenter = this.math.centerOfArea(this.canvas.clientWidth, this.canvas.clientHeight);
const baseArea = Math.abs(this.config.width * this.config.height);
const baseScale = Math.pow(totalArea / baseArea * this.config.scale, 0.5);
const baseWidthScaled = baseScale * this.config.width;
const baseHeightScaled = baseScale * this.config.height;
- const baseCenter = this.arithmetics.centerPoint(baseWidthScaled, baseHeightScaled);
+ const baseCenter = this.math.centerOfArea(baseWidthScaled, baseHeightScaled);
this.matrix = {
start: {
@@ -189,7 +182,7 @@ export class GraphsComponent implements OnChanges, OnInit {
}
private genVectorPoint(point: Point, vector: number) {
- const range = this.arithmetics.Δ(this.matrix.start, this.matrix.end) * this.config.vectors.range;
+ const range = this.math.Δ(this.matrix.start, this.matrix.end) * this.config.vectors.range;
return {
x: range * Math.sin(Math.PI * vector) + point.x,
@@ -215,7 +208,7 @@ export class GraphsComponent implements OnChanges, OnInit {
private *shiftNumber(space: number, vector: number) {
let current = 0;
let index = 0;
- const sign = this.flipSign();
+ const sign = this.math.flipSign();
while (true) {
yield current = sign.next().value * index * space + current;
@@ -223,14 +216,6 @@ export class GraphsComponent implements OnChanges, OnInit {
}
}
- private *flipSign() {
- let sign = 1;
-
- while (true) {
- yield sign = sign * (-1);
- }
- }
-
public prepareGuillocheExport(guillocheElement) {
if (this.genLoadedAllGraphs.next().value) {
this.svgChange.emit(this.svgElementRef);
diff --git a/src/app/directives/guilloche.directive.ts b/src/app/directives/guilloche.directive.ts
index af9cfed..fcbf4ea 100644
--- a/src/app/directives/guilloche.directive.ts
+++ b/src/app/directives/guilloche.directive.ts
@@ -14,11 +14,13 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { ElementRef, HostListener, Output, EventEmitter, Input, Directive, OnChanges, SimpleChanges } from '@angular/core';
+import { ElementRef, HostListener, Output, EventEmitter, Input, Directive, OnChanges, OnInit, SimpleChanges } 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 * as Ease from 'd3-ease';
+import * as Timer from 'd3-timer';
import { environment as env } from './../../environments/environment';
import { Config } from './../models/config.model';
@@ -26,33 +28,74 @@ import { Graph } from './../models/graph.model';
import { Point } from './../models/point.model';
import { Param } from './../models/param.model';
import { CanvasService } from './../services/canvas.service';
-import { ArithmeticService } from './../services/arithmetic.service';
+import { MathService } from './../services/math.service';
+import { GraphService } from '../services/graph.service';
+import { AnimationService } from './../services/animation.service';
+import { spread } from 'q';
@Directive({
selector: '[guilloche]'
})
-export class GuillocheDirective implements OnChanges {
+export class GuillocheDirective implements OnChanges, OnInit {
private canvas: any;
private group: any;
+ private animationInterval: any;
+ private x: any;
+ private y: any;
@Input() graph: Graph;
@Input() matrix: any;
@Input() config: any;
+ @Input() animate: boolean;
@Output() guillocheChange = new EventEmitter();
constructor(
private canvasService: CanvasService,
private el: ElementRef,
- private arithmetics: ArithmeticService
+ private math: MathService,
+ private graphService: GraphService,
+ private animationService: AnimationService
) {
this.group = Selection.select(el.nativeElement);
this.canvas = Selection.select(this.canvasService.get);
}
+ ngOnInit() {
+ // console.log('guilloche:init');
+ // Timer.timer(function(elapsed) {
+ // let t = (elapsed % 3000) / 3000;
+ // console.log(t);
+ // // dot1.attr("cx", x(t)).attr("cy", y(ease(t)));
+ // // dot2.attr("cy", y(ease(t)));
+ // });
+
+ // console.log(Ease.easeLinear(0.5));
+ // const t = Timer.timer(function(elapsed) {
+ // if (elapsed > 200) {
+ // t.stop();
+ // }
+ // }, 1000);
+ }
+
ngOnChanges(changes: SimpleChanges) {
- // console.log(this.graph);
+ // @todo modify graph here instead of in graphs.component.ts
+ this.group.selectAll('*').remove();
+
+ // console.log('guilloche:changes', changes);
+
+ if (this.graphService.isAnimated) {
+ console.log('is animated');
+ // this.graphService.startAnimation();
+ this.animationInterval = setInterval(() => this.animateGraph(), 60);
+ } else {
+ if (this.animationInterval) {
+ console.log('not animated');
+ // this.graphService.stopAnimation();
+ clearInterval(this.animationInterval);
+ }
+ }
const points = [
this.graph.start.point,
@@ -63,6 +106,18 @@ export class GuillocheDirective implements OnChanges {
this.guillocheChanged();
}
+ private animateGraph() {
+ this.group.selectAll('*').remove();
+ this.graph = this.animationService.animate(this.graph);
+ // this.saveGraph();
+ const points = [
+ this.graph.start.point,
+ ...this.graph.nodes,
+ this.graph.end.point
+ ];
+ this.spreadLines(points);
+ }
+
public guillocheChanged() {
this.guillocheChange.emit(this.el.nativeElement);
}
@@ -77,50 +132,93 @@ export class GuillocheDirective implements OnChanges {
.attr('stroke-width', this.graph.stroke)
.attr('fill', 'none');
- if (!env.production) {
+ if (env.grid) {
this.showGrid();
}
}
private spreadLines(points: Point[]) {
- const indexMiddle = Math.floor(points.length * 0.5);
- const pointMiddle = points[indexMiddle];
- const closestCenter = this.arithmetics.getClosestCenter(pointMiddle, this.matrix);
- const radius = this.arithmetics.Δ(pointMiddle, closestCenter);
- const spreadPoints = [];
- const pies = 80;
+ const shiftedMedians = [];
+ const medianPoint = this.math.centerOfCurve(points);
+ const medianIndex = this.math.medianIndex(points);
+ const genshiftedMedians = this.graphService.spreadOrthogonal(medianPoint, 20);
- for (let i = 0; i < pies; i++) {
- spreadPoints.push({
- x: radius * Math.cos(2 * i * Math.PI / pies) + closestCenter.x,
- y: radius * Math.sin(2 * i * Math.PI / pies) + closestCenter.y,
+ for (let i = 0; i < this.config.spread; i++) {
+ shiftedMedians.push(genshiftedMedians.next().value);
+ }
+
+ // const indexMiddle = Math.floor(points.length * 0.5);
+ // const pointMiddle = points[indexMiddle];
+ // const closestCenter = this.math.getClosestCenter(pointMiddle, this.matrix);
+ // const radius = this.math.Δ(pointMiddle, closestCenter);
+ // const shiftedMedians = [];
+ // const pies = 200;
+
+ // for (let i = 0; i < pies; i++) {
+ // shiftedMedians.push({
+ // x: radius * Math.cos(2 * i * Math.PI / pies) + closestCenter.x,
+ // y: radius * Math.sin(2 * i * Math.PI / pies) + closestCenter.y,
+ // });
+ // }
+
+ // shiftedMedians.sort((a, b) => {
+ // // Good possibility to align orientation points outsite
+ // return this.math.Δ(b, pointMiddle) - this.math.Δ(a, pointMiddle);
+ // });
+
+ // console.log(shiftedMedians);
+
+ // shiftedMedians.some((point, index) => {
+ // points[indexMiddle] = point;
+
+ // this.drawGraph(points);
+
+ // return index === this.config.spread - 1;
+ // });
+ if (env.grid) {
+ [medianPoint, ...shiftedMedians].forEach((point, index) => {
+ this.group.append('circle')
+ .attr('cx', point.x)
+ .attr('cy', point.y)
+ .attr('r', 10 / index)
+ .attr('fill-opacity', 0.6)
+ .attr('fill', 'darkgray');
});
}
- spreadPoints.sort((a, b) => {
- // Good possibility to align orientation points outsite
- return this.arithmetics.Δ(b, pointMiddle) - this.arithmetics.Δ(a, pointMiddle);
- });
-
- spreadPoints.some((point, index) => {
- points[indexMiddle] = point;
-
- this.drawGraph(points);
-
- return index === this.config.spread - 1;
+ shiftedMedians.forEach(median => {
+ const shiftedGraph = points.slice();
+ shiftedGraph.splice(medianIndex, 1, median);
+ this.drawGraph(shiftedGraph);
});
+ // this.drawGraph(points);
}
+ // private animateRange(n: number) {
+ // return Ease.scaleLinear().range([n, n + 100]);
+ // }
+
private showGrid() {
- this.graph.nodes.forEach(point => {
- this.group.append('circle')
+ this.graph.nodes.forEach((point, index) => {
+ const circle = this.group.append('g');
+ // const xRange = this.animateRange(point.x);
+ // const yRange = this.animateRange(point.y);
+
+ circle.append('circle')
.attr('cx', point.x)
.attr('cy', point.y)
.attr('r', 3)
- .attr('stroke-width', 0.1)
- .attr('fill-opacity', 0)
- .attr('stroke', 'darkgray');
+ .attr('fill-opacity', 0.6)
+ .attr('fill', this.graph.color);
+
+ circle.append('text')
+ .attr('x', point.x)
+ .attr('y', point.y)
+ .attr('dx', 8)
+ .attr('dy', 15)
+ .attr('fill', this.graph.color)
+ .text(index);
});
}
}
diff --git a/src/app/models/point.model.ts b/src/app/models/point.model.ts
index aff2e70..f1103e6 100644
--- a/src/app/models/point.model.ts
+++ b/src/app/models/point.model.ts
@@ -18,4 +18,5 @@ export interface Point {
x: number;
y: number;
color?: string;
+ ascent?: number;
}
diff --git a/src/app/services/animation.service.ts b/src/app/services/animation.service.ts
index 2f37612..4251f39 100644
--- a/src/app/services/animation.service.ts
+++ b/src/app/services/animation.service.ts
@@ -19,7 +19,7 @@ import { interval, Observable } from 'rxjs';
import * as Selection from 'd3-selection';
import { Graph } from '../models/graph.model';
-import { ArithmeticService } from './arithmetic.service';
+import { MathService } from './math.service';
import { HistoryService } from './history.service';
@Injectable()
@@ -33,27 +33,28 @@ export class AnimationService {
// private subscribtion: any;
constructor(
- private arithmetics: ArithmeticService,
+ private math: MathService,
private historyService: HistoryService,
) {
}
- public animate(initialGraphs: Graph[]) {
- const newGraphs = initialGraphs.slice();
+ // public animate(initialGraphs: Graph[]) {
+ public animate(initialGraph: Graph) {
+ // const newGraphs = initialGraphs.slice();
- return newGraphs.map(graph => {
+ // return newGraphs.map(graph => {
- const newGraph = Object.assign({}, graph);
+ const newGraph = Object.assign({}, initialGraph);
const indexMiddle = Math.floor(newGraph.nodes.length * 0.5);
const pointMiddle = newGraph.nodes[indexMiddle];
newGraph.nodes.splice(indexMiddle, 1, {
- x: pointMiddle.x - 1,
- y: pointMiddle.y + 1,
+ x: pointMiddle.x - 2,
+ y: pointMiddle.y + 2,
});
return newGraph;
- });
+ // });
}
}
diff --git a/src/app/services/graph.service.ts b/src/app/services/graph.service.ts
new file mode 100644
index 0000000..c053a64
--- /dev/null
+++ b/src/app/services/graph.service.ts
@@ -0,0 +1,76 @@
+import { Validators } from '@angular/forms';
+/**
+ * Copyright (C) 2018 Michael Czechowski
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
+import * as Selection from 'd3-selection';
+
+import { MathService } from './math.service';
+import { Graph } from './../models/graph.model';
+import { Point } from './../models/point.model';
+
+@Injectable()
+export class GraphService {
+ private graphs: Graph[];
+ private animation: boolean | null;
+
+ constructor(
+ private math: MathService
+ ) {}
+
+ public get() {
+ return this.graphs;
+ }
+
+ public set(newGraphs: Graph[]) {
+ // console.log('GraphService:set', newGraphs);
+ this.graphs = newGraphs;
+ }
+
+ public get isAnimated() {
+ return this.animation;
+ }
+
+ public startAnimation() {
+ this.animation = true;
+ }
+
+ public stopAnimation() {
+ this.animation = false;
+ }
+
+ public *spreadOrthogonal(start: Point, spacing: number) {
+ const sign = this.math.flipSign();
+ let currentPoint = start;
+ let i = 0;
+
+ while (true) {
+ const currentSpacing = sign.next().value * spacing * i;
+ currentPoint = this.shiftPoint(currentPoint, start.ascent, currentSpacing);
+
+ yield currentPoint;
+
+ i++;
+ }
+ }
+
+ public shiftPoint(point: Point, radians: number, spacing: number) {
+ return {
+ x: Math.sin(radians * Math.PI) * spacing + point.x,
+ y: Math.cos(radians * Math.PI) * spacing + point.y
+ };
+ }
+}
diff --git a/src/app/services/arithmetic.service.ts b/src/app/services/math.service.ts
similarity index 63%
rename from src/app/services/arithmetic.service.ts
rename to src/app/services/math.service.ts
index f79ff79..aa5cde9 100644
--- a/src/app/services/arithmetic.service.ts
+++ b/src/app/services/math.service.ts
@@ -19,9 +19,10 @@ import * as Selection from 'd3-selection';
import * as Random from 'd3-random';
import { Point } from './../models/point.model';
+import { Graph } from './../models/graph.model';
@Injectable()
-export class ArithmeticService {
+export class MathService {
/**
* Calculate distance between to points with coordinates.
@@ -64,10 +65,58 @@ export class ArithmeticService {
};
}
- public centerPoint(width, height): Point {
+ public centerOfArea(width, height): Point {
return {
x: width * 0.5,
y: height * 0.5
};
}
+
+ public centerOfPoints(p1: Point, p2: Point) {
+ return {
+ x: (p1.x + p2.x) * 0.5,
+ y: (p1.y + p2.y) * 0.5
+ };
+ }
+
+ public centerOfCurve(curve: Point[]) {
+ const genMedian = this.medianPoint(curve);
+ const p1 = genMedian.next().value;
+ const p2 = genMedian.next().value;
+ const radians = this.angleRadians(p1, p2);
+
+ return Object.assign(this.centerOfPoints(p1, p2), { ascent: radians });
+ }
+
+ public angleRadians(p1: Point, p2: Point) {
+ return Math.atan2(p2.y - p1.y, p2.x - p1.x);
+ }
+
+ public angleDegree(p1: Point, p2: Point) {
+ return this.angleRadians(p1, p2) * 180 / Math.PI;
+ }
+
+ public medianIndex(list: any): number {
+ return Math.floor(list.length * 0.5);
+ }
+
+ public *medianPoint(points: Point[]) {
+ let index: number;
+ const list: Point[] = points.slice();
+
+ while (list) {
+ index = this.medianIndex(points);
+ yield list[index];
+
+ list.splice(index, 1);
+ }
+ }
+
+ public *flipSign() {
+ let sign = 1;
+
+ while (true) {
+ yield sign = sign * (-1);
+ }
+ }
}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index a71c77f..4a6a2ec 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -16,6 +16,7 @@
export const environment = {
production: false,
+ grid: false,
guilloche: {
colors: {
primary: '#cb0c4d',
@@ -30,11 +31,11 @@ export const environment = {
vectors: {
start: 1,
end: 0,
- range: 0.4,
+ range: 0.4
},
nodes: 4,
stroke: 1,
spread: 12,
- space: 5
+ space: 10
}
};