Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/#507 Implement autoarrange #509

Open
wants to merge 9 commits into
base: vnext
Choose a base branch
from
Open
6 changes: 6 additions & 0 deletions src/common/autoarrange-table/autoarrange-table.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Box {
x: number;
y: number;
width: number;
height: number;
}
28 changes: 28 additions & 0 deletions src/common/autoarrange-table/autoarrange-table.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Box } from './autoarrange-table.model';

export const getRandomInt = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};

export const isOverlapping = (box1: Box, box2: Box): boolean => {
return (
box1.x < box2.x + box2.width &&
box1.x + box1.width > box2.x &&
box1.y < box2.y + box2.height &&
box1.y + box1.height > box2.y
);
};

export const calculateCollisionArea = (box1: Box, box2: Box): number => {
const xOverlap = Math.max(
0,
Math.min(box1.x + box1.width, box2.x + box2.width) -
Math.max(box1.x, box2.x)
);
const yOverlap = Math.max(
0,
Math.min(box1.y + box1.height, box2.y + box2.height) -
Math.max(box1.y, box2.y)
);
return xOverlap * yOverlap;
};
84 changes: 84 additions & 0 deletions src/common/autoarrange-table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Size } from '@/core/model';
import { Box } from './autoarrange-table.model';
import {
calculateCollisionArea,
isOverlapping,
} from './autoarrange-table.utils';

function* spiralPositions(
centerX: number,
centerY: number,
canvasSize: Size
): Generator<[number, number]> {
let x = 0,
y = 0,
dx = 0,
dy = -1;

for (let i = 0; i < Math.max(canvasSize.width, canvasSize.height) ** 2; i++) {
if (
-canvasSize.width / 2 < x &&
x < canvasSize.width / 2 &&
-canvasSize.height / 2 < y &&
y < canvasSize.height / 2
) {
yield [centerX + x, centerY + y];
}
if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1 - y)) {
[dx, dy] = [-dy, dx];
}
x += dx;
y += dy;
}
}

export function findFreePositionOrMinCollision(
boxes: Box[],
newBoxSize: Size,
canvasSize: Size
): Box | null {
const centerX = Math.floor(canvasSize.width / 2);
const centerY = Math.floor(canvasSize.height / 2);
let minCollisionBox: Box | null = null;
let minCollisionArea = Infinity;

for (const [x, y] of spiralPositions(centerX, centerY, canvasSize)) {
const newBox = {
x,
y,
width: newBoxSize.width,
height: newBoxSize.height,
};
if (
x >= 0 &&
y >= 0 &&
x + newBoxSize.width <= canvasSize.width &&
y + newBoxSize.height <= canvasSize.height
) {
let collisionArea = 0;
let isFree = true;

for (const existingBox of boxes) {
if (isOverlapping(newBox, existingBox)) {
isFree = false;
collisionArea += calculateCollisionArea(newBox, existingBox);
}
}

if (isFree) {
return newBox;
}

if (collisionArea < minCollisionArea) {
minCollisionArea = collisionArea;
minCollisionBox = newBox;
}
}
}

if (minCollisionBox !== null) {
return minCollisionBox;
}
// TODO: if no free position is found, return a random one
return null;
}
13 changes: 13 additions & 0 deletions src/common/helpers/set-off-set-zoom-to-coords.helper.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('setOffSetZoomToCoords', () => {
const initialContextState: CanvasViewSettingsContextModel = {
canvasViewSettings: {
canvasSize: { width: 5000, height: 5000 },
canvasViewSize: { width: 5000, height: 5000 },
viewBoxSize: { width: 20000, height: 20000 },
zoomFactor: 2,
scrollPosition: { x: 0, y: 0 },
Expand All @@ -25,6 +26,7 @@ describe('setOffSetZoomToCoords', () => {
setLoadSample: () => {},
setViewBoxSize: () => {},
setAutoSave: () => {},
setCanvasViewSize: () => {},
};

// Act
Expand All @@ -51,6 +53,7 @@ describe('setOffSetZoomToCoords', () => {
const initialContextState: CanvasViewSettingsContextModel = {
canvasViewSettings: {
canvasSize: { width: 10000, height: 10000 },
canvasViewSize: { width: 5000, height: 5000 },
viewBoxSize: { width: 25000, height: 15000 },
zoomFactor: 2,
scrollPosition: { x: 0, y: 0 },
Expand All @@ -66,6 +69,7 @@ describe('setOffSetZoomToCoords', () => {
setLoadSample: () => {},
setViewBoxSize: () => {},
setAutoSave: () => {},
setCanvasViewSize: () => {},
};

// Act
Expand All @@ -92,6 +96,7 @@ describe('setOffSetZoomToCoords', () => {
const initialContextState: CanvasViewSettingsContextModel = {
canvasViewSettings: {
canvasSize: { width: 300, height: 100 },
canvasViewSize: { width: 5000, height: 5000 },
viewBoxSize: { width: 2000, height: 5000 },
zoomFactor: 5,
scrollPosition: { x: 0, y: 0 },
Expand All @@ -107,6 +112,7 @@ describe('setOffSetZoomToCoords', () => {
setLoadSample: () => {},
setViewBoxSize: () => {},
setAutoSave: () => {},
setCanvasViewSize: () => {},
};

// Act
Expand Down Expand Up @@ -135,6 +141,10 @@ describe('setOffSetZoomToCoords', () => {
width: Number.MAX_SAFE_INTEGER,
height: Number.MAX_SAFE_INTEGER,
},
canvasViewSize: {
width: Number.MAX_SAFE_INTEGER,
height: Number.MAX_SAFE_INTEGER,
},
viewBoxSize: {
width: Number.MAX_SAFE_INTEGER,
height: Number.MAX_SAFE_INTEGER,
Expand All @@ -153,6 +163,7 @@ describe('setOffSetZoomToCoords', () => {
setLoadSample: () => {},
setViewBoxSize: () => {},
setAutoSave: () => {},
setCanvasViewSize: () => {},
};

// Act
Expand All @@ -178,6 +189,7 @@ describe('setOffSetZoomToCoords', () => {
const initialContextState: CanvasViewSettingsContextModel = {
canvasViewSettings: {
canvasSize: { width: 5000, height: 5000 },
canvasViewSize: { width: 5000, height: 5000 },
viewBoxSize: { width: 20000, height: 20000 },
zoomFactor: 2,
scrollPosition: { x: 0, y: 0 },
Expand All @@ -193,6 +205,7 @@ describe('setOffSetZoomToCoords', () => {
setLoadSample: () => {},
setViewBoxSize: () => {},
setAutoSave: () => {},
setCanvasViewSize: () => {},
};

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Coords, Size } from '@/core/model';

export interface CanvasViewSettingsModel {
canvasSize: Size;
canvasViewSize: Size;
viewBoxSize: Size;
zoomFactor: number;
scrollPosition: Coords;
Expand All @@ -26,6 +27,7 @@ const initialAutoSaveValue =

export const createInitialSettings = (DEFAULT_ZOOM_FACTOR: number) => ({
canvasSize: CANVAS_SIZE,
canvasViewSize: CANVAS_SIZE,
viewBoxSize: { width: 0, height: 0 },
zoomFactor: DEFAULT_ZOOM_FACTOR,
scrollPosition: { x: 0, y: 0 },
Expand All @@ -38,6 +40,7 @@ export interface CanvasViewSettingsContextModel {
canvasViewSettings: CanvasViewSettingsModel;
setScrollPosition: (scrollPosition: Coords) => void;
setCanvasSize: (canvasSize: Size) => void;
setCanvasViewSize: (canvasViewSize: Size) => void;
setFilename: (filename: string) => void;
zoomIn: () => void;
zoomOut: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export const CanvasViewSettingsProvider: React.FC<Props> = props => {
}));
};

const setCanvasViewSize = (canvasSize: Size) => {
setCanvasViewSettings(canvasViewSettings => ({
...canvasViewSettings,
canvasViewSize: canvasSize,
}));
};

const setFilename = (filename: string) => {
setCanvasViewSettings(canvasViewSettings => ({
...canvasViewSettings,
Expand Down Expand Up @@ -104,6 +111,7 @@ export const CanvasViewSettingsProvider: React.FC<Props> = props => {
canvasViewSettings,
setScrollPosition,
setCanvasSize,
setCanvasViewSize,
setFilename,
zoomIn,
zoomOut,
Expand Down
29 changes: 29 additions & 0 deletions src/pods/canvas/canvas.pod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const CanvasPod: React.FC = () => {
} = useCanvasSchemaContext();
const {
canvasViewSettings,
setCanvasViewSize,
setScrollPosition,
setLoadSample,
setViewBoxSize,
Expand Down Expand Up @@ -121,6 +122,34 @@ export const CanvasPod: React.FC = () => {

const containerRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
if (containerRef.current) {
const container = containerRef.current;

// TODO: Rename setOffSetZoomToCoords so that it does not only apply to coords
// maybe setOffsetZoom?

const CANVAS_VIEW_SIZE_WITH_APPLIED_ZOOM_OFFSET = setOffSetZoomToCoords(
container.clientWidth + containerRef.current.scrollLeft * 2,
container.clientHeight + containerRef.current.scrollTop * 2,
viewBoxSize,
canvasSize,
zoomFactor
);

setCanvasViewSize({
width: CANVAS_VIEW_SIZE_WITH_APPLIED_ZOOM_OFFSET.x,
height: CANVAS_VIEW_SIZE_WITH_APPLIED_ZOOM_OFFSET.y,
});
}
}, [
containerRef.current?.clientWidth,
containerRef.current?.scrollLeft,
containerRef.current?.clientHeight,
containerRef.current?.scrollTop,
viewBoxSize,
]);

const handleScroll = () => {
if (containerRef.current) {
setScrollPosition(
Expand Down
43 changes: 27 additions & 16 deletions src/pods/canvas/components/table/database-table.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from './components';
import { renderRows } from './database-table-render-rows.helper';
import classes from './database-table.module.css';
import { motion } from 'framer-motion';

// TODO: We should add an optional field to indicate FONT_SIZE in case we override the standard class
// TODO: There's is a solution more elaborated (using JS) to show elipsis ... if text is too long
Expand Down Expand Up @@ -82,22 +83,32 @@ export const DatabaseTable: React.FC<Props> = ({
};

return (
<g
transform={`translate(${tableInfo.x}, ${tableInfo.y})`}
onMouseDown={onMouseDown}
onTouchStart={onTouchStart}
className={classes.tableContainer}
ref={ref as React.LegacyRef<SVGGElement> | undefined}
>
<DatabaseTableBorder totalHeight={totalHeight} isSelected={isSelected} />
<DatabaseTableHeader
onEditTable={handleDoubleClick}
onSelectTable={handleSelectTable}
isSelected={isSelected}
tableName={tableInfo.tableName}
isTabletOrMobileDevice={isTabletOrMobileDevice}
/>
<DatabaseTableBody renderedRows={renderedRows} />
<g transform={`translate(${tableInfo.x}, ${tableInfo.y})`}>
<motion.g
onMouseDown={onMouseDown}
onTouchStart={onTouchStart}
className={classes.tableContainer}
ref={ref as React.Ref<SVGGElement> | undefined}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: [0, 1, 0, 1], scale: 1 }}
transition={{
opacity: { duration: 2 },
scale: { duration: 0.8 },
}}
>
<DatabaseTableBorder
totalHeight={totalHeight}
isSelected={isSelected}
/>
<DatabaseTableHeader
onEditTable={handleDoubleClick}
onSelectTable={handleSelectTable}
isSelected={isSelected}
tableName={tableInfo.tableName}
isTabletOrMobileDevice={isTabletOrMobileDevice}
/>
<DatabaseTableBody renderedRows={renderedRows} />
</motion.g>
</g>
);
};
Loading
Loading