Browse Source

Improves distribute algorithm (#2415)

* Update disitrubte.ts

* Update disitrubte.ts

* Simplifies operations

* Combines algorithms
Steve Ruiz 4 years ago
parent
commit
fec48060f7
1 changed files with 66 additions and 17 deletions
  1. 66 17
      src/disitrubte.ts

+ 66 - 17
src/disitrubte.ts

@@ -7,6 +7,10 @@ interface Box {
   minY: number;
   maxX: number;
   maxY: number;
+  midX: number;
+  midY: number;
+  width: number;
+  height: number;
 }
 
 export interface Distribution {
@@ -18,22 +22,62 @@ export const distributeElements = (
   selectedElements: ExcalidrawElement[],
   distribution: Distribution,
 ): ExcalidrawElement[] => {
-  const start = distribution.axis === "x" ? "minX" : "minY";
-  const extent = distribution.axis === "x" ? "width" : "height";
-
-  const selectionBoundingBox = getCommonBoundingBox(selectedElements);
+  const [start, mid, end, extent] =
+    distribution.axis === "x"
+      ? (["minX", "midX", "maxX", "width"] as const)
+      : (["minY", "midY", "maxY", "height"] as const);
 
+  const bounds = getCommonBoundingBox(selectedElements);
   const groups = getMaximumGroups(selectedElements)
     .map((group) => [group, getCommonBoundingBox(group)] as const)
-    .sort((a, b) => a[1][start] - b[1][start]);
+    .sort((a, b) => a[1][mid] - b[1][mid]);
 
   let span = 0;
   for (const group of groups) {
     span += group[1][extent];
   }
 
-  const step = (selectionBoundingBox[extent] - span) / (groups.length - 1);
-  let pos = selectionBoundingBox[start];
+  const step = (bounds[extent] - span) / (groups.length - 1);
+
+  if (step < 0) {
+    // If we have a negative step, we'll need to distribute from centers
+    // rather than from gaps. Buckle up, this is a weird one.
+
+    // Get indices of boxes that define start and end of our bounding box
+    const i0 = groups.findIndex((g) => g[1][start] === bounds[start]);
+    const i1 = groups.findIndex((g) => g[1][end] === bounds[end]);
+
+    // Get our step, based on the distance between the center points of our
+    // start and end boxes
+    const step =
+      (groups[i1][1][mid] - groups[i0][1][mid]) / (groups.length - 1);
+
+    let pos = groups[i0][1][mid];
+
+    return groups.flatMap(([group, box], i) => {
+      const translation = {
+        x: 0,
+        y: 0,
+      };
+
+      // Don't move our start and end boxes
+      if (i !== i0 && i !== i1) {
+        pos += step;
+        translation[distribution.axis] = pos - box[mid];
+      }
+
+      return group.map((element) =>
+        newElementWith(element, {
+          x: element.x + translation.x,
+          y: element.y + translation.y,
+        }),
+      );
+    });
+  }
+
+  // Distribute from gaps
+
+  let pos = bounds[start];
 
   return groups.flatMap(([group, box]) => {
     const translation = {
@@ -41,17 +85,15 @@ export const distributeElements = (
       y: 0,
     };
 
-    if (Math.abs(pos - box[start]) >= 1e-6) {
-      translation[distribution.axis] = pos - box[start];
-    }
+    translation[distribution.axis] = pos - box[start];
 
-    pos += box[extent];
     pos += step;
+    pos += box[extent];
 
     return group.map((element) =>
       newElementWith(element, {
-        x: Math.round(element.x + translation.x),
-        y: Math.round(element.y + translation.y),
+        x: element.x + translation.x,
+        y: element.y + translation.y,
       }),
     );
   });
@@ -79,9 +121,16 @@ export const getMaximumGroups = (
   return Array.from(groups.values());
 };
 
-const getCommonBoundingBox = (
-  elements: ExcalidrawElement[],
-): Box & { width: number; height: number } => {
+const getCommonBoundingBox = (elements: ExcalidrawElement[]): Box => {
   const [minX, minY, maxX, maxY] = getCommonBounds(elements);
-  return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
+  return {
+    minX,
+    minY,
+    maxX,
+    maxY,
+    width: maxX - minX,
+    height: maxY - minY,
+    midX: (minX + maxX) / 2,
+    midY: (minY + maxY) / 2,
+  };
 };