enhanced graph shifting
This commit is contained in:
@@ -29,7 +29,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve --aot -o",
|
||||||
"build": "ng build --prod",
|
"build": "ng build --prod",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
|
|||||||
@@ -99,8 +99,8 @@
|
|||||||
Animation
|
Animation
|
||||||
</label>
|
</label>
|
||||||
<div class="btn-group" role="group" aria-label="Basic example">
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
<button type="button" class="btn" (click)="animationActive = true" [ngClass]="{'btn-outline-primary': !animationActive, 'btn-primary': animationActive}">An</button>
|
<button type="button" class="btn" (click)="startAnimation()" [ngClass]="{'btn-outline-primary': !animationActive, 'btn-primary': animationActive}">An</button>
|
||||||
<button type="button" class="btn" (click)="animationActive = false" [ngClass]="{'btn-outline-primary': animationActive, 'btn-primary': !animationActive}">Aus</button>
|
<button type="button" class="btn" (click)="stopAnimation()" [ngClass]="{'btn-outline-primary': animationActive, 'btn-primary': !animationActive}">Aus</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-divider mb-4"></div>
|
<div class="dropdown-divider mb-4"></div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AnimationService } from './services/animation.service';
|
|
||||||
/**
|
/**
|
||||||
* Copyright (C) 2018 Michael Czechowski <mail@dailysh.it>
|
* Copyright (C) 2018 Michael Czechowski <mail@dailysh.it>
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
* 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 { CanvasService } from './services/canvas.service';
|
||||||
import { HistoryService } from './services/history.service';
|
import { HistoryService } from './services/history.service';
|
||||||
import { Graph } from './models/graph.model';
|
import { Graph } from './models/graph.model';
|
||||||
|
import { GraphService } from './services/graph.service';
|
||||||
|
import { AnimationService } from './services/animation.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -49,6 +50,7 @@ export class AppComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private canvasService: CanvasService,
|
private canvasService: CanvasService,
|
||||||
private historyService: HistoryService,
|
private historyService: HistoryService,
|
||||||
|
private graphService: GraphService,
|
||||||
) {
|
) {
|
||||||
moment.locale('de');
|
moment.locale('de');
|
||||||
|
|
||||||
@@ -100,4 +102,14 @@ export class AppComponent implements OnInit {
|
|||||||
this.configForm.reset({...history.config});
|
this.configForm.reset({...history.config});
|
||||||
this.restoredHistory = history;
|
this.restoredHistory = history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public startAnimation() {
|
||||||
|
this.animationActive = true;
|
||||||
|
this.graphService.startAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopAnimation() {
|
||||||
|
this.animationActive = false;
|
||||||
|
this.graphService.stopAnimation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import { GuillocheDirective } from './directives/guilloche.directive';
|
|||||||
import { CanvasService } from './services/canvas.service';
|
import { CanvasService } from './services/canvas.service';
|
||||||
import { HistoryService } from './services/history.service';
|
import { HistoryService } from './services/history.service';
|
||||||
import { AnimationService } from './services/animation.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -43,7 +44,8 @@ import { ArithmeticService } from './services/arithmetic.service';
|
|||||||
CanvasService,
|
CanvasService,
|
||||||
HistoryService,
|
HistoryService,
|
||||||
AnimationService,
|
AnimationService,
|
||||||
ArithmeticService,
|
MathService,
|
||||||
|
GraphService,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<svg #svg width="100%" height="100%">
|
<svg #svg width="100%" height="100%">
|
||||||
<g guilloche *ngFor="let graph of graphs" [graph]="graph" [matrix]="matrix" [config]="config" (guillocheChange)="prepareGuillocheExport($event)"></g>
|
<g guilloche [graph]="graphs[0]" [matrix]="matrix" [config]="config" [animate]="true" (guillocheChange)="prepareGuillocheExport($event)"></g>
|
||||||
|
<g guilloche [graph]="graphs[1]" [matrix]="matrix" [config]="config" [animate]="true" (guillocheChange)="prepareGuillocheExport($event)"></g>
|
||||||
|
<!-- <g guilloche *ngFor="let graph of graphs" [graph]="graph" [matrix]="matrix" [config]="config" [animate]="true" (guillocheChange)="prepareGuillocheExport($event)"></g> -->
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 511 B |
@@ -25,10 +25,11 @@ import { environment as env } from '../../environments/environment';
|
|||||||
import { CanvasService } from './../services/canvas.service';
|
import { CanvasService } from './../services/canvas.service';
|
||||||
import { HistoryService } from './../services/history.service';
|
import { HistoryService } from './../services/history.service';
|
||||||
import { AnimationService } from '../services/animation.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 { GuillocheDirective } from './../directives/guilloche.directive';
|
||||||
import { Graph } from '../models/graph.model';
|
import { Graph } from '../models/graph.model';
|
||||||
import { Point } from '../models/point.model';
|
import { Point } from '../models/point.model';
|
||||||
|
import { GraphService } from '../services/graph.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-graphs',
|
selector: 'app-graphs',
|
||||||
@@ -48,7 +49,6 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
private hash: string;
|
private hash: string;
|
||||||
private animation: Observable<Graph[]>;
|
private animation: Observable<Graph[]>;
|
||||||
private timer: Observable<number>;
|
private timer: Observable<number>;
|
||||||
private animationSteps: any;
|
|
||||||
|
|
||||||
@Input() config: any;
|
@Input() config: any;
|
||||||
@Input() restoredHistory: any;
|
@Input() restoredHistory: any;
|
||||||
@@ -66,7 +66,8 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
private canvasService: CanvasService,
|
private canvasService: CanvasService,
|
||||||
private historyService: HistoryService,
|
private historyService: HistoryService,
|
||||||
private animationService: AnimationService,
|
private animationService: AnimationService,
|
||||||
private arithmetics: ArithmeticService
|
private math: MathService,
|
||||||
|
private graphService: GraphService
|
||||||
) {
|
) {
|
||||||
this.genLoadedAllGraphs = this.countLoadedGraphs();
|
this.genLoadedAllGraphs = this.countLoadedGraphs();
|
||||||
this.timer = interval(500);
|
this.timer = interval(500);
|
||||||
@@ -90,19 +91,6 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
this.graphs = this.restoredHistory.graphs;
|
this.graphs = this.restoredHistory.graphs;
|
||||||
this.hash = this.restoredHistory.hash;
|
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() {
|
private saveHistory() {
|
||||||
@@ -110,6 +98,10 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
this.historyService.save(this.graphs, this.config);
|
this.historyService.save(this.graphs, this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveGraph() {
|
||||||
|
this.graphService.set(this.graphs);
|
||||||
|
}
|
||||||
|
|
||||||
private updateGraphs(): void {
|
private updateGraphs(): void {
|
||||||
const genShiftStart = this.shiftPoint(this.matrix.start, this.config.vectors.start);
|
const genShiftStart = this.shiftPoint(this.matrix.start, this.config.vectors.start);
|
||||||
const genShiftEnd = this.shiftPoint(this.matrix.end, this.config.vectors.end);
|
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.graphs = curveList.map(curve => this.adjustGraph(curve));
|
||||||
this.hash = this.historyService.hash(this.graphs);
|
this.hash = this.historyService.hash(this.graphs);
|
||||||
this.saveHistory();
|
this.saveHistory();
|
||||||
|
this.saveGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
private adjustGraph(curve) {
|
private adjustGraph(curve) {
|
||||||
@@ -147,7 +140,7 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
const generatedPoints = [];
|
const generatedPoints = [];
|
||||||
|
|
||||||
for (let i = 0; i < this.config.nodes; i++) {
|
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;
|
return generatedPoints;
|
||||||
@@ -165,13 +158,13 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
|
|
||||||
private updateMatrix() {
|
private updateMatrix() {
|
||||||
const totalArea = Math.abs(this.canvas.clientWidth * this.canvas.clientHeight);
|
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 baseArea = Math.abs(this.config.width * this.config.height);
|
||||||
const baseScale = Math.pow(totalArea / baseArea * this.config.scale, 0.5);
|
const baseScale = Math.pow(totalArea / baseArea * this.config.scale, 0.5);
|
||||||
const baseWidthScaled = baseScale * this.config.width;
|
const baseWidthScaled = baseScale * this.config.width;
|
||||||
const baseHeightScaled = baseScale * this.config.height;
|
const baseHeightScaled = baseScale * this.config.height;
|
||||||
const baseCenter = this.arithmetics.centerPoint(baseWidthScaled, baseHeightScaled);
|
const baseCenter = this.math.centerOfArea(baseWidthScaled, baseHeightScaled);
|
||||||
|
|
||||||
this.matrix = {
|
this.matrix = {
|
||||||
start: {
|
start: {
|
||||||
@@ -189,7 +182,7 @@ export class GraphsComponent implements OnChanges, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private genVectorPoint(point: Point, vector: number) {
|
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 {
|
return {
|
||||||
x: range * Math.sin(Math.PI * vector) + point.x,
|
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) {
|
private *shiftNumber(space: number, vector: number) {
|
||||||
let current = 0;
|
let current = 0;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const sign = this.flipSign();
|
const sign = this.math.flipSign();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
yield current = sign.next().value * index * space + current;
|
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) {
|
public prepareGuillocheExport(guillocheElement) {
|
||||||
if (this.genLoadedAllGraphs.next().value) {
|
if (this.genLoadedAllGraphs.next().value) {
|
||||||
this.svgChange.emit(this.svgElementRef);
|
this.svgChange.emit(this.svgElementRef);
|
||||||
|
|||||||
@@ -14,11 +14,13 @@
|
|||||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
* 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 Selection from 'd3-selection';
|
||||||
import * as Shape from 'd3-shape';
|
import * as Shape from 'd3-shape';
|
||||||
import * as Random from 'd3-random';
|
import * as Random from 'd3-random';
|
||||||
import * as Drag from 'd3-drag';
|
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 { environment as env } from './../../environments/environment';
|
||||||
import { Config } from './../models/config.model';
|
import { Config } from './../models/config.model';
|
||||||
@@ -26,33 +28,74 @@ import { Graph } from './../models/graph.model';
|
|||||||
import { Point } from './../models/point.model';
|
import { Point } from './../models/point.model';
|
||||||
import { Param } from './../models/param.model';
|
import { Param } from './../models/param.model';
|
||||||
import { CanvasService } from './../services/canvas.service';
|
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({
|
@Directive({
|
||||||
selector: '[guilloche]'
|
selector: '[guilloche]'
|
||||||
})
|
})
|
||||||
export class GuillocheDirective implements OnChanges {
|
export class GuillocheDirective implements OnChanges, OnInit {
|
||||||
|
|
||||||
private canvas: any;
|
private canvas: any;
|
||||||
private group: any;
|
private group: any;
|
||||||
|
private animationInterval: any;
|
||||||
|
private x: any;
|
||||||
|
private y: any;
|
||||||
|
|
||||||
@Input() graph: Graph;
|
@Input() graph: Graph;
|
||||||
@Input() matrix: any;
|
@Input() matrix: any;
|
||||||
@Input() config: any;
|
@Input() config: any;
|
||||||
|
@Input() animate: boolean;
|
||||||
|
|
||||||
@Output() guillocheChange = new EventEmitter();
|
@Output() guillocheChange = new EventEmitter();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private canvasService: CanvasService,
|
private canvasService: CanvasService,
|
||||||
private el: ElementRef,
|
private el: ElementRef,
|
||||||
private arithmetics: ArithmeticService
|
private math: MathService,
|
||||||
|
private graphService: GraphService,
|
||||||
|
private animationService: AnimationService
|
||||||
) {
|
) {
|
||||||
this.group = Selection.select(el.nativeElement);
|
this.group = Selection.select(el.nativeElement);
|
||||||
this.canvas = Selection.select(this.canvasService.get);
|
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) {
|
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 = [
|
const points = [
|
||||||
this.graph.start.point,
|
this.graph.start.point,
|
||||||
@@ -63,6 +106,18 @@ export class GuillocheDirective implements OnChanges {
|
|||||||
this.guillocheChanged();
|
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() {
|
public guillocheChanged() {
|
||||||
this.guillocheChange.emit(this.el.nativeElement);
|
this.guillocheChange.emit(this.el.nativeElement);
|
||||||
}
|
}
|
||||||
@@ -77,50 +132,93 @@ export class GuillocheDirective implements OnChanges {
|
|||||||
.attr('stroke-width', this.graph.stroke)
|
.attr('stroke-width', this.graph.stroke)
|
||||||
.attr('fill', 'none');
|
.attr('fill', 'none');
|
||||||
|
|
||||||
if (!env.production) {
|
if (env.grid) {
|
||||||
this.showGrid();
|
this.showGrid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private spreadLines(points: Point[]) {
|
private spreadLines(points: Point[]) {
|
||||||
const indexMiddle = Math.floor(points.length * 0.5);
|
const shiftedMedians = [];
|
||||||
const pointMiddle = points[indexMiddle];
|
const medianPoint = this.math.centerOfCurve(points);
|
||||||
const closestCenter = this.arithmetics.getClosestCenter(pointMiddle, this.matrix);
|
const medianIndex = this.math.medianIndex(points);
|
||||||
const radius = this.arithmetics.Δ(pointMiddle, closestCenter);
|
const genshiftedMedians = this.graphService.spreadOrthogonal(medianPoint, 20);
|
||||||
const spreadPoints = [];
|
|
||||||
const pies = 80;
|
|
||||||
|
|
||||||
for (let i = 0; i < pies; i++) {
|
for (let i = 0; i < this.config.spread; i++) {
|
||||||
spreadPoints.push({
|
shiftedMedians.push(genshiftedMedians.next().value);
|
||||||
x: radius * Math.cos(2 * i * Math.PI / pies) + closestCenter.x,
|
}
|
||||||
y: radius * Math.sin(2 * i * Math.PI / pies) + closestCenter.y,
|
|
||||||
|
// 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) => {
|
shiftedMedians.forEach(median => {
|
||||||
// Good possibility to align orientation points outsite
|
const shiftedGraph = points.slice();
|
||||||
return this.arithmetics.Δ(b, pointMiddle) - this.arithmetics.Δ(a, pointMiddle);
|
shiftedGraph.splice(medianIndex, 1, median);
|
||||||
});
|
this.drawGraph(shiftedGraph);
|
||||||
|
|
||||||
spreadPoints.some((point, index) => {
|
|
||||||
points[indexMiddle] = point;
|
|
||||||
|
|
||||||
this.drawGraph(points);
|
|
||||||
|
|
||||||
return index === this.config.spread - 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// this.drawGraph(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private animateRange(n: number) {
|
||||||
|
// return Ease.scaleLinear().range([n, n + 100]);
|
||||||
|
// }
|
||||||
|
|
||||||
private showGrid() {
|
private showGrid() {
|
||||||
this.graph.nodes.forEach(point => {
|
this.graph.nodes.forEach((point, index) => {
|
||||||
this.group.append('circle')
|
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('cx', point.x)
|
||||||
.attr('cy', point.y)
|
.attr('cy', point.y)
|
||||||
.attr('r', 3)
|
.attr('r', 3)
|
||||||
.attr('stroke-width', 0.1)
|
.attr('fill-opacity', 0.6)
|
||||||
.attr('fill-opacity', 0)
|
.attr('fill', this.graph.color);
|
||||||
.attr('stroke', 'darkgray');
|
|
||||||
|
circle.append('text')
|
||||||
|
.attr('x', point.x)
|
||||||
|
.attr('y', point.y)
|
||||||
|
.attr('dx', 8)
|
||||||
|
.attr('dy', 15)
|
||||||
|
.attr('fill', this.graph.color)
|
||||||
|
.text(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ export interface Point {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
ascent?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { interval, Observable } from 'rxjs';
|
|||||||
import * as Selection from 'd3-selection';
|
import * as Selection from 'd3-selection';
|
||||||
|
|
||||||
import { Graph } from '../models/graph.model';
|
import { Graph } from '../models/graph.model';
|
||||||
import { ArithmeticService } from './arithmetic.service';
|
import { MathService } from './math.service';
|
||||||
import { HistoryService } from './history.service';
|
import { HistoryService } from './history.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -33,27 +33,28 @@ export class AnimationService {
|
|||||||
// private subscribtion: any;
|
// private subscribtion: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private arithmetics: ArithmeticService,
|
private math: MathService,
|
||||||
private historyService: HistoryService,
|
private historyService: HistoryService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public animate(initialGraphs: Graph[]) {
|
// public animate(initialGraphs: Graph[]) {
|
||||||
const newGraphs = initialGraphs.slice();
|
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 indexMiddle = Math.floor(newGraph.nodes.length * 0.5);
|
||||||
const pointMiddle = newGraph.nodes[indexMiddle];
|
const pointMiddle = newGraph.nodes[indexMiddle];
|
||||||
|
|
||||||
newGraph.nodes.splice(indexMiddle, 1, {
|
newGraph.nodes.splice(indexMiddle, 1, {
|
||||||
x: pointMiddle.x - 1,
|
x: pointMiddle.x - 2,
|
||||||
y: pointMiddle.y + 1,
|
y: pointMiddle.y + 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
return newGraph;
|
return newGraph;
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
src/app/services/graph.service.ts
Normal file
76
src/app/services/graph.service.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { Validators } from '@angular/forms';
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2018 Michael Czechowski <mail@dailysh.it>
|
||||||
|
* 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,9 +19,10 @@ import * as Selection from 'd3-selection';
|
|||||||
import * as Random from 'd3-random';
|
import * as Random from 'd3-random';
|
||||||
|
|
||||||
import { Point } from './../models/point.model';
|
import { Point } from './../models/point.model';
|
||||||
|
import { Graph } from './../models/graph.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ArithmeticService {
|
export class MathService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate distance between to points with coordinates.
|
* 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 {
|
return {
|
||||||
x: width * 0.5,
|
x: width * 0.5,
|
||||||
y: height * 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
|
grid: false,
|
||||||
guilloche: {
|
guilloche: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#cb0c4d',
|
primary: '#cb0c4d',
|
||||||
@@ -30,11 +31,11 @@ export const environment = {
|
|||||||
vectors: {
|
vectors: {
|
||||||
start: 1,
|
start: 1,
|
||||||
end: 0,
|
end: 0,
|
||||||
range: 0.4,
|
range: 0.4
|
||||||
},
|
},
|
||||||
nodes: 4,
|
nodes: 4,
|
||||||
stroke: 1,
|
stroke: 1,
|
||||||
spread: 12,
|
spread: 12,
|
||||||
space: 5
|
space: 10
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user