funnelLayout.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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. var _config = require("../../config");
  20. var __DEV__ = _config.__DEV__;
  21. var layout = require("../../util/layout");
  22. var _number = require("../../util/number");
  23. var parsePercent = _number.parsePercent;
  24. var linearMap = _number.linearMap;
  25. /*
  26. * Licensed to the Apache Software Foundation (ASF) under one
  27. * or more contributor license agreements. See the NOTICE file
  28. * distributed with this work for additional information
  29. * regarding copyright ownership. The ASF licenses this file
  30. * to you under the Apache License, Version 2.0 (the
  31. * "License"); you may not use this file except in compliance
  32. * with the License. You may obtain a copy of the License at
  33. *
  34. * http://www.apache.org/licenses/LICENSE-2.0
  35. *
  36. * Unless required by applicable law or agreed to in writing,
  37. * software distributed under the License is distributed on an
  38. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  39. * KIND, either express or implied. See the License for the
  40. * specific language governing permissions and limitations
  41. * under the License.
  42. */
  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. } // Add custom sortable function & none sortable opetion by "options.sort"
  59. if (typeof sort === 'function') {
  60. indices.sort(sort);
  61. } else if (sort !== 'none') {
  62. indices.sort(function (a, b) {
  63. return isAscending ? valueArr[a] - valueArr[b] : valueArr[b] - valueArr[a];
  64. });
  65. }
  66. return indices;
  67. }
  68. function labelLayout(data) {
  69. data.each(function (idx) {
  70. var itemModel = data.getItemModel(idx);
  71. var labelModel = itemModel.getModel('label');
  72. var labelPosition = labelModel.get('position');
  73. var orient = itemModel.get('orient');
  74. var labelLineModel = itemModel.getModel('labelLine');
  75. var layout = data.getItemLayout(idx);
  76. var points = layout.points;
  77. var isLabelInside = labelPosition === 'inner' || labelPosition === 'inside' || labelPosition === 'center' || labelPosition === 'insideLeft' || labelPosition === 'insideRight';
  78. var textAlign;
  79. var textX;
  80. var textY;
  81. var linePoints;
  82. if (isLabelInside) {
  83. if (labelPosition === 'insideLeft') {
  84. textX = (points[0][0] + points[3][0]) / 2 + 5;
  85. textY = (points[0][1] + points[3][1]) / 2;
  86. textAlign = 'left';
  87. } else if (labelPosition === 'insideRight') {
  88. textX = (points[1][0] + points[2][0]) / 2 - 5;
  89. textY = (points[1][1] + points[2][1]) / 2;
  90. textAlign = 'right';
  91. } else {
  92. textX = (points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4;
  93. textY = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4;
  94. textAlign = 'center';
  95. }
  96. linePoints = [[textX, textY], [textX, textY]];
  97. } else {
  98. var x1;
  99. var y1;
  100. var x2;
  101. var y2;
  102. var labelLineLen = labelLineModel.get('length');
  103. if (labelPosition === 'left') {
  104. // Left side
  105. x1 = (points[3][0] + points[0][0]) / 2;
  106. y1 = (points[3][1] + points[0][1]) / 2;
  107. x2 = x1 - labelLineLen;
  108. textX = x2 - 5;
  109. textAlign = 'right';
  110. } else if (labelPosition === 'right') {
  111. // Right side
  112. x1 = (points[1][0] + points[2][0]) / 2;
  113. y1 = (points[1][1] + points[2][1]) / 2;
  114. x2 = x1 + labelLineLen;
  115. textX = x2 + 5;
  116. textAlign = 'left';
  117. } else if (labelPosition === 'top') {
  118. // Top side
  119. x1 = (points[3][0] + points[0][0]) / 2;
  120. y1 = (points[3][1] + points[0][1]) / 2;
  121. y2 = y1 - labelLineLen;
  122. textY = y2 - 5;
  123. textAlign = 'center';
  124. } else if (labelPosition === 'bottom') {
  125. // Bottom side
  126. x1 = (points[1][0] + points[2][0]) / 2;
  127. y1 = (points[1][1] + points[2][1]) / 2;
  128. y2 = y1 + labelLineLen;
  129. textY = y2 + 5;
  130. textAlign = 'center';
  131. } else if (labelPosition === 'rightTop') {
  132. // RightTop side
  133. x1 = orient === 'horizontal' ? points[3][0] : points[1][0];
  134. y1 = orient === 'horizontal' ? points[3][1] : points[1][1];
  135. if (orient === 'horizontal') {
  136. y2 = y1 - labelLineLen;
  137. textY = y2 - 5;
  138. textAlign = 'center';
  139. } else {
  140. x2 = x1 + labelLineLen;
  141. textX = x2 + 5;
  142. textAlign = 'top';
  143. }
  144. } else if (labelPosition === 'rightBottom') {
  145. // RightBottom side
  146. x1 = points[2][0];
  147. y1 = points[2][1];
  148. if (orient === 'horizontal') {
  149. y2 = y1 + labelLineLen;
  150. textY = y2 + 5;
  151. textAlign = 'center';
  152. } else {
  153. x2 = x1 + labelLineLen;
  154. textX = x2 + 5;
  155. textAlign = 'bottom';
  156. }
  157. } else if (labelPosition === 'leftTop') {
  158. // LeftTop side
  159. x1 = points[0][0];
  160. y1 = orient === 'horizontal' ? points[0][1] : points[1][1];
  161. if (orient === 'horizontal') {
  162. y2 = y1 - labelLineLen;
  163. textY = y2 - 5;
  164. textAlign = 'center';
  165. } else {
  166. x2 = x1 - labelLineLen;
  167. textX = x2 - 5;
  168. textAlign = 'right';
  169. }
  170. } else if (labelPosition === 'leftBottom') {
  171. // LeftBottom side
  172. x1 = orient === 'horizontal' ? points[1][0] : points[3][0];
  173. y1 = orient === 'horizontal' ? points[1][1] : points[2][1];
  174. if (orient === 'horizontal') {
  175. y2 = y1 + labelLineLen;
  176. textY = y2 + 5;
  177. textAlign = 'center';
  178. } else {
  179. x2 = x1 - labelLineLen;
  180. textX = x2 - 5;
  181. textAlign = 'right';
  182. }
  183. } else {
  184. // Right side or Bottom side
  185. x1 = (points[1][0] + points[2][0]) / 2;
  186. y1 = (points[1][1] + points[2][1]) / 2;
  187. if (orient === 'horizontal') {
  188. y2 = y1 + labelLineLen;
  189. textY = y2 + 5;
  190. textAlign = 'center';
  191. } else {
  192. x2 = x1 + labelLineLen;
  193. textX = x2 + 5;
  194. textAlign = 'left';
  195. }
  196. }
  197. if (orient === 'horizontal') {
  198. x2 = x1;
  199. textX = x2;
  200. } else {
  201. y2 = y1;
  202. textY = y2;
  203. }
  204. linePoints = [[x1, y1], [x2, y2]];
  205. }
  206. layout.label = {
  207. linePoints: linePoints,
  208. x: textX,
  209. y: textY,
  210. verticalAlign: 'middle',
  211. textAlign: textAlign,
  212. inside: isLabelInside
  213. };
  214. });
  215. }
  216. function _default(ecModel, api, payload) {
  217. ecModel.eachSeriesByType('funnel', function (seriesModel) {
  218. var data = seriesModel.getData();
  219. var valueDim = data.mapDimension('value');
  220. var sort = seriesModel.get('sort');
  221. var viewRect = getViewRect(seriesModel, api);
  222. var indices = getSortedIndices(data, sort);
  223. var orient = seriesModel.get('orient');
  224. var viewWidth = viewRect.width;
  225. var viewHeight = viewRect.height;
  226. var x = viewRect.x;
  227. var y = viewRect.y;
  228. var sizeExtent = orient === 'horizontal' ? [parsePercent(seriesModel.get('minSize'), viewHeight), parsePercent(seriesModel.get('maxSize'), viewHeight)] : [parsePercent(seriesModel.get('minSize'), viewWidth), parsePercent(seriesModel.get('maxSize'), viewWidth)];
  229. var dataExtent = data.getDataExtent(valueDim);
  230. var min = seriesModel.get('min');
  231. var max = seriesModel.get('max');
  232. if (min == null) {
  233. min = Math.min(dataExtent[0], 0);
  234. }
  235. if (max == null) {
  236. max = dataExtent[1];
  237. }
  238. var funnelAlign = seriesModel.get('funnelAlign');
  239. var gap = seriesModel.get('gap');
  240. var viewSize = orient === 'horizontal' ? viewWidth : viewHeight;
  241. var itemSize = (viewSize - gap * (data.count() - 1)) / data.count();
  242. var getLinePoints = function (idx, offset) {
  243. // End point index is data.count() and we assign it 0
  244. if (orient === 'horizontal') {
  245. var val = data.get(valueDim, idx) || 0;
  246. var itemHeight = linearMap(val, [min, max], sizeExtent, true);
  247. var y0;
  248. switch (funnelAlign) {
  249. case 'top':
  250. y0 = y;
  251. break;
  252. case 'center':
  253. y0 = y + (viewHeight - itemHeight) / 2;
  254. break;
  255. case 'bottom':
  256. y0 = y + (viewHeight - itemHeight);
  257. break;
  258. }
  259. return [[offset, y0], [offset, y0 + itemHeight]];
  260. }
  261. var val = data.get(valueDim, idx) || 0;
  262. var itemWidth = linearMap(val, [min, max], sizeExtent, true);
  263. var x0;
  264. switch (funnelAlign) {
  265. case 'left':
  266. x0 = x;
  267. break;
  268. case 'center':
  269. x0 = x + (viewWidth - itemWidth) / 2;
  270. break;
  271. case 'right':
  272. x0 = x + viewWidth - itemWidth;
  273. break;
  274. }
  275. return [[x0, offset], [x0 + itemWidth, offset]];
  276. };
  277. if (sort === 'ascending') {
  278. // From bottom to top
  279. itemSize = -itemSize;
  280. gap = -gap;
  281. if (orient === 'horizontal') {
  282. x += viewWidth;
  283. } else {
  284. y += viewHeight;
  285. }
  286. indices = indices.reverse();
  287. }
  288. for (var i = 0; i < indices.length; i++) {
  289. var idx = indices[i];
  290. var nextIdx = indices[i + 1];
  291. var itemModel = data.getItemModel(idx);
  292. if (orient === 'horizontal') {
  293. var width = itemModel.get('itemStyle.width');
  294. if (width == null) {
  295. width = itemSize;
  296. } else {
  297. width = parsePercent(width, viewWidth);
  298. if (sort === 'ascending') {
  299. width = -width;
  300. }
  301. }
  302. var start = getLinePoints(idx, x);
  303. var end = getLinePoints(nextIdx, x + width);
  304. x += width + gap;
  305. data.setItemLayout(idx, {
  306. points: start.concat(end.slice().reverse())
  307. });
  308. } else {
  309. var height = itemModel.get('itemStyle.height');
  310. if (height == null) {
  311. height = itemSize;
  312. } else {
  313. height = parsePercent(height, viewHeight);
  314. if (sort === 'ascending') {
  315. height = -height;
  316. }
  317. }
  318. var start = orient === 'horizontal' ? getLinePoints(idx, x) : getLinePoints(idx, y);
  319. var end = orient === 'horizontal' ? getLinePoints(nextIdx, x + width) : getLinePoints(nextIdx, y + height);
  320. y += height + gap;
  321. data.setItemLayout(idx, {
  322. points: start.concat(end.slice().reverse())
  323. });
  324. }
  325. }
  326. labelLayout(data);
  327. });
  328. }
  329. module.exports = _default;