|
1 |
| -import { IPoint, ISize } from "@/types"; |
| 1 | +import { IPoint, IRect, ISize } from "@/types"; |
2 | 2 | import { nanoid } from "nanoid";
|
3 | 3 | import MathUtils from "@/utils/MathUtils";
|
4 | 4 | import { isNumber } from "lodash";
|
@@ -515,4 +515,133 @@ export default class CommonUtils {
|
515 | 515 | height,
|
516 | 516 | };
|
517 | 517 | }
|
| 518 | + |
| 519 | + /** |
| 520 | + * 二维平铺算法, 确保矩形在二维空间均匀分布 |
| 521 | + * |
| 522 | + * 1. 每行的高度不需要相同 |
| 523 | + * 2. 每行的个数不需要相同 |
| 524 | + * 3. 第一行在中间,第二行在第一行的上方,第三行在第一行的下方,呈上下上下的方式进行布局 |
| 525 | + * 4. 每行按照左对齐的方式进行布局 |
| 526 | + * 5. 每行的矩形之间的间距为margin |
| 527 | + * |
| 528 | + * @param rectangles 矩形数组 |
| 529 | + * @param center 中心点 |
| 530 | + * @param margin 矩形之间的间距 |
| 531 | + * @returns 矩形数组 |
| 532 | + */ |
| 533 | + static packRectangles(rectangles: ISize[], center: IPoint, margin: number = 0): IRect[] { |
| 534 | + if (rectangles.length === 0) return []; |
| 535 | + // 利用np加减枝算法,计算第一行的最大宽度 |
| 536 | + let rowSize = Math.ceil(Math.sqrt(rectangles.length)); |
| 537 | + if (rowSize >= 1) { |
| 538 | + rowSize--; |
| 539 | + } |
| 540 | + let rowWidth = 0; |
| 541 | + while (true) { |
| 542 | + rowSize++; |
| 543 | + rowWidth = rectangles.slice(0, rowSize).reduce((prev, curr) => prev + curr.width, 0) + (rowSize - 1) * margin; |
| 544 | + if (rowSize === rectangles.length) { |
| 545 | + break; |
| 546 | + } |
| 547 | + const widths: number[] = []; |
| 548 | + for (let i = 0; i < rectangles.length; i += rowSize) { |
| 549 | + const width = rectangles.slice(i, i + rowSize).reduce((prev, curr) => prev + curr.width, 0) + (rowSize - 1) * margin; |
| 550 | + widths.push(width); |
| 551 | + i += rowSize; |
| 552 | + } |
| 553 | + let gtCounter = 0; |
| 554 | + for (let i = 0; i < widths.length; i++) { |
| 555 | + if (widths[i] <= rowWidth) { |
| 556 | + gtCounter++; |
| 557 | + } |
| 558 | + } |
| 559 | + if (gtCounter >= Math.ceil(widths.length / 2)) { |
| 560 | + break; |
| 561 | + } |
| 562 | + } |
| 563 | + const result: IRect[][] = []; |
| 564 | + let totalHeight = 0; |
| 565 | + let currentRowWidth = 0; |
| 566 | + let currentRowHeight = 0; |
| 567 | + // 计算第一行的矩形 |
| 568 | + const x = center.x - rowWidth / 2; |
| 569 | + const y = center.y - rectangles[0].height / 2; |
| 570 | + let row: IRect[] = []; |
| 571 | + for (let i = 0; i < rowSize; i++) { |
| 572 | + const { width, height } = rectangles[i]; |
| 573 | + row.push({ |
| 574 | + x: x + currentRowWidth + (currentRowWidth === 0 ? 0: margin) + width / 2, |
| 575 | + y: y + height / 2, |
| 576 | + width, |
| 577 | + height, |
| 578 | + }); |
| 579 | + if (height > currentRowHeight) { |
| 580 | + currentRowHeight = height; |
| 581 | + } |
| 582 | + currentRowWidth += width + (currentRowWidth === 0? 0: margin); |
| 583 | + } |
| 584 | + result.push(row); |
| 585 | + |
| 586 | + if (rowSize < rectangles.length) { |
| 587 | + totalHeight += currentRowHeight; |
| 588 | + currentRowWidth = 0; |
| 589 | + currentRowHeight = 0; |
| 590 | + row = []; |
| 591 | + let currentRowSize = 0; |
| 592 | + let currentRowDirection = 1; // 1: 向上, -1: 向下 |
| 593 | + let lastY = y + totalHeight; |
| 594 | + let lastUpperRowIndex: number = -1; |
| 595 | + |
| 596 | + // 重置当前行的状态 |
| 597 | + function reset(): void { |
| 598 | + currentRowDirection = -currentRowDirection; |
| 599 | + totalHeight += currentRowHeight + margin; |
| 600 | + currentRowWidth = 0; |
| 601 | + currentRowHeight = 0; |
| 602 | + currentRowSize = 0; |
| 603 | + row = []; |
| 604 | + } |
| 605 | + |
| 606 | + // 计算其他行的矩形,以第一行的第一个矩形为基准,左对齐 |
| 607 | + for (let i = rowSize; i < rectangles.length; i++) { |
| 608 | + const { width, height } = rectangles[i]; |
| 609 | + row.push({ |
| 610 | + x: x + currentRowWidth + (currentRowWidth === 0 ? 0: margin) + width / 2, |
| 611 | + y: 0, |
| 612 | + width, |
| 613 | + height, |
| 614 | + }); |
| 615 | + if (height > currentRowHeight) { |
| 616 | + currentRowHeight = height; |
| 617 | + } |
| 618 | + currentRowSize++; |
| 619 | + currentRowWidth += width + (currentRowWidth === 0? 0: margin); |
| 620 | + if (i < rectangles.length - 1 && currentRowWidth + rectangles[i + 1].width + margin > rowWidth || i === rectangles.length - 1) { |
| 621 | + result.push(row); |
| 622 | + // 计算当前行的y坐标 |
| 623 | + if (currentRowDirection === 1) { |
| 624 | + lastY = lastY - totalHeight - margin - currentRowHeight; |
| 625 | + lastUpperRowIndex = result.length - 1; |
| 626 | + } else { |
| 627 | + lastY = lastY + totalHeight + margin + currentRowHeight; |
| 628 | + } |
| 629 | + row.forEach((item, index) => { |
| 630 | + item.y = lastY + (currentRowDirection === -1? -currentRowHeight: 0) + row[index].height / 2; |
| 631 | + }); |
| 632 | + reset(); |
| 633 | + } |
| 634 | + } |
| 635 | + |
| 636 | + if (lastUpperRowIndex !== -1) { |
| 637 | + const row = result[lastUpperRowIndex]; |
| 638 | + const rowHeight = Math.max(...row.map(item => item.height)); |
| 639 | + row.forEach((item) => { |
| 640 | + item.y = item.y + (rowHeight - item.height); |
| 641 | + }); |
| 642 | + } |
| 643 | + } |
| 644 | + |
| 645 | + return result.flat(); |
| 646 | + } |
518 | 647 | }
|
0 commit comments