web/src/app/pages/graph/architecture-graph/graph/base/element.ts (112 lines of code) (raw):

/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { GraphObjectWithElementType, ElementStyle } from './base-containers'; const DEFAULT_SHAPE_COLOR = '#333'; export class ArchShape< T extends SVGGraphicsElement | null, > extends GraphObjectWithElementType<T> { constructor(shapeElement: T) { super(shapeElement); this.withStyle({ fill: DEFAULT_SHAPE_COLOR, }); this.transform.onGetChildDomAttachTarget = () => { return this.transform.parent!.onGetChildDomAttachTarget(); }; } } export class ArchRect extends ArchShape<SVGRectElement> { constructor() { super(document.createElementNS('http://www.w3.org/2000/svg', 'rect')); } } export class ArchEmpty extends GraphObjectWithElementType<null> { constructor() { super(null); this.transform.onGetChildDomAttachTarget = () => { return this.transform.parent!.onGetChildDomAttachTarget(); }; } } export class ArchCircle extends ArchShape<SVGCircleElement> { constructor() { super(document.createElementNS('http://www.w3.org/2000/svg', 'circle')); this.transform.onSvgElementUpdate = (pos) => { this.withStyle({ cx: pos.x, cy: pos.y, }); }; } } export class ArchLabel extends ArchShape<SVGTextElement> { DEFAULT_FONT_SIZE = 14; constructor(label: string) { super(document.createElementNS('http://www.w3.org/2000/svg', 'text')); // Default font size is necessary to adjust text location to fit parent box this.withStyle({ 'font-size': this.DEFAULT_FONT_SIZE, 'font-family': 'Roboto', }); this.transform.requireChildSizeToLayout = false; this.transform.onSvgElementUpdate = (pos) => { this.withStyle({ x: pos.x, y: pos.y + this.fontSize, }); }; this.transform.onCustomContentSizeCalculate = () => { const bbox = this.typedElement.getBoundingClientRect(); return { width: bbox.width, height: bbox.height }; }; this.typedElement.innerHTML = label; } private get fontSize(): number { return this.getAttribute()['font-size'] || this.DEFAULT_FONT_SIZE; } public override withStyle(style: ElementStyle): this { super.withStyle(style); this.applyAttribute(); return this; } } export class SizedRect extends ArchShape<SVGRectElement> { constructor( private readonly width: number, private readonly height: number, ) { super(document.createElementNS('http://www.w3.org/2000/svg', 'rect')); this.transform.requireChildSizeToLayout = false; this.transform.onCustomContentSizeCalculate = () => { return { width: this.width, height: this.height }; }; this.transform.onSvgElementUpdate = (pos) => { this.withStyle({ x: pos.x, y: pos.y, }); this.applyAttribute(); }; this.withStyle({}); this.applyAttribute(); } public override withStyle(style: ElementStyle): this { super.withStyle({ ...style, width: this.width, height: this.height, }); return this; } } export class ArrowHead extends ArchShape<SVGPathElement> { constructor(arrowHead: number, rotation: number) { super(document.createElementNS('http://www.w3.org/2000/svg', 'path')); this.transform.onSvgElementUpdate = (p) => { this.typedElement.setAttribute( 'transform', `rotate(${rotation + 180})translate(${p.x},${p.y})`, ); }; const l = arrowHead; const path = `M 0,${l} L${-l},${-l} L0,${-l / 2} L${l},${-l}Z`; this.typedElement.setAttribute('d', path); } }