Grid.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. var _config = require("../../config");
  2. var __DEV__ = _config.__DEV__;
  3. var zrUtil = require("zrender/lib/core/util");
  4. var BoundingRect = require("zrender/lib/core/BoundingRect");
  5. var _layout = require("../../util/layout");
  6. var getLayoutRect = _layout.getLayoutRect;
  7. var axisHelper = require("../../coord/axisHelper");
  8. var Cartesian2D = require("./Cartesian2D");
  9. var Axis2D = require("./Axis2D");
  10. var CoordinateSystem = require("../../CoordinateSystem");
  11. require("./GridModel");
  12. /**
  13. * Grid is a region which contains at most 4 cartesian systems
  14. *
  15. * TODO Default cartesian
  16. */
  17. // Depends on GridModel, AxisModel, which performs preprocess.
  18. var each = zrUtil.each;
  19. var ifAxisCrossZero = axisHelper.ifAxisCrossZero;
  20. var niceScaleExtent = axisHelper.niceScaleExtent;
  21. /**
  22. * Check if the axis is used in the specified grid
  23. * @inner
  24. */
  25. function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
  26. return axisModel.getCoordSysModel() === gridModel;
  27. }
  28. function rotateTextRect(textRect, rotate) {
  29. var rotateRadians = rotate * Math.PI / 180;
  30. var boundingBox = textRect.plain();
  31. var beforeWidth = boundingBox.width;
  32. var beforeHeight = boundingBox.height;
  33. var afterWidth = beforeWidth * Math.cos(rotateRadians) + beforeHeight * Math.sin(rotateRadians);
  34. var afterHeight = beforeWidth * Math.sin(rotateRadians) + beforeHeight * Math.cos(rotateRadians);
  35. var rotatedRect = new BoundingRect(boundingBox.x, boundingBox.y, afterWidth, afterHeight);
  36. return rotatedRect;
  37. }
  38. function getLabelUnionRect(axis) {
  39. var axisModel = axis.model;
  40. var labels = axisModel.getFormattedLabels();
  41. var axisLabelModel = axisModel.getModel('axisLabel');
  42. var rect;
  43. var step = 1;
  44. var labelCount = labels.length;
  45. if (labelCount > 40) {
  46. // Simple optimization for large amount of labels
  47. step = Math.ceil(labelCount / 40);
  48. }
  49. for (var i = 0; i < labelCount; i += step) {
  50. if (!axis.isLabelIgnored(i)) {
  51. var unrotatedSingleRect = axisLabelModel.getTextRect(labels[i]);
  52. var singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);
  53. rect ? rect.union(singleRect) : rect = singleRect;
  54. }
  55. }
  56. return rect;
  57. }
  58. function Grid(gridModel, ecModel, api) {
  59. /**
  60. * @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
  61. * @private
  62. */
  63. this._coordsMap = {};
  64. /**
  65. * @type {Array.<module:echarts/coord/cartesian/Cartesian>}
  66. * @private
  67. */
  68. this._coordsList = [];
  69. /**
  70. * @type {Object.<string, module:echarts/coord/cartesian/Axis2D>}
  71. * @private
  72. */
  73. this._axesMap = {};
  74. /**
  75. * @type {Array.<module:echarts/coord/cartesian/Axis2D>}
  76. * @private
  77. */
  78. this._axesList = [];
  79. this._initCartesian(gridModel, ecModel, api);
  80. this.model = gridModel;
  81. }
  82. var gridProto = Grid.prototype;
  83. gridProto.type = 'grid';
  84. gridProto.axisPointerEnabled = true;
  85. gridProto.getRect = function () {
  86. return this._rect;
  87. };
  88. gridProto.update = function (ecModel, api) {
  89. var axesMap = this._axesMap;
  90. this._updateScale(ecModel, this.model);
  91. each(axesMap.x, function (xAxis) {
  92. niceScaleExtent(xAxis.scale, xAxis.model);
  93. });
  94. each(axesMap.y, function (yAxis) {
  95. niceScaleExtent(yAxis.scale, yAxis.model);
  96. });
  97. each(axesMap.x, function (xAxis) {
  98. fixAxisOnZero(axesMap, 'y', xAxis);
  99. });
  100. each(axesMap.y, function (yAxis) {
  101. fixAxisOnZero(axesMap, 'x', yAxis);
  102. }); // Resize again if containLabel is enabled
  103. // FIXME It may cause getting wrong grid size in data processing stage
  104. this.resize(this.model, api);
  105. };
  106. function fixAxisOnZero(axesMap, otherAxisDim, axis) {
  107. // onZero can not be enabled in these two situations:
  108. // 1. When any other axis is a category axis.
  109. // 2. When no axis is cross 0 point.
  110. var axes = axesMap[otherAxisDim];
  111. if (!axis.onZero) {
  112. return;
  113. }
  114. var onZeroAxisIndex = axis.onZeroAxisIndex; // If target axis is specified.
  115. if (onZeroAxisIndex != null) {
  116. var otherAxis = axes[onZeroAxisIndex];
  117. if (otherAxis && canNotOnZeroToAxis(otherAxis)) {
  118. axis.onZero = false;
  119. }
  120. return;
  121. }
  122. for (var idx in axes) {
  123. if (axes.hasOwnProperty(idx)) {
  124. var otherAxis = axes[idx];
  125. if (otherAxis && !canNotOnZeroToAxis(otherAxis)) {
  126. onZeroAxisIndex = +idx;
  127. break;
  128. }
  129. }
  130. }
  131. if (onZeroAxisIndex == null) {
  132. axis.onZero = false;
  133. }
  134. axis.onZeroAxisIndex = onZeroAxisIndex;
  135. }
  136. function canNotOnZeroToAxis(axis) {
  137. return axis.type === 'category' || axis.type === 'time' || !ifAxisCrossZero(axis);
  138. }
  139. /**
  140. * Resize the grid
  141. * @param {module:echarts/coord/cartesian/GridModel} gridModel
  142. * @param {module:echarts/ExtensionAPI} api
  143. */
  144. gridProto.resize = function (gridModel, api, ignoreContainLabel) {
  145. var gridRect = getLayoutRect(gridModel.getBoxLayoutParams(), {
  146. width: api.getWidth(),
  147. height: api.getHeight()
  148. });
  149. this._rect = gridRect;
  150. var axesList = this._axesList;
  151. adjustAxes(); // Minus label size
  152. if (!ignoreContainLabel && gridModel.get('containLabel')) {
  153. each(axesList, function (axis) {
  154. if (!axis.model.get('axisLabel.inside')) {
  155. var labelUnionRect = getLabelUnionRect(axis);
  156. if (labelUnionRect) {
  157. var dim = axis.isHorizontal() ? 'height' : 'width';
  158. var margin = axis.model.get('axisLabel.margin');
  159. gridRect[dim] -= labelUnionRect[dim] + margin;
  160. if (axis.position === 'top') {
  161. gridRect.y += labelUnionRect.height + margin;
  162. } else if (axis.position === 'left') {
  163. gridRect.x += labelUnionRect.width + margin;
  164. }
  165. }
  166. }
  167. });
  168. adjustAxes();
  169. }
  170. function adjustAxes() {
  171. each(axesList, function (axis) {
  172. var isHorizontal = axis.isHorizontal();
  173. var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
  174. var idx = axis.inverse ? 1 : 0;
  175. axis.setExtent(extent[idx], extent[1 - idx]);
  176. updateAxisTransfrom(axis, isHorizontal ? gridRect.x : gridRect.y);
  177. });
  178. }
  179. };
  180. /**
  181. * @param {string} axisType
  182. * @param {number} [axisIndex]
  183. */
  184. gridProto.getAxis = function (axisType, axisIndex) {
  185. var axesMapOnDim = this._axesMap[axisType];
  186. if (axesMapOnDim != null) {
  187. if (axisIndex == null) {
  188. // Find first axis
  189. for (var name in axesMapOnDim) {
  190. if (axesMapOnDim.hasOwnProperty(name)) {
  191. return axesMapOnDim[name];
  192. }
  193. }
  194. }
  195. return axesMapOnDim[axisIndex];
  196. }
  197. };
  198. /**
  199. * @return {Array.<module:echarts/coord/Axis>}
  200. */
  201. gridProto.getAxes = function () {
  202. return this._axesList.slice();
  203. };
  204. /**
  205. * Usage:
  206. * grid.getCartesian(xAxisIndex, yAxisIndex);
  207. * grid.getCartesian(xAxisIndex);
  208. * grid.getCartesian(null, yAxisIndex);
  209. * grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...});
  210. *
  211. * @param {number|Object} [xAxisIndex]
  212. * @param {number} [yAxisIndex]
  213. */
  214. gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
  215. if (xAxisIndex != null && yAxisIndex != null) {
  216. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  217. return this._coordsMap[key];
  218. }
  219. if (zrUtil.isObject(xAxisIndex)) {
  220. yAxisIndex = xAxisIndex.yAxisIndex;
  221. xAxisIndex = xAxisIndex.xAxisIndex;
  222. } // When only xAxisIndex or yAxisIndex given, find its first cartesian.
  223. for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) {
  224. if (coordList[i].getAxis('x').index === xAxisIndex || coordList[i].getAxis('y').index === yAxisIndex) {
  225. return coordList[i];
  226. }
  227. }
  228. };
  229. gridProto.getCartesians = function () {
  230. return this._coordsList.slice();
  231. };
  232. /**
  233. * @implements
  234. * see {module:echarts/CoodinateSystem}
  235. */
  236. gridProto.convertToPixel = function (ecModel, finder, value) {
  237. var target = this._findConvertTarget(ecModel, finder);
  238. return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null;
  239. };
  240. /**
  241. * @implements
  242. * see {module:echarts/CoodinateSystem}
  243. */
  244. gridProto.convertFromPixel = function (ecModel, finder, value) {
  245. var target = this._findConvertTarget(ecModel, finder);
  246. return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null;
  247. };
  248. /**
  249. * @inner
  250. */
  251. gridProto._findConvertTarget = function (ecModel, finder) {
  252. var seriesModel = finder.seriesModel;
  253. var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents('xAxis')[0];
  254. var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents('yAxis')[0];
  255. var gridModel = finder.gridModel;
  256. var coordsList = this._coordsList;
  257. var cartesian;
  258. var axis;
  259. if (seriesModel) {
  260. cartesian = seriesModel.coordinateSystem;
  261. zrUtil.indexOf(coordsList, cartesian) < 0 && (cartesian = null);
  262. } else if (xAxisModel && yAxisModel) {
  263. cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  264. } else if (xAxisModel) {
  265. axis = this.getAxis('x', xAxisModel.componentIndex);
  266. } else if (yAxisModel) {
  267. axis = this.getAxis('y', yAxisModel.componentIndex);
  268. } // Lowest priority.
  269. else if (gridModel) {
  270. var grid = gridModel.coordinateSystem;
  271. if (grid === this) {
  272. cartesian = this._coordsList[0];
  273. }
  274. }
  275. return {
  276. cartesian: cartesian,
  277. axis: axis
  278. };
  279. };
  280. /**
  281. * @implements
  282. * see {module:echarts/CoodinateSystem}
  283. */
  284. gridProto.containPoint = function (point) {
  285. var coord = this._coordsList[0];
  286. if (coord) {
  287. return coord.containPoint(point);
  288. }
  289. };
  290. /**
  291. * Initialize cartesian coordinate systems
  292. * @private
  293. */
  294. gridProto._initCartesian = function (gridModel, ecModel, api) {
  295. var axisPositionUsed = {
  296. left: false,
  297. right: false,
  298. top: false,
  299. bottom: false
  300. };
  301. var axesMap = {
  302. x: {},
  303. y: {}
  304. };
  305. var axesCount = {
  306. x: 0,
  307. y: 0
  308. }; /// Create axis
  309. ecModel.eachComponent('xAxis', createAxisCreator('x'), this);
  310. ecModel.eachComponent('yAxis', createAxisCreator('y'), this);
  311. if (!axesCount.x || !axesCount.y) {
  312. // Roll back when there no either x or y axis
  313. this._axesMap = {};
  314. this._axesList = [];
  315. return;
  316. }
  317. this._axesMap = axesMap; /// Create cartesian2d
  318. each(axesMap.x, function (xAxis, xAxisIndex) {
  319. each(axesMap.y, function (yAxis, yAxisIndex) {
  320. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  321. var cartesian = new Cartesian2D(key);
  322. cartesian.grid = this;
  323. cartesian.model = gridModel;
  324. this._coordsMap[key] = cartesian;
  325. this._coordsList.push(cartesian);
  326. cartesian.addAxis(xAxis);
  327. cartesian.addAxis(yAxis);
  328. }, this);
  329. }, this);
  330. function createAxisCreator(axisType) {
  331. return function (axisModel, idx) {
  332. if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
  333. return;
  334. }
  335. var axisPosition = axisModel.get('position');
  336. if (axisType === 'x') {
  337. // Fix position
  338. if (axisPosition !== 'top' && axisPosition !== 'bottom') {
  339. // Default bottom of X
  340. axisPosition = 'bottom';
  341. if (axisPositionUsed[axisPosition]) {
  342. axisPosition = axisPosition === 'top' ? 'bottom' : 'top';
  343. }
  344. }
  345. } else {
  346. // Fix position
  347. if (axisPosition !== 'left' && axisPosition !== 'right') {
  348. // Default left of Y
  349. axisPosition = 'left';
  350. if (axisPositionUsed[axisPosition]) {
  351. axisPosition = axisPosition === 'left' ? 'right' : 'left';
  352. }
  353. }
  354. }
  355. axisPositionUsed[axisPosition] = true;
  356. var axis = new Axis2D(axisType, axisHelper.createScaleByModel(axisModel), [0, 0], axisModel.get('type'), axisPosition);
  357. var isCategory = axis.type === 'category';
  358. axis.onBand = isCategory && axisModel.get('boundaryGap');
  359. axis.inverse = axisModel.get('inverse');
  360. axis.onZero = axisModel.get('axisLine.onZero');
  361. axis.onZeroAxisIndex = axisModel.get('axisLine.onZeroAxisIndex'); // Inject axis into axisModel
  362. axisModel.axis = axis; // Inject axisModel into axis
  363. axis.model = axisModel; // Inject grid info axis
  364. axis.grid = this; // Index of axis, can be used as key
  365. axis.index = idx;
  366. this._axesList.push(axis);
  367. axesMap[axisType][idx] = axis;
  368. axesCount[axisType]++;
  369. };
  370. }
  371. };
  372. /**
  373. * Update cartesian properties from series
  374. * @param {module:echarts/model/Option} option
  375. * @private
  376. */
  377. gridProto._updateScale = function (ecModel, gridModel) {
  378. // Reset scale
  379. zrUtil.each(this._axesList, function (axis) {
  380. axis.scale.setExtent(Infinity, -Infinity);
  381. });
  382. ecModel.eachSeries(function (seriesModel) {
  383. if (isCartesian2D(seriesModel)) {
  384. var axesModels = findAxesModels(seriesModel, ecModel);
  385. var xAxisModel = axesModels[0];
  386. var yAxisModel = axesModels[1];
  387. if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel)) {
  388. return;
  389. }
  390. var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  391. var data = seriesModel.getData();
  392. var xAxis = cartesian.getAxis('x');
  393. var yAxis = cartesian.getAxis('y');
  394. if (data.type === 'list') {
  395. unionExtent(data, xAxis, seriesModel);
  396. unionExtent(data, yAxis, seriesModel);
  397. }
  398. }
  399. }, this);
  400. function unionExtent(data, axis, seriesModel) {
  401. each(seriesModel.coordDimToDataDim(axis.dim), function (dim) {
  402. axis.scale.unionExtentFromData(data, dim);
  403. });
  404. }
  405. };
  406. /**
  407. * @param {string} [dim] 'x' or 'y' or 'auto' or null/undefined
  408. * @return {Object} {baseAxes: [], otherAxes: []}
  409. */
  410. gridProto.getTooltipAxes = function (dim) {
  411. var baseAxes = [];
  412. var otherAxes = [];
  413. each(this.getCartesians(), function (cartesian) {
  414. var baseAxis = dim != null && dim !== 'auto' ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
  415. var otherAxis = cartesian.getOtherAxis(baseAxis);
  416. zrUtil.indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
  417. zrUtil.indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
  418. });
  419. return {
  420. baseAxes: baseAxes,
  421. otherAxes: otherAxes
  422. };
  423. };
  424. /**
  425. * @inner
  426. */
  427. function updateAxisTransfrom(axis, coordBase) {
  428. var axisExtent = axis.getExtent();
  429. var axisExtentSum = axisExtent[0] + axisExtent[1]; // Fast transform
  430. axis.toGlobalCoord = axis.dim === 'x' ? function (coord) {
  431. return coord + coordBase;
  432. } : function (coord) {
  433. return axisExtentSum - coord + coordBase;
  434. };
  435. axis.toLocalCoord = axis.dim === 'x' ? function (coord) {
  436. return coord - coordBase;
  437. } : function (coord) {
  438. return axisExtentSum - coord + coordBase;
  439. };
  440. }
  441. var axesTypes = ['xAxis', 'yAxis'];
  442. /**
  443. * @inner
  444. */
  445. function findAxesModels(seriesModel, ecModel) {
  446. return zrUtil.map(axesTypes, function (axisType) {
  447. var axisModel = seriesModel.getReferringComponents(axisType)[0];
  448. return axisModel;
  449. });
  450. }
  451. /**
  452. * @inner
  453. */
  454. function isCartesian2D(seriesModel) {
  455. return seriesModel.get('coordinateSystem') === 'cartesian2d';
  456. }
  457. Grid.create = function (ecModel, api) {
  458. var grids = [];
  459. ecModel.eachComponent('grid', function (gridModel, idx) {
  460. var grid = new Grid(gridModel, ecModel, api);
  461. grid.name = 'grid_' + idx; // dataSampling requires axis extent, so resize
  462. // should be performed in create stage.
  463. grid.resize(gridModel, api, true);
  464. gridModel.coordinateSystem = grid;
  465. grids.push(grid);
  466. }); // Inject the coordinateSystems into seriesModel
  467. ecModel.eachSeries(function (seriesModel) {
  468. if (!isCartesian2D(seriesModel)) {
  469. return;
  470. }
  471. var axesModels = findAxesModels(seriesModel, ecModel);
  472. var xAxisModel = axesModels[0];
  473. var yAxisModel = axesModels[1];
  474. var gridModel = xAxisModel.getCoordSysModel();
  475. var grid = gridModel.coordinateSystem;
  476. seriesModel.coordinateSystem = grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  477. });
  478. return grids;
  479. }; // For deciding which dimensions to use when creating list data
  480. Grid.dimensions = Grid.prototype.dimensions = Cartesian2D.prototype.dimensions;
  481. CoordinateSystem.register('cartesian2d', Grid);
  482. var _default = Grid;
  483. module.exports = _default;