ScrollableLegendView.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { __extends } from "tslib";
  41. /**
  42. * Separate legend and scrollable legend to reduce package size.
  43. */
  44. import * as zrUtil from 'zrender/lib/core/util.js';
  45. import * as graphic from '../../util/graphic.js';
  46. import * as layoutUtil from '../../util/layout.js';
  47. import LegendView from './LegendView.js';
  48. var Group = graphic.Group;
  49. var WH = ['width', 'height'];
  50. var XY = ['x', 'y'];
  51. var ScrollableLegendView = /** @class */function (_super) {
  52. __extends(ScrollableLegendView, _super);
  53. function ScrollableLegendView() {
  54. var _this = _super !== null && _super.apply(this, arguments) || this;
  55. _this.type = ScrollableLegendView.type;
  56. _this.newlineDisabled = true;
  57. _this._currentIndex = 0;
  58. return _this;
  59. }
  60. ScrollableLegendView.prototype.init = function () {
  61. _super.prototype.init.call(this);
  62. this.group.add(this._containerGroup = new Group());
  63. this._containerGroup.add(this.getContentGroup());
  64. this.group.add(this._controllerGroup = new Group());
  65. };
  66. /**
  67. * @override
  68. */
  69. ScrollableLegendView.prototype.resetInner = function () {
  70. _super.prototype.resetInner.call(this);
  71. this._controllerGroup.removeAll();
  72. this._containerGroup.removeClipPath();
  73. this._containerGroup.__rectSize = null;
  74. };
  75. /**
  76. * @override
  77. */
  78. ScrollableLegendView.prototype.renderInner = function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) {
  79. var self = this;
  80. // Render content items.
  81. _super.prototype.renderInner.call(this, itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition);
  82. var controllerGroup = this._controllerGroup;
  83. // FIXME: support be 'auto' adapt to size number text length,
  84. // e.g., '3/12345' should not overlap with the control arrow button.
  85. var pageIconSize = legendModel.get('pageIconSize', true);
  86. var pageIconSizeArr = zrUtil.isArray(pageIconSize) ? pageIconSize : [pageIconSize, pageIconSize];
  87. createPageButton('pagePrev', 0);
  88. var pageTextStyleModel = legendModel.getModel('pageTextStyle');
  89. controllerGroup.add(new graphic.Text({
  90. name: 'pageText',
  91. style: {
  92. // Placeholder to calculate a proper layout.
  93. text: 'xx/xx',
  94. fill: pageTextStyleModel.getTextColor(),
  95. font: pageTextStyleModel.getFont(),
  96. verticalAlign: 'middle',
  97. align: 'center'
  98. },
  99. silent: true
  100. }));
  101. createPageButton('pageNext', 1);
  102. function createPageButton(name, iconIdx) {
  103. var pageDataIndexName = name + 'DataIndex';
  104. var icon = graphic.createIcon(legendModel.get('pageIcons', true)[legendModel.getOrient().name][iconIdx], {
  105. // Buttons will be created in each render, so we do not need
  106. // to worry about avoiding using legendModel kept in scope.
  107. onclick: zrUtil.bind(self._pageGo, self, pageDataIndexName, legendModel, api)
  108. }, {
  109. x: -pageIconSizeArr[0] / 2,
  110. y: -pageIconSizeArr[1] / 2,
  111. width: pageIconSizeArr[0],
  112. height: pageIconSizeArr[1]
  113. });
  114. icon.name = name;
  115. controllerGroup.add(icon);
  116. }
  117. };
  118. /**
  119. * @override
  120. */
  121. ScrollableLegendView.prototype.layoutInner = function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) {
  122. var selectorGroup = this.getSelectorGroup();
  123. var orientIdx = legendModel.getOrient().index;
  124. var wh = WH[orientIdx];
  125. var xy = XY[orientIdx];
  126. var hw = WH[1 - orientIdx];
  127. var yx = XY[1 - orientIdx];
  128. selector && layoutUtil.box(
  129. // Buttons in selectorGroup always layout horizontally
  130. 'horizontal', selectorGroup, legendModel.get('selectorItemGap', true));
  131. var selectorButtonGap = legendModel.get('selectorButtonGap', true);
  132. var selectorRect = selectorGroup.getBoundingRect();
  133. var selectorPos = [-selectorRect.x, -selectorRect.y];
  134. var processMaxSize = zrUtil.clone(maxSize);
  135. selector && (processMaxSize[wh] = maxSize[wh] - selectorRect[wh] - selectorButtonGap);
  136. var mainRect = this._layoutContentAndController(legendModel, isFirstRender, processMaxSize, orientIdx, wh, hw, yx, xy);
  137. if (selector) {
  138. if (selectorPosition === 'end') {
  139. selectorPos[orientIdx] += mainRect[wh] + selectorButtonGap;
  140. } else {
  141. var offset = selectorRect[wh] + selectorButtonGap;
  142. selectorPos[orientIdx] -= offset;
  143. mainRect[xy] -= offset;
  144. }
  145. mainRect[wh] += selectorRect[wh] + selectorButtonGap;
  146. selectorPos[1 - orientIdx] += mainRect[yx] + mainRect[hw] / 2 - selectorRect[hw] / 2;
  147. mainRect[hw] = Math.max(mainRect[hw], selectorRect[hw]);
  148. mainRect[yx] = Math.min(mainRect[yx], selectorRect[yx] + selectorPos[1 - orientIdx]);
  149. selectorGroup.x = selectorPos[0];
  150. selectorGroup.y = selectorPos[1];
  151. selectorGroup.markRedraw();
  152. }
  153. return mainRect;
  154. };
  155. ScrollableLegendView.prototype._layoutContentAndController = function (legendModel, isFirstRender, maxSize, orientIdx, wh, hw, yx, xy) {
  156. var contentGroup = this.getContentGroup();
  157. var containerGroup = this._containerGroup;
  158. var controllerGroup = this._controllerGroup;
  159. // Place items in contentGroup.
  160. layoutUtil.box(legendModel.get('orient'), contentGroup, legendModel.get('itemGap'), !orientIdx ? null : maxSize.width, orientIdx ? null : maxSize.height);
  161. layoutUtil.box(
  162. // Buttons in controller are layout always horizontally.
  163. 'horizontal', controllerGroup, legendModel.get('pageButtonItemGap', true));
  164. var contentRect = contentGroup.getBoundingRect();
  165. var controllerRect = controllerGroup.getBoundingRect();
  166. var showController = this._showController = contentRect[wh] > maxSize[wh];
  167. // In case that the inner elements of contentGroup layout do not based on [0, 0]
  168. var contentPos = [-contentRect.x, -contentRect.y];
  169. // Remain contentPos when scroll animation perfroming.
  170. // If first rendering, `contentGroup.position` is [0, 0], which
  171. // does not make sense and may cause unexepcted animation if adopted.
  172. if (!isFirstRender) {
  173. contentPos[orientIdx] = contentGroup[xy];
  174. }
  175. // Layout container group based on 0.
  176. var containerPos = [0, 0];
  177. var controllerPos = [-controllerRect.x, -controllerRect.y];
  178. var pageButtonGap = zrUtil.retrieve2(legendModel.get('pageButtonGap', true), legendModel.get('itemGap', true));
  179. // Place containerGroup and controllerGroup and contentGroup.
  180. if (showController) {
  181. var pageButtonPosition = legendModel.get('pageButtonPosition', true);
  182. // controller is on the right / bottom.
  183. if (pageButtonPosition === 'end') {
  184. controllerPos[orientIdx] += maxSize[wh] - controllerRect[wh];
  185. }
  186. // controller is on the left / top.
  187. else {
  188. containerPos[orientIdx] += controllerRect[wh] + pageButtonGap;
  189. }
  190. }
  191. // Always align controller to content as 'middle'.
  192. controllerPos[1 - orientIdx] += contentRect[hw] / 2 - controllerRect[hw] / 2;
  193. contentGroup.setPosition(contentPos);
  194. containerGroup.setPosition(containerPos);
  195. controllerGroup.setPosition(controllerPos);
  196. // Calculate `mainRect` and set `clipPath`.
  197. // mainRect should not be calculated by `this.group.getBoundingRect()`
  198. // for sake of the overflow.
  199. var mainRect = {
  200. x: 0,
  201. y: 0
  202. };
  203. // Consider content may be overflow (should be clipped).
  204. mainRect[wh] = showController ? maxSize[wh] : contentRect[wh];
  205. mainRect[hw] = Math.max(contentRect[hw], controllerRect[hw]);
  206. // `containerRect[yx] + containerPos[1 - orientIdx]` is 0.
  207. mainRect[yx] = Math.min(0, controllerRect[yx] + controllerPos[1 - orientIdx]);
  208. containerGroup.__rectSize = maxSize[wh];
  209. if (showController) {
  210. var clipShape = {
  211. x: 0,
  212. y: 0
  213. };
  214. clipShape[wh] = Math.max(maxSize[wh] - controllerRect[wh] - pageButtonGap, 0);
  215. clipShape[hw] = mainRect[hw];
  216. containerGroup.setClipPath(new graphic.Rect({
  217. shape: clipShape
  218. }));
  219. // Consider content may be larger than container, container rect
  220. // can not be obtained from `containerGroup.getBoundingRect()`.
  221. containerGroup.__rectSize = clipShape[wh];
  222. } else {
  223. // Do not remove or ignore controller. Keep them set as placeholders.
  224. controllerGroup.eachChild(function (child) {
  225. child.attr({
  226. invisible: true,
  227. silent: true
  228. });
  229. });
  230. }
  231. // Content translate animation.
  232. var pageInfo = this._getPageInfo(legendModel);
  233. pageInfo.pageIndex != null && graphic.updateProps(contentGroup, {
  234. x: pageInfo.contentPosition[0],
  235. y: pageInfo.contentPosition[1]
  236. },
  237. // When switch from "show controller" to "not show controller", view should be
  238. // updated immediately without animation, otherwise causes weird effect.
  239. showController ? legendModel : null);
  240. this._updatePageInfoView(legendModel, pageInfo);
  241. return mainRect;
  242. };
  243. ScrollableLegendView.prototype._pageGo = function (to, legendModel, api) {
  244. var scrollDataIndex = this._getPageInfo(legendModel)[to];
  245. scrollDataIndex != null && api.dispatchAction({
  246. type: 'legendScroll',
  247. scrollDataIndex: scrollDataIndex,
  248. legendId: legendModel.id
  249. });
  250. };
  251. ScrollableLegendView.prototype._updatePageInfoView = function (legendModel, pageInfo) {
  252. var controllerGroup = this._controllerGroup;
  253. zrUtil.each(['pagePrev', 'pageNext'], function (name) {
  254. var key = name + 'DataIndex';
  255. var canJump = pageInfo[key] != null;
  256. var icon = controllerGroup.childOfName(name);
  257. if (icon) {
  258. icon.setStyle('fill', canJump ? legendModel.get('pageIconColor', true) : legendModel.get('pageIconInactiveColor', true));
  259. icon.cursor = canJump ? 'pointer' : 'default';
  260. }
  261. });
  262. var pageText = controllerGroup.childOfName('pageText');
  263. var pageFormatter = legendModel.get('pageFormatter');
  264. var pageIndex = pageInfo.pageIndex;
  265. var current = pageIndex != null ? pageIndex + 1 : 0;
  266. var total = pageInfo.pageCount;
  267. pageText && pageFormatter && pageText.setStyle('text', zrUtil.isString(pageFormatter) ? pageFormatter.replace('{current}', current == null ? '' : current + '').replace('{total}', total == null ? '' : total + '') : pageFormatter({
  268. current: current,
  269. total: total
  270. }));
  271. };
  272. /**
  273. * contentPosition: Array.<number>, null when data item not found.
  274. * pageIndex: number, null when data item not found.
  275. * pageCount: number, always be a number, can be 0.
  276. * pagePrevDataIndex: number, null when no previous page.
  277. * pageNextDataIndex: number, null when no next page.
  278. * }
  279. */
  280. ScrollableLegendView.prototype._getPageInfo = function (legendModel) {
  281. var scrollDataIndex = legendModel.get('scrollDataIndex', true);
  282. var contentGroup = this.getContentGroup();
  283. var containerRectSize = this._containerGroup.__rectSize;
  284. var orientIdx = legendModel.getOrient().index;
  285. var wh = WH[orientIdx];
  286. var xy = XY[orientIdx];
  287. var targetItemIndex = this._findTargetItemIndex(scrollDataIndex);
  288. var children = contentGroup.children();
  289. var targetItem = children[targetItemIndex];
  290. var itemCount = children.length;
  291. var pCount = !itemCount ? 0 : 1;
  292. var result = {
  293. contentPosition: [contentGroup.x, contentGroup.y],
  294. pageCount: pCount,
  295. pageIndex: pCount - 1,
  296. pagePrevDataIndex: null,
  297. pageNextDataIndex: null
  298. };
  299. if (!targetItem) {
  300. return result;
  301. }
  302. var targetItemInfo = getItemInfo(targetItem);
  303. result.contentPosition[orientIdx] = -targetItemInfo.s;
  304. // Strategy:
  305. // (1) Always align based on the left/top most item.
  306. // (2) It is user-friendly that the last item shown in the
  307. // current window is shown at the begining of next window.
  308. // Otherwise if half of the last item is cut by the window,
  309. // it will have no chance to display entirely.
  310. // (3) Consider that item size probably be different, we
  311. // have calculate pageIndex by size rather than item index,
  312. // and we can not get page index directly by division.
  313. // (4) The window is to narrow to contain more than
  314. // one item, we should make sure that the page can be fliped.
  315. for (var i = targetItemIndex + 1, winStartItemInfo = targetItemInfo, winEndItemInfo = targetItemInfo, currItemInfo = null; i <= itemCount; ++i) {
  316. currItemInfo = getItemInfo(children[i]);
  317. if (
  318. // Half of the last item is out of the window.
  319. !currItemInfo && winEndItemInfo.e > winStartItemInfo.s + containerRectSize
  320. // If the current item does not intersect with the window, the new page
  321. // can be started at the current item or the last item.
  322. || currItemInfo && !intersect(currItemInfo, winStartItemInfo.s)) {
  323. if (winEndItemInfo.i > winStartItemInfo.i) {
  324. winStartItemInfo = winEndItemInfo;
  325. } else {
  326. // e.g., when page size is smaller than item size.
  327. winStartItemInfo = currItemInfo;
  328. }
  329. if (winStartItemInfo) {
  330. if (result.pageNextDataIndex == null) {
  331. result.pageNextDataIndex = winStartItemInfo.i;
  332. }
  333. ++result.pageCount;
  334. }
  335. }
  336. winEndItemInfo = currItemInfo;
  337. }
  338. for (var i = targetItemIndex - 1, winStartItemInfo = targetItemInfo, winEndItemInfo = targetItemInfo, currItemInfo = null; i >= -1; --i) {
  339. currItemInfo = getItemInfo(children[i]);
  340. if (
  341. // If the the end item does not intersect with the window started
  342. // from the current item, a page can be settled.
  343. (!currItemInfo || !intersect(winEndItemInfo, currItemInfo.s)
  344. // e.g., when page size is smaller than item size.
  345. ) && winStartItemInfo.i < winEndItemInfo.i) {
  346. winEndItemInfo = winStartItemInfo;
  347. if (result.pagePrevDataIndex == null) {
  348. result.pagePrevDataIndex = winStartItemInfo.i;
  349. }
  350. ++result.pageCount;
  351. ++result.pageIndex;
  352. }
  353. winStartItemInfo = currItemInfo;
  354. }
  355. return result;
  356. function getItemInfo(el) {
  357. if (el) {
  358. var itemRect = el.getBoundingRect();
  359. var start = itemRect[xy] + el[xy];
  360. return {
  361. s: start,
  362. e: start + itemRect[wh],
  363. i: el.__legendDataIndex
  364. };
  365. }
  366. }
  367. function intersect(itemInfo, winStart) {
  368. return itemInfo.e >= winStart && itemInfo.s <= winStart + containerRectSize;
  369. }
  370. };
  371. ScrollableLegendView.prototype._findTargetItemIndex = function (targetDataIndex) {
  372. if (!this._showController) {
  373. return 0;
  374. }
  375. var index;
  376. var contentGroup = this.getContentGroup();
  377. var defaultIndex;
  378. contentGroup.eachChild(function (child, idx) {
  379. var legendDataIdx = child.__legendDataIndex;
  380. // FIXME
  381. // If the given targetDataIndex (from model) is illegal,
  382. // we use defaultIndex. But the index on the legend model and
  383. // action payload is still illegal. That case will not be
  384. // changed until some scenario requires.
  385. if (defaultIndex == null && legendDataIdx != null) {
  386. defaultIndex = idx;
  387. }
  388. if (legendDataIdx === targetDataIndex) {
  389. index = idx;
  390. }
  391. });
  392. return index != null ? index : defaultIndex;
  393. };
  394. ScrollableLegendView.type = 'legend.scroll';
  395. return ScrollableLegendView;
  396. }(LegendView);
  397. export default ScrollableLegendView;