Source: src/layouts/SquareLayout.ts

  1. import FrameLayout, { IFrameLayoutInterface, IFrameShape } from "./FrameLayout";
  2. import { fill } from "../utils";
  3. import { IInfiniteGridItem } from "../types";
  4. function makeShapeOutline(
  5. outline: number[],
  6. itemSize: number,
  7. columnLength: number,
  8. isAppend?: boolean,
  9. ) {
  10. const point = Math[isAppend ? "min" : "max"](...outline) || 0;
  11. if (outline.length !== columnLength) {
  12. return fill(new Array(columnLength), 0);
  13. }
  14. return outline.map(l => Math.floor((l - point) / itemSize));
  15. }
  16. function getColumn(item: IInfiniteGridItem) {
  17. if (item.column) {
  18. return item.column;
  19. }
  20. let column = 1;
  21. if (item.el) {
  22. column = parseInt(item.el.getAttribute("data-column")!, 10) || 1;
  23. }
  24. item.column = column;
  25. return column;
  26. }
  27. export interface ISquareLayoutOptions extends IFrameLayoutInterface {
  28. column: number;
  29. }
  30. /**
  31. * @classdesc SquareLayout is a layout that places all cards like squares on a checkerboard, and important cards are n times larger. The main card can be enlarged, and then a small card can be placed to naturally show the relationship of the card.
  32. * @ko SquareLayout은 바둑판처럼 모든 카드를 정사각형으로 배치하고 중요한 카드는 크기를 N배로 키워서 보여주는 레이아웃이다. 주요 카드를 크게 표시하고, 그 다음에 작은 카드를 배치해 자연스럽게 카드의 관계를 나타낼 수 있습니다.
  33. * @class eg.InfiniteGrid.SquareLayout
  34. * @extends eg.InfiniteGrid.FrameLayout
  35. * @param {Object} [options] The option object of eg.InfiniteGrid.SquareLayout module <ko>eg.InfiniteGrid.SquareLayout 모듈의 옵션 객체</ko>
  36. * @param {String} [options.margin=0] Margin used to create space around items <ko>아이템들 사이의 공간</ko>
  37. * @param {Boolean} [options.horizontal=false] Direction of the scroll movement (false: vertical, true: horizontal) <ko>스크롤 이동 방향 (false: 세로방향, true: 가로방향)</ko>
  38. * @param {Boolean} [options.itemSize=0] The size of the items. If it is 0, it is calculated as the size of the first item in items. (priority: `column` > `itemSize` > element's size)<ko> 아이템의 사이즈. 만약 아이템 사이즈가 0이면, 아이템들의 첫번째 아이템의 사이즈로 계산이 된다. (우선순위: `column` > `itemSize` > 엘리먼트의 사이즈) </ko>
  39. * @param {Boolean} [options.column=0] The number of columns in the layout. If it is 0, the column is returned by `itemSize`. (priority: `column` > `itemSize` > element's size)<ko> 레이아웃의 열의 개수. 만약 column이 0이면, `itemSize`로 열을 구합니다. (우선순위: `column` > `itemSize` > 엘리먼트의 사이즈) </ko>
  40. * @example
  41. ```
  42. <script>
  43. var ig = new eg.InfiniteGrid("#grid". {
  44. horizontal: true,
  45. });
  46. ig.setLayout(eg.InfiniteGrid.SquareLayout, {
  47. margin: 10,
  48. itemSize: 200,
  49. });
  50. // or
  51. var layout = new eg.InfiniteGrid.SquareLayout({
  52. margin: 10,
  53. itemSize: 200,
  54. horizontal: true,
  55. });
  56. var item1 = '<div data-column="2"></div>';
  57. var item2 = "<div></div>"
  58. layout.append([item1, item2]);
  59. </script>
  60. ```
  61. **/
  62. export default class SquareLayout extends FrameLayout {
  63. public options: ISquareLayoutOptions;
  64. constructor(options: Partial<ISquareLayoutOptions> = {}) {
  65. super(options);
  66. }
  67. protected _layout(
  68. items: IInfiniteGridItem[],
  69. outline: number[] = [],
  70. isAppend: boolean = false,
  71. ) {
  72. const itemSize = this._getSquareSize(items[0]) as number;
  73. const margin = this.options.margin;
  74. const columnLength = this.options.column ||
  75. Math.floor((this._size + margin) / (itemSize + margin)) || 1;
  76. const length = items.length;
  77. const endOutline = makeShapeOutline(outline, Math.floor(itemSize), columnLength, isAppend);
  78. const pointCaculateName = isAppend ? "min" : "max";
  79. const shapes: IFrameShape[] = [];
  80. const sign = isAppend ? 1 : -1;
  81. const style = this._style;
  82. const pos1Name = style.startPos1;
  83. const pos2Name = style.startPos2;
  84. for (let i = 0; i < length; ++i) {
  85. const point = Math[pointCaculateName](...endOutline);
  86. let index = endOutline[isAppend ? "indexOf" : "lastIndexOf"](point);
  87. const item = items[i];
  88. const columnWidth = item.columnWidth;
  89. const column = (columnWidth && columnWidth[0] === columnLength &&
  90. columnWidth[1]) || getColumn(item);
  91. let columnCount = 1;
  92. if (column > 1) {
  93. for (let j = 1; j < column &&
  94. ((isAppend && index + j < columnLength) || (!isAppend && index - j >= 0)); ++j) {
  95. if ((isAppend && endOutline[index + sign * j] <= point) ||
  96. (!isAppend && endOutline[index + sign * j] >= point)) {
  97. ++columnCount;
  98. continue;
  99. }
  100. break;
  101. }
  102. if (!isAppend) {
  103. index -= columnCount - 1;
  104. }
  105. }
  106. item.columnWidth = [columnLength, columnCount];
  107. shapes.push({
  108. width: columnCount,
  109. height: columnCount,
  110. [pos1Name]: point - (!isAppend ? columnCount : 0),
  111. [pos2Name]: index,
  112. type: i + 1,
  113. index: i,
  114. });
  115. for (let j = 0; j < columnCount; ++j) {
  116. endOutline[index + j] = point + sign * columnCount;
  117. }
  118. }
  119. this._shapes = {
  120. shapes,
  121. [style.size2]: columnLength,
  122. };
  123. const result = super._layout(items, outline, isAppend);
  124. if (!isAppend) {
  125. shapes.sort((shape1, shape2) => {
  126. const item1pos1 = shape1[pos1Name]!;
  127. const item1pos2 = shape1[pos2Name]!;
  128. const item2pos1 = shape2[pos1Name]!;
  129. const item2pos2 = shape2[pos2Name]!;
  130. if (item1pos1 - item2pos1) {
  131. return item1pos1 - item2pos1;
  132. }
  133. return item1pos2 - item2pos2;
  134. });
  135. items.sort((a, b) => {
  136. const item1pos1 = a.rect[pos1Name];
  137. const item1pos2 = a.rect[pos2Name];
  138. const item2pos1 = b.rect[pos1Name];
  139. const item2pos2 = b.rect[pos2Name];
  140. if (item1pos1 - item2pos1) {
  141. return item1pos1 - item2pos1;
  142. }
  143. return item1pos2 - item2pos2;
  144. });
  145. }
  146. return result;
  147. }
  148. private _getSquareSize(item: IInfiniteGridItem) {
  149. const { column, margin, itemSize } = this.options;
  150. if (column) {
  151. // if column is in options, caculate itemSize from column.
  152. this._itemSize = (this._size + margin) / column - margin;
  153. } else if (itemSize) {
  154. this._itemSize = this.options.itemSize;
  155. } else {
  156. const sizeName = this._style.size2;
  157. // if frameSize is 0, caculate frameSize from item.size.
  158. const frameSize
  159. = this._shapes[sizeName]
  160. || Math.floor((this._size + margin) / (item.size![sizeName]! + margin) / getColumn(item));
  161. this._itemSize = (this._size + margin) / frameSize - margin;
  162. }
  163. return this._itemSize;
  164. }
  165. }
comments powered by Disqus