Introduction
Recently, while working on a project, I came across this unique requirement where I needed to show the mapping between two HTML table records, simulating which record from the 1st table is mapped/linked to a record of the 2nd table. The functionality should allow the user to easily drag & drop row from one table over the table row & accordingly, the mapping should be created. After doing some R &D, I made up my mind to make use of SVG (Scalable Vector Graphics) to implement the mapping functionality between two HTML table records. When we talk about SVG, the first thing that comes to our mind is an image, but it’s more than that. You can do programming in it by dynamically creating SVG tags, elements, etc. You could even add animations to your SVG images. For more information on SVG, please self-explore more on it.
Enough of the theory. Let’s get on to our project requirements. The technology stack we’ll use is Angular 7 & SVG. I’ll use Angular CLI to create a new project with the name demo-app.
For this demo, we will try to create a mapping between customers & products. We could have any data for that matter, since our main aim from this article is to understand the logic behind creating the mapping between 2 HTML tables using SVG. I’ll be using JSON files for both datasets, which we’ll try to fetch using HTTP requests in order to simulate the HTTP calls in the real world. Below is the snapshot of what we’ll build in this demo.
Here is the architecture of our application, we’ve one shared module wherein we’ll keep our mapper component since it needs to be generic & reusable across the application. We also have a “modules” module which will store our application feature modules.
Application architecture

We’ll use PrimeNG turbotable to showing our data in tabular format. Here are the libraries that I’m using in my application.

After installing the required packages, I’ve added the below entry in angular.json file
- "node_modules/primeng/resources/themes/nova-light/theme.css",
- "node_modules/font-awesome/css/font-awesome.min.css",
- "node_modules/primeng/resources/primeng.min.css",
- "src/styles.css"
For listing PrimeNG modules, I’ve added a new module named app-primeng.module.ts & added the reference of this to our application main module i.e. in shared.module.ts file.
Our application does have some constant which we are storing in app.constants.ts file. Here is the content of the app.constants.ts file.
- export class AppConstants {
- public static readonly FIELD_TXT = 'field';
- public static readonly HEADER_TXT = 'header';
- public static readonly HASHTAG_TXT = '#';
- public static readonly ID_ATTR = 'id';
- public static readonly STYLE_ATTR = 'style';
- public static readonly WIDTH_ATTR = 'width';
- public static readonly HEIGHT_ATTR = 'height';
- public static readonly TRANSPARENT_BG = '#ffffff00';
- public static readonly BLACK_COLOR = '#000000';
- public static readonly WHITE_COLOR = '#FFFFFF';
- static ColorCodes = class {
- public static readonly CODES: any[] = ['#00ffff', '#802A2A', '#4d4d00', '#000000', '#0000ff', '#a52a2a', '#4b0026', '#00008b', '#008b8b', '#a9a9a9', '#006400', '#bdb76b', '#8b008b', '#556b2f', '#ff8c00', '#9932cc', '#8b0000', '#e9967a', '#9400d3', '#ff00ff', '#ffd700', '#008000', '#4b0082', '#939365', '#99994d', '#8B6969', '#90ee90', '#F3AE9A', '#ffb6c1', '#96C8A2', '#00ff00', '#800000', '#000080', '#808000', '#ffa500', '#862d2d', '#800080', '#ff0000', '#2d5986', '#ffff00', '#354b83', '#116805', '#47887f', '#79341d', '#febc52', '#7d7050', '#c96b8f', '#66dd38', '#61535b', '#512818', '#a320d0', '#2b583c', '#f19057', '#3c53a7', '#b42c7a', '#61a31b', '#9b0c2f', '#ec87aa', '#5e1654', '#b36807', '#52143d', '#7d4b61', '#a62638', '#a15d7b', '#a72c0c', '#6F4242', '#271f35', '#8B8878', '#324F17', '#46523C', '#6B9C75', '#7F9A65', '#414F12', '#859C27', '#79341d', '#98A148', '#808000'];
- };
- static SVGConstants = class {
- public static readonly SVG_NS: string = 'http://www.w3.org/2000/svg';
- public static readonly SVG_XML_NS: string = 'http://www.w3.org/2000/xmlns/';
- public static readonly SVG_XLINK_NS: string = 'http://www.w3.org/1999/xlink';
- public static readonly XMLNS_XLINK_ATTR: string = 'xmlns:xlink';
- public static readonly PATH_TENSION: number = 0.2;
- public static readonly SVG_ID_ATTR = 'svg-canvas';
- public static readonly SVG_PARENT_ID_ATTR = 'svg-parent';
- public static readonly SVG_CONTAINER_ID_ATTR = 'svg-container';
- public static readonly SVG_STYLE_ATTR = 'position:absolute;';
- public static readonly SVG_TAG = 'svg';
- public static readonly VIEWBOX_ATTR = 'viewBox';
- public static readonly REF_X_ATTR = 'refX';
- public static readonly REF_Y_ATTR = 'refY';
- public static readonly PRESERVE_ASPECT_RATIO = 'preserveAspectRatio';
- public static readonly MARKER_UNITS_ATTR = 'markerUnits';
- public static readonly MARKER_HEIGHT_ATTR = 'markerHeight';
- public static readonly MARKER_WIDTH_ATTR = 'markerWidth';
- public static readonly ORIENT_ATTR = 'orient';
- public static readonly DEFS_TAG = 'defs';
- public static readonly MARKER_TAG = 'marker';
- public static readonly PATH_TAG = 'path';
- public static readonly TITLE_TAG = 'title';
- public static readonly GROUP_TAG = 'g';
- public static readonly CIRCLE_TAG = 'circle';
- };
- }
As you could see from the above file, we have some SVG constants created for use in the application. The advantage of doing so is that some of the SVG attributes are case sensitive. By following the above approach, it will be less error prone since we have all our attributes defined at one place. If any change is needed, we can simply update it over here. Inside our Shared module, we’ll now create a mapper component which will help the user create a mapping. I’m keeping the table datasource type as Object (since I need it to be generic in nature).
MapperComponent.ts
- import {
- Component,
- OnInit,
- OnDestroy,
- ElementRef,
- ViewChild,
- Input,
- AfterViewInit,
- ViewChildren,
- QueryList,
- HostListener,
- } from '@angular/core';
- import {
- Table
- } from 'primeng/table';
- import {
- MessageService,
- SortEvent
- } from 'primeng/api';
- import {
- fromEvent,
- Subject,
- zip
- } from 'rxjs';
- import {
- debounceTime,
- takeUntil
- } from 'rxjs/operators';
- import {
- AppConstants
- } from 'src/app/app-constant';
- import {
- IColumn
- } from '../../models/table-config.model';
- import {
- Utility
- } from 'src/app/app-utility';
- @Component({
- selector: 'app-mapper',
- templateUrl: './mapper.component.html'
- })
- export class MapperComponent implements OnInit, AfterViewInit, OnDestroy {
- @ViewChild('masterTable') masterTable: Table;
- @ViewChild('referenceTable') referenceTable: Table;
- @Input() mstDataKey: string = null; // unique identifier from Master Table
- @Input() refDataKey: string = null; // unique identifier from Reference Table
- @Input() mstTableName: string = null;
- @Input() refTableName: string = null;
- @Input() mstEmptyMessage = 'No Records Found';
- @Input() refEmptyMessage = 'No Records Found';
- @Input() mstDataSource: object[] = null; // master DataSource
- @Input() refDataSource: object[] = null; // reference DataSource
- isFirstLoad = true;
- loading = false;
- enableBgColorMapping = false;
- mstSelectedCols: IColumn[] = [];
- refSelectedCols: IColumn[] = [];
- draggedObj: Object = null;
- droppedOnToObj: Object = null;
- reRenderMapping = false;
- randomColor: string = AppConstants.TRANSPARENT_BG;
- private readonly MST_TXT = 'mst';
- private readonly REF_TXT = 'ref';
- private rowsBgPainted = false;
- private componentDestroyed$: Subject < boolean > = new Subject < false > ();
- // SVG Caching Related fields
- private _svgParentPosition: any = null;
- private _mstParentDiv: HTMLDivElement = null;
- private _mstParentDivLeft: number = null;
- private _refParentDiv: HTMLDivElement = null;
- private _refParentDivLeft: number = null;
- constructor(private elRef: ElementRef, private messageService: MessageService) {}
- ngOnInit(): void {
- this.getDefaultTableCols();
- }
- ngAfterViewInit(): void {
- // Call to DrawMapping Method on initial Load;
- if (this.mstSelectedCols.length > 0 && this.refSelectedCols.length > 0) {
- this.drawMapping();
- }
- const rowsRendered$ = zip(this.mstRows.changes, this.refRows.changes);
- rowsRendered$.pipe(takeUntil(this.componentDestroyed$), debounceTime(1000)).subscribe(([]) => {
- if (this.reRenderMapping && this.mstSelectedCols.length > 0 && this.refSelectedCols.length > 0) {
- this.drawMapping();
- this.reRenderMapping = false;
- } else if (this.reRenderMapping) {
- this.removeSVG();
- this.reRenderMapping = false;
- }
- });
- this.windowResizeEventBinding();
- }
- toggleMappingDisplayMode(): void {
- this.drawMapping();
- }
- /**
- * Sorts the Table Data
- * @param event SortEvent Object
- */
- customSort(event: SortEvent): void {
- event.data.sort((data1, data2) => {
- const value1 = data1[event.field];
- const value2 = data2[event.field];
- let result = null;
- if (value1 == null && value2 != null) {
- result = -1;
- } else if (value1 != null && value2 == null) {
- result = 1;
- } else if (value1 == null && value2 == null) {
- result = 0;
- } else if (typeof value1 === 'string' && typeof value2 === 'string') {
- result = value1.localeCompare(value2);
- } else {
- result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;
- }
- return (event.order * result);
- });
- }
- //#region Drag & Drop Events
- dragStart(event, rowData: Object) {
- event.dataTransfer.effectAllowed = 'copy';
- event.dataTransfer.dropEffect = 'move';
- this.draggedObj = rowData;
- }
- drop(event, refTableRowRef: HTMLTableRowElement, rowData: Object): void {
- event.preventDefault();
- event.dataTransfer.dropEffect = 'move';
- let referenceTableRow = refTableRowRef || event.path[1];
- this.droppedOnToObj = referenceTableRow ? rowData : null;
- }
- dragEnd(event, mstTableRowRef: HTMLTableRowElement): void {
- let masterTableRow = mstTableRowRef || event.path[0];
- if (masterTableRow && this.droppedOnToObj) {
- this.mapRelation(this.draggedObj, this.droppedOnToObj);
- }
- this.draggedObj = null;
- this.droppedOnToObj = null;
- }
- private getDefaultTableCols(): void {
- if (this.mstDataSource && this.mstDataSource.length > 0) {
- this.mstSelectedCols.push(...this.getDefaultMasterColsFromObj());
- }
- if (this.refDataSource && this.refDataSource.length > 0) {
- this.refSelectedCols.push(...this.getDefaultReferenceColsFromObj());
- }
- }
- private getDefaultMasterColsFromObj(): IColumn[] {
- const objMstColumns: IColumn[] = [];
- Object.keys(this.mstDataSource[0]).map(x => {
- objMstColumns.push(this.getColumn(x));
- });
- return objMstColumns;
- }
- private getDefaultReferenceColsFromObj(): IColumn[] {
- const objRefColumns: IColumn[] = [];
- Object.keys(this.refDataSource[0]).map(x => {
- objRefColumns.push(this.getColumn(x, false));
- });
- return objRefColumns;
- }
- private getColumn(fieldName, isMasterField = true, isVisible = true, width = '120px'): IColumn {
- const objCol: IColumn = {
- field: fieldName,
- displayName: Utility.insertSpace(Utility.initCapitalize(fieldName)),
- width: width,
- textAlign: isMasterField ? Utility.getTextAlignFromValueType(this.mstDataSource[0][fieldName]) : Utility.getTextAlignFromValueType(this.refDataSource[0][fieldName]),
- allowFiltering: true,
- allowSorting: true,
- allowReordering: true,
- allowResizing: true,
- isVisible: isVisible,
- cssClass: 'ui-resizable-column',
- };
- return objCol;
- }
- /**
- * GET's the HTML Table Row Reference
- * @param objData Object Data
- * @param searchTable Search in Master Or Reference Table
- */
- private getHTMLTableRowRef(objData: Object, searchTable: string): HTMLTableRowElement {
- let elementId: string = null;
- if (searchTable === this.MST_TXT) {
- let dataKey = objData[this.mstDataKey];
- if (this.masterTable.hasFilter() && this.masterTable.filteredValue) {
- elementId = this.MST_TXT + (dataKey === null ? this.masterTable.filteredValue.findIndex(x => x === objData).toString() : this.masterTable.filteredValue.findIndex(x => x[this.mstDataKey] === objData[this.mstDataKey]).toString());
- } else {
- elementId = this.MST_TXT + (dataKey === null ? this.masterTable.value.findIndex(x => x === objData).toString() : this.masterTable.value.findIndex(x => x[this.mstDataKey] === objData[this.mstDataKey]).toString());
- }
- } else {
- let dataKey = objData[this.refDataKey];
- if (this.referenceTable.hasFilter() && this.referenceTable.filteredValue) {
- elementId = this.REF_TXT + (dataKey === null ? this.referenceTable.filteredValue.findIndex(x => x === objData).toString() : this.referenceTable.filteredValue.findIndex(x => x[this.refDataKey] === objData[this.refDataKey]).toString());
- } else {
- elementId = this.REF_TXT + (dataKey === null ? this.referenceTable.value.findIndex(x => x === objData).toString() : this.referenceTable.value.findIndex(x => x[this.refDataKey] === objData[this.refDataKey]).toString());
- }
- }
- return (elementId && document.getElementById(elementId)) as HTMLTableRowElement;
- }
- /**
- * Maps the newly dragged Row from Master Table to Reference Table
- */
- private mapRelation(draggedObject: Object, droppedOnToObject: Object): void {
- this.paintBgOrDrawArrow(draggedObject, droppedOnToObject);
- }
- private drawMapping(): void {
- this.redrawSVG();
- }
- /**
- * Paints the Bg color the MasterTable & Reference Table Mapped Rows
- */
- private paintBgOrDrawArrow(draggedObject: Object, droppedOnToObject: Object): void {
- let masterTableRow = this.getHTMLTableRowRef(draggedObject, this.MST_TXT);
- let referenceTableRow = this.getHTMLTableRowRef(droppedOnToObject, this.REF_TXT);
- if (masterTableRow && referenceTableRow) {
- this.randomColor = Utility.getRandomHexColor();
- if (this.enableBgColorMapping) {
- this.paintBackground(this.randomColor, masterTableRow, referenceTableRow);
- } else {
- this.drawArrowRelations(this.randomColor, AppConstants.SVGConstants.PATH_TENSION, masterTableRow, referenceTableRow, draggedObject, droppedOnToObject);
- }
- }
- }
- private paintBackground(bgColor: string, masterTableRow: HTMLTableRowElement, referenceTableRow: HTMLTableRowElement): void {
- const textColor = Utility.hexInverseBw(bgColor);
- const tdClass = textColor === AppConstants.WHITE_COLOR ? 'tdWhite' : 'tdBlack';
- masterTableRow.style.background = bgColor;
- masterTableRow.classList.add(tdClass);
- referenceTableRow.style.background = bgColor;
- referenceTableRow.classList.add(tdClass);
- }
- /**
- * WindowResize Event
- */
- private windowResizeEventBinding(): void {
- fromEvent(window, 'resize').pipe(takeUntil(this.componentDestroyed$), debounceTime(1000)).subscribe(() => {
- if (!this.enableBgColorMapping) {
- this.redrawSVG();
- }
- });
- }
- // #region 'SVG Region'
- /**
- * Removs the SVG element from the page
- */
- removeSVG(): void {
- const htmlSVGElement = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_ID_ATTR}`);
- if (htmlSVGElement) {
- const svgContainer = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_CONTAINER_ID_ATTR}`);
- svgContainer.removeChild(htmlSVGElement);
- }
- }
- /**
- * Redraws the SVG
- */
- redrawSVG(): void {
- this.removeSVG();
- this.clearSVGCache();
- }
- private getSVGGroup(mstRowRef: HTMLTableRowElement, rowData: Object): SVGGElement {
- const groupId = this.mstDataKey ? `group${rowData[this.mstDataKey]}` : `group${mstRowRef.id}`;
- return this.elRef.nativeElement.querySelector(`#${groupId}`);
- }
- /**
- * Removes the SVG Group
- * @param svgGroup SVGGroup to be removed
- */
- private removeSVGGroup(svgGroup: SVGGElement): void {
- const svgElement = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_ID_ATTR}`);
- if (svgElement && svgGroup) {
- svgElement.removeChild(svgGroup);
- }
- }
- /**
- * GET's the element's Bounding Client Rect position.
- * @param element HTML Element
- */
- private getAbsolutePosition(element) {
- const rect = element.getBoundingClientRect();
- let xVal = rect.left;
- let yVal = rect.top;
- if (window.scrollX) {
- xVal += window.scrollX;
- }
- if (window.scrollY) {
- yVal += window.scrollY;
- }
- return {
- x: xVal,
- y: yVal
- };
- }
- /**
- * Creates the SVG Element
- */
- private createSVG(): any {
- const htmlSVGElement = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_ID_ATTR}`);
- const svgParent = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_PARENT_ID_ATTR}`);
- const svgParentPosition = this.getAbsolutePosition(svgParent);
- const svgContainer = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_CONTAINER_ID_ATTR}`);
- const svgContainerPosition = this.getAbsolutePosition(svgContainer);
- if (null == htmlSVGElement) {
- const svg = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.SVG_TAG);
- svg.setAttribute(AppConstants.ID_ATTR, AppConstants.SVGConstants.SVG_ID_ATTR);
- svg.setAttribute(AppConstants.STYLE_ATTR, AppConstants.SVGConstants.SVG_STYLE_ATTR);
- svg.setAttribute(AppConstants.WIDTH_ATTR, svgParent.clientWidth.toString());
- svg.setAttribute(AppConstants.HEIGHT_ATTR, svgParent.clientHeight.toString());
- svg.setAttribute(AppConstants.SVGConstants.VIEWBOX_ATTR, `${svgContainerPosition.x - svgParentPosition.x}
- ${svgContainerPosition.y - svgParentPosition.y} ${svgContainer.clientWidth} ${svgContainer.clientHeight}`);
- svg.setAttribute(AppConstants.SVGConstants.PRESERVE_ASPECT_RATIO, 'xMinYMin meet');
- svg.setAttributeNS(AppConstants.SVGConstants.SVG_XML_NS, AppConstants.SVGConstants.XMLNS_XLINK_ATTR, AppConstants.SVGConstants.SVG_XLINK_NS);
- svgContainer.appendChild(svg);
- return svg;
- }
- return htmlSVGElement;
- }
- /**
- * Adds Title to the SVG Group Element
- */
- private addSVGGroupTitle(svgGroupElement: SVGGElement, draggedObject: Object): void {
- const svgGroupTitle = < SVGTitleElement > document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.TITLE_TAG);
- svgGroupTitle.textContent = this.mstDataKey && this.mstDataKey !== '' ? draggedObject[this.mstDataKey] : '';
- svgGroupElement.appendChild(svgGroupTitle);
- }
- /**
- * Creates a new SVG Group
- */
- private createSVGGroupElement(masterTableRow: HTMLTableRowElement, draggedObject: Object): SVGGElement {
- // re-use existing group incase there are one to many mappings (corrupted data)
- const svgGroup = this.getSVGGroup(masterTableRow, draggedObject)
- if (svgGroup) {
- return svgGroup;
- }
- const id = this.mstDataKey && this.mstDataKey !== '' ? `group${draggedObject[this.mstDataKey]}` : `group${masterTableRow.id}`;
- const svgGroupElement = < SVGGElement > document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.GROUP_TAG);
- svgGroupElement.setAttribute('id', id);
- svgGroupElement.setAttribute('shape-rendering', 'inherit');
- svgGroupElement.setAttribute('pointer-events', 'all');
- const svg = this.createSVG();
- svg.appendChild(svgGroupElement);
- return svgGroupElement;
- }
- /**
- * Draws a circle on the given co-ordinates
- * @param x Element xPosition
- * @param y Element yPosition
- * @param radius Radius of the Circle
- * @param color Color of the Circle
- */
- private drawCircle(x, y, radius, color, svgGroupElement: SVGGElement): void {
- const shape = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.CIRCLE_TAG);
- shape.setAttributeNS(null, 'cx', x);
- shape.setAttributeNS(null, 'cy', y);
- shape.setAttributeNS(null, 'r', radius);
- shape.setAttributeNS(null, 'fill', color);
- svgGroupElement.appendChild(shape);
- }
- /**
- * Creates the Arrow marker
- */
- private createArrowMarker(color: string, svgGroupElement: SVGGElement, referenceTableRow: HTMLTableRowElement): void {
- const defs = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.DEFS_TAG);
- svgGroupElement.appendChild(defs);
- const id = 'triangle' + referenceTableRow.id;
- const marker = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.MARKER_TAG);
- marker.setAttribute(AppConstants.ID_ATTR, id);
- marker.setAttribute(AppConstants.SVGConstants.VIEWBOX_ATTR, '0 0 10 10');
- marker.setAttribute(AppConstants.SVGConstants.REF_X_ATTR, '0');
- marker.setAttribute(AppConstants.SVGConstants.REF_Y_ATTR, '5');
- marker.setAttribute(AppConstants.SVGConstants.MARKER_UNITS_ATTR, 'strokeWidth');
- marker.setAttribute(AppConstants.SVGConstants.MARKER_WIDTH_ATTR, '10');
- marker.setAttribute(AppConstants.SVGConstants.MARKER_HEIGHT_ATTR, '8');
- marker.setAttribute(AppConstants.SVGConstants.ORIENT_ATTR, 'auto');
- marker.setAttribute('fill', color);
- const path = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.PATH_TAG);
- marker.appendChild(path);
- path.setAttribute('d', 'M 0 0 L 10 5 L 0 10 z');
- defs.appendChild(marker);
- }
- /**
- * Draws a curved line based on the parameters passed & with given color & path tension
- * @param x1 x1 Position
- * @param y1 y1 Position
- * @param x2 x2 Position
- * @param y2 y2 Position
- * @param color Stroke Color
- * @param tension Path Tension
- */
- private drawCurvedLine(x1, y1, x2, y2, color, tension, svgGroupElement: SVGGElement, referenceTableRow: HTMLTableRowElement, draggedObject: Object, droppedOnToObject: Object): void {
- const shape = document.createElementNS(AppConstants.SVGConstants.SVG_NS, AppConstants.SVGConstants.PATH_TAG); {
- const delta = (x2 - x1) * tension;
- const hx1 = x1 + delta;
- const hy1 = y1;
- const hx2 = x2 - delta;
- const hy2 = y2;
- const path = 'M ' + x1 + ' ' + y1 + ' C ' + hx1 + ' ' + hy1 + ' ' + hx2 + ' ' + hy2 + ' ' + x2 + ' ' + y2;
- shape.setAttributeNS(null, 'd', path);
- shape.setAttributeNS(null, 'fill', 'none');
- shape.setAttributeNS(null, 'stroke', color);
- shape.setAttributeNS(null, 'stroke-width', '1.1');
- shape.setAttributeNS(null, 'marker-end', `url(#triangle${referenceTableRow.id})`);
- svgGroupElement.appendChild(shape);
- }
- }
- /**
- * Draws the relation between the rows
- * @param color Color value
- * @param tension Path Tension
- */
- private drawArrowRelations(color: string, tension: number, masterTableRow: HTMLTableRowElement, referenceTableRow: HTMLTableRowElement, draggedObject: Object, droppedOnToObject: Object): void {
- let svgParentPosition = this._svgParentPosition;
- if (!svgParentPosition) {
- const svgParent = this.elRef.nativeElement.querySelector(`#${AppConstants.SVGConstants.SVG_PARENT_ID_ATTR}`);
- svgParentPosition = this.getAbsolutePosition(svgParent);
- this._svgParentPosition = svgParentPosition;
- }
- if (!this._mstParentDiv) {
- this._mstParentDiv = masterTableRow.closest('div.col-12') as HTMLDivElement;
- }
- const mstParentDiv = this._mstParentDiv;
- let leftPos = this.getAbsolutePosition(masterTableRow);
- if (!this._mstParentDivLeft) {
- this._mstParentDivLeft = mstParentDiv.getBoundingClientRect().left;
- }
- leftPos.x = this._mstParentDivLeft;
- let x1 = leftPos.x - svgParentPosition.x;
- let y1 = leftPos.y - svgParentPosition.y;
- x1 += mstParentDiv.offsetWidth;
- y1 += (masterTableRow.offsetHeight / 2);
- if (!this._refParentDiv) {
- this._refParentDiv = referenceTableRow.closest('div.col-12') as HTMLDivElement;
- }
- const refParentDiv = this._refParentDiv;
- const rightPos = this.getAbsolutePosition(referenceTableRow);
- if (!this._refParentDivLeft) {
- this._refParentDivLeft = refParentDiv.getBoundingClientRect().left;
- }
- rightPos.x = this._refParentDivLeft;
- const x2 = rightPos.x - svgParentPosition.x - 8;
- let y2 = rightPos.y - svgParentPosition.y;
- y2 += (referenceTableRow.offsetHeight / 2);
- let svgGroupElement = this.createSVGGroupElement(masterTableRow, draggedObject);
- this.addSVGGroupTitle(svgGroupElement, draggedObject);
- this.drawCircle(x1, y1, 4, color, svgGroupElement);
- this.createArrowMarker(color, svgGroupElement, referenceTableRow);
- this.drawCurvedLine(x1, y1, x2, y2, color, tension, svgGroupElement, referenceTableRow, draggedObject, droppedOnToObject);
- }
- // #endregion
- private clearSVGCache(): void {
- this._svgParentPosition = null;
- this._mstParentDiv = null;
- this._mstParentDivLeft = null;
- this._refParentDiv = null;
- this._refParentDivLeft = null;
- }
- @HostListener('window:beforeunload')
- ngOnDestroy(): void {
- this.componentDestroyed$.next(true);
- this.componentDestroyed$.complete();
- }
- }

deepanshuPosted Sep 30, 2020, 9:27 AM
Hi Vishal, its an awesome article but doesn't work in ie.
RahulPosted Mar 18, 2020, 3:56 AM
Nice article. Its good to display final output always at the end of your article.