mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Unify bounds calculation
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
c0915c6b98
commit
0c47bd0004
9 changed files with 80 additions and 94 deletions
|
@ -1,6 +1,6 @@
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
|
@ -12,7 +12,6 @@ import { resizeMultipleElements } from "../element/resizeElements";
|
||||||
import type { AppClassProperties, AppState } from "../types";
|
import type { AppClassProperties, AppState } from "../types";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import { CODES, KEYS } from "../keys";
|
import { CODES, KEYS } from "../keys";
|
||||||
import { getCommonBoundingBox } from "../element/bounds";
|
|
||||||
import {
|
import {
|
||||||
bindOrUnbindLinearElements,
|
bindOrUnbindLinearElements,
|
||||||
isBindingEnabled,
|
isBindingEnabled,
|
||||||
|
@ -132,8 +131,9 @@ const flipElements = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY, midX, midY } =
|
const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
|
||||||
getCommonBoundingBox(selectedElements);
|
const midX = (minX + maxX) / 2;
|
||||||
|
const midY = (minY + maxY) / 2;
|
||||||
|
|
||||||
resizeMultipleElements(
|
resizeMultipleElements(
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
@ -175,8 +175,11 @@ const flipElements = (
|
||||||
{ elbowArrows: [], otherElements: [] },
|
{ elbowArrows: [], otherElements: [] },
|
||||||
);
|
);
|
||||||
|
|
||||||
const { midX: newMidX, midY: newMidY } =
|
const [newMinX, newMinY, newMaxX, newMaxY] =
|
||||||
getCommonBoundingBox(selectedElements);
|
getCommonBounds(selectedElements);
|
||||||
|
const newMidX = (newMinX + newMaxX) / 2;
|
||||||
|
const newMidY = (newMinY + newMaxY) / 2;
|
||||||
|
|
||||||
const [diffX, diffY] = [midX - newMidX, midY - newMidY];
|
const [diffX, diffY] = [midX - newMidX, midY - newMidY];
|
||||||
otherElements.forEach((element) =>
|
otherElements.forEach((element) =>
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||||
import { newElementWith } from "./element/mutateElement";
|
import { newElementWith } from "./element/mutateElement";
|
||||||
import type { BoundingBox } from "./element/bounds";
|
import { getCommonBounds, type Bounds } from "./element/bounds";
|
||||||
import { getCommonBoundingBox } from "./element/bounds";
|
|
||||||
import { getMaximumGroups } from "./groups";
|
import { getMaximumGroups } from "./groups";
|
||||||
|
|
||||||
export interface Alignment {
|
export interface Alignment {
|
||||||
|
@ -18,14 +17,10 @@ export const alignElements = (
|
||||||
selectedElements,
|
selectedElements,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
const selectionBoundingBox = getCommonBoundingBox(selectedElements);
|
const selectionBounds = getCommonBounds(selectedElements);
|
||||||
|
|
||||||
return groups.flatMap((group) => {
|
return groups.flatMap((group) => {
|
||||||
const translation = calculateTranslation(
|
const translation = calculateTranslation(group, selectionBounds, alignment);
|
||||||
group,
|
|
||||||
selectionBoundingBox,
|
|
||||||
alignment,
|
|
||||||
);
|
|
||||||
return group.map((element) =>
|
return group.map((element) =>
|
||||||
newElementWith(element, {
|
newElementWith(element, {
|
||||||
x: element.x + translation.x,
|
x: element.x + translation.x,
|
||||||
|
@ -37,10 +32,30 @@ export const alignElements = (
|
||||||
|
|
||||||
const calculateTranslation = (
|
const calculateTranslation = (
|
||||||
group: ExcalidrawElement[],
|
group: ExcalidrawElement[],
|
||||||
selectionBoundingBox: BoundingBox,
|
selectionBounds: Bounds,
|
||||||
{ axis, position }: Alignment,
|
{ axis, position }: Alignment,
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const groupBoundingBox = getCommonBoundingBox(group);
|
const selectionBoundingBox = {
|
||||||
|
minX: selectionBounds[0],
|
||||||
|
minY: selectionBounds[1],
|
||||||
|
maxX: selectionBounds[2],
|
||||||
|
maxY: selectionBounds[3],
|
||||||
|
midX: (selectionBounds[0] + selectionBounds[2]) / 2,
|
||||||
|
midY: (selectionBounds[1] + selectionBounds[3]) / 2,
|
||||||
|
width: selectionBounds[2] - selectionBounds[0],
|
||||||
|
height: selectionBounds[3] - selectionBounds[1],
|
||||||
|
};
|
||||||
|
const groupBounds = getCommonBounds(group);
|
||||||
|
const groupBoundingBox = {
|
||||||
|
minX: groupBounds[0],
|
||||||
|
minY: groupBounds[1],
|
||||||
|
maxX: groupBounds[2],
|
||||||
|
maxY: groupBounds[3],
|
||||||
|
midX: (groupBounds[0] + groupBounds[2]) / 2,
|
||||||
|
midY: (groupBounds[1] + groupBounds[3]) / 2,
|
||||||
|
width: groupBounds[2] - groupBounds[0],
|
||||||
|
height: groupBounds[3] - groupBounds[1],
|
||||||
|
};
|
||||||
|
|
||||||
const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
|
const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
|
||||||
axis === "x" ? ["minX", "maxX"] : ["minY", "maxY"];
|
axis === "x" ? ["minX", "maxX"] : ["minY", "maxY"];
|
||||||
|
|
|
@ -11,7 +11,6 @@ import type App from "../components/App";
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { jotaiStore } from "../jotai";
|
import { jotaiStore } from "../jotai";
|
||||||
import type { ExcalidrawElement } from "../element/types";
|
import type { ExcalidrawElement } from "../element/types";
|
||||||
import { getCommonBoundingBox } from "../element/bounds";
|
|
||||||
import { AbortError } from "../errors";
|
import { AbortError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
@ -34,7 +33,7 @@ import {
|
||||||
import type { MaybePromise } from "../utility-types";
|
import type { MaybePromise } from "../utility-types";
|
||||||
import { Emitter } from "../emitter";
|
import { Emitter } from "../emitter";
|
||||||
import { Queue } from "../queue";
|
import { Queue } from "../queue";
|
||||||
import { hashElementsVersion, hashString } from "../element";
|
import { getCommonBounds, hashElementsVersion, hashString } from "../element";
|
||||||
|
|
||||||
type LibraryUpdate = {
|
type LibraryUpdate = {
|
||||||
/** deleted library items since last onLibraryChange event */
|
/** deleted library items since last onLibraryChange event */
|
||||||
|
@ -387,7 +386,8 @@ export const distributeLibraryItemsOnSquareGrid = (
|
||||||
const maxHeight = libraryItems
|
const maxHeight = libraryItems
|
||||||
.slice(row * ITEMS_PER_ROW, row * ITEMS_PER_ROW + ITEMS_PER_ROW)
|
.slice(row * ITEMS_PER_ROW, row * ITEMS_PER_ROW + ITEMS_PER_ROW)
|
||||||
.reduce((acc, item) => {
|
.reduce((acc, item) => {
|
||||||
const { height } = getCommonBoundingBox(item.elements);
|
const bounds = getCommonBounds(item.elements);
|
||||||
|
const height = bounds[3] - bounds[1];
|
||||||
return Math.max(acc, height);
|
return Math.max(acc, height);
|
||||||
}, 0);
|
}, 0);
|
||||||
return maxHeight;
|
return maxHeight;
|
||||||
|
@ -402,7 +402,9 @@ export const distributeLibraryItemsOnSquareGrid = (
|
||||||
currCol = 0;
|
currCol = 0;
|
||||||
}
|
}
|
||||||
if (currCol === targetCol) {
|
if (currCol === targetCol) {
|
||||||
const { width } = getCommonBoundingBox(item.elements);
|
const bounds = getCommonBounds(item.elements);
|
||||||
|
const width = bounds[2] - bounds[0];
|
||||||
|
|
||||||
maxWidth = Math.max(maxWidth, width);
|
maxWidth = Math.max(maxWidth, width);
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
|
@ -434,7 +436,10 @@ export const distributeLibraryItemsOnSquareGrid = (
|
||||||
}
|
}
|
||||||
maxWidthCurrCol = getMaxWidthPerCol(col);
|
maxWidthCurrCol = getMaxWidthPerCol(col);
|
||||||
|
|
||||||
const { minX, minY, width, height } = getCommonBoundingBox(item.elements);
|
const bounds = getCommonBounds(item.elements);
|
||||||
|
const [minX, minY, maxX, maxY] = bounds;
|
||||||
|
const width = maxX - minX;
|
||||||
|
const height = maxY - minY;
|
||||||
const offsetCenterX = (maxWidthCurrCol - width) / 2;
|
const offsetCenterX = (maxWidthCurrCol - width) / 2;
|
||||||
const offsetCenterY = (maxHeightCurrRow - height) / 2;
|
const offsetCenterY = (maxHeightCurrRow - height) / 2;
|
||||||
resElements.push(
|
resElements.push(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { newElementWith } from "./element/mutateElement";
|
import { newElementWith } from "./element/mutateElement";
|
||||||
import { getMaximumGroups } from "./groups";
|
import { getMaximumGroups } from "./groups";
|
||||||
import { getCommonBoundingBox } from "./element/bounds";
|
|
||||||
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||||
|
import { getCommonBounds } from "./element/bounds";
|
||||||
|
|
||||||
export interface Distribution {
|
export interface Distribution {
|
||||||
space: "between";
|
space: "between";
|
||||||
|
@ -18,9 +18,35 @@ export const distributeElements = (
|
||||||
? (["minX", "midX", "maxX", "width"] as const)
|
? (["minX", "midX", "maxX", "width"] as const)
|
||||||
: (["minY", "midY", "maxY", "height"] as const);
|
: (["minY", "midY", "maxY", "height"] as const);
|
||||||
|
|
||||||
const bounds = getCommonBoundingBox(selectedElements);
|
const bb = getCommonBounds(selectedElements);
|
||||||
|
const bounds = {
|
||||||
|
minX: bb[0],
|
||||||
|
minY: bb[1],
|
||||||
|
maxX: bb[2],
|
||||||
|
maxY: bb[3],
|
||||||
|
midX: (bb[0] + bb[2]) / 2,
|
||||||
|
midY: (bb[1] + bb[3]) / 2,
|
||||||
|
width: bb[2] - bb[0],
|
||||||
|
height: bb[3] - bb[1],
|
||||||
|
};
|
||||||
const groups = getMaximumGroups(selectedElements, elementsMap)
|
const groups = getMaximumGroups(selectedElements, elementsMap)
|
||||||
.map((group) => [group, getCommonBoundingBox(group)] as const)
|
.map((group) => {
|
||||||
|
const bounds = getCommonBounds(group);
|
||||||
|
|
||||||
|
return [
|
||||||
|
group,
|
||||||
|
{
|
||||||
|
minX: bounds[0],
|
||||||
|
minY: bounds[1],
|
||||||
|
maxX: bounds[2],
|
||||||
|
maxY: bounds[3],
|
||||||
|
midX: (bounds[0] + bounds[2]) / 2,
|
||||||
|
midY: (bounds[1] + bounds[3]) / 2,
|
||||||
|
width: bounds[2] - bounds[0],
|
||||||
|
height: bounds[3] - bounds[1],
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
})
|
||||||
.sort((a, b) => a[1][mid] - b[1][mid]);
|
.sort((a, b) => a[1][mid] - b[1][mid]);
|
||||||
|
|
||||||
let span = 0;
|
let span = 0;
|
||||||
|
|
|
@ -2,7 +2,6 @@ import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
NonDeleted,
|
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
@ -643,33 +642,6 @@ export const getClosestElementBounds = (
|
||||||
return getElementBounds(closestElement, elementsMap);
|
return getElementBounds(closestElement, elementsMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BoundingBox {
|
|
||||||
minX: number;
|
|
||||||
minY: number;
|
|
||||||
maxX: number;
|
|
||||||
maxY: number;
|
|
||||||
midX: number;
|
|
||||||
midY: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCommonBoundingBox = (
|
|
||||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
|
||||||
): BoundingBox => {
|
|
||||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
|
||||||
return {
|
|
||||||
minX,
|
|
||||||
minY,
|
|
||||||
maxX,
|
|
||||||
maxY,
|
|
||||||
width: maxX - minX,
|
|
||||||
height: maxY - minY,
|
|
||||||
midX: (minX + maxX) / 2,
|
|
||||||
midY: (minY + maxY) / 2,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns scene coords of user's editor viewport (visible canvas area) bounds
|
* returns scene coords of user's editor viewport (visible canvas area) bounds
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
getCommonBoundingBox,
|
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
|
@ -808,9 +807,11 @@ export const resizeMultipleElements = (
|
||||||
return [...acc, { ...text, ...xy }];
|
return [...acc, { ...text, ...xy }];
|
||||||
}, [] as ExcalidrawTextElementWithContainer[]);
|
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY, midX, midY } = getCommonBoundingBox(
|
const [minX, minY, maxX, maxY] = getCommonBounds(
|
||||||
targetElements.map(({ orig }) => orig).concat(boundTextElements),
|
targetElements.map(({ orig }) => orig).concat(boundTextElements),
|
||||||
);
|
);
|
||||||
|
const midX = (minX + maxX) / 2;
|
||||||
|
const midY = (minY + maxY) / 2;
|
||||||
const width = maxX - minX;
|
const width = maxX - minX;
|
||||||
const height = maxY - minY;
|
const height = maxY - minY;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { vi } from "vitest";
|
||||||
import { fireEvent, render, waitFor } from "./test-utils";
|
import { fireEvent, render, waitFor } from "./test-utils";
|
||||||
import { act, queryByTestId } from "@testing-library/react";
|
import { act, queryByTestId } from "@testing-library/react";
|
||||||
|
|
||||||
import { Excalidraw } from "../index";
|
import { Excalidraw, getCommonBounds } from "../index";
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { MIME_TYPES } from "../constants";
|
import { MIME_TYPES } from "../constants";
|
||||||
import type { LibraryItem, LibraryItems } from "../types";
|
import type { LibraryItem, LibraryItems } from "../types";
|
||||||
|
@ -11,7 +11,6 @@ import { UI } from "./helpers/ui";
|
||||||
import { serializeLibraryAsJSON } from "../data/json";
|
import { serializeLibraryAsJSON } from "../data/json";
|
||||||
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||||
import type { ExcalidrawGenericElement } from "../element/types";
|
import type { ExcalidrawGenericElement } from "../element/types";
|
||||||
import { getCommonBoundingBox } from "../element/bounds";
|
|
||||||
import { parseLibraryJSON } from "../data/blob";
|
import { parseLibraryJSON } from "../data/blob";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
@ -294,6 +293,7 @@ describe("distributeLibraryItemsOnSquareGrid()", () => {
|
||||||
expect(distributed.length).toEqual(
|
expect(distributed.length).toEqual(
|
||||||
libraryItems.map((x) => x.elements).flat().length,
|
libraryItems.map((x) => x.elements).flat().length,
|
||||||
);
|
);
|
||||||
|
const el4_el5_bounds = getCommonBounds([el4, el5]);
|
||||||
expect(distributed).toEqual(
|
expect(distributed).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
@ -306,7 +306,7 @@ describe("distributeLibraryItemsOnSquareGrid()", () => {
|
||||||
x:
|
x:
|
||||||
el1.width +
|
el1.width +
|
||||||
PADDING +
|
PADDING +
|
||||||
(getCommonBoundingBox([el4, el5]).width - el2.width) / 2,
|
(el4_el5_bounds[2] - el4_el5_bounds[0] - el2.width) / 2,
|
||||||
y: Math.abs(el1.height - el2.height) / 2,
|
y: Math.abs(el1.height - el2.height) / 2,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { LineSegment } from "../math";
|
import type { LineSegment } from "../math";
|
||||||
import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math";
|
import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math";
|
||||||
import type { BoundingBox, Bounds } from "./element/bounds";
|
import type { Bounds } from "./element/bounds";
|
||||||
import { isBounds } from "./element/typeChecks";
|
import { isBounds } from "./element/typeChecks";
|
||||||
|
|
||||||
// The global data holder to collect the debug operations
|
// The global data holder to collect the debug operations
|
||||||
|
@ -72,41 +72,6 @@ export const debugDrawPoint = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const debugDrawBoundingBox = (
|
|
||||||
box: BoundingBox | BoundingBox[],
|
|
||||||
opts?: {
|
|
||||||
color?: string;
|
|
||||||
permanent?: boolean;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
(Array.isArray(box) ? box : [box]).forEach((bbox) =>
|
|
||||||
debugDrawLine(
|
|
||||||
[
|
|
||||||
lineSegment(
|
|
||||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
|
||||||
),
|
|
||||||
lineSegment(
|
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
|
||||||
),
|
|
||||||
lineSegment(
|
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
|
||||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
|
||||||
),
|
|
||||||
lineSegment(
|
|
||||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
|
||||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
{
|
|
||||||
color: opts?.color ?? "cyan",
|
|
||||||
permanent: opts?.permanent,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const debugDrawBounds = (
|
export const debugDrawBounds = (
|
||||||
box: Bounds | Bounds[],
|
box: Bounds | Bounds[],
|
||||||
opts?: {
|
opts?: {
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from "./export";
|
export * from "./export";
|
||||||
export * from "./withinBounds";
|
export * from "./withinBounds";
|
||||||
export { getCommonBounds } from "../excalidraw/element/bounds";
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue