funnelLayout.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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 * as layout from '../../util/layout.js';
  41. import { parsePercent, linearMap } from '../../util/number.js';
  42. import { isFunction } from 'zrender/lib/core/util.js';
  43. function getViewRect(seriesModel, api) {
  44. return layout.getLayoutRect(seriesModel.getBoxLayoutParams(), {
  45. width: api.getWidth(),
  46. height: api.getHeight()
  47. });
  48. }
  49. function getSortedIndices(data, sort) {
  50. var valueDim = data.mapDimension('value');
  51. var valueArr = data.mapArray(valueDim, function (val) {
  52. return val;
  53. });
  54. var indices = [];
  55. var isAscending = sort === 'ascending';
  56. for (var i = 0, len = data.count(); i < len; i++) {
  57. indices[i] = i;
  58. }
  59. // Add custom sortable function & none sortable opetion by "options.sort"
  60. if (isFunction(sort)) {
  61. indices.sort(sort);
  62. } else if (sort !== 'none') {
  63. indices.sort(function (a, b) {
  64. return isAscending ? valueArr[a] - valueArr[b] : valueArr[b] - valueArr[a];
  65. });
  66. }
  67. return indices;
  68. }
  69. function labelLayout(data) {
  70. var seriesModel = data.hostModel;
  71. var orient = seriesModel.get('orient');
  72. data.each(function (idx) {
  73. var itemModel = data.getItemModel(idx);
  74. var labelModel = itemModel.getModel('label');
  75. var labelPosition = labelModel.get('position');
  76. var labelLineModel = itemModel.getModel('labelLine');
  77. var layout = data.getItemLayout(idx);
  78. var points = layout.points;
  79. var isLabelInside = labelPosition === 'inner' || labelPosition === 'inside' || labelPosition === 'center' || labelPosition === 'insideLeft' || labelPosition === 'insideRight';
  80. var textAlign;
  81. var textX;
  82. var textY;
  83. var linePoints;
  84. if (isLabelInside) {
  85. if (labelPosition === 'insideLeft') {
  86. textX = (points[0][0] + points[3][0]) / 2 + 5;
  87. textY = (points[0][1] + points[3][1]) / 2;
  88. textAlign = 'left';
  89. } else if (labelPosition === 'insideRight') {
  90. textX = (points[1][0] + points[2][0]) / 2 - 5;
  91. textY = (points[1][1] + points[2][1]) / 2;
  92. textAlign = 'right';
  93. } else {
  94. textX = (points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4;
  95. textY = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4;
  96. textAlign = 'center';
  97. }
  98. linePoints = [[textX, textY], [textX, textY]];
  99. } else {
  100. var x1 = void 0;
  101. var y1 = void 0;
  102. var x2 = void 0;
  103. var y2 = void 0;
  104. var labelLineLen = labelLineModel.get('length');
  105. if (process.env.NODE_ENV !== 'production') {
  106. if (orient === 'vertical' && ['top', 'bottom'].indexOf(labelPosition) > -1) {
  107. labelPosition = 'left';
  108. console.warn('Position error: Funnel chart on vertical orient dose not support top and bottom.');
  109. }
  110. if (orient === 'horizontal' && ['left', 'right'].indexOf(labelPosition) > -1) {
  111. labelPosition = 'bottom';
  112. console.warn('Position error: Funnel chart on horizontal orient dose not support left and right.');
  113. }
  114. }
  115. if (labelPosition === 'left') {
  116. // Left side
  117. x1 = (points[3][0] + points[0][0]) / 2;
  118. y1 = (points[3][1] + points[0][1]) / 2;
  119. x2 = x1 - labelLineLen;
  120. textX = x2 - 5;
  121. textAlign = 'right';
  122. } else if (labelPosition === 'right') {
  123. // Right side
  124. x1 = (points[1][0] + points[2][0]) / 2;
  125. y1 = (points[1][1] + points[2][1]) / 2;
  126. x2 = x1 + labelLineLen;
  127. textX = x2 + 5;
  128. textAlign = 'left';
  129. } else if (labelPosition === 'top') {
  130. // Top side
  131. x1 = (points[3][0] + points[0][0]) / 2;
  132. y1 = (points[3][1] + points[0][1]) / 2;
  133. y2 = y1 - labelLineLen;
  134. textY = y2 - 5;
  135. textAlign = 'center';
  136. } else if (labelPosition === 'bottom') {
  137. // Bottom side
  138. x1 = (points[1][0] + points[2][0]) / 2;
  139. y1 = (points[1][1] + points[2][1]) / 2;
  140. y2 = y1 + labelLineLen;
  141. textY = y2 + 5;
  142. textAlign = 'center';
  143. } else if (labelPosition === 'rightTop') {
  144. // RightTop side
  145. x1 = orient === 'horizontal' ? points[3][0] : points[1][0];
  146. y1 = orient === 'horizontal' ? points[3][1] : points[1][1];
  147. if (orient === 'horizontal') {
  148. y2 = y1 - labelLineLen;
  149. textY = y2 - 5;
  150. textAlign = 'center';
  151. } else {
  152. x2 = x1 + labelLineLen;
  153. textX = x2 + 5;
  154. textAlign = 'top';
  155. }
  156. } else if (labelPosition === 'rightBottom') {
  157. // RightBottom side
  158. x1 = points[2][0];
  159. y1 = points[2][1];
  160. if (orient === 'horizontal') {
  161. y2 = y1 + labelLineLen;
  162. textY = y2 + 5;
  163. textAlign = 'center';
  164. } else {
  165. x2 = x1 + labelLineLen;
  166. textX = x2 + 5;
  167. textAlign = 'bottom';
  168. }
  169. } else if (labelPosition === 'leftTop') {
  170. // LeftTop side
  171. x1 = points[0][0];
  172. y1 = orient === 'horizontal' ? points[0][1] : points[1][1];
  173. if (orient === 'horizontal') {
  174. y2 = y1 - labelLineLen;
  175. textY = y2 - 5;
  176. textAlign = 'center';
  177. } else {
  178. x2 = x1 - labelLineLen;
  179. textX = x2 - 5;
  180. textAlign = 'right';
  181. }
  182. } else if (labelPosition === 'leftBottom') {
  183. // LeftBottom side
  184. x1 = orient === 'horizontal' ? points[1][0] : points[3][0];
  185. y1 = orient === 'horizontal' ? points[1][1] : points[2][1];
  186. if (orient === 'horizontal') {
  187. y2 = y1 + labelLineLen;
  188. textY = y2 + 5;
  189. textAlign = 'center';
  190. } else {
  191. x2 = x1 - labelLineLen;
  192. textX = x2 - 5;
  193. textAlign = 'right';
  194. }
  195. } else {
  196. // Right side or Bottom side
  197. x1 = (points[1][0] + points[2][0]) / 2;
  198. y1 = (points[1][1] + points[2][1]) / 2;
  199. if (orient === 'horizontal') {
  200. y2 = y1 + labelLineLen;
  201. textY = y2 + 5;
  202. textAlign = 'center';
  203. } else {
  204. x2 = x1 + labelLineLen;
  205. textX = x2 + 5;
  206. textAlign = 'left';
  207. }
  208. }
  209. if (orient === 'horizontal') {
  210. x2 = x1;
  211. textX = x2;
  212. } else {
  213. y2 = y1;
  214. textY = y2;
  215. }
  216. linePoints = [[x1, y1], [x2, y2]];
  217. }
  218. layout.label = {
  219. linePoints: linePoints,
  220. x: textX,
  221. y: textY,
  222. verticalAlign: 'middle',
  223. textAlign: textAlign,
  224. inside: isLabelInside
  225. };
  226. });
  227. }
  228. export default function funnelLayout(ecModel, api) {
  229. ecModel.eachSeriesByType('funnel', function (seriesModel) {
  230. var data = seriesModel.getData();
  231. var valueDim = data.mapDimension('value');
  232. var sort = seriesModel.get('sort');
  233. var viewRect = getViewRect(seriesModel, api);
  234. var orient = seriesModel.get('orient');
  235. var viewWidth = viewRect.width;
  236. var viewHeight = viewRect.height;
  237. var indices = getSortedIndices(data, sort);
  238. var x = viewRect.x;
  239. var y = viewRect.y;
  240. var sizeExtent = orient === 'horizontal' ? [parsePercent(seriesModel.get('minSize'), viewHeight), parsePercent(seriesModel.get('maxSize'), viewHeight)] : [parsePercent(seriesModel.get('minSize'), viewWidth), parsePercent(seriesModel.get('maxSize'), viewWidth)];
  241. var dataExtent = data.getDataExtent(valueDim);
  242. var min = seriesModel.get('min');
  243. var max = seriesModel.get('max');
  244. if (min == null) {
  245. min = Math.min(dataExtent[0], 0);
  246. }
  247. if (max == null) {
  248. max = dataExtent[1];
  249. }
  250. var funnelAlign = seriesModel.get('funnelAlign');
  251. var gap = seriesModel.get('gap');
  252. var viewSize = orient === 'horizontal' ? viewWidth : viewHeight;
  253. var itemSize = (viewSize - gap * (data.count() - 1)) / data.count();
  254. var getLinePoints = function (idx, offset) {
  255. // End point index is data.count() and we assign it 0
  256. if (orient === 'horizontal') {
  257. var val_1 = data.get(valueDim, idx) || 0;
  258. var itemHeight = linearMap(val_1, [min, max], sizeExtent, true);
  259. var y0 = void 0;
  260. switch (funnelAlign) {
  261. case 'top':
  262. y0 = y;
  263. break;
  264. case 'center':
  265. y0 = y + (viewHeight - itemHeight) / 2;
  266. break;
  267. case 'bottom':
  268. y0 = y + (viewHeight - itemHeight);
  269. break;
  270. }
  271. return [[offset, y0], [offset, y0 + itemHeight]];
  272. }
  273. var val = data.get(valueDim, idx) || 0;
  274. var itemWidth = linearMap(val, [min, max], sizeExtent, true);
  275. var x0;
  276. switch (funnelAlign) {
  277. case 'left':
  278. x0 = x;
  279. break;
  280. case 'center':
  281. x0 = x + (viewWidth - itemWidth) / 2;
  282. break;
  283. case 'right':
  284. x0 = x + viewWidth - itemWidth;
  285. break;
  286. }
  287. return [[x0, offset], [x0 + itemWidth, offset]];
  288. };
  289. if (sort === 'ascending') {
  290. // From bottom to top
  291. itemSize = -itemSize;
  292. gap = -gap;
  293. if (orient === 'horizontal') {
  294. x += viewWidth;
  295. } else {
  296. y += viewHeight;
  297. }
  298. indices = indices.reverse();
  299. }
  300. for (var i = 0; i < indices.length; i++) {
  301. var idx = indices[i];
  302. var nextIdx = indices[i + 1];
  303. var itemModel = data.getItemModel(idx);
  304. if (orient === 'horizontal') {
  305. var width = itemModel.get(['itemStyle', 'width']);
  306. if (width == null) {
  307. width = itemSize;
  308. } else {
  309. width = parsePercent(width, viewWidth);
  310. if (sort === 'ascending') {
  311. width = -width;
  312. }
  313. }
  314. var start = getLinePoints(idx, x);
  315. var end = getLinePoints(nextIdx, x + width);
  316. x += width + gap;
  317. data.setItemLayout(idx, {
  318. points: start.concat(end.slice().reverse())
  319. });
  320. } else {
  321. var height = itemModel.get(['itemStyle', 'height']);
  322. if (height == null) {
  323. height = itemSize;
  324. } else {
  325. height = parsePercent(height, viewHeight);
  326. if (sort === 'ascending') {
  327. height = -height;
  328. }
  329. }
  330. var start = getLinePoints(idx, y);
  331. var end = getLinePoints(nextIdx, y + height);
  332. y += height + gap;
  333. data.setItemLayout(idx, {
  334. points: start.concat(end.slice().reverse())
  335. });
  336. }
  337. }
  338. labelLayout(data);
  339. });
  340. }