DataZoomModel.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. var _config = require("../../config");
  2. var __DEV__ = _config.__DEV__;
  3. var echarts = require("../../echarts");
  4. var zrUtil = require("zrender/lib/core/util");
  5. var env = require("zrender/lib/core/env");
  6. var modelUtil = require("../../util/model");
  7. var helper = require("./helper");
  8. var AxisProxy = require("./AxisProxy");
  9. var each = zrUtil.each;
  10. var eachAxisDim = helper.eachAxisDim;
  11. var DataZoomModel = echarts.extendComponentModel({
  12. type: 'dataZoom',
  13. dependencies: ['xAxis', 'yAxis', 'zAxis', 'radiusAxis', 'angleAxis', 'singleAxis', 'series'],
  14. /**
  15. * @protected
  16. */
  17. defaultOption: {
  18. zlevel: 0,
  19. z: 4,
  20. // Higher than normal component (z: 2).
  21. orient: null,
  22. // Default auto by axisIndex. Possible value: 'horizontal', 'vertical'.
  23. xAxisIndex: null,
  24. // Default the first horizontal category axis.
  25. yAxisIndex: null,
  26. // Default the first vertical category axis.
  27. filterMode: 'filter',
  28. // Possible values: 'filter' or 'empty' or 'weakFilter'.
  29. // 'filter': data items which are out of window will be removed. This option is
  30. // applicable when filtering outliers. For each data item, it will be
  31. // filtered if one of the relevant dimensions is out of the window.
  32. // 'weakFilter': data items which are out of window will be removed. This option
  33. // is applicable when filtering outliers. For each data item, it will be
  34. // filtered only if all of the relevant dimensions are out of the same
  35. // side of the window.
  36. // 'empty': data items which are out of window will be set to empty.
  37. // This option is applicable when user should not neglect
  38. // that there are some data items out of window.
  39. // 'none': Do not filter.
  40. // Taking line chart as an example, line will be broken in
  41. // the filtered points when filterModel is set to 'empty', but
  42. // be connected when set to 'filter'.
  43. throttle: null,
  44. // Dispatch action by the fixed rate, avoid frequency.
  45. // default 100. Do not throttle when use null/undefined.
  46. // If animation === true and animationDurationUpdate > 0,
  47. // default value is 100, otherwise 20.
  48. start: 0,
  49. // Start percent. 0 ~ 100
  50. end: 100,
  51. // End percent. 0 ~ 100
  52. startValue: null,
  53. // Start value. If startValue specified, start is ignored.
  54. endValue: null,
  55. // End value. If endValue specified, end is ignored.
  56. minSpan: null,
  57. // 0 ~ 100
  58. maxSpan: null,
  59. // 0 ~ 100
  60. minValueSpan: null,
  61. // The range of dataZoom can not be smaller than that.
  62. maxValueSpan: null,
  63. // The range of dataZoom can not be larger than that.
  64. rangeMode: null // Array, can be 'value' or 'percent'.
  65. },
  66. /**
  67. * @override
  68. */
  69. init: function (option, parentModel, ecModel) {
  70. /**
  71. * key like x_0, y_1
  72. * @private
  73. * @type {Object}
  74. */
  75. this._dataIntervalByAxis = {};
  76. /**
  77. * @private
  78. */
  79. this._dataInfo = {};
  80. /**
  81. * key like x_0, y_1
  82. * @private
  83. */
  84. this._axisProxies = {};
  85. /**
  86. * @readOnly
  87. */
  88. this.textStyleModel;
  89. /**
  90. * @private
  91. */
  92. this._autoThrottle = true;
  93. /**
  94. * 'percent' or 'value'
  95. * @private
  96. */
  97. this._rangePropMode = ['percent', 'percent'];
  98. var rawOption = retrieveRaw(option);
  99. this.mergeDefaultAndTheme(option, ecModel);
  100. this.doInit(rawOption);
  101. },
  102. /**
  103. * @override
  104. */
  105. mergeOption: function (newOption) {
  106. var rawOption = retrieveRaw(newOption); //FIX #2591
  107. zrUtil.merge(this.option, newOption, true);
  108. this.doInit(rawOption);
  109. },
  110. /**
  111. * @protected
  112. */
  113. doInit: function (rawOption) {
  114. var thisOption = this.option; // Disable realtime view update if canvas is not supported.
  115. if (!env.canvasSupported) {
  116. thisOption.realtime = false;
  117. }
  118. this._setDefaultThrottle(rawOption);
  119. updateRangeUse(this, rawOption);
  120. each([['start', 'startValue'], ['end', 'endValue']], function (names, index) {
  121. // start/end has higher priority over startValue/endValue if they
  122. // both set, but we should make chart.setOption({endValue: 1000})
  123. // effective, rather than chart.setOption({endValue: 1000, end: null}).
  124. if (this._rangePropMode[index] === 'value') {
  125. thisOption[names[0]] = null;
  126. } // Otherwise do nothing and use the merge result.
  127. }, this);
  128. this.textStyleModel = this.getModel('textStyle');
  129. this._resetTarget();
  130. this._giveAxisProxies();
  131. },
  132. /**
  133. * @private
  134. */
  135. _giveAxisProxies: function () {
  136. var axisProxies = this._axisProxies;
  137. this.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel, ecModel) {
  138. var axisModel = this.dependentModels[dimNames.axis][axisIndex]; // If exists, share axisProxy with other dataZoomModels.
  139. var axisProxy = axisModel.__dzAxisProxy || ( // Use the first dataZoomModel as the main model of axisProxy.
  140. axisModel.__dzAxisProxy = new AxisProxy(dimNames.name, axisIndex, this, ecModel)); // FIXME
  141. // dispose __dzAxisProxy
  142. axisProxies[dimNames.name + '_' + axisIndex] = axisProxy;
  143. }, this);
  144. },
  145. /**
  146. * @private
  147. */
  148. _resetTarget: function () {
  149. var thisOption = this.option;
  150. var autoMode = this._judgeAutoMode();
  151. eachAxisDim(function (dimNames) {
  152. var axisIndexName = dimNames.axisIndex;
  153. thisOption[axisIndexName] = modelUtil.normalizeToArray(thisOption[axisIndexName]);
  154. }, this);
  155. if (autoMode === 'axisIndex') {
  156. this._autoSetAxisIndex();
  157. } else if (autoMode === 'orient') {
  158. this._autoSetOrient();
  159. }
  160. },
  161. /**
  162. * @private
  163. */
  164. _judgeAutoMode: function () {
  165. // Auto set only works for setOption at the first time.
  166. // The following is user's reponsibility. So using merged
  167. // option is OK.
  168. var thisOption = this.option;
  169. var hasIndexSpecified = false;
  170. eachAxisDim(function (dimNames) {
  171. // When user set axisIndex as a empty array, we think that user specify axisIndex
  172. // but do not want use auto mode. Because empty array may be encountered when
  173. // some error occured.
  174. if (thisOption[dimNames.axisIndex] != null) {
  175. hasIndexSpecified = true;
  176. }
  177. }, this);
  178. var orient = thisOption.orient;
  179. if (orient == null && hasIndexSpecified) {
  180. return 'orient';
  181. } else if (!hasIndexSpecified) {
  182. if (orient == null) {
  183. thisOption.orient = 'horizontal';
  184. }
  185. return 'axisIndex';
  186. }
  187. },
  188. /**
  189. * @private
  190. */
  191. _autoSetAxisIndex: function () {
  192. var autoAxisIndex = true;
  193. var orient = this.get('orient', true);
  194. var thisOption = this.option;
  195. var dependentModels = this.dependentModels;
  196. if (autoAxisIndex) {
  197. // Find axis that parallel to dataZoom as default.
  198. var dimName = orient === 'vertical' ? 'y' : 'x';
  199. if (dependentModels[dimName + 'Axis'].length) {
  200. thisOption[dimName + 'AxisIndex'] = [0];
  201. autoAxisIndex = false;
  202. } else {
  203. each(dependentModels.singleAxis, function (singleAxisModel) {
  204. if (autoAxisIndex && singleAxisModel.get('orient', true) === orient) {
  205. thisOption.singleAxisIndex = [singleAxisModel.componentIndex];
  206. autoAxisIndex = false;
  207. }
  208. });
  209. }
  210. }
  211. if (autoAxisIndex) {
  212. // Find the first category axis as default. (consider polar)
  213. eachAxisDim(function (dimNames) {
  214. if (!autoAxisIndex) {
  215. return;
  216. }
  217. var axisIndices = [];
  218. var axisModels = this.dependentModels[dimNames.axis];
  219. if (axisModels.length && !axisIndices.length) {
  220. for (var i = 0, len = axisModels.length; i < len; i++) {
  221. if (axisModels[i].get('type') === 'category') {
  222. axisIndices.push(i);
  223. }
  224. }
  225. }
  226. thisOption[dimNames.axisIndex] = axisIndices;
  227. if (axisIndices.length) {
  228. autoAxisIndex = false;
  229. }
  230. }, this);
  231. }
  232. if (autoAxisIndex) {
  233. // FIXME
  234. // 这里是兼容ec2的写法(没指定xAxisIndex和yAxisIndex时把scatter和双数值轴折柱纳入dataZoom控制),
  235. // 但是实际是否需要Grid.js#getScaleByOption来判断(考虑time,log等axis type)?
  236. // If both dataZoom.xAxisIndex and dataZoom.yAxisIndex is not specified,
  237. // dataZoom component auto adopts series that reference to
  238. // both xAxis and yAxis which type is 'value'.
  239. this.ecModel.eachSeries(function (seriesModel) {
  240. if (this._isSeriesHasAllAxesTypeOf(seriesModel, 'value')) {
  241. eachAxisDim(function (dimNames) {
  242. var axisIndices = thisOption[dimNames.axisIndex];
  243. var axisIndex = seriesModel.get(dimNames.axisIndex);
  244. var axisId = seriesModel.get(dimNames.axisId);
  245. var axisModel = seriesModel.ecModel.queryComponents({
  246. mainType: dimNames.axis,
  247. index: axisIndex,
  248. id: axisId
  249. })[0];
  250. axisIndex = axisModel.componentIndex;
  251. if (zrUtil.indexOf(axisIndices, axisIndex) < 0) {
  252. axisIndices.push(axisIndex);
  253. }
  254. });
  255. }
  256. }, this);
  257. }
  258. },
  259. /**
  260. * @private
  261. */
  262. _autoSetOrient: function () {
  263. var dim; // Find the first axis
  264. this.eachTargetAxis(function (dimNames) {
  265. !dim && (dim = dimNames.name);
  266. }, this);
  267. this.option.orient = dim === 'y' ? 'vertical' : 'horizontal';
  268. },
  269. /**
  270. * @private
  271. */
  272. _isSeriesHasAllAxesTypeOf: function (seriesModel, axisType) {
  273. // FIXME
  274. // 需要series的xAxisIndex和yAxisIndex都首先自动设置上。
  275. // 例如series.type === scatter时。
  276. var is = true;
  277. eachAxisDim(function (dimNames) {
  278. var seriesAxisIndex = seriesModel.get(dimNames.axisIndex);
  279. var axisModel = this.dependentModels[dimNames.axis][seriesAxisIndex];
  280. if (!axisModel || axisModel.get('type') !== axisType) {
  281. is = false;
  282. }
  283. }, this);
  284. return is;
  285. },
  286. /**
  287. * @private
  288. */
  289. _setDefaultThrottle: function (rawOption) {
  290. // When first time user set throttle, auto throttle ends.
  291. if (rawOption.hasOwnProperty('throttle')) {
  292. this._autoThrottle = false;
  293. }
  294. if (this._autoThrottle) {
  295. var globalOption = this.ecModel.option;
  296. this.option.throttle = globalOption.animation && globalOption.animationDurationUpdate > 0 ? 100 : 20;
  297. }
  298. },
  299. /**
  300. * @public
  301. */
  302. getFirstTargetAxisModel: function () {
  303. var firstAxisModel;
  304. eachAxisDim(function (dimNames) {
  305. if (firstAxisModel == null) {
  306. var indices = this.get(dimNames.axisIndex);
  307. if (indices.length) {
  308. firstAxisModel = this.dependentModels[dimNames.axis][indices[0]];
  309. }
  310. }
  311. }, this);
  312. return firstAxisModel;
  313. },
  314. /**
  315. * @public
  316. * @param {Function} callback param: axisModel, dimNames, axisIndex, dataZoomModel, ecModel
  317. */
  318. eachTargetAxis: function (callback, context) {
  319. var ecModel = this.ecModel;
  320. eachAxisDim(function (dimNames) {
  321. each(this.get(dimNames.axisIndex), function (axisIndex) {
  322. callback.call(context, dimNames, axisIndex, this, ecModel);
  323. }, this);
  324. }, this);
  325. },
  326. /**
  327. * @param {string} dimName
  328. * @param {number} axisIndex
  329. * @return {module:echarts/component/dataZoom/AxisProxy} If not found, return null/undefined.
  330. */
  331. getAxisProxy: function (dimName, axisIndex) {
  332. return this._axisProxies[dimName + '_' + axisIndex];
  333. },
  334. /**
  335. * @param {string} dimName
  336. * @param {number} axisIndex
  337. * @return {module:echarts/model/Model} If not found, return null/undefined.
  338. */
  339. getAxisModel: function (dimName, axisIndex) {
  340. var axisProxy = this.getAxisProxy(dimName, axisIndex);
  341. return axisProxy && axisProxy.getAxisModel();
  342. },
  343. /**
  344. * If not specified, set to undefined.
  345. *
  346. * @public
  347. * @param {Object} opt
  348. * @param {number} [opt.start]
  349. * @param {number} [opt.end]
  350. * @param {number} [opt.startValue]
  351. * @param {number} [opt.endValue]
  352. * @param {boolean} [ignoreUpdateRangeUsg=false]
  353. */
  354. setRawRange: function (opt, ignoreUpdateRangeUsg) {
  355. var option = this.option;
  356. each([['start', 'startValue'], ['end', 'endValue']], function (names) {
  357. // If only one of 'start' and 'startValue' is not null/undefined, the other
  358. // should be cleared, which enable clear the option.
  359. // If both of them are not set, keep option with the original value, which
  360. // enable use only set start but not set end when calling `dispatchAction`.
  361. // The same as 'end' and 'endValue'.
  362. if (opt[names[0]] != null || opt[names[1]] != null) {
  363. option[names[0]] = opt[names[0]];
  364. option[names[1]] = opt[names[1]];
  365. }
  366. }, this);
  367. !ignoreUpdateRangeUsg && updateRangeUse(this, opt);
  368. },
  369. /**
  370. * @public
  371. * @return {Array.<number>} [startPercent, endPercent]
  372. */
  373. getPercentRange: function () {
  374. var axisProxy = this.findRepresentativeAxisProxy();
  375. if (axisProxy) {
  376. return axisProxy.getDataPercentWindow();
  377. }
  378. },
  379. /**
  380. * @public
  381. * For example, chart.getModel().getComponent('dataZoom').getValueRange('y', 0);
  382. *
  383. * @param {string} [axisDimName]
  384. * @param {number} [axisIndex]
  385. * @return {Array.<number>} [startValue, endValue] value can only be '-' or finite number.
  386. */
  387. getValueRange: function (axisDimName, axisIndex) {
  388. if (axisDimName == null && axisIndex == null) {
  389. var axisProxy = this.findRepresentativeAxisProxy();
  390. if (axisProxy) {
  391. return axisProxy.getDataValueWindow();
  392. }
  393. } else {
  394. return this.getAxisProxy(axisDimName, axisIndex).getDataValueWindow();
  395. }
  396. },
  397. /**
  398. * @public
  399. * @param {module:echarts/model/Model} [axisModel] If axisModel given, find axisProxy
  400. * corresponding to the axisModel
  401. * @return {module:echarts/component/dataZoom/AxisProxy}
  402. */
  403. findRepresentativeAxisProxy: function (axisModel) {
  404. if (axisModel) {
  405. return axisModel.__dzAxisProxy;
  406. } // Find the first hosted axisProxy
  407. var axisProxies = this._axisProxies;
  408. for (var key in axisProxies) {
  409. if (axisProxies.hasOwnProperty(key) && axisProxies[key].hostedBy(this)) {
  410. return axisProxies[key];
  411. }
  412. } // If no hosted axis find not hosted axisProxy.
  413. // Consider this case: dataZoomModel1 and dataZoomModel2 control the same axis,
  414. // and the option.start or option.end settings are different. The percentRange
  415. // should follow axisProxy.
  416. // (We encounter this problem in toolbox data zoom.)
  417. for (var key in axisProxies) {
  418. if (axisProxies.hasOwnProperty(key) && !axisProxies[key].hostedBy(this)) {
  419. return axisProxies[key];
  420. }
  421. }
  422. },
  423. /**
  424. * @return {Array.<string>}
  425. */
  426. getRangePropMode: function () {
  427. return this._rangePropMode.slice();
  428. }
  429. });
  430. function retrieveRaw(option) {
  431. var ret = {};
  432. each(['start', 'end', 'startValue', 'endValue', 'throttle'], function (name) {
  433. option.hasOwnProperty(name) && (ret[name] = option[name]);
  434. });
  435. return ret;
  436. }
  437. function updateRangeUse(dataZoomModel, rawOption) {
  438. var rangePropMode = dataZoomModel._rangePropMode;
  439. var rangeModeInOption = dataZoomModel.get('rangeMode');
  440. each([['start', 'startValue'], ['end', 'endValue']], function (names, index) {
  441. var percentSpecified = rawOption[names[0]] != null;
  442. var valueSpecified = rawOption[names[1]] != null;
  443. if (percentSpecified && !valueSpecified) {
  444. rangePropMode[index] = 'percent';
  445. } else if (!percentSpecified && valueSpecified) {
  446. rangePropMode[index] = 'value';
  447. } else if (rangeModeInOption) {
  448. rangePropMode[index] = rangeModeInOption[index];
  449. } else if (percentSpecified) {
  450. // percentSpecified && valueSpecified
  451. rangePropMode[index] = 'percent';
  452. } // else remain its original setting.
  453. });
  454. }
  455. var _default = DataZoomModel;
  456. module.exports = _default;