ngx-emfular-diagram 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +33 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +51 -0
  4. package/src/lib/components/arrows/arrow-between-boxes/arrow-between-boxes.component.css +0 -0
  5. package/src/lib/components/arrows/arrow-between-boxes/arrow-between-boxes.component.svg +12 -0
  6. package/src/lib/components/arrows/arrow-between-boxes/arrow-between-boxes.component.ts +60 -0
  7. package/src/lib/components/arrows/arrow-between-elems/arrow-between-elems.component.css +0 -0
  8. package/src/lib/components/arrows/arrow-between-elems/arrow-between-elems.component.svg +9 -0
  9. package/src/lib/components/arrows/arrow-between-elems/arrow-between-elems.component.ts +96 -0
  10. package/src/lib/components/arrows/arrow-between-points/arrow-between-points.component.css +0 -0
  11. package/src/lib/components/arrows/arrow-between-points/arrow-between-points.component.svg +15 -0
  12. package/src/lib/components/arrows/arrow-between-points/arrow-between-points.component.ts +39 -0
  13. package/src/lib/components/draggable/draggable.component.css +0 -0
  14. package/src/lib/components/draggable/draggable.component.svg +8 -0
  15. package/src/lib/components/draggable/draggable.component.ts +49 -0
  16. package/src/lib/components/rectangle/rectangle.component.css +0 -0
  17. package/src/lib/components/rectangle/rectangle.component.svg +3 -0
  18. package/src/lib/components/rectangle/rectangle.component.ts +14 -0
  19. package/src/lib/components/rectangle-with-text/rectangle-with-text.component.css +0 -0
  20. package/src/lib/components/rectangle-with-text/rectangle-with-text.component.svg +14 -0
  21. package/src/lib/components/rectangle-with-text/rectangle-with-text.component.ts +22 -0
  22. package/src/lib/components/text-area-svg/text-area-svg.component.css +0 -0
  23. package/src/lib/components/text-area-svg/text-area-svg.component.spec.ts +60 -0
  24. package/src/lib/components/text-area-svg/text-area-svg.component.svg +6 -0
  25. package/src/lib/components/text-area-svg/text-area-svg.component.ts +57 -0
  26. package/src/lib/models/arrow-style-configuration.ts +8 -0
  27. package/src/lib/models/bounding-box.ts +7 -0
  28. package/src/lib/models/dragger.ts +54 -0
  29. package/src/lib/models/identifiable.ts +3 -0
  30. package/src/lib/models/positionable.ts +8 -0
  31. package/src/lib/services/arrow-style-configuration.service.ts +25 -0
  32. package/src/lib/services/svg-access.service.ts +35 -0
  33. package/src/lib/utils/path-layouter.ts +41 -0
  34. package/src/lib/utils/position-helper.spec.ts +158 -0
  35. package/src/lib/utils/position-helper.ts +59 -0
  36. package/src/lib/utils/text-distributor.spec.ts +110 -0
  37. package/src/lib/utils/text-distributor.ts +68 -0
  38. package/src/public-api.ts +23 -0
  39. package/src/test-setup.ts +10 -0
  40. package/tsconfig.lib.json +18 -0
  41. package/tsconfig.lib.prod.json +11 -0
  42. package/tsconfig.spec.json +13 -0
  43. package/vitest.config.ts +22 -0
  44. package/vitest.setup.ts +6 -0
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # EMFular-diagram
2
+
3
+ This project supplies components and services for the easier creation of ***svg-based*** diagrams.
4
+ To support such components, the library provides and uses explicit contracts of ***graphical identity*** (`interface Identifiable`) and ***position*** (`interface Positionable`), that using code should implement.
5
+ Diagram currently offers text alignment, drag-and-drop-support and arrow components that can attach to explicit points, bounding boxes, or identifiable objects.
6
+ Further contributions are welcome.
7
+
8
+ ## Getting started
9
+
10
+ Install the library via `npm`:
11
+ ```
12
+ npm install ngx-emfular-diagram
13
+ ```
14
+ or adapt your package.json manually to the required version:
15
+ ```
16
+ "ngx-emfular-diagram": "^1.0.0",
17
+ ```
18
+ older versions are available under the old package name, `ngx-svg-graphics`. The latest old version is:
19
+ ```
20
+ "ngx-svg-graphics": "^3.0.0",
21
+ ```
22
+
23
+ ## Support
24
+ Support is currently offered by the main developer, Susanne Göbel under goebel@uni-koblenz.de.
25
+
26
+ ## Contributing
27
+ We are open to contributors. Maybe you would like to write your bachelor's or master's thesis on EMFular? Read our [arXiv-paper](https://arxiv.org/abs/2606.11442) and get in touch with Susanne Göbel goebel@uni-koblenz.de.
28
+
29
+ ## License
30
+ EMFular-diagram is subject to (C) 2026, SoftLang Research Team, University of Koblenz, Faculty of CS, contact Susanne Göbel or Ralf Lämmel.
31
+ It is provided under the ***CC BY 4.0 license***.
32
+ Basically, you are free to share and adapt the material as long as you give proper credit to us and our project.
33
+ Feel free to include EMFular into your research but please cite us.
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "dist",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "ngx-emfular-diagram",
3
+ "version": "1.0.0",
4
+ "description": "Small svg library to link components or svg elements with arrows and allow to drag components.",
5
+ "keywords": [
6
+ "Drag",
7
+ "Arrows",
8
+ "SVG",
9
+ "Angular",
10
+ "EMF"
11
+ ],
12
+ "author": {
13
+ "name": "Susanne Göbel",
14
+ "email": "goebel@uni-koblenz.de"
15
+ },
16
+ "scripts": {
17
+ "build": "ng-packagr -p ng-package.json",
18
+ "test": "vitest --config vitest.config.ts"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/softlang/EMFular-diagram"
23
+ },
24
+ "peerDependencies": {
25
+ "@angular/cdk": "^19.2.7",
26
+ "@angular/common": "^19.2.7",
27
+ "@angular/core": "^19.2.7",
28
+ "@angular/forms": "^19.2.7",
29
+ "@types/uuid": "^10.0.0"
30
+ },
31
+ "dependencies": {
32
+ "tslib": "^2.3.0"
33
+ },
34
+ "sideEffects": false,
35
+ "devDependencies": {
36
+ "@analogjs/vite-plugin-angular": "^1.19.2",
37
+ "@angular-devkit/build-angular": "^19.2.25",
38
+ "@angular/build": "^19.2.25",
39
+ "@angular/compiler": "^19.2.7",
40
+ "@angular/platform-browser": "^19.2.7",
41
+ "@angular/platform-browser-dynamic": "^19.2.7",
42
+ "@testing-library/dom": "^10.4.1",
43
+ "@vitest/browser": "^4.1.10",
44
+ "@vitest/browser-playwright": "^4.1.10",
45
+ "jsdom": "^29.1.1",
46
+ "ng-packagr": "^19.2.2",
47
+ "playwright": "^1.61.1",
48
+ "vite": "^8.1.5",
49
+ "vitest": "^4.1.10"
50
+ }
51
+ }
@@ -0,0 +1,12 @@
1
+ <svg:g>
2
+ <g arrow-between-points
3
+ [startX]="x1"
4
+ [startY]="y1"
5
+ [endX]="x2"
6
+ [endY]="y2"
7
+ [text]="text"
8
+ [arrowType]="arrowType"
9
+ [style]="style"
10
+ >
11
+ </g>
12
+ </svg:g>
@@ -0,0 +1,60 @@
1
+ import { AfterViewInit, ChangeDetectorRef, Component, Input, OnChanges,} from '@angular/core';
2
+ import { Point } from "@angular/cdk/drag-drop";
3
+ import {v4 as uuidv4} from "uuid";
4
+ import {BoundingBox} from "../../../models/bounding-box";
5
+ import {PathLayouter} from "../../../utils/path-layouter";
6
+ import {ArrowBetweenPointsComponent} from "../arrow-between-points/arrow-between-points.component";
7
+
8
+ @Component({
9
+ selector: '[arrow-between-boxes]',
10
+ templateUrl: './arrow-between-boxes.component.svg',
11
+ styleUrl: './arrow-between-boxes.component.css',
12
+ imports: [ArrowBetweenPointsComponent]
13
+ })
14
+ export class ArrowBetweenBoxesComponent implements OnChanges, AfterViewInit {
15
+ @Input() start!: BoundingBox;
16
+ @Input() end!: BoundingBox;
17
+ @Input() arrowType?: string;
18
+ @Input() text?: string;
19
+ @Input() style?: string;
20
+
21
+
22
+ x1: number = 0;
23
+ y1: number = 0;
24
+ x2: number = 5;
25
+ y2: number = 5;
26
+
27
+ id = uuidv4();
28
+
29
+ positioned= false;
30
+
31
+ constructor(
32
+ private cdr: ChangeDetectorRef,
33
+ ) {}
34
+
35
+ ngAfterViewInit() {
36
+ this.computePositions()
37
+ this.positioned = true;
38
+ this.cdr.detectChanges();
39
+ }
40
+
41
+ ngOnChanges() {
42
+ if(this.positioned) {
43
+ this.computePositions()
44
+ }
45
+ }
46
+
47
+
48
+ private computePositions() {
49
+ let res = PathLayouter.bestPoints(this.start, this.end);
50
+ this.applyBestPoints(res)
51
+ }
52
+
53
+ private applyBestPoints(res: Point[]) {
54
+ this.x1 = res[0].x;
55
+ this.y1 = res[0].y;
56
+ this.x2 = res[1].x;
57
+ this.y2 = res[1].y;
58
+ }
59
+
60
+ }
@@ -0,0 +1,9 @@
1
+ <svg:g #arrow>
2
+ <g arrow-between-boxes *ngIf="start && end"
3
+ [start]="start"
4
+ [end]="end"
5
+ [arrowType]="arrowType"
6
+ [text]="text"
7
+ [style]="style">
8
+ </g>
9
+ ></svg:g>
@@ -0,0 +1,96 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef,
4
+ Component, ElementRef,
5
+ Input, OnChanges, OnDestroy,
6
+ OnInit, SimpleChanges,
7
+ ViewChild
8
+ } from '@angular/core';
9
+ import {Observable, Subscription} from "rxjs";
10
+ import { NgIf } from '@angular/common';
11
+ import {BoundingBox} from "../../../models/bounding-box";
12
+ import {ArrowBetweenBoxesComponent} from "../arrow-between-boxes/arrow-between-boxes.component";
13
+ import {SVGAccessService} from "../../../services/svg-access.service";
14
+
15
+ @Component({
16
+ selector: '[arrowElems]',
17
+ templateUrl: './arrow-between-elems.component.svg',
18
+ styleUrl: './arrow-between-elems.component.css',
19
+ imports: [NgIf, ArrowBetweenBoxesComponent]
20
+ })
21
+ export class ArrowBetweenElemsComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
22
+
23
+ @Input() startGID!: string;
24
+ @Input() startSuffix!: string;
25
+ @Input() endGID!: string;
26
+ @Input() endSuffix!: string;
27
+ @Input() arrowType?: string;
28
+
29
+ @Input() breaks: BoundingBox[] = [];
30
+ @Input() text?: string;
31
+ @Input() style?: string; //todo move into ArrowStyleConfig?
32
+
33
+ startId!: string;
34
+ endId!: string;
35
+
36
+ start?: BoundingBox;
37
+ end?: BoundingBox;
38
+
39
+ positioned= false;
40
+ @ViewChild('arrow') node!: ElementRef<SVGGraphicsElement>;
41
+
42
+ changeNotifier: Observable<string>;
43
+ changeSubscription: Subscription;
44
+
45
+ //idea: compute the two input positions as relative to the current elem
46
+ constructor(
47
+ private svgAccessService: SVGAccessService,
48
+ private cdr: ChangeDetectorRef) {
49
+ this.changeNotifier = this.svgAccessService.listenToPositionChange()
50
+ this.changeSubscription = this.changeNotifier.subscribe(nextString => {
51
+ if (nextString == this.startGID || nextString == this.endGID) {
52
+ setTimeout(() => {
53
+ this.computePositionsByIds()
54
+ this.cdr.detectChanges()
55
+ }, 0)
56
+ }
57
+ })
58
+ }
59
+
60
+ ngOnInit() {
61
+ this.startId = this.startGID+this.startSuffix;
62
+ this.endId = this.endGID+this.endSuffix;
63
+ }
64
+
65
+ ngOnChanges(_: SimpleChanges) {
66
+ this.startId = this.startGID+this.startSuffix;
67
+ this.endId = this.endGID+this.endSuffix;
68
+ this.computePositionsByIds()
69
+ this.cdr.detectChanges()
70
+ }
71
+
72
+ ngAfterViewInit() {
73
+ this.positioned = true;
74
+ this.computePositionsByIds()
75
+ this.cdr.detectChanges()
76
+ }
77
+
78
+ private computePositionsByIds() {
79
+ if (this.node?.nativeElement){
80
+ let rel = this.node.nativeElement as SVGGraphicsElement
81
+ let startOpt = this.svgAccessService.getRelativePosition(this.startId, rel)
82
+ if (startOpt) {
83
+ this.start = startOpt
84
+ }
85
+ let endOpt = this.svgAccessService.getRelativePosition(this.endId, rel)
86
+ if (endOpt) {
87
+ this.end = endOpt
88
+ }
89
+ } else console.log('No native element yet')
90
+ }
91
+
92
+ ngOnDestroy() {
93
+ this.changeSubscription.unsubscribe();
94
+ }
95
+
96
+ }
@@ -0,0 +1,15 @@
1
+ <svg:g>
2
+ <path [attr.id]="id+'-path'"
3
+ [attr.d]="'M '+startX+','+startY+' L '+endX+','+endY"
4
+ [attr.stroke]="arrowStyleConfiguration.color"
5
+ [attr.stroke-dasharray]="arrowStyleConfiguration.dashed"
6
+ [attr.marker-start]='"url(#"+arrowStyleConfiguration.startPointer+")"'
7
+ [attr.marker-end]='"url(#"+arrowStyleConfiguration.endPointer+")"'
8
+ style="{{style}}">
9
+ </path>
10
+ <text *ngIf="text">
11
+ <textPath [attr.href]="'#'+id+'-path'">
12
+ {{text}}
13
+ </textPath>
14
+ </text>
15
+ </svg:g>
@@ -0,0 +1,39 @@
1
+ import {Component, Input, OnChanges} from '@angular/core';
2
+ import {NgIf} from "@angular/common";
3
+ import {ArrowStyleConfigurationService} from "../../../services/arrow-style-configuration.service";
4
+ import {ArrowStyleConfiguration} from "../../../models/arrow-style-configuration";
5
+ import {v4 as uuidv4} from "uuid";
6
+
7
+ @Component({
8
+ selector: '[arrow-between-points]',
9
+ imports: [
10
+ NgIf
11
+ ],
12
+ templateUrl: './arrow-between-points.component.svg',
13
+ styleUrl: './arrow-between-points.component.css'
14
+ })
15
+ export class ArrowBetweenPointsComponent implements OnChanges {
16
+
17
+ @Input() startX!: number;
18
+ @Input() startY!: number;
19
+ @Input() endX!: number;
20
+ @Input() endY!: number;
21
+ @Input() text?: string;
22
+ @Input() style?: string;
23
+
24
+ @Input() arrowType?: string;
25
+
26
+ arrowStyleConfiguration: ArrowStyleConfiguration;
27
+ id = uuidv4();
28
+
29
+ constructor(
30
+ private arrowStyleConfigService: ArrowStyleConfigurationService,) {
31
+
32
+ this.arrowStyleConfiguration = this.arrowStyleConfigService.styleArrow()
33
+ }
34
+
35
+ ngOnChanges() {
36
+ this.arrowStyleConfiguration = this.arrowStyleConfigService.styleArrow(this.arrowType)
37
+ }
38
+
39
+ }
@@ -0,0 +1,8 @@
1
+ <svg:g>
2
+ <g [attr.id]="elem.$gId"
3
+ (mousedown)="startDrag($event)"
4
+ (mousemove)="drag($event)"
5
+ (mouseup)="endDrag($event)"
6
+ (click)="clickElem($event)">
7
+ </g>
8
+ </svg:g>
@@ -0,0 +1,49 @@
1
+ import {AfterViewInit, Component, EventEmitter, Output} from '@angular/core';
2
+ import {SVGAccessService} from '../../services/svg-access.service';
3
+ import { Draggable } from '../../models/positionable';
4
+ import {Dragger} from "../../models/dragger";
5
+
6
+ @Component({
7
+ imports: [],
8
+ selector: '[draggable]',
9
+ templateUrl: './draggable.component.svg',
10
+ styleUrl: './draggable.component.css'
11
+ })
12
+ export abstract class DraggableComponent<T extends Draggable> implements AfterViewInit {
13
+
14
+ @Output() chooseElem = new EventEmitter<T>();
15
+ //the caller must initialize both required elements (elem and elementDragger) either in the constructor
16
+ // (or if they are inputs) in the ngOnInit life cycle hook
17
+ elem!: T;
18
+ elemDragger!: Dragger<T>;
19
+
20
+ protected constructor(
21
+ protected svgAccessService: SVGAccessService
22
+ ) {}
23
+
24
+
25
+ ngAfterViewInit() {
26
+ this.svgAccessService.notifyPositionChange(this.elem.$gId)
27
+ }
28
+
29
+ startDrag(event: MouseEvent) {
30
+ this.elemDragger.startDrag(event);
31
+ }
32
+
33
+ drag(event: MouseEvent) {
34
+ if (this.elemDragger.drag(event)) {
35
+ this.svgAccessService.notifyPositionChange(this.elem.$gId)
36
+ }
37
+ }
38
+
39
+ endDrag(event: MouseEvent) {
40
+ this.elemDragger.endDrag(event);
41
+ }
42
+
43
+ clickElem(event: MouseEvent) {
44
+ if (this.elemDragger.clickElem(event)) {
45
+ this.chooseElem.emit(this.elem);
46
+ }
47
+ }
48
+
49
+ }
@@ -0,0 +1,3 @@
1
+ <svg:g>
2
+ <rect [attr.x]="position.x" [attr.y]="position.y" [attr.width]="position.w" [attr.height]="position.h" [attr.fill]="color"></rect>
3
+ </svg:g>
@@ -0,0 +1,14 @@
1
+ import {Component, Input} from '@angular/core';
2
+ import {BoundingBox} from "../../models/bounding-box";
3
+
4
+ @Component({
5
+ selector: '[rectangleG]',
6
+ imports: [],
7
+ templateUrl: './rectangle.component.svg',
8
+ styleUrl: './rectangle.component.css'
9
+ })
10
+ export class RectangleComponent {
11
+ @Input() position!: BoundingBox
12
+ @Input() color?: string = '#ccffff';
13
+
14
+ }
@@ -0,0 +1,14 @@
1
+ <svg:g [attr.id]="id">
2
+ <g rectangleG
3
+ [position]="position"
4
+ [color]="color">
5
+ </g>
6
+ <!-- class name -->
7
+ <g text-area-svg
8
+ [(text)]="text"
9
+ [x]="position.x"
10
+ [y]="position.y"
11
+ [w]="position.w"
12
+ [h]="position.h">
13
+ </g>
14
+ </svg:g>
@@ -0,0 +1,22 @@
1
+ import {Component, EventEmitter, Input, Output} from '@angular/core';
2
+ import {BoundingBox} from "../../models/bounding-box";
3
+ import {RectangleComponent} from "../rectangle/rectangle.component";
4
+ import {TextAreaSvgComponent} from "../text-area-svg/text-area-svg.component";
5
+
6
+ @Component({
7
+ selector: '[rectangle-with-text]',
8
+ imports: [
9
+ TextAreaSvgComponent,
10
+ RectangleComponent
11
+ ],
12
+ templateUrl: './rectangle-with-text.component.svg',
13
+ styleUrl: './rectangle-with-text.component.css'
14
+ })
15
+ export class RectangleWithTextComponent {
16
+ @Input() id!: string;
17
+ @Input() text!: string;
18
+ @Output() textChange = new EventEmitter<string>();
19
+
20
+ @Input() position!: BoundingBox
21
+ @Input() color?: string;
22
+ }
@@ -0,0 +1,60 @@
1
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
2
+ import {afterEach, beforeEach, describe, expect, it } from 'vitest';
3
+
4
+ import { TextAreaSvgComponent } from './text-area-svg.component';
5
+
6
+ describe('TextAreaSvgComponent', () => {
7
+ let component: TextAreaSvgComponent;
8
+ let fixture: ComponentFixture<TextAreaSvgComponent>;
9
+
10
+ beforeEach(async () => {
11
+ await TestBed.configureTestingModule({
12
+ imports: [TextAreaSvgComponent]
13
+ }).compileComponents();
14
+
15
+ fixture = TestBed.createComponent(TextAreaSvgComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ afterEach(() => {
21
+ TestBed.resetTestingModule();
22
+ });
23
+
24
+ it('should create', () => {
25
+ expect(component).toBeTruthy();
26
+ });
27
+
28
+ function initializeDistributedText() {
29
+ component.x = 20;
30
+ component.y = 40;
31
+ component.h = 50;
32
+ component.w = 200;
33
+ }
34
+
35
+ function verifyDistributedText(text: string, distributedText: string[]) {
36
+ component.text = text;
37
+ component.distributeText();
38
+ expect(component.distributedText).toEqual(distributedText);
39
+ }
40
+
41
+ it('should set a two line distributed text', () => {
42
+ initializeDistributedText();
43
+
44
+ verifyDistributedText(
45
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
46
+ ['Lorem ipsum dolor sit', 'amet, consetetur ...']
47
+ );
48
+ });
49
+
50
+ it('should set a short distributed text after setting a longer one', () => {
51
+ initializeDistributedText();
52
+
53
+ verifyDistributedText(
54
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
55
+ ['Lorem ipsum dolor sit', 'amet, consetetur ...']
56
+ );
57
+
58
+ verifyDistributedText('Short', ['Short']);
59
+ });
60
+ });
@@ -0,0 +1,6 @@
1
+ <svg:text [attr.x]="x" [attr.y]="y" [attr.width]="w" [attr.height]="h" dy="0" [attr.style]="" (click)="handleClick()">
2
+ <tspan dy="1.2em" [attr.x]="x" *ngFor="let line of distributedText">{{line}}</tspan>
3
+ </svg:text>
4
+ <svg:foreignObject *ngIf="isActive" [attr.x]="x" [attr.y]="y" [attr.width]="w" [attr.height]="h">
5
+ <input id="text-area" type="text" [(ngModel)]="this.text" (focusout)="leaveTextInput()"/>
6
+ </svg:foreignObject>
@@ -0,0 +1,57 @@
1
+ import {
2
+ Component,
3
+ EventEmitter,
4
+ Input,
5
+ OnChanges,
6
+ Output,
7
+ } from '@angular/core';
8
+ import { FormsModule } from '@angular/forms';
9
+ import { NgFor, NgIf } from '@angular/common';
10
+ import {TextDistributor} from "../../utils/text-distributor";
11
+
12
+ @Component({
13
+ selector: '[text-area-svg]',
14
+ templateUrl: './text-area-svg.component.svg',
15
+ styleUrl: './text-area-svg.component.css',
16
+ imports: [NgFor, NgIf, FormsModule]
17
+ })
18
+ export class TextAreaSvgComponent implements OnChanges {
19
+ /*
20
+ a fixed size svg. If the text exceeds the possible size, we will do a ... for now
21
+ */
22
+
23
+ @Input() text!: string
24
+ @Input() x!: number
25
+ @Input() y!: number
26
+ @Input() w!: number
27
+ @Input() h!: number
28
+ @Input() singleEdit: boolean = false
29
+ @Output() textChange = new EventEmitter<string>();
30
+ //only with singleEdit since that opens an overlay where one can change the text in place
31
+
32
+ distributedText: string[] = []
33
+ isActive: boolean = false;
34
+
35
+
36
+ ngOnChanges() {
37
+ this.distributeText();
38
+ }
39
+
40
+ handleClick() {
41
+ if(this.singleEdit) {
42
+ this.isActive = true
43
+ }
44
+ }
45
+
46
+ leaveTextInput() {
47
+ this.textChange.emit(this.text)
48
+ this.isActive = false;
49
+ }
50
+
51
+ distributeText(){
52
+ this.distributedText = TextDistributor.distributeText(
53
+ this.text, this.w, this.h
54
+ )
55
+ }
56
+
57
+ }
@@ -0,0 +1,8 @@
1
+ export interface ArrowStyleConfiguration {
2
+ color: string;
3
+ dashed: number[];
4
+ //style?: string
5
+
6
+ startPointer?: string;
7
+ endPointer?: string;
8
+ }
@@ -0,0 +1,7 @@
1
+ export interface BoundingBox {
2
+ x: number;
3
+ y: number;
4
+ w: number;
5
+ h: number;
6
+
7
+ }
@@ -0,0 +1,54 @@
1
+ import {Positionable} from './positionable';
2
+
3
+ export class Dragger<T extends Positionable> {
4
+ elem: T;
5
+
6
+ dragActive = false;
7
+ wasReallyDragged = false;
8
+ dragStartX: number = 0;
9
+ dragStartY: number = 0;
10
+
11
+ constructor(element: T) {
12
+ this.elem = element;
13
+ }
14
+
15
+ startDrag(event: MouseEvent) {
16
+ this.dragStartX = event.clientX;
17
+ this.dragStartY = event.clientY;
18
+ this.dragActive = true;
19
+ }
20
+
21
+ // returns true in the case of a real drag event, false otherwise
22
+ drag(event: MouseEvent): boolean {
23
+ if (this.dragActive) {
24
+ this.wasReallyDragged = true;
25
+ event.preventDefault();
26
+ const dragX = event.clientX;
27
+ this.elem.position.x+= (dragX - this.dragStartX);
28
+ this.dragStartX = dragX;
29
+ const dragY = event.clientY;
30
+ this.elem.position.y+= (dragY - this.dragStartY);
31
+ this.dragStartY = dragY;
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+
37
+ endDrag(event: MouseEvent) {
38
+ this.dragActive = false;
39
+ // todo was working for click vs drag, not now setTimeout(() => {this.dragActive = false;}, 50);
40
+ event.preventDefault();
41
+ }
42
+
43
+ //returns true if the click should be treated as click, false if it was from drag
44
+ clickElem(event: MouseEvent): boolean {
45
+ if (this.wasReallyDragged) {
46
+ this.wasReallyDragged = false;
47
+ return false;
48
+ } else {
49
+ event.preventDefault();
50
+ return true;
51
+ }
52
+ }
53
+
54
+ }
@@ -0,0 +1,3 @@
1
+ export interface Identifiable {
2
+ $gId: string;
3
+ }