Axis.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 _util = require("zrender/lib/core/util");
  20. var each = _util.each;
  21. var map = _util.map;
  22. var _number = require("../util/number");
  23. var linearMap = _number.linearMap;
  24. var getPixelPrecision = _number.getPixelPrecision;
  25. var round = _number.round;
  26. var _axisTickLabelBuilder = require("./axisTickLabelBuilder");
  27. var createAxisTicks = _axisTickLabelBuilder.createAxisTicks;
  28. var createAxisLabels = _axisTickLabelBuilder.createAxisLabels;
  29. var calculateCategoryInterval = _axisTickLabelBuilder.calculateCategoryInterval;
  30. /*
  31. * Licensed to the Apache Software Foundation (ASF) under one
  32. * or more contributor license agreements. See the NOTICE file
  33. * distributed with this work for additional information
  34. * regarding copyright ownership. The ASF licenses this file
  35. * to you under the Apache License, Version 2.0 (the
  36. * "License"); you may not use this file except in compliance
  37. * with the License. You may obtain a copy of the License at
  38. *
  39. * http://www.apache.org/licenses/LICENSE-2.0
  40. *
  41. * Unless required by applicable law or agreed to in writing,
  42. * software distributed under the License is distributed on an
  43. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  44. * KIND, either express or implied. See the License for the
  45. * specific language governing permissions and limitations
  46. * under the License.
  47. */
  48. var NORMALIZED_EXTENT = [0, 1];
  49. /**
  50. * Base class of Axis.
  51. * @constructor
  52. */
  53. var Axis = function (dim, scale, extent) {
  54. /**
  55. * Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'.
  56. * @type {string}
  57. */
  58. this.dim = dim;
  59. /**
  60. * Axis scale
  61. * @type {module:echarts/coord/scale/*}
  62. */
  63. this.scale = scale;
  64. /**
  65. * @type {Array.<number>}
  66. * @private
  67. */
  68. this._extent = extent || [0, 0];
  69. /**
  70. * @type {boolean}
  71. */
  72. this.inverse = false;
  73. /**
  74. * Usually true when axis has a ordinal scale
  75. * @type {boolean}
  76. */
  77. this.onBand = false;
  78. };
  79. Axis.prototype = {
  80. constructor: Axis,
  81. /**
  82. * If axis extent contain given coord
  83. * @param {number} coord
  84. * @return {boolean}
  85. */
  86. contain: function (coord) {
  87. var extent = this._extent;
  88. var min = Math.min(extent[0], extent[1]);
  89. var max = Math.max(extent[0], extent[1]);
  90. return coord >= min && coord <= max;
  91. },
  92. /**
  93. * If axis extent contain given data
  94. * @param {number} data
  95. * @return {boolean}
  96. */
  97. containData: function (data) {
  98. return this.scale.contain(data);
  99. },
  100. /**
  101. * Get coord extent.
  102. * @return {Array.<number>}
  103. */
  104. getExtent: function () {
  105. return this._extent.slice();
  106. },
  107. /**
  108. * Get precision used for formatting
  109. * @param {Array.<number>} [dataExtent]
  110. * @return {number}
  111. */
  112. getPixelPrecision: function (dataExtent) {
  113. return getPixelPrecision(dataExtent || this.scale.getExtent(), this._extent);
  114. },
  115. /**
  116. * Set coord extent
  117. * @param {number} start
  118. * @param {number} end
  119. */
  120. setExtent: function (start, end) {
  121. var extent = this._extent;
  122. extent[0] = start;
  123. extent[1] = end;
  124. },
  125. /**
  126. * Convert data to coord. Data is the rank if it has an ordinal scale
  127. * @param {number} data
  128. * @param {boolean} clamp
  129. * @return {number}
  130. */
  131. dataToCoord: function (data, clamp) {
  132. var extent = this._extent;
  133. var scale = this.scale;
  134. data = scale.normalize(data);
  135. if (this.onBand && scale.type === 'ordinal') {
  136. extent = extent.slice();
  137. fixExtentWithBands(extent, scale.count());
  138. }
  139. return linearMap(data, NORMALIZED_EXTENT, extent, clamp);
  140. },
  141. /**
  142. * Convert coord to data. Data is the rank if it has an ordinal scale
  143. * @param {number} coord
  144. * @param {boolean} clamp
  145. * @return {number}
  146. */
  147. coordToData: function (coord, clamp) {
  148. var extent = this._extent;
  149. var scale = this.scale;
  150. if (this.onBand && scale.type === 'ordinal') {
  151. extent = extent.slice();
  152. fixExtentWithBands(extent, scale.count());
  153. }
  154. var t = linearMap(coord, extent, NORMALIZED_EXTENT, clamp);
  155. return this.scale.scale(t);
  156. },
  157. /**
  158. * Convert pixel point to data in axis
  159. * @param {Array.<number>} point
  160. * @param {boolean} clamp
  161. * @return {number} data
  162. */
  163. pointToData: function (point, clamp) {// Should be implemented in derived class if necessary.
  164. },
  165. /**
  166. * Different from `zrUtil.map(axis.getTicks(), axis.dataToCoord, axis)`,
  167. * `axis.getTicksCoords` considers `onBand`, which is used by
  168. * `boundaryGap:true` of category axis and splitLine and splitArea.
  169. * @param {Object} [opt]
  170. * @param {Model} [opt.tickModel=axis.model.getModel('axisTick')]
  171. * @param {boolean} [opt.clamp] If `true`, the first and the last
  172. * tick must be at the axis end points. Otherwise, clip ticks
  173. * that outside the axis extent.
  174. * @return {Array.<Object>} [{
  175. * coord: ...,
  176. * tickValue: ...
  177. * }, ...]
  178. */
  179. getTicksCoords: function (opt) {
  180. opt = opt || {};
  181. var tickModel = opt.tickModel || this.getTickModel();
  182. var result = createAxisTicks(this, tickModel);
  183. var ticks = result.ticks;
  184. var ticksCoords = map(ticks, function (tickValue) {
  185. return {
  186. coord: this.dataToCoord(tickValue),
  187. tickValue: tickValue
  188. };
  189. }, this);
  190. var alignWithLabel = tickModel.get('alignWithLabel');
  191. fixOnBandTicksCoords(this, ticksCoords, alignWithLabel, opt.clamp);
  192. return ticksCoords;
  193. },
  194. /**
  195. * @return {Array.<Array.<Object>>} [{ coord: ..., tickValue: ...}]
  196. */
  197. getMinorTicksCoords: function () {
  198. if (this.scale.type === 'ordinal') {
  199. // Category axis doesn't support minor ticks
  200. return [];
  201. }
  202. var minorTickModel = this.model.getModel('minorTick');
  203. var splitNumber = minorTickModel.get('splitNumber'); // Protection.
  204. if (!(splitNumber > 0 && splitNumber < 100)) {
  205. splitNumber = 5;
  206. }
  207. var minorTicks = this.scale.getMinorTicks(splitNumber);
  208. var minorTicksCoords = map(minorTicks, function (minorTicksGroup) {
  209. return map(minorTicksGroup, function (minorTick) {
  210. return {
  211. coord: this.dataToCoord(minorTick),
  212. tickValue: minorTick
  213. };
  214. }, this);
  215. }, this);
  216. return minorTicksCoords;
  217. },
  218. /**
  219. * @return {Array.<Object>} [{
  220. * formattedLabel: string,
  221. * rawLabel: axis.scale.getLabel(tickValue)
  222. * tickValue: number
  223. * }, ...]
  224. */
  225. getViewLabels: function () {
  226. return createAxisLabels(this).labels;
  227. },
  228. /**
  229. * @return {module:echarts/coord/model/Model}
  230. */
  231. getLabelModel: function () {
  232. return this.model.getModel('axisLabel');
  233. },
  234. /**
  235. * Notice here we only get the default tick model. For splitLine
  236. * or splitArea, we should pass the splitLineModel or splitAreaModel
  237. * manually when calling `getTicksCoords`.
  238. * In GL, this method may be overrided to:
  239. * `axisModel.getModel('axisTick', grid3DModel.getModel('axisTick'));`
  240. * @return {module:echarts/coord/model/Model}
  241. */
  242. getTickModel: function () {
  243. return this.model.getModel('axisTick');
  244. },
  245. /**
  246. * Get width of band
  247. * @return {number}
  248. */
  249. getBandWidth: function () {
  250. var axisExtent = this._extent;
  251. var dataExtent = this.scale.getExtent();
  252. var len = dataExtent[1] - dataExtent[0] + (this.onBand ? 1 : 0); // Fix #2728, avoid NaN when only one data.
  253. len === 0 && (len = 1);
  254. var size = Math.abs(axisExtent[1] - axisExtent[0]);
  255. return Math.abs(size) / len;
  256. },
  257. /**
  258. * @abstract
  259. * @return {boolean} Is horizontal
  260. */
  261. isHorizontal: null,
  262. /**
  263. * @abstract
  264. * @return {number} Get axis rotate, by degree.
  265. */
  266. getRotate: null,
  267. /**
  268. * Only be called in category axis.
  269. * Can be overrided, consider other axes like in 3D.
  270. * @return {number} Auto interval for cateogry axis tick and label
  271. */
  272. calculateCategoryInterval: function () {
  273. return calculateCategoryInterval(this);
  274. }
  275. };
  276. function fixExtentWithBands(extent, nTick) {
  277. var size = extent[1] - extent[0];
  278. var len = nTick;
  279. var margin = size / len / 2;
  280. extent[0] += margin;
  281. extent[1] -= margin;
  282. } // If axis has labels [1, 2, 3, 4]. Bands on the axis are
  283. // |---1---|---2---|---3---|---4---|.
  284. // So the displayed ticks and splitLine/splitArea should between
  285. // each data item, otherwise cause misleading (e.g., split tow bars
  286. // of a single data item when there are two bar series).
  287. // Also consider if tickCategoryInterval > 0 and onBand, ticks and
  288. // splitLine/spliteArea should layout appropriately corresponding
  289. // to displayed labels. (So we should not use `getBandWidth` in this
  290. // case).
  291. function fixOnBandTicksCoords(axis, ticksCoords, alignWithLabel, clamp) {
  292. var ticksLen = ticksCoords.length;
  293. if (!axis.onBand || alignWithLabel || !ticksLen) {
  294. return;
  295. }
  296. var axisExtent = axis.getExtent();
  297. var last;
  298. var diffSize;
  299. if (ticksLen === 1) {
  300. ticksCoords[0].coord = axisExtent[0];
  301. last = ticksCoords[1] = {
  302. coord: axisExtent[0]
  303. };
  304. } else {
  305. var crossLen = ticksCoords[ticksLen - 1].tickValue - ticksCoords[0].tickValue;
  306. var shift = (ticksCoords[ticksLen - 1].coord - ticksCoords[0].coord) / crossLen;
  307. each(ticksCoords, function (ticksItem) {
  308. ticksItem.coord -= shift / 2;
  309. });
  310. var dataExtent = axis.scale.getExtent();
  311. diffSize = 1 + dataExtent[1] - ticksCoords[ticksLen - 1].tickValue;
  312. last = {
  313. coord: ticksCoords[ticksLen - 1].coord + shift * diffSize
  314. };
  315. ticksCoords.push(last);
  316. }
  317. var inverse = axisExtent[0] > axisExtent[1]; // Handling clamp.
  318. if (littleThan(ticksCoords[0].coord, axisExtent[0])) {
  319. clamp ? ticksCoords[0].coord = axisExtent[0] : ticksCoords.shift();
  320. }
  321. if (clamp && littleThan(axisExtent[0], ticksCoords[0].coord)) {
  322. ticksCoords.unshift({
  323. coord: axisExtent[0]
  324. });
  325. }
  326. if (littleThan(axisExtent[1], last.coord)) {
  327. clamp ? last.coord = axisExtent[1] : ticksCoords.pop();
  328. }
  329. if (clamp && littleThan(last.coord, axisExtent[1])) {
  330. ticksCoords.push({
  331. coord: axisExtent[1]
  332. });
  333. }
  334. function littleThan(a, b) {
  335. // Avoid rounding error cause calculated tick coord different with extent.
  336. // It may cause an extra unecessary tick added.
  337. a = round(a);
  338. b = round(b);
  339. return inverse ? a > b : a < b;
  340. }
  341. }
  342. var _default = Axis;
  343. module.exports = _default;