barGrid.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. /* global Float32Array */
  41. import * as zrUtil from 'zrender/lib/core/util';
  42. import { parsePercent } from '../util/number';
  43. import { isDimensionStacked } from '../data/helper/dataStackHelper';
  44. import createRenderPlanner from '../chart/helper/createRenderPlanner';
  45. var STACK_PREFIX = '__ec_stack_';
  46. var LARGE_BAR_MIN_WIDTH = 0.5;
  47. var LargeArr = typeof Float32Array !== 'undefined' ? Float32Array : Array;
  48. function getSeriesStackId(seriesModel) {
  49. return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex;
  50. }
  51. function getAxisKey(axis) {
  52. return axis.dim + axis.index;
  53. }
  54. /**
  55. * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined.
  56. */
  57. export function getLayoutOnAxis(opt) {
  58. var params = [];
  59. var baseAxis = opt.axis;
  60. var axisKey = 'axis0';
  61. if (baseAxis.type !== 'category') {
  62. return;
  63. }
  64. var bandWidth = baseAxis.getBandWidth();
  65. for (var i = 0; i < opt.count || 0; i++) {
  66. params.push(zrUtil.defaults({
  67. bandWidth: bandWidth,
  68. axisKey: axisKey,
  69. stackId: STACK_PREFIX + i
  70. }, opt));
  71. }
  72. var widthAndOffsets = doCalBarWidthAndOffset(params);
  73. var result = [];
  74. for (var i = 0; i < opt.count; i++) {
  75. var item = widthAndOffsets[axisKey][STACK_PREFIX + i];
  76. item.offsetCenter = item.offset + item.width / 2;
  77. result.push(item);
  78. }
  79. return result;
  80. }
  81. export function prepareLayoutBarSeries(seriesType, ecModel) {
  82. var seriesModels = [];
  83. ecModel.eachSeriesByType(seriesType, function (seriesModel) {
  84. // Check series coordinate, do layout for cartesian2d only
  85. if (isOnCartesian(seriesModel) && !isInLargeMode(seriesModel)) {
  86. seriesModels.push(seriesModel);
  87. }
  88. });
  89. return seriesModels;
  90. }
  91. /**
  92. * Map from (baseAxis.dim + '_' + baseAxis.index) to min gap of two adjacent
  93. * values.
  94. * This works for time axes, value axes, and log axes.
  95. * For a single time axis, return value is in the form like
  96. * {'x_0': [1000000]}.
  97. * The value of 1000000 is in milliseconds.
  98. */
  99. function getValueAxesMinGaps(barSeries) {
  100. /**
  101. * Map from axis.index to values.
  102. * For a single time axis, axisValues is in the form like
  103. * {'x_0': [1495555200000, 1495641600000, 1495728000000]}.
  104. * Items in axisValues[x], e.g. 1495555200000, are time values of all
  105. * series.
  106. */
  107. var axisValues = {};
  108. zrUtil.each(barSeries, function (seriesModel) {
  109. var cartesian = seriesModel.coordinateSystem;
  110. var baseAxis = cartesian.getBaseAxis();
  111. if (baseAxis.type !== 'time' && baseAxis.type !== 'value') {
  112. return;
  113. }
  114. var data = seriesModel.getData();
  115. var key = baseAxis.dim + '_' + baseAxis.index;
  116. var dimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim));
  117. var store = data.getStore();
  118. for (var i = 0, cnt = store.count(); i < cnt; ++i) {
  119. var value = store.get(dimIdx, i);
  120. if (!axisValues[key]) {
  121. // No previous data for the axis
  122. axisValues[key] = [value];
  123. } else {
  124. // No value in previous series
  125. axisValues[key].push(value);
  126. } // Ignore duplicated time values in the same axis
  127. }
  128. });
  129. var axisMinGaps = {};
  130. for (var key in axisValues) {
  131. if (axisValues.hasOwnProperty(key)) {
  132. var valuesInAxis = axisValues[key];
  133. if (valuesInAxis) {
  134. // Sort axis values into ascending order to calculate gaps
  135. valuesInAxis.sort(function (a, b) {
  136. return a - b;
  137. });
  138. var min = null;
  139. for (var j = 1; j < valuesInAxis.length; ++j) {
  140. var delta = valuesInAxis[j] - valuesInAxis[j - 1];
  141. if (delta > 0) {
  142. // Ignore 0 delta because they are of the same axis value
  143. min = min === null ? delta : Math.min(min, delta);
  144. }
  145. } // Set to null if only have one data
  146. axisMinGaps[key] = min;
  147. }
  148. }
  149. }
  150. return axisMinGaps;
  151. }
  152. export function makeColumnLayout(barSeries) {
  153. var axisMinGaps = getValueAxesMinGaps(barSeries);
  154. var seriesInfoList = [];
  155. zrUtil.each(barSeries, function (seriesModel) {
  156. var cartesian = seriesModel.coordinateSystem;
  157. var baseAxis = cartesian.getBaseAxis();
  158. var axisExtent = baseAxis.getExtent();
  159. var bandWidth;
  160. if (baseAxis.type === 'category') {
  161. bandWidth = baseAxis.getBandWidth();
  162. } else if (baseAxis.type === 'value' || baseAxis.type === 'time') {
  163. var key = baseAxis.dim + '_' + baseAxis.index;
  164. var minGap = axisMinGaps[key];
  165. var extentSpan = Math.abs(axisExtent[1] - axisExtent[0]);
  166. var scale = baseAxis.scale.getExtent();
  167. var scaleSpan = Math.abs(scale[1] - scale[0]);
  168. bandWidth = minGap ? extentSpan / scaleSpan * minGap : extentSpan; // When there is only one data value
  169. } else {
  170. var data = seriesModel.getData();
  171. bandWidth = Math.abs(axisExtent[1] - axisExtent[0]) / data.count();
  172. }
  173. var barWidth = parsePercent(seriesModel.get('barWidth'), bandWidth);
  174. var barMaxWidth = parsePercent(seriesModel.get('barMaxWidth'), bandWidth);
  175. var barMinWidth = parsePercent( // barMinWidth by default is 1 in cartesian. Because in value axis,
  176. // the auto-calculated bar width might be less than 1.
  177. seriesModel.get('barMinWidth') || 1, bandWidth);
  178. var barGap = seriesModel.get('barGap');
  179. var barCategoryGap = seriesModel.get('barCategoryGap');
  180. seriesInfoList.push({
  181. bandWidth: bandWidth,
  182. barWidth: barWidth,
  183. barMaxWidth: barMaxWidth,
  184. barMinWidth: barMinWidth,
  185. barGap: barGap,
  186. barCategoryGap: barCategoryGap,
  187. axisKey: getAxisKey(baseAxis),
  188. stackId: getSeriesStackId(seriesModel)
  189. });
  190. });
  191. return doCalBarWidthAndOffset(seriesInfoList);
  192. }
  193. function doCalBarWidthAndOffset(seriesInfoList) {
  194. // Columns info on each category axis. Key is cartesian name
  195. var columnsMap = {};
  196. zrUtil.each(seriesInfoList, function (seriesInfo, idx) {
  197. var axisKey = seriesInfo.axisKey;
  198. var bandWidth = seriesInfo.bandWidth;
  199. var columnsOnAxis = columnsMap[axisKey] || {
  200. bandWidth: bandWidth,
  201. remainedWidth: bandWidth,
  202. autoWidthCount: 0,
  203. categoryGap: null,
  204. gap: '20%',
  205. stacks: {}
  206. };
  207. var stacks = columnsOnAxis.stacks;
  208. columnsMap[axisKey] = columnsOnAxis;
  209. var stackId = seriesInfo.stackId;
  210. if (!stacks[stackId]) {
  211. columnsOnAxis.autoWidthCount++;
  212. }
  213. stacks[stackId] = stacks[stackId] || {
  214. width: 0,
  215. maxWidth: 0
  216. }; // Caution: In a single coordinate system, these barGrid attributes
  217. // will be shared by series. Consider that they have default values,
  218. // only the attributes set on the last series will work.
  219. // Do not change this fact unless there will be a break change.
  220. var barWidth = seriesInfo.barWidth;
  221. if (barWidth && !stacks[stackId].width) {
  222. // See #6312, do not restrict width.
  223. stacks[stackId].width = barWidth;
  224. barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);
  225. columnsOnAxis.remainedWidth -= barWidth;
  226. }
  227. var barMaxWidth = seriesInfo.barMaxWidth;
  228. barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth);
  229. var barMinWidth = seriesInfo.barMinWidth;
  230. barMinWidth && (stacks[stackId].minWidth = barMinWidth);
  231. var barGap = seriesInfo.barGap;
  232. barGap != null && (columnsOnAxis.gap = barGap);
  233. var barCategoryGap = seriesInfo.barCategoryGap;
  234. barCategoryGap != null && (columnsOnAxis.categoryGap = barCategoryGap);
  235. });
  236. var result = {};
  237. zrUtil.each(columnsMap, function (columnsOnAxis, coordSysName) {
  238. result[coordSysName] = {};
  239. var stacks = columnsOnAxis.stacks;
  240. var bandWidth = columnsOnAxis.bandWidth;
  241. var categoryGapPercent = columnsOnAxis.categoryGap;
  242. if (categoryGapPercent == null) {
  243. var columnCount = zrUtil.keys(stacks).length; // More columns in one group
  244. // the spaces between group is smaller. Or the column will be too thin.
  245. categoryGapPercent = Math.max(35 - columnCount * 4, 15) + '%';
  246. }
  247. var categoryGap = parsePercent(categoryGapPercent, bandWidth);
  248. var barGapPercent = parsePercent(columnsOnAxis.gap, 1);
  249. var remainedWidth = columnsOnAxis.remainedWidth;
  250. var autoWidthCount = columnsOnAxis.autoWidthCount;
  251. var autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
  252. autoWidth = Math.max(autoWidth, 0); // Find if any auto calculated bar exceeded maxBarWidth
  253. zrUtil.each(stacks, function (column) {
  254. var maxWidth = column.maxWidth;
  255. var minWidth = column.minWidth;
  256. if (!column.width) {
  257. var finalWidth = autoWidth;
  258. if (maxWidth && maxWidth < finalWidth) {
  259. finalWidth = Math.min(maxWidth, remainedWidth);
  260. } // `minWidth` has higher priority. `minWidth` decide that wheter the
  261. // bar is able to be visible. So `minWidth` should not be restricted
  262. // by `maxWidth` or `remainedWidth` (which is from `bandWidth`). In
  263. // the extreme cases for `value` axis, bars are allowed to overlap
  264. // with each other if `minWidth` specified.
  265. if (minWidth && minWidth > finalWidth) {
  266. finalWidth = minWidth;
  267. }
  268. if (finalWidth !== autoWidth) {
  269. column.width = finalWidth;
  270. remainedWidth -= finalWidth + barGapPercent * finalWidth;
  271. autoWidthCount--;
  272. }
  273. } else {
  274. // `barMinWidth/barMaxWidth` has higher priority than `barWidth`, as
  275. // CSS does. Becuase barWidth can be a percent value, where
  276. // `barMaxWidth` can be used to restrict the final width.
  277. var finalWidth = column.width;
  278. if (maxWidth) {
  279. finalWidth = Math.min(finalWidth, maxWidth);
  280. } // `minWidth` has higher priority, as described above
  281. if (minWidth) {
  282. finalWidth = Math.max(finalWidth, minWidth);
  283. }
  284. column.width = finalWidth;
  285. remainedWidth -= finalWidth + barGapPercent * finalWidth;
  286. autoWidthCount--;
  287. }
  288. }); // Recalculate width again
  289. autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
  290. autoWidth = Math.max(autoWidth, 0);
  291. var widthSum = 0;
  292. var lastColumn;
  293. zrUtil.each(stacks, function (column, idx) {
  294. if (!column.width) {
  295. column.width = autoWidth;
  296. }
  297. lastColumn = column;
  298. widthSum += column.width * (1 + barGapPercent);
  299. });
  300. if (lastColumn) {
  301. widthSum -= lastColumn.width * barGapPercent;
  302. }
  303. var offset = -widthSum / 2;
  304. zrUtil.each(stacks, function (column, stackId) {
  305. result[coordSysName][stackId] = result[coordSysName][stackId] || {
  306. bandWidth: bandWidth,
  307. offset: offset,
  308. width: column.width
  309. };
  310. offset += column.width * (1 + barGapPercent);
  311. });
  312. });
  313. return result;
  314. }
  315. function retrieveColumnLayout(barWidthAndOffset, axis, seriesModel) {
  316. if (barWidthAndOffset && axis) {
  317. var result = barWidthAndOffset[getAxisKey(axis)];
  318. if (result != null && seriesModel != null) {
  319. return result[getSeriesStackId(seriesModel)];
  320. }
  321. return result;
  322. }
  323. }
  324. export { retrieveColumnLayout };
  325. export function layout(seriesType, ecModel) {
  326. var seriesModels = prepareLayoutBarSeries(seriesType, ecModel);
  327. var barWidthAndOffset = makeColumnLayout(seriesModels);
  328. var lastStackCoords = {};
  329. zrUtil.each(seriesModels, function (seriesModel) {
  330. var data = seriesModel.getData();
  331. var cartesian = seriesModel.coordinateSystem;
  332. var baseAxis = cartesian.getBaseAxis();
  333. var stackId = getSeriesStackId(seriesModel);
  334. var columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId];
  335. var columnOffset = columnLayoutInfo.offset;
  336. var columnWidth = columnLayoutInfo.width;
  337. var valueAxis = cartesian.getOtherAxis(baseAxis);
  338. var barMinHeight = seriesModel.get('barMinHeight') || 0;
  339. lastStackCoords[stackId] = lastStackCoords[stackId] || [];
  340. data.setLayout({
  341. bandWidth: columnLayoutInfo.bandWidth,
  342. offset: columnOffset,
  343. size: columnWidth
  344. });
  345. var valueDim = data.mapDimension(valueAxis.dim);
  346. var baseDim = data.mapDimension(baseAxis.dim);
  347. var stacked = isDimensionStacked(data, valueDim);
  348. var isValueAxisH = valueAxis.isHorizontal();
  349. var valueAxisStart = getValueAxisStart(baseAxis, valueAxis, stacked);
  350. var store = data.getStore();
  351. var valueDimIdx = data.getDimensionIndex(valueDim);
  352. var baseDimIdx = data.getDimensionIndex(baseDim);
  353. for (var idx = 0, len = store.count(); idx < len; idx++) {
  354. var value = store.get(valueDimIdx, idx);
  355. var baseValue = store.get(baseDimIdx, idx);
  356. var sign = value >= 0 ? 'p' : 'n';
  357. var baseCoord = valueAxisStart; // Because of the barMinHeight, we can not use the value in
  358. // stackResultDimension directly.
  359. if (stacked) {
  360. // Only ordinal axis can be stacked.
  361. if (!lastStackCoords[stackId][baseValue]) {
  362. lastStackCoords[stackId][baseValue] = {
  363. p: valueAxisStart,
  364. n: valueAxisStart // Negative stack
  365. };
  366. } // Should also consider #4243
  367. baseCoord = lastStackCoords[stackId][baseValue][sign];
  368. }
  369. var x = void 0;
  370. var y = void 0;
  371. var width = void 0;
  372. var height = void 0;
  373. if (isValueAxisH) {
  374. var coord = cartesian.dataToPoint([value, baseValue]);
  375. x = baseCoord;
  376. y = coord[1] + columnOffset;
  377. width = coord[0] - valueAxisStart;
  378. height = columnWidth;
  379. if (Math.abs(width) < barMinHeight) {
  380. width = (width < 0 ? -1 : 1) * barMinHeight;
  381. } // Ignore stack from NaN value
  382. if (!isNaN(width)) {
  383. stacked && (lastStackCoords[stackId][baseValue][sign] += width);
  384. }
  385. } else {
  386. var coord = cartesian.dataToPoint([baseValue, value]);
  387. x = coord[0] + columnOffset;
  388. y = baseCoord;
  389. width = columnWidth;
  390. height = coord[1] - valueAxisStart;
  391. if (Math.abs(height) < barMinHeight) {
  392. // Include zero to has a positive bar
  393. height = (height <= 0 ? -1 : 1) * barMinHeight;
  394. } // Ignore stack from NaN value
  395. if (!isNaN(height)) {
  396. stacked && (lastStackCoords[stackId][baseValue][sign] += height);
  397. }
  398. }
  399. data.setItemLayout(idx, {
  400. x: x,
  401. y: y,
  402. width: width,
  403. height: height
  404. });
  405. }
  406. });
  407. } // TODO: Do not support stack in large mode yet.
  408. export var largeLayout = {
  409. seriesType: 'bar',
  410. plan: createRenderPlanner(),
  411. reset: function (seriesModel) {
  412. if (!isOnCartesian(seriesModel) || !isInLargeMode(seriesModel)) {
  413. return;
  414. }
  415. var data = seriesModel.getData();
  416. var cartesian = seriesModel.coordinateSystem;
  417. var coordLayout = cartesian.master.getRect();
  418. var baseAxis = cartesian.getBaseAxis();
  419. var valueAxis = cartesian.getOtherAxis(baseAxis);
  420. var valueDimI = data.getDimensionIndex(data.mapDimension(valueAxis.dim));
  421. var baseDimI = data.getDimensionIndex(data.mapDimension(baseAxis.dim));
  422. var valueAxisHorizontal = valueAxis.isHorizontal();
  423. var valueDimIdx = valueAxisHorizontal ? 0 : 1;
  424. var barWidth = retrieveColumnLayout(makeColumnLayout([seriesModel]), baseAxis, seriesModel).width;
  425. if (!(barWidth > LARGE_BAR_MIN_WIDTH)) {
  426. // jshint ignore:line
  427. barWidth = LARGE_BAR_MIN_WIDTH;
  428. }
  429. return {
  430. progress: function (params, data) {
  431. var count = params.count;
  432. var largePoints = new LargeArr(count * 2);
  433. var largeBackgroundPoints = new LargeArr(count * 2);
  434. var largeDataIndices = new LargeArr(count);
  435. var dataIndex;
  436. var coord = [];
  437. var valuePair = [];
  438. var pointsOffset = 0;
  439. var idxOffset = 0;
  440. var store = data.getStore();
  441. while ((dataIndex = params.next()) != null) {
  442. valuePair[valueDimIdx] = store.get(valueDimI, dataIndex);
  443. valuePair[1 - valueDimIdx] = store.get(baseDimI, dataIndex);
  444. coord = cartesian.dataToPoint(valuePair, null); // Data index might not be in order, depends on `progressiveChunkMode`.
  445. largeBackgroundPoints[pointsOffset] = valueAxisHorizontal ? coordLayout.x + coordLayout.width : coord[0];
  446. largePoints[pointsOffset++] = coord[0];
  447. largeBackgroundPoints[pointsOffset] = valueAxisHorizontal ? coord[1] : coordLayout.y + coordLayout.height;
  448. largePoints[pointsOffset++] = coord[1];
  449. largeDataIndices[idxOffset++] = dataIndex;
  450. }
  451. data.setLayout({
  452. largePoints: largePoints,
  453. largeDataIndices: largeDataIndices,
  454. largeBackgroundPoints: largeBackgroundPoints,
  455. barWidth: barWidth,
  456. valueAxisStart: getValueAxisStart(baseAxis, valueAxis, false),
  457. backgroundStart: valueAxisHorizontal ? coordLayout.x : coordLayout.y,
  458. valueAxisHorizontal: valueAxisHorizontal
  459. });
  460. }
  461. };
  462. }
  463. };
  464. function isOnCartesian(seriesModel) {
  465. return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d';
  466. }
  467. function isInLargeMode(seriesModel) {
  468. return seriesModel.pipelineContext && seriesModel.pipelineContext.large;
  469. } // See cases in `test/bar-start.html` and `#7412`, `#8747`.
  470. function getValueAxisStart(baseAxis, valueAxis, stacked) {
  471. return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? 1 : 0));
  472. }