/** * This program has been developed by students from the bachelor Computer Science at * Utrecht University within the Software Project course. * © Copyright Utrecht University (Department of Information and Computing Sciences) */ import { Position } from 'reactflow'; import { BoundingBox } from '../../vis/common'; // TODO: MOVE ELSEWHERE /** This is the interface to get the center of an edge */ export interface GetCenterParams { sourceX: number; sourceY: number; targetX: number; targetY: number; offset: number; sourcePosition?: Position; targetPosition?: Position; } const LeftOrRight = [Position.Left, Position.Right]; /** * Gets the center of an edge using the source position and the target position of the edge. * The offset is used in order to correct for the displacement created by the schema positioning algorithm. * @param {GetCenterParams} param0 The coordinates of the start and end of the line, and its offset. * @returns {[number, number, number, number]} The coordinates of the center of the edge. */ export const getCenter = ({ sourceX, sourceY, targetX, targetY, offset, sourcePosition = Position.Bottom, targetPosition = Position.Top, }: GetCenterParams): [number, number, number, number] => { const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition); const targetIsLeftOrRight = LeftOrRight.includes(targetPosition); // we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom) // a mixed edge is when one the source is on the left and the target is on the top for example. const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight); if (mixedEdge) { const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0; const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset; const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY); const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset; return [centerX, centerY, xOffset, yOffset]; } // Add the offset to the position parameters const xOffset = Math.abs(targetX - sourceX) / 2; const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; return [centerX + offset, centerY, xOffset, yOffset]; }; // /** // * Calculate the width of the specified text. // * @param text Text input as string. // * @param fontname Name of the font. // * @param fontsize Size of the fond in px. // * @param fontWeight The weight of the font. // * @returns {number} Width of the textfield in px. // */ // export const getWidthOfText = (text: string, fontname: string, fontsize: string, fontWeight = 'normal'): number => { // let canvas = document.createElement('canvas'); // let canvasText = canvas.getContext('2d') as CanvasRenderingContext2D; // return canvasText.measureText(text).width; // }; /** * This calculates the width of the attributesBox and nodesBox of an entityNode * @param attributeCount Number of attributes of the entityNode * @param nodeCount Amount of nodes the entityNode has * @returns {number} the width of the largest box (attributesBox or nodesBox) of an entityNode in ch */ export const calcWidthEntityNodeBox = (attributeCount: number, nodeCount: number): number => { if (attributeCount >= nodeCount) return (attributeCount?.toString()?.length || 0) + 5.5; else return (nodeCount?.toString()?.length || 0) + 5.5; }; /** * This calculates the width of the attributesBox and nodesBox of an relationNode * @param attributeCount Number of attributes of the relationNode * @param nodeCount Amount of nodes the entityNode has * @returns {number} the width of the largest box (attributesBox or nodesBox) of an relationNode in px */ export const calcWidthRelationNodeBox = (attributeCount: number, nodeCount: number): number => { if (attributeCount >= nodeCount) return attributeCount.toString().length * 5 + 60; else return nodeCount.toString().length * 5 + 60; }; /** * Creates a bounding box for a Shema component in order to check for collisions. * @param x Top left x position of the component. * @param y Top left y position of the component. * @param width Width of the component. * @param height Height of the component. * @returns {BoundingBox} The bounding box of the component. */ export const makeBoundingBox = (x: number, y: number, width: number, height: number): BoundingBox => { return { topLeft: { x: x, y: y }, bottomRight: { x: x + width, y: y + height }, }; }; /** * Check if two bounding boxes overlap in order to avoid overlapping components. * @param boundingBoxOne bounding box of the first component. * @param boundingBoxTwo bounding box of the second component. * @returns {boolean} True if boxes overlap and false if there is no overlap. */ export const doBoxesOverlap = (boundingBoxOne: BoundingBox, boundingBoxTwo: BoundingBox): boolean => { if (boundingBoxOne.topLeft.x >= boundingBoxTwo.bottomRight.x || boundingBoxTwo.topLeft.x >= boundingBoxOne.bottomRight.x) return false; if (boundingBoxOne.topLeft.y >= boundingBoxTwo.bottomRight.y || boundingBoxTwo.topLeft.y >= boundingBoxOne.bottomRight.y) return false; return true; }; /** * Capitalize the first letter of a string. * @param string This is the given string. * @returns {string} This is the modified string. */ export const capitalizeFirstLetter = (string: string) => { return string.charAt(0).toUpperCase() + string.slice(1); }; /** * This calculates the quality of the attributes from the given data in percentages. * @param data This is the data of which the quality of the attributes must be calculated. * @returns {number} This is the percentage of the quality of the attributes from the given data. */ export const calculateAttributeQuality = (data: any) => { const maxPossibleNullValues = data.nodeCount * data.attributes.length; if (data.summedNullAmount == 0) return 0; return (data.summedNullAmount / maxPossibleNullValues) * 100; }; /** * This calculates the quality of the entity from the given data in percentages, based on the connectedRatio. * @param data This is the data of which the quality of the entity must be calculated. * @returns {number} This is the percentage of the quality of the entity from the given data. */ export const calculateEntityQuality = (data: any) => { const nodeQuality = (1 - data.connectedRatio) * 100; return nodeQuality; }; /** * This calculates the quality of the relation from the given data in percentages, based on the connectedRatio. * @param data This is the data of which the quality of the relation must be calculated. * @returns {number} This is the percentage of the quality of the relation from the given data. */ export const calculateRelationQuality = (data: any) => { const nodeQuality = (1 - (data.fromRatio + data.toRatio) / 2) * 100; return nodeQuality; }; type NumberPredicate = (a1: number, a2: number) => boolean; /** * Determines the true-value of two values, based on the predicate. */ export const numberPredicates: Record<string, NumberPredicate> = { Equal: (a1, a2) => a1 == a2, NotEqual: (a1, a2) => a1 != a2, Smaller: (a1, a2) => a1 < a2, SmallerOrEqual: (a1, a2) => a1 <= a2, Bigger: (a1, a2) => a1 > a2, BiggerOrEqual: (a1, a2) => a1 >= a2, };