layout.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.placeComponents = exports.computeLayout = void 0;
  4. const d3_array_1 = require("d3-array");
  5. const coordinate_1 = require("../utils/coordinate");
  6. const helper_1 = require("../utils/helper");
  7. const array_1 = require("../utils/array");
  8. const string_1 = require("../utils/string");
  9. const component_1 = require("./component");
  10. function computeLayout(components, options, theme, library) {
  11. const { width, height, x = 0, y = 0, inset = 0, insetLeft = inset, insetTop = inset, insetBottom = inset, insetRight = inset, margin = 0, marginLeft = margin, marginBottom = margin, marginTop = margin, marginRight = margin, padding, paddingBottom = padding, paddingLeft = padding, paddingRight = padding, paddingTop = padding, } = options;
  12. const MAX_PADDING_RATIO = 1 / 3;
  13. const clamp = (origin, computed, max) => origin === 'auto' ? Math.min(max, computed) : computed;
  14. // Compute paddingLeft and paddingRight first to get innerWidth.
  15. const horizontalPadding = computePadding(components, height, [0, 0], ['left', 'right'], options, theme, library);
  16. const { paddingLeft: pl0, paddingRight: pr0 } = horizontalPadding;
  17. const viewWidth = width - marginLeft - marginRight;
  18. const pl = clamp(paddingLeft, pl0, viewWidth * MAX_PADDING_RATIO);
  19. const pr = clamp(paddingRight, pr0, viewWidth * MAX_PADDING_RATIO);
  20. const iw = viewWidth - pl - pr;
  21. // Compute paddingBottom and paddingTop based on innerWidth.
  22. const verticalPadding = computePadding(components, iw, [pl, pr], ['bottom', 'top'], options, theme, library);
  23. const { paddingTop: pt0, paddingBottom: pb0 } = verticalPadding;
  24. const viewHeight = height - marginBottom - marginTop;
  25. const pb = clamp(paddingBottom, pb0, viewHeight * MAX_PADDING_RATIO);
  26. const pt = clamp(paddingTop, pt0, viewHeight * MAX_PADDING_RATIO);
  27. const ih = viewHeight - pb - pt;
  28. return {
  29. width,
  30. height,
  31. insetLeft,
  32. insetTop,
  33. insetBottom,
  34. insetRight,
  35. innerWidth: iw,
  36. innerHeight: ih,
  37. paddingLeft: pl,
  38. paddingRight: pr,
  39. paddingTop: pt,
  40. paddingBottom: pb,
  41. marginLeft,
  42. marginBottom,
  43. marginTop,
  44. marginRight,
  45. x,
  46. y,
  47. };
  48. }
  49. exports.computeLayout = computeLayout;
  50. /**
  51. * @todo Support percentage size(e.g. 50%)
  52. */
  53. function computePadding(components, crossSize, crossPadding, positions, options, theme, library) {
  54. const positionComponents = (0, d3_array_1.group)(components, (d) => d.position);
  55. const { padding, paddingLeft = padding, paddingRight = padding, paddingBottom = padding, paddingTop = padding, } = options;
  56. const layout = {
  57. paddingBottom,
  58. paddingLeft,
  59. paddingTop,
  60. paddingRight,
  61. };
  62. for (const position of positions) {
  63. const key = `padding${(0, helper_1.capitalizeFirst)((0, string_1.camelCase)(position))}`;
  64. const value = layout[key];
  65. if (value === undefined || value === 'auto') {
  66. if (!positionComponents.has(position)) {
  67. layout[key] = 30;
  68. }
  69. else {
  70. const components = positionComponents.get(position);
  71. if (value === 'auto') {
  72. components.forEach((component) => (0, component_1.computeComponentSize)(component, crossSize, crossPadding, position, theme, library));
  73. }
  74. const totalSize = components.reduce((sum, { size }) => sum + size, 0);
  75. layout[key] = totalSize;
  76. }
  77. }
  78. }
  79. return layout;
  80. }
  81. function placeComponents(components, coordinate, layout) {
  82. const positionComponents = (0, d3_array_1.group)(components, (d) => d.position);
  83. const { paddingLeft, paddingRight, paddingTop, paddingBottom, marginLeft, marginTop, marginBottom, marginRight, innerHeight, innerWidth, height, width, } = layout;
  84. const pl = paddingLeft + marginLeft;
  85. const pt = paddingTop + marginTop;
  86. const pr = paddingRight + marginRight;
  87. const pb = paddingBottom + marginBottom;
  88. const section = {
  89. top: [pl, 0, innerWidth, pt, 'vertical', true, d3_array_1.ascending],
  90. right: [width - pr, pt, pr, innerHeight, 'horizontal', false, d3_array_1.ascending],
  91. bottom: [pl, height - pb, innerWidth, pb, 'vertical', false, d3_array_1.ascending],
  92. left: [0, pt, pl, innerHeight, 'horizontal', true, d3_array_1.ascending],
  93. 'top-left': [pl, 0, innerWidth, pt, 'vertical', true, d3_array_1.ascending],
  94. 'top-right': [pl, 0, innerWidth, pt, 'vertical', true, d3_array_1.ascending],
  95. 'bottom-left': [
  96. pl,
  97. height - pb,
  98. innerWidth,
  99. pb,
  100. 'vertical',
  101. false,
  102. d3_array_1.ascending,
  103. ],
  104. 'bottom-right': [
  105. pl,
  106. height - pb,
  107. innerWidth,
  108. pb,
  109. 'vertical',
  110. false,
  111. d3_array_1.ascending,
  112. ],
  113. center: [pl, pt, innerWidth, innerHeight, 'center', null, null],
  114. inner: [pl, pt, innerWidth, innerHeight, 'center', null, null],
  115. outer: [pl, pt, innerWidth, innerHeight, 'center', null, null],
  116. };
  117. for (const [position, components] of positionComponents.entries()) {
  118. const area = section[position];
  119. /**
  120. * @description non-entity components: axis in the center, inner, outer, component in the center
  121. * @description entity components: other components
  122. * @description no volume components take up no extra space
  123. */
  124. const [nonEntityComponents, entityComponents] = (0, array_1.divide)(components, (component) => {
  125. if (typeof component.type !== 'string')
  126. return false;
  127. if (position === 'center')
  128. return true;
  129. if (component.type.startsWith('axis') &&
  130. ['inner', 'outer'].includes(position)) {
  131. return true;
  132. }
  133. return false;
  134. });
  135. if (nonEntityComponents.length) {
  136. placeNonEntityComponents(nonEntityComponents, coordinate, area, position);
  137. }
  138. if (entityComponents.length) {
  139. placePaddingArea(components, coordinate, area);
  140. }
  141. }
  142. }
  143. exports.placeComponents = placeComponents;
  144. function placeNonEntityComponents(components, coordinate, area, position) {
  145. const [axisComponents, nonAxisComponents] = (0, array_1.divide)(components, (component) => {
  146. if (typeof component.type === 'string' &&
  147. component.type.startsWith('axis')) {
  148. return true;
  149. }
  150. return false;
  151. });
  152. placeNonEntityAxis(axisComponents, coordinate, area, position);
  153. // in current stage, only legend component which located in the center can be placed
  154. placeCenter(nonAxisComponents, coordinate, area);
  155. }
  156. function placeNonEntityAxis(components, coordinate, area, position) {
  157. if (position === 'center') {
  158. if ((0, coordinate_1.isRadar)(coordinate)) {
  159. placeAxisRadar(components, coordinate, area, position);
  160. }
  161. else if ((0, coordinate_1.isPolar)(coordinate)) {
  162. placeArcLinear(components, coordinate, area);
  163. }
  164. else if ((0, coordinate_1.isParallel)(coordinate)) {
  165. placeAxisParallel(components, coordinate, area, components[0].orientation);
  166. }
  167. }
  168. else if (position === 'inner') {
  169. placeAxisArcInner(components, coordinate, area);
  170. }
  171. else if (position === 'outer') {
  172. placeAxisArcOuter(components, coordinate, area);
  173. }
  174. }
  175. function placeAxisArcInner(components, coordinate, area) {
  176. const [x, y, , height] = area;
  177. const [cx, cy] = coordinate.getCenter();
  178. const [innerRadius] = (0, coordinate_1.radiusOf)(coordinate);
  179. const r = height / 2;
  180. const size = innerRadius * r;
  181. const x0 = cx - size;
  182. const y0 = cy - size;
  183. for (let i = 0; i < components.length; i++) {
  184. const component = components[i];
  185. component.bbox = {
  186. x: x + x0,
  187. y: y + y0,
  188. width: size * 2,
  189. height: size * 2,
  190. };
  191. }
  192. }
  193. function placeAxisArcOuter(components, coordinate, area) {
  194. const [x, y, width, height] = area;
  195. for (const component of components) {
  196. component.bbox = { x, y, width, height };
  197. }
  198. }
  199. /**
  200. * @example arcX, arcY, axisLinear with angle
  201. */
  202. function placeArcLinear(components, coordinate, area) {
  203. const [x, y, width, height] = area;
  204. for (const component of components) {
  205. component.bbox = { x: x, y, width, height };
  206. }
  207. }
  208. function placeAxisParallel(components, coordinate, area, orientation) {
  209. if (orientation === 'horizontal') {
  210. placeAxisParallelHorizontal(components, coordinate, area);
  211. }
  212. else if (orientation === 'vertical') {
  213. placeAxisParallelVertical(components, coordinate, area);
  214. }
  215. }
  216. function placeAxisParallelVertical(components, coordinate, area) {
  217. const [x, y, , height] = area;
  218. // Create a high dimension vector and map to a list of two-dimension points.
  219. // [0, 0, 0] -> [x0, 0, x1, 0, x2, 0]
  220. const vector = new Array(components.length + 1).fill(0);
  221. const points = coordinate.map(vector);
  222. // Extract x of each points.
  223. // [x0, 0, x1, 0, x2, 0] -> [x0, x1, x2]
  224. const X = points.filter((_, i) => i % 2 === 0).map((d) => d + x);
  225. // Place each axis by coordinate in parallel coordinate.
  226. for (let i = 0; i < components.length; i++) {
  227. const component = components[i];
  228. const x = X[i];
  229. const width = X[i + 1] - x;
  230. component.bbox = { x, y, width, height };
  231. }
  232. }
  233. function placeAxisParallelHorizontal(components, coordinate, area) {
  234. const [x, y, width] = area;
  235. // Create a high dimension vector and map to a list of two-dimension points.
  236. // [0, 0, 0] -> [height, y0, height, y1, height, y2]
  237. const vector = new Array(components.length + 1).fill(0);
  238. const points = coordinate.map(vector);
  239. // Extract y of each points.
  240. // [x0, 0, x1, 0, x2, 0] -> [x0, x1, x2]
  241. const Y = points.filter((_, i) => i % 2 === 1).map((d) => d + y);
  242. // Place each axis by coordinate in parallel coordinate.
  243. for (let i = 0; i < components.length; i++) {
  244. const component = components[i];
  245. const y = Y[i];
  246. const height = Y[i + 1] - y;
  247. component.bbox = { x, y, width, height };
  248. }
  249. // override style
  250. }
  251. function placeAxisRadar(components, coordinate, area, position) {
  252. const [x, y, width, height] = area;
  253. for (const component of components) {
  254. component.bbox = { x, y, width, height };
  255. component.radar = {
  256. index: components.indexOf(component),
  257. count: components.length,
  258. };
  259. }
  260. }
  261. function placePaddingArea(components, coordinate, area) {
  262. const [x, y, width, height, direction, reverse, comparator] = area;
  263. const [mainStartKey, mainStartValue, crossStartKey, crossStartValue, mainSizeKey, mainSizeValue, crossSizeKey, crossSizeValue,] = direction === 'vertical'
  264. ? ['y', y, 'x', x, 'height', height, 'width', width]
  265. : ['x', x, 'y', y, 'width', width, 'height', height];
  266. // Sort components by order.
  267. // The smaller the order, the closer to center.
  268. components.sort((a, b) => comparator === null || comparator === void 0 ? void 0 : comparator(a.order, b.order));
  269. const startValue = reverse ? mainStartValue + mainSizeValue : mainStartValue;
  270. for (let i = 0, start = startValue; i < components.length; i++) {
  271. const component = components[i];
  272. const { size } = component;
  273. component.bbox = {
  274. [mainStartKey]: reverse ? start - size : start,
  275. [crossStartKey]: crossStartValue,
  276. [mainSizeKey]: size,
  277. [crossSizeKey]: crossSizeValue,
  278. };
  279. start += size * (reverse ? -1 : 1);
  280. }
  281. }
  282. /**
  283. * @example legend in the center of radial or polar system
  284. */
  285. function placeCenter(components, coordinate, area) {
  286. if (components.length === 0)
  287. return;
  288. const [x, y, width, height] = area;
  289. const [innerRadius] = (0, coordinate_1.radiusOf)(coordinate);
  290. const r = ((height / 2) * innerRadius) / Math.sqrt(2);
  291. const cx = x + width / 2;
  292. const cy = y + height / 2;
  293. for (let i = 0; i < components.length; i++) {
  294. const component = components[i];
  295. component.bbox = { x: cx - r, y: cy - r, width: r * 2, height: r * 2 };
  296. }
  297. }
  298. //# sourceMappingURL=layout.js.map