Grid.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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. /**
  41. * Grid is a region which contains at most 4 cartesian systems
  42. *
  43. * TODO Default cartesian
  44. */
  45. import { isObject, each, indexOf, retrieve3, keys } from 'zrender/lib/core/util.js';
  46. import { getLayoutRect } from '../../util/layout.js';
  47. import { createScaleByModel, ifAxisCrossZero, niceScaleExtent, estimateLabelUnionRect, getDataDimensionsOnAxis } from '../../coord/axisHelper.js';
  48. import Cartesian2D, { cartesian2DDimensions } from './Cartesian2D.js';
  49. import Axis2D from './Axis2D.js';
  50. import { SINGLE_REFERRING } from '../../util/model.js';
  51. import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper.js';
  52. import { isIntervalOrLogScale } from '../../scale/helper.js';
  53. import { alignScaleTicks } from '../axisAlignTicks.js';
  54. var Grid =
  55. /** @class */
  56. function () {
  57. function Grid(gridModel, ecModel, api) {
  58. // FIXME:TS where used (different from registered type 'cartesian2d')?
  59. this.type = 'grid';
  60. this._coordsMap = {};
  61. this._coordsList = [];
  62. this._axesMap = {};
  63. this._axesList = [];
  64. this.axisPointerEnabled = true;
  65. this.dimensions = cartesian2DDimensions;
  66. this._initCartesian(gridModel, ecModel, api);
  67. this.model = gridModel;
  68. }
  69. Grid.prototype.getRect = function () {
  70. return this._rect;
  71. };
  72. Grid.prototype.update = function (ecModel, api) {
  73. var axesMap = this._axesMap;
  74. this._updateScale(ecModel, this.model);
  75. function updateAxisTicks(axes) {
  76. var alignTo; // Axis is added in order of axisIndex.
  77. var axesIndices = keys(axes);
  78. var len = axesIndices.length;
  79. if (!len) {
  80. return;
  81. }
  82. var axisNeedsAlign = []; // Process once and calculate the ticks for those don't use alignTicks.
  83. for (var i = len - 1; i >= 0; i--) {
  84. var idx = +axesIndices[i]; // Convert to number.
  85. var axis = axes[idx];
  86. var model = axis.model;
  87. var scale = axis.scale;
  88. if ( // Only value and log axis without interval support alignTicks.
  89. isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null) {
  90. axisNeedsAlign.push(axis);
  91. } else {
  92. niceScaleExtent(scale, model);
  93. if (isIntervalOrLogScale(scale)) {
  94. // Can only align to interval or log axis.
  95. alignTo = axis;
  96. }
  97. }
  98. }
  99. ; // All axes has set alignTicks. Pick the first one.
  100. // PENDING. Should we find the axis that both set interval, min, max and align to this one?
  101. if (axisNeedsAlign.length) {
  102. if (!alignTo) {
  103. alignTo = axisNeedsAlign.pop();
  104. niceScaleExtent(alignTo.scale, alignTo.model);
  105. }
  106. each(axisNeedsAlign, function (axis) {
  107. alignScaleTicks(axis.scale, axis.model, alignTo.scale);
  108. });
  109. }
  110. }
  111. updateAxisTicks(axesMap.x);
  112. updateAxisTicks(axesMap.y); // Key: axisDim_axisIndex, value: boolean, whether onZero target.
  113. var onZeroRecords = {};
  114. each(axesMap.x, function (xAxis) {
  115. fixAxisOnZero(axesMap, 'y', xAxis, onZeroRecords);
  116. });
  117. each(axesMap.y, function (yAxis) {
  118. fixAxisOnZero(axesMap, 'x', yAxis, onZeroRecords);
  119. }); // Resize again if containLabel is enabled
  120. // FIXME It may cause getting wrong grid size in data processing stage
  121. this.resize(this.model, api);
  122. };
  123. /**
  124. * Resize the grid
  125. */
  126. Grid.prototype.resize = function (gridModel, api, ignoreContainLabel) {
  127. var boxLayoutParams = gridModel.getBoxLayoutParams();
  128. var isContainLabel = !ignoreContainLabel && gridModel.get('containLabel');
  129. var gridRect = getLayoutRect(boxLayoutParams, {
  130. width: api.getWidth(),
  131. height: api.getHeight()
  132. });
  133. this._rect = gridRect;
  134. var axesList = this._axesList;
  135. adjustAxes(); // Minus label size
  136. if (isContainLabel) {
  137. each(axesList, function (axis) {
  138. if (!axis.model.get(['axisLabel', 'inside'])) {
  139. var labelUnionRect = estimateLabelUnionRect(axis);
  140. if (labelUnionRect) {
  141. var dim = axis.isHorizontal() ? 'height' : 'width';
  142. var margin = axis.model.get(['axisLabel', 'margin']);
  143. gridRect[dim] -= labelUnionRect[dim] + margin;
  144. if (axis.position === 'top') {
  145. gridRect.y += labelUnionRect.height + margin;
  146. } else if (axis.position === 'left') {
  147. gridRect.x += labelUnionRect.width + margin;
  148. }
  149. }
  150. }
  151. });
  152. adjustAxes();
  153. }
  154. each(this._coordsList, function (coord) {
  155. // Calculate affine matrix to accelerate the data to point transform.
  156. // If all the axes scales are time or value.
  157. coord.calcAffineTransform();
  158. });
  159. function adjustAxes() {
  160. each(axesList, function (axis) {
  161. var isHorizontal = axis.isHorizontal();
  162. var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
  163. var idx = axis.inverse ? 1 : 0;
  164. axis.setExtent(extent[idx], extent[1 - idx]);
  165. updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y);
  166. });
  167. }
  168. };
  169. Grid.prototype.getAxis = function (dim, axisIndex) {
  170. var axesMapOnDim = this._axesMap[dim];
  171. if (axesMapOnDim != null) {
  172. return axesMapOnDim[axisIndex || 0];
  173. }
  174. };
  175. Grid.prototype.getAxes = function () {
  176. return this._axesList.slice();
  177. };
  178. Grid.prototype.getCartesian = function (xAxisIndex, yAxisIndex) {
  179. if (xAxisIndex != null && yAxisIndex != null) {
  180. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  181. return this._coordsMap[key];
  182. }
  183. if (isObject(xAxisIndex)) {
  184. yAxisIndex = xAxisIndex.yAxisIndex;
  185. xAxisIndex = xAxisIndex.xAxisIndex;
  186. }
  187. for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) {
  188. if (coordList[i].getAxis('x').index === xAxisIndex || coordList[i].getAxis('y').index === yAxisIndex) {
  189. return coordList[i];
  190. }
  191. }
  192. };
  193. Grid.prototype.getCartesians = function () {
  194. return this._coordsList.slice();
  195. };
  196. /**
  197. * @implements
  198. */
  199. Grid.prototype.convertToPixel = function (ecModel, finder, value) {
  200. var target = this._findConvertTarget(finder);
  201. return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null;
  202. };
  203. /**
  204. * @implements
  205. */
  206. Grid.prototype.convertFromPixel = function (ecModel, finder, value) {
  207. var target = this._findConvertTarget(finder);
  208. return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null;
  209. };
  210. Grid.prototype._findConvertTarget = function (finder) {
  211. var seriesModel = finder.seriesModel;
  212. var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents('xAxis', SINGLE_REFERRING).models[0];
  213. var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents('yAxis', SINGLE_REFERRING).models[0];
  214. var gridModel = finder.gridModel;
  215. var coordsList = this._coordsList;
  216. var cartesian;
  217. var axis;
  218. if (seriesModel) {
  219. cartesian = seriesModel.coordinateSystem;
  220. indexOf(coordsList, cartesian) < 0 && (cartesian = null);
  221. } else if (xAxisModel && yAxisModel) {
  222. cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  223. } else if (xAxisModel) {
  224. axis = this.getAxis('x', xAxisModel.componentIndex);
  225. } else if (yAxisModel) {
  226. axis = this.getAxis('y', yAxisModel.componentIndex);
  227. } // Lowest priority.
  228. else if (gridModel) {
  229. var grid = gridModel.coordinateSystem;
  230. if (grid === this) {
  231. cartesian = this._coordsList[0];
  232. }
  233. }
  234. return {
  235. cartesian: cartesian,
  236. axis: axis
  237. };
  238. };
  239. /**
  240. * @implements
  241. */
  242. Grid.prototype.containPoint = function (point) {
  243. var coord = this._coordsList[0];
  244. if (coord) {
  245. return coord.containPoint(point);
  246. }
  247. };
  248. /**
  249. * Initialize cartesian coordinate systems
  250. */
  251. Grid.prototype._initCartesian = function (gridModel, ecModel, api) {
  252. var _this = this;
  253. var grid = this;
  254. var axisPositionUsed = {
  255. left: false,
  256. right: false,
  257. top: false,
  258. bottom: false
  259. };
  260. var axesMap = {
  261. x: {},
  262. y: {}
  263. };
  264. var axesCount = {
  265. x: 0,
  266. y: 0
  267. }; // Create axis
  268. ecModel.eachComponent('xAxis', createAxisCreator('x'), this);
  269. ecModel.eachComponent('yAxis', createAxisCreator('y'), this);
  270. if (!axesCount.x || !axesCount.y) {
  271. // Roll back when there no either x or y axis
  272. this._axesMap = {};
  273. this._axesList = [];
  274. return;
  275. }
  276. this._axesMap = axesMap; // Create cartesian2d
  277. each(axesMap.x, function (xAxis, xAxisIndex) {
  278. each(axesMap.y, function (yAxis, yAxisIndex) {
  279. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  280. var cartesian = new Cartesian2D(key);
  281. cartesian.master = _this;
  282. cartesian.model = gridModel;
  283. _this._coordsMap[key] = cartesian;
  284. _this._coordsList.push(cartesian);
  285. cartesian.addAxis(xAxis);
  286. cartesian.addAxis(yAxis);
  287. });
  288. });
  289. function createAxisCreator(dimName) {
  290. return function (axisModel, idx) {
  291. if (!isAxisUsedInTheGrid(axisModel, gridModel)) {
  292. return;
  293. }
  294. var axisPosition = axisModel.get('position');
  295. if (dimName === 'x') {
  296. // Fix position
  297. if (axisPosition !== 'top' && axisPosition !== 'bottom') {
  298. // Default bottom of X
  299. axisPosition = axisPositionUsed.bottom ? 'top' : 'bottom';
  300. }
  301. } else {
  302. // Fix position
  303. if (axisPosition !== 'left' && axisPosition !== 'right') {
  304. // Default left of Y
  305. axisPosition = axisPositionUsed.left ? 'right' : 'left';
  306. }
  307. }
  308. axisPositionUsed[axisPosition] = true;
  309. var axis = new Axis2D(dimName, createScaleByModel(axisModel), [0, 0], axisModel.get('type'), axisPosition);
  310. var isCategory = axis.type === 'category';
  311. axis.onBand = isCategory && axisModel.get('boundaryGap');
  312. axis.inverse = axisModel.get('inverse'); // Inject axis into axisModel
  313. axisModel.axis = axis; // Inject axisModel into axis
  314. axis.model = axisModel; // Inject grid info axis
  315. axis.grid = grid; // Index of axis, can be used as key
  316. axis.index = idx;
  317. grid._axesList.push(axis);
  318. axesMap[dimName][idx] = axis;
  319. axesCount[dimName]++;
  320. };
  321. }
  322. };
  323. /**
  324. * Update cartesian properties from series.
  325. */
  326. Grid.prototype._updateScale = function (ecModel, gridModel) {
  327. // Reset scale
  328. each(this._axesList, function (axis) {
  329. axis.scale.setExtent(Infinity, -Infinity);
  330. if (axis.type === 'category') {
  331. var categorySortInfo = axis.model.get('categorySortInfo');
  332. axis.scale.setSortInfo(categorySortInfo);
  333. }
  334. });
  335. ecModel.eachSeries(function (seriesModel) {
  336. if (isCartesian2DSeries(seriesModel)) {
  337. var axesModelMap = findAxisModels(seriesModel);
  338. var xAxisModel = axesModelMap.xAxisModel;
  339. var yAxisModel = axesModelMap.yAxisModel;
  340. if (!isAxisUsedInTheGrid(xAxisModel, gridModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel)) {
  341. return;
  342. }
  343. var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  344. var data = seriesModel.getData();
  345. var xAxis = cartesian.getAxis('x');
  346. var yAxis = cartesian.getAxis('y');
  347. unionExtent(data, xAxis);
  348. unionExtent(data, yAxis);
  349. }
  350. }, this);
  351. function unionExtent(data, axis) {
  352. each(getDataDimensionsOnAxis(data, axis.dim), function (dim) {
  353. axis.scale.unionExtentFromData(data, dim);
  354. });
  355. }
  356. };
  357. /**
  358. * @param dim 'x' or 'y' or 'auto' or null/undefined
  359. */
  360. Grid.prototype.getTooltipAxes = function (dim) {
  361. var baseAxes = [];
  362. var otherAxes = [];
  363. each(this.getCartesians(), function (cartesian) {
  364. var baseAxis = dim != null && dim !== 'auto' ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
  365. var otherAxis = cartesian.getOtherAxis(baseAxis);
  366. indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
  367. indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
  368. });
  369. return {
  370. baseAxes: baseAxes,
  371. otherAxes: otherAxes
  372. };
  373. };
  374. Grid.create = function (ecModel, api) {
  375. var grids = [];
  376. ecModel.eachComponent('grid', function (gridModel, idx) {
  377. var grid = new Grid(gridModel, ecModel, api);
  378. grid.name = 'grid_' + idx; // dataSampling requires axis extent, so resize
  379. // should be performed in create stage.
  380. grid.resize(gridModel, api, true);
  381. gridModel.coordinateSystem = grid;
  382. grids.push(grid);
  383. }); // Inject the coordinateSystems into seriesModel
  384. ecModel.eachSeries(function (seriesModel) {
  385. if (!isCartesian2DSeries(seriesModel)) {
  386. return;
  387. }
  388. var axesModelMap = findAxisModels(seriesModel);
  389. var xAxisModel = axesModelMap.xAxisModel;
  390. var yAxisModel = axesModelMap.yAxisModel;
  391. var gridModel = xAxisModel.getCoordSysModel();
  392. if (process.env.NODE_ENV !== 'production') {
  393. if (!gridModel) {
  394. throw new Error('Grid "' + retrieve3(xAxisModel.get('gridIndex'), xAxisModel.get('gridId'), 0) + '" not found');
  395. }
  396. if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) {
  397. throw new Error('xAxis and yAxis must use the same grid');
  398. }
  399. }
  400. var grid = gridModel.coordinateSystem;
  401. seriesModel.coordinateSystem = grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  402. });
  403. return grids;
  404. }; // For deciding which dimensions to use when creating list data
  405. Grid.dimensions = cartesian2DDimensions;
  406. return Grid;
  407. }();
  408. /**
  409. * Check if the axis is used in the specified grid.
  410. */
  411. function isAxisUsedInTheGrid(axisModel, gridModel) {
  412. return axisModel.getCoordSysModel() === gridModel;
  413. }
  414. function fixAxisOnZero(axesMap, otherAxisDim, axis, // Key: see `getOnZeroRecordKey`
  415. onZeroRecords) {
  416. axis.getAxesOnZeroOf = function () {
  417. // TODO: onZero of multiple axes.
  418. return otherAxisOnZeroOf ? [otherAxisOnZeroOf] : [];
  419. }; // onZero can not be enabled in these two situations:
  420. // 1. When any other axis is a category axis.
  421. // 2. When no axis is cross 0 point.
  422. var otherAxes = axesMap[otherAxisDim];
  423. var otherAxisOnZeroOf;
  424. var axisModel = axis.model;
  425. var onZero = axisModel.get(['axisLine', 'onZero']);
  426. var onZeroAxisIndex = axisModel.get(['axisLine', 'onZeroAxisIndex']);
  427. if (!onZero) {
  428. return;
  429. } // If target axis is specified.
  430. if (onZeroAxisIndex != null) {
  431. if (canOnZeroToAxis(otherAxes[onZeroAxisIndex])) {
  432. otherAxisOnZeroOf = otherAxes[onZeroAxisIndex];
  433. }
  434. } else {
  435. // Find the first available other axis.
  436. for (var idx in otherAxes) {
  437. if (otherAxes.hasOwnProperty(idx) && canOnZeroToAxis(otherAxes[idx]) // Consider that two Y axes on one value axis,
  438. // if both onZero, the two Y axes overlap.
  439. && !onZeroRecords[getOnZeroRecordKey(otherAxes[idx])]) {
  440. otherAxisOnZeroOf = otherAxes[idx];
  441. break;
  442. }
  443. }
  444. }
  445. if (otherAxisOnZeroOf) {
  446. onZeroRecords[getOnZeroRecordKey(otherAxisOnZeroOf)] = true;
  447. }
  448. function getOnZeroRecordKey(axis) {
  449. return axis.dim + '_' + axis.index;
  450. }
  451. }
  452. function canOnZeroToAxis(axis) {
  453. return axis && axis.type !== 'category' && axis.type !== 'time' && ifAxisCrossZero(axis);
  454. }
  455. function updateAxisTransform(axis, coordBase) {
  456. var axisExtent = axis.getExtent();
  457. var axisExtentSum = axisExtent[0] + axisExtent[1]; // Fast transform
  458. axis.toGlobalCoord = axis.dim === 'x' ? function (coord) {
  459. return coord + coordBase;
  460. } : function (coord) {
  461. return axisExtentSum - coord + coordBase;
  462. };
  463. axis.toLocalCoord = axis.dim === 'x' ? function (coord) {
  464. return coord - coordBase;
  465. } : function (coord) {
  466. return axisExtentSum - coord + coordBase;
  467. };
  468. }
  469. export default Grid;