@obliczeniowo/elementary
Version:
Library made in Angular version 20
466 lines (459 loc) • 25.7 kB
JavaScript
import * as i0 from '@angular/core';
import { input, EventEmitter, effect, HostListener, Output, Component, ViewChildren, HostBinding, Input, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { GroupClass } from '@obliczeniowo/elementary/circle-categories-tree';
import { Point2D } from '@obliczeniowo/elementary/classes';
import * as i1 from '@obliczeniowo/elementary/connectors';
import { DirectionalConnectorComponent, ConnectorsModule } from '@obliczeniowo/elementary/connectors';
import { ElementaryMath } from '@obliczeniowo/elementary/math';
import * as i2 from '@obliczeniowo/elementary/menu';
import { MenuModule } from '@obliczeniowo/elementary/menu';
class CategoryItemComponent {
//
ray = input.required();
//
angle = input.required();
//
textLength = input(100);
//
item = input.required();
//
connectors = input([]);
//
menu = input();
//
clicked = new EventEmitter();
//
over = new EventEmitter();
//
leave = new EventEmitter();
/** */
remove = new EventEmitter();
reverse;
filtered;
menuItems = [
{
id: 1,
text: 'Delete',
command: 'delete',
}
];
subscription;
mouseOver() {
this.filtered?.forEach(connector => {
connector.highlight = true;
connector.changes.detectChanges();
});
this.over.emit({
item: this.item(),
pos: this.position(),
angle: this.angle()
});
const menu = this.menu();
if (menu && this.filtered?.length) {
menu.items = this.menuItems;
}
}
mouseLeave() {
this.filtered?.forEach(connector => {
connector.highlight = false;
connector.changes.detectChanges();
});
this.leave.emit();
const menu = this.menu();
if (menu && !menu?.opened) {
menu.items = [];
}
}
constructor() {
effect(() => {
this.ray();
this.angle();
if (!this.connectors) {
return;
}
this.filtered = this.connectors().filter(connector => connector.startName === this.item().name || connector.endName === this.item().name);
if (this.filtered.length) {
const position = this.position();
this.filtered.forEach(connector => {
if (connector.endName === this.item().name) {
connector.end = position;
connector.endAngle = ElementaryMath.degreesToRadians(this.angle());
}
else {
connector.start = position;
connector.startAngle = ElementaryMath.degreesToRadians(this.angle()) + Math.PI;
}
if (connector.endAngle !== undefined && connector.startAngle !== undefined) {
let dAngle = ElementaryMath.fmod(Math.abs(connector.startAngle - connector.endAngle), Math.PI * 2);
dAngle = (dAngle < Math.PI ? dAngle : 2 * Math.PI - dAngle);
connector.k = (1 - dAngle / (Math.PI)) * 6.5 + 1.5;
connector.changes.detectChanges();
}
});
}
});
effect(() => {
this.reverse = this.angle() > 90 && this.angle() < 270;
});
effect(() => {
if (this.subscription && !this.subscription.closed) {
this.subscription.unsubscribe();
}
const menu = this.menu();
if (menu) {
this.subscription = menu.command.subscribe(command => {
if (menu?.items !== this.menuItems) {
return;
}
switch (command) {
case 'delete':
this.remove.emit(this.item());
break;
}
menu.items = [];
});
}
});
}
ngOnDestroy() {
if (this.subscription && !this.subscription.closed) {
this.subscription.unsubscribe();
}
}
position() {
return {
x: this.ray() + Math.cos(ElementaryMath.degreesToRadians(this.angle())) * (this.r - 10),
y: this.ray() + Math.sin(ElementaryMath.degreesToRadians(this.angle())) * (this.r - 10)
};
}
get r() {
return this.ray() - this.textLength();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.4", type: CategoryItemComponent, isStandalone: false, selector: "g[obl-category-item]", inputs: { ray: { classPropertyName: "ray", publicName: "ray", isSignal: true, isRequired: true, transformFunction: null }, angle: { classPropertyName: "angle", publicName: "angle", isSignal: true, isRequired: true, transformFunction: null }, textLength: { classPropertyName: "textLength", publicName: "textLength", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: true, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", over: "over", leave: "leave", remove: "remove" }, host: { listeners: { "mouseover": "mouseOver()", "mouseleave": "mouseLeave()" } }, ngImport: i0, template: "<svg:g\n [attr.transform]=\"\n 'translate(' +\n ray() +\n ',' +\n ray() +\n ') rotate(' +\n angle() +\n ') translate(' +\n r +\n ', ' +\n (reverse ? -2 : 5) +\n ')' +\n (reverse ? ' rotate(180)' : '')\n \"\n>\n <svg:circle\n [attr.cx]=\"reverse ? 10 : -10\"\n [attr.cy]=\"reverse ? -2 : -5\"\n r=\"2\"\n ></svg:circle>\n <svg:text [attr.text-anchor]=\"reverse ? 'end' : 'start'\">\n {{ item().name }}\n </svg:text>\n</svg:g>\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}circle{fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryItemComponent, decorators: [{
type: Component,
args: [{ selector: 'g[obl-category-item]', standalone: false, template: "<svg:g\n [attr.transform]=\"\n 'translate(' +\n ray() +\n ',' +\n ray() +\n ') rotate(' +\n angle() +\n ') translate(' +\n r +\n ', ' +\n (reverse ? -2 : 5) +\n ')' +\n (reverse ? ' rotate(180)' : '')\n \"\n>\n <svg:circle\n [attr.cx]=\"reverse ? 10 : -10\"\n [attr.cy]=\"reverse ? -2 : -5\"\n r=\"2\"\n ></svg:circle>\n <svg:text [attr.text-anchor]=\"reverse ? 'end' : 'start'\">\n {{ item().name }}\n </svg:text>\n</svg:g>\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}circle{fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] }]
}], ctorParameters: () => [], propDecorators: { clicked: [{
type: Output
}], over: [{
type: Output
}], leave: [{
type: Output
}], remove: [{
type: Output
}], mouseOver: [{
type: HostListener,
args: ['mouseover']
}], mouseLeave: [{
type: HostListener,
args: ['mouseleave']
}] } });
class CategoryRelationGroupComponent {
// Group class as required
group = input.required();
// Ray of circle
ray = input(400);
// max text length
textLength = input(100);
//
connectors = input([]);
/** */
menu = input();
/** */
remove = new EventEmitter();
// emit when user click on item leaf or leaf (category) name
clicked = new EventEmitter();
//
over = new EventEmitter();
//
leave = new EventEmitter();
get r() {
return this.ray() - this.textLength();
}
endAngle(index) {
return ElementaryMath.degreesToRadians(this.group().dAngle * (index + this.group().offset));
}
startAngle() {
return ElementaryMath.degreesToRadians(this.group().dAngle * (this.group().items.length / 2 + this.group().offset));
}
end(index) {
const angle = this.endAngle(index);
return {
x: this.ray() + (this.r - 10) * Math.cos(angle),
y: this.ray() + (this.r - 10) * Math.sin(angle)
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryRelationGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: CategoryRelationGroupComponent, isStandalone: false, selector: "g[obl-category-relation-group]", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, ray: { classPropertyName: "ray", publicName: "ray", isSignal: true, isRequired: false, transformFunction: null }, textLength: { classPropertyName: "textLength", publicName: "textLength", isSignal: true, isRequired: false, transformFunction: null }, connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: true, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { remove: "remove", clicked: "clicked", over: "over", leave: "leave" }, ngImport: i0, template: "@for (item of group().items; track item) {\n <svg:g\n (click)=\"clicked.emit(item)\"\n >\n <svg:g\n obl-category-item\n [angle]=\"group().dAngle * ($index + group().offset) + group().dCategoryOffset\"\n [item]=\"item\"\n [ray]=\"ray()\"\n [connectors]=\"connectors()\"\n [menu]=\"menu()\"\n (clicked)=\"clicked.emit($event)\"\n (over)=\"over.emit($event)\"\n (leave)=\"leave.emit($event)\"\n (remove)=\"remove.emit($event)\"\n ></svg:g>\n </svg:g>\n}\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"], dependencies: [{ kind: "component", type: CategoryItemComponent, selector: "g[obl-category-item]", inputs: ["ray", "angle", "textLength", "item", "connectors", "menu"], outputs: ["clicked", "over", "leave", "remove"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryRelationGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'g[obl-category-relation-group]', standalone: false, template: "@for (item of group().items; track item) {\n <svg:g\n (click)=\"clicked.emit(item)\"\n >\n <svg:g\n obl-category-item\n [angle]=\"group().dAngle * ($index + group().offset) + group().dCategoryOffset\"\n [item]=\"item\"\n [ray]=\"ray()\"\n [connectors]=\"connectors()\"\n [menu]=\"menu()\"\n (clicked)=\"clicked.emit($event)\"\n (over)=\"over.emit($event)\"\n (leave)=\"leave.emit($event)\"\n (remove)=\"remove.emit($event)\"\n ></svg:g>\n </svg:g>\n}\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] }]
}], propDecorators: { remove: [{
type: Output
}], clicked: [{
type: Output
}], over: [{
type: Output
}], leave: [{
type: Output
}] } });
class CircleCategoriesTreeRelationsComponent {
element;
connectors = [];
// Grouping model table
groups = [];
//
disabled;
//
menu = input();
// emit when user click on specific item
itemClicked = new EventEmitter();
//
connectorClicked = new EventEmitter();
//
connectorsChange = new EventEmitter();
total = 0;
// converted/prepared groups to display
groupsClasses = [];
//
newConnector;
set relations(relation) {
setTimeout(() => this.relationsChanged = relation.toArray());
}
;
relationsChanged = [];
mouse;
angle;
menuItems = [
{
id: 1,
text: 'Delete',
command: 'delete',
}
];
constructor(element) {
this.element = element;
}
ngOnChanges(changes) {
if (changes.groups && this.groups) {
this.prepareGroups();
}
}
move(event) {
if (this.newConnector) {
const rect = this.element.nativeElement.getBoundingClientRect();
const k = 800 / rect.width;
const mouse = new Point2D((event.x - rect.left) * k, (event.y - rect.top) * k);
if (mouse.subtract(new Point2D(400, 400)).sickLength() > 300 * 300) {
return;
}
this.mouse = { x: (event.x - rect.left) * k, y: (event.y - rect.top) * k };
if (this.mouse.x !== 400 && this.mouse.y !== 400) {
this.angle = Math.atan2(this.mouse.y - 400, this.mouse.x - 400);
}
}
}
mouseOver() {
const menu = this.menu();
if (menu) {
menu.items = this.menuItems;
menu.target = this.element.nativeElement;
}
}
clicked(event) {
if (this.newConnector && this.mouse) {
this.newConnector = undefined;
this.angle = undefined;
this.mouse = undefined;
}
}
remove(item) {
this.connectors = this.connectors.filter(connector => connector.left !== item.name && connector.right !== item.name);
this.connectorsChange.emit(this.connectors);
}
onItemClicked(item) {
if (this.newConnector && !this.disabled) {
if (item.name === this.newConnector.left ||
this.connectors.find(connector => connector.left === this.newConnector?.left && connector.right === item.name || connector.right === this.newConnector?.left && connector.left === item.name)) {
return;
}
this.newConnector.right = item.name;
this.connectors.push(this.newConnector);
this.connectorsChange.emit(this.connectors);
this.newConnector = undefined;
this.update();
}
else {
this.mouse = undefined;
this.angle = undefined;
this.newConnector = {
left: item.name,
right: undefined
};
}
this.itemClicked.emit({
item,
connectors: this.connectors.filter(connector => connector.left === item.name || connector.right === item.name)
});
}
update() {
if (this.onChange) {
this.onChange({ groups: this.groups, connectors: this.connectors });
}
else {
this.writeValue({ groups: this.groups, connectors: this.connectors });
}
}
onConnectorClicked(connector) {
if (!this.disabled) {
this.connectors = this.connectors.filter(con => con.left !== connector.left || con.right !== connector.right);
this.update();
}
this.connectorClicked.emit({
left: this.groupsClasses
.map(group => group.items)
.reduce((p, c) => (p = [...p, ...c], p), [])
.find(item => item.name === connector.left),
right: this.groupsClasses
.map(group => group.items)
.reduce((p, c) => (p = [...p, ...c], p), [])
.find(item => item.name === connector.right)
});
}
removeConnector(connector) {
this.connectors = this.connectors.filter(con => con !== connector);
this.connectorsChange.emit(this.connectors);
}
over(data) {
if (this.newConnector) {
this.newConnector = { ...this.newConnector, right: data.item.name };
this.mouse = data.pos;
this.angle = ElementaryMath.degreesToRadians(data.angle);
}
}
prepareGroups() {
this.total = this.groups.map(group => group.items.length).reduce((p, c) => (p += c, p), 0);
const dCategoryAngle = 1.5;
const dAngle = (360 - dCategoryAngle * this.groups.length) / (this.total || 1);
let offset = 0;
this.groupsClasses = this.groups.map((group, index) => {
const current = offset;
offset += group.items.length;
return new GroupClass(group, dAngle, current, dCategoryAngle * index);
});
}
onChange;
onTouched = () => { };
writeValue(value) {
Object.assign(this, value);
if (this.groups) {
this.prepareGroups();
}
}
registerOnChange(onChange) {
this.onChange = onChange;
}
registerOnTouched(onTouched) {
this.onTouched = onTouched;
}
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
validate(control) {
return null;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleCategoriesTreeRelationsComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: CircleCategoriesTreeRelationsComponent, isStandalone: false, selector: "obl-circle-categories-tree-relations", inputs: { connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: false, isRequired: false, transformFunction: null }, groups: { classPropertyName: "groups", publicName: "groups", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClicked: "itemClicked", connectorClicked: "connectorClicked", connectorsChange: "connectorsChange" }, host: { listeners: { "mousemove": "move($event)", "mouseover": "mouseOver()", "click": "clicked($event)" }, properties: { "class.disabled": "this.disabled" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: CircleCategoriesTreeRelationsComponent
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: CircleCategoriesTreeRelationsComponent
}
], viewQueries: [{ propertyName: "relations", predicate: DirectionalConnectorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<svg viewBox=\"0 0 800 800\">\n @for (connector of connectors; track $index) {\n <g\n obl-directional-connector\n [startName]=\"connector.left\"\n [endName]=\"connector.right\"\n [menu]=\"menuRef\"\n (click)=\"onConnectorClicked(connector)\"\n (remove)=\"removeConnector(connector)\"\n ></g>\n }\n\n @for (group of groupsClasses; track $index) {\n <g\n obl-category-relation-group\n [group]=\"group\"\n [connectors]=\"relationsChanged\"\n [menu]=\"menuRef\"\n (clicked)=\"onItemClicked($event)\"\n (over)=\"over($event)\"\n (remove)=\"remove($event)\"\n ></g>\n }\n\n @if (newConnector) {\n <g\n obl-directional-connector\n class=\"created\"\n [startName]=\"newConnector.left\"\n [endName]=\"newConnector.right\"\n [end]=\"mouse\"\n [endAngle]=\"angle\"\n [color]=\"'#5fbcd3'\"\n ></g>\n }\n</svg>\n\n<obl-menu #menuRef [target]=\"element.nativeElement\"></obl-menu>", styles: [":host{display:flex}\n"], dependencies: [{ kind: "component", type: i1.DirectionalConnectorComponent, selector: "g[obl-directional-connector]", inputs: ["start", "end", "startAngle", "endAngle", "offset", "k", "color", "startName", "endName", "menu", "highlight"], outputs: ["remove"] }, { kind: "component", type: i2.MenuComponent, selector: "obl-menu", inputs: ["opened", "target", "items", "translations"], outputs: ["clicked"] }, { kind: "component", type: CategoryRelationGroupComponent, selector: "g[obl-category-relation-group]", inputs: ["group", "ray", "textLength", "connectors", "menu"], outputs: ["remove", "clicked", "over", "leave"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleCategoriesTreeRelationsComponent, decorators: [{
type: Component,
args: [{ selector: 'obl-circle-categories-tree-relations', providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: CircleCategoriesTreeRelationsComponent
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: CircleCategoriesTreeRelationsComponent
}
], standalone: false, template: "<svg viewBox=\"0 0 800 800\">\n @for (connector of connectors; track $index) {\n <g\n obl-directional-connector\n [startName]=\"connector.left\"\n [endName]=\"connector.right\"\n [menu]=\"menuRef\"\n (click)=\"onConnectorClicked(connector)\"\n (remove)=\"removeConnector(connector)\"\n ></g>\n }\n\n @for (group of groupsClasses; track $index) {\n <g\n obl-category-relation-group\n [group]=\"group\"\n [connectors]=\"relationsChanged\"\n [menu]=\"menuRef\"\n (clicked)=\"onItemClicked($event)\"\n (over)=\"over($event)\"\n (remove)=\"remove($event)\"\n ></g>\n }\n\n @if (newConnector) {\n <g\n obl-directional-connector\n class=\"created\"\n [startName]=\"newConnector.left\"\n [endName]=\"newConnector.right\"\n [end]=\"mouse\"\n [endAngle]=\"angle\"\n [color]=\"'#5fbcd3'\"\n ></g>\n }\n</svg>\n\n<obl-menu #menuRef [target]=\"element.nativeElement\"></obl-menu>", styles: [":host{display:flex}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { connectors: [{
type: Input
}], groups: [{
type: Input
}], disabled: [{
type: HostBinding,
args: ['class.disabled']
}, {
type: Input
}], itemClicked: [{
type: Output
}], connectorClicked: [{
type: Output
}], connectorsChange: [{
type: Output
}], relations: [{
type: ViewChildren,
args: [DirectionalConnectorComponent]
}], move: [{
type: HostListener,
args: ['mousemove', ['$event']]
}], mouseOver: [{
type: HostListener,
args: ['mouseover']
}], clicked: [{
type: HostListener,
args: ['click', ['$event']]
}] } });
class CircleTreeCategoriesRelationsModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, declarations: [CircleCategoriesTreeRelationsComponent,
CategoryRelationGroupComponent,
CategoryItemComponent], imports: [CommonModule,
ConnectorsModule,
MenuModule], exports: [CircleCategoriesTreeRelationsComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, imports: [CommonModule,
ConnectorsModule,
MenuModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, decorators: [{
type: NgModule,
args: [{
declarations: [
CircleCategoriesTreeRelationsComponent,
CategoryRelationGroupComponent,
CategoryItemComponent
],
imports: [
CommonModule,
ConnectorsModule,
MenuModule
],
exports: [
CircleCategoriesTreeRelationsComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { CircleCategoriesTreeRelationsComponent, CircleTreeCategoriesRelationsModule };
//# sourceMappingURL=obliczeniowo-elementary-circle-tree-categories-relations.mjs.map