DataZoomModel.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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 _config = require("../../config");
  20. var __DEV__ = _config.__DEV__;
  21. var echarts = require("../../echarts");
  22. var zrUtil = require("zrender/lib/core/util");
  23. var env = require("zrender/lib/core/env");
  24. var modelUtil = require("../../util/model");
  25. var helper = require("./helper");
  26. var AxisProxy = require("./AxisProxy");
  27. /*
  28. * Licensed to the Apache Software Foundation (ASF) under one
  29. * or more contributor license agreements. See the NOTICE file
  30. * distributed with this work for additional information
  31. * regarding copyright ownership. The ASF licenses this file
  32. * to you under the Apache License, Version 2.0 (the
  33. * "License"); you may not use this file except in compliance
  34. * with the License. You may obtain a copy of the License at
  35. *
  36. * http://www.apache.org/licenses/LICENSE-2.0
  37. *
  38. * Unless required by applicable law or agreed to in writing,
  39. * software distributed under the License is distributed on an
  40. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  41. * KIND, either express or implied. See the License for the
  42. * specific language governing permissions and limitations
  43. * under the License.
  44. */
  45. var each = zrUtil.each;
  46. var eachAxisDim = helper.eachAxisDim;
  47. var DataZoomModel = echarts.extendComponentModel({
  48. type: 'dataZoom',
  49. dependencies: ['xAxis', 'yAxis', 'zAxis', 'radiusAxis', 'angleAxis', 'singleAxis', 'series'],
  50. /**
  51. * @protected
  52. */
  53. defaultOption: {
  54. zlevel: 0,
  55. z: 4,
  56. // Higher than normal component (z: 2).
  57. orient: null,
  58. // Default auto by axisIndex. Possible value: 'horizontal', 'vertical'.
  59. xAxisIndex: null,
  60. // Default the first horizontal category axis.
  61. yAxisIndex: null,
  62. // Default the first vertical category axis.
  63. filterMode: 'filter',
  64. // Possible values: 'filter' or 'empty' or 'weakFilter'.
  65. // 'filter': data items which are out of window will be removed. This option is
  66. // applicable when filtering outliers. For each data item, it will be
  67. // filtered if one of the relevant dimensions is out of the window.
  68. // 'weakFilter': data items which are out of window will be removed. This option
  69. // is applicable when filtering outliers. For each data item, it will be
  70. // filtered only if all of the relevant dimensions are out of the same
  71. // side of the window.
  72. // 'empty': data items which are out of window will be set to empty.
  73. // This option is applicable when user should not neglect
  74. // that there are some data items out of window.
  75. // 'none': Do not filter.
  76. // Taking line chart as an example, line will be broken in
  77. // the filtered points when filterModel is set to 'empty', but
  78. // be connected when set to 'filter'.
  79. throttle: null,
  80. // Dispatch action by the fixed rate, avoid frequency.
  81. // default 100. Do not throttle when use null/undefined.
  82. // If animation === true and animationDurationUpdate > 0,
  83. // default value is 100, otherwise 20.
  84. start: 0,
  85. // Start percent. 0 ~ 100
  86. end: 100,
  87. // End percent. 0 ~ 100
  88. startValue: null,
  89. // Start value. If startValue specified, start is ignored.
  90. endValue: null,
  91. // End value. If endValue specified, end is ignored.
  92. minSpan: null,
  93. // 0 ~ 100
  94. maxSpan: null,
  95. // 0 ~ 100
  96. minValueSpan: null,
  97. // The range of dataZoom can not be smaller than that.
  98. maxValueSpan: null,
  99. // The range of dataZoom can not be larger than that.
  100. rangeMode: null // Array, can be 'value' or 'percent'.
  101. },
  102. /**
  103. * @override
  104. */
  105. init: function (option, parentModel, ecModel) {
  106. /**
  107. * key like x_0, y_1
  108. * @private
  109. * @type {Object}
  110. */
  111. this._dataIntervalByAxis = {};
  112. /**
  113. * @private
  114. */
  115. this._dataInfo = {};
  116. /**
  117. * key like x_0, y_1
  118. * @private
  119. */
  120. this._axisProxies = {};
  121. /**
  122. * @readOnly
  123. */
  124. this.textStyleModel;
  125. /**
  126. * @private
  127. */
  128. this._autoThrottle = true;
  129. /**
  130. * It is `[rangeModeForMin, rangeModeForMax]`.
  131. * The optional values for `rangeMode`:
  132. * + `'value'` mode: the axis extent will always be determined by
  133. * `dataZoom.startValue` and `dataZoom.endValue`, despite
  134. * how data like and how `axis.min` and `axis.max` are.
  135. * + `'percent'` mode: `100` represents 100% of the `[dMin, dMax]`,
  136. * where `dMin` is `axis.min` if `axis.min` specified, otherwise `data.extent[0]`,
  137. * and `dMax` is `axis.max` if `axis.max` specified, otherwise `data.extent[1]`.
  138. * Axis extent will be determined by the result of the percent of `[dMin, dMax]`.
  139. *
  140. * For example, when users are using dynamic data (update data periodically via `setOption`),
  141. * if in `'value`' mode, the window will be kept in a fixed value range despite how
  142. * data are appended, while if in `'percent'` mode, whe window range will be changed alone with
  143. * the appended data (suppose `axis.min` and `axis.max` are not specified).
  144. *
  145. * @private
  146. */
  147. this._rangePropMode = ['percent', 'percent'];
  148. var inputRawOption = retrieveRawOption(option);
  149. /**
  150. * Suppose a "main process" start at the point that model prepared (that is,
  151. * model initialized or merged or method called in `action`).
  152. * We should keep the `main process` idempotent, that is, given a set of values
  153. * on `option`, we get the same result.
  154. *
  155. * But sometimes, values on `option` will be updated for providing users
  156. * a "final calculated value" (`dataZoomProcessor` will do that). Those value
  157. * should not be the base/input of the `main process`.
  158. *
  159. * So in that case we should save and keep the input of the `main process`
  160. * separately, called `settledOption`.
  161. *
  162. * For example, consider the case:
  163. * (Step_1) brush zoom the grid by `toolbox.dataZoom`,
  164. * where the original input `option.startValue`, `option.endValue` are earsed by
  165. * calculated value.
  166. * (Step)2) click the legend to hide and show a series,
  167. * where the new range is calculated by the earsed `startValue` and `endValue`,
  168. * which brings incorrect result.
  169. *
  170. * @readOnly
  171. */
  172. this.settledOption = inputRawOption;
  173. this.mergeDefaultAndTheme(option, ecModel);
  174. this.doInit(inputRawOption);
  175. },
  176. /**
  177. * @override
  178. */
  179. mergeOption: function (newOption) {
  180. var inputRawOption = retrieveRawOption(newOption); //FIX #2591
  181. zrUtil.merge(this.option, newOption, true);
  182. zrUtil.merge(this.settledOption, inputRawOption, true);
  183. this.doInit(inputRawOption);
  184. },
  185. /**
  186. * @protected
  187. */
  188. doInit: function (inputRawOption) {
  189. var thisOption = this.option; // Disable realtime view update if canvas is not supported.
  190. if (!env.canvasSupported) {
  191. thisOption.realtime = false;
  192. }
  193. this._setDefaultThrottle(inputRawOption);
  194. updateRangeUse(this, inputRawOption);
  195. var settledOption = this.settledOption;
  196. each([['start', 'startValue'], ['end', 'endValue']], function (names, index) {
  197. // start/end has higher priority over startValue/endValue if they
  198. // both set, but we should make chart.setOption({endValue: 1000})
  199. // effective, rather than chart.setOption({endValue: 1000, end: null}).
  200. if (this._rangePropMode[index] === 'value') {
  201. thisOption[names[0]] = settledOption[names[0]] = null;
  202. } // Otherwise do nothing and use the merge result.
  203. }, this);
  204. this.textStyleModel = this.getModel('textStyle');
  205. this._resetTarget();
  206. this._giveAxisProxies();
  207. },
  208. /**
  209. * @private
  210. */
  211. _giveAxisProxies: function () {
  212. var axisProxies = this._axisProxies;
  213. this.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel, ecModel) {
  214. var axisModel = this.dependentModels[dimNames.axis][axisIndex]; // If exists, share axisProxy with other dataZoomModels.
  215. var axisProxy = axisModel.__dzAxisProxy || ( // Use the first dataZoomModel as the main model of axisProxy.
  216. axisModel.__dzAxisProxy = new AxisProxy(dimNames.name, axisIndex, this, ecModel)); // FIXME
  217. // dispose __dzAxisProxy
  218. axisProxies[dimNames.name + '_' + axisIndex] = axisProxy;
  219. }, this);
  220. },
  221. /**
  222. * @private
  223. */
  224. _resetTarget: function () {
  225. var thisOption = this.option;
  226. var autoMode = this._judgeAutoMode();
  227. eachAxisDim(function (dimNames) {
  228. var axisIndexName = dimNames.axisIndex;
  229. thisOption[axisIndexName] = modelUtil.normalizeToArray(thisOption[axisIndexName]);
  230. }, this);
  231. if (autoMode === 'axisIndex') {
  232. this._autoSetAxisIndex();
  233. } else if (autoMode === 'orient') {
  234. this._autoSetOrient();
  235. }
  236. },
  237. /**
  238. * @private
  239. */
  240. _judgeAutoMode: function () {
  241. // Auto set only works for setOption at the first time.
  242. // The following is user's reponsibility. So using merged
  243. // option is OK.
  244. var thisOption = this.option;
  245. var hasIndexSpecified = false;
  246. eachAxisDim(function (dimNames) {
  247. // When user set axisIndex as a empty array, we think that user specify axisIndex
  248. // but do not want use auto mode. Because empty array may be encountered when
  249. // some error occured.
  250. if (thisOption[dimNames.axisIndex] != null) {
  251. hasIndexSpecified = true;
  252. }
  253. }, this);
  254. var orient = thisOption.orient;
  255. if (orient == null && hasIndexSpecified) {
  256. return 'orient';
  257. } else if (!hasIndexSpecified) {
  258. if (orient == null) {
  259. thisOption.orient = 'horizontal';
  260. }
  261. return 'axisIndex';
  262. }
  263. },
  264. /**
  265. * @private
  266. */
  267. _autoSetAxisIndex: function () {
  268. var autoAxisIndex = true;
  269. var orient = this.get('orient', true);
  270. var thisOption = this.option;
  271. var dependentModels = this.dependentModels;
  272. if (autoAxisIndex) {
  273. // Find axis that parallel to dataZoom as default.
  274. var dimName = orient === 'vertical' ? 'y' : 'x';
  275. if (dependentModels[dimName + 'Axis'].length) {
  276. thisOption[dimName + 'AxisIndex'] = [0];
  277. autoAxisIndex = false;
  278. } else {
  279. each(dependentModels.singleAxis, function (singleAxisModel) {
  280. if (autoAxisIndex && singleAxisModel.get('orient', true) === orient) {
  281. thisOption.singleAxisIndex = [singleAxisModel.componentIndex];
  282. autoAxisIndex = false;
  283. }
  284. });
  285. }
  286. }
  287. if (autoAxisIndex) {
  288. // Find the first category axis as default. (consider polar)
  289. eachAxisDim(function (dimNames) {
  290. if (!autoAxisIndex) {
  291. return;
  292. }
  293. var axisIndices = [];
  294. var axisModels = this.dependentModels[dimNames.axis];
  295. if (axisModels.length && !axisIndices.length) {
  296. for (var i = 0, len = axisModels.length; i < len; i++) {
  297. if (axisModels[i].get('type') === 'category') {
  298. axisIndices.push(i);
  299. }
  300. }
  301. }
  302. thisOption[dimNames.axisIndex] = axisIndices;
  303. if (axisIndices.length) {
  304. autoAxisIndex = false;
  305. }
  306. }, this);
  307. }
  308. if (autoAxisIndex) {
  309. // FIXME
  310. // 这里是兼容ec2的写法(没指定xAxisIndex和yAxisIndex时把scatter和双数值轴折柱纳入dataZoom控制),
  311. // 但是实际是否需要Grid.js#getScaleByOption来判断(考虑time,log等axis type)?
  312. // If both dataZoom.xAxisIndex and dataZoom.yAxisIndex is not specified,
  313. // dataZoom component auto adopts series that reference to
  314. // both xAxis and yAxis which type is 'value'.
  315. this.ecModel.eachSeries(function (seriesModel) {
  316. if (this._isSeriesHasAllAxesTypeOf(seriesModel, 'value')) {
  317. eachAxisDim(function (dimNames) {
  318. var axisIndices = thisOption[dimNames.axisIndex];
  319. var axisIndex = seriesModel.get(dimNames.axisIndex);
  320. var axisId = seriesModel.get(dimNames.axisId);
  321. var axisModel = seriesModel.ecModel.queryComponents({
  322. mainType: dimNames.axis,
  323. index: axisIndex,
  324. id: axisId
  325. })[0];
  326. axisIndex = axisModel.componentIndex;
  327. if (zrUtil.indexOf(axisIndices, axisIndex) < 0) {
  328. axisIndices.push(axisIndex);
  329. }
  330. });
  331. }
  332. }, this);
  333. }
  334. },
  335. /**
  336. * @private
  337. */
  338. _autoSetOrient: function () {
  339. var dim; // Find the first axis
  340. this.eachTargetAxis(function (dimNames) {
  341. !dim && (dim = dimNames.name);
  342. }, this);
  343. this.option.orient = dim === 'y' ? 'vertical' : 'horizontal';
  344. },
  345. /**
  346. * @private
  347. */
  348. _isSeriesHasAllAxesTypeOf: function (seriesModel, axisType) {
  349. // FIXME
  350. // 需要series的xAxisIndex和yAxisIndex都首先自动设置上。
  351. // 例如series.type === scatter时。
  352. var is = true;
  353. eachAxisDim(function (dimNames) {
  354. var seriesAxisIndex = seriesModel.get(dimNames.axisIndex);
  355. var axisModel = this.dependentModels[dimNames.axis][seriesAxisIndex];
  356. if (!axisModel || axisModel.get('type') !== axisType) {
  357. is = false;
  358. }
  359. }, this);
  360. return is;
  361. },
  362. /**
  363. * @private
  364. */
  365. _setDefaultThrottle: function (inputRawOption) {
  366. // When first time user set throttle, auto throttle ends.
  367. if (inputRawOption.hasOwnProperty('throttle')) {
  368. this._autoThrottle = false;
  369. }
  370. if (this._autoThrottle) {
  371. var globalOption = this.ecModel.option;
  372. this.option.throttle = globalOption.animation && globalOption.animationDurationUpdate > 0 ? 100 : 20;
  373. }
  374. },
  375. /**
  376. * @public
  377. */
  378. getFirstTargetAxisModel: function () {
  379. var firstAxisModel;
  380. eachAxisDim(function (dimNames) {
  381. if (firstAxisModel == null) {
  382. var indices = this.get(dimNames.axisIndex);
  383. if (indices.length) {
  384. firstAxisModel = this.dependentModels[dimNames.axis][indices[0]];
  385. }
  386. }
  387. }, this);
  388. return firstAxisModel;
  389. },
  390. /**
  391. * @public
  392. * @param {Function} callback param: axisModel, dimNames, axisIndex, dataZoomModel, ecModel
  393. */
  394. eachTargetAxis: function (callback, context) {
  395. var ecModel = this.ecModel;
  396. eachAxisDim(function (dimNames) {
  397. each(this.get(dimNames.axisIndex), function (axisIndex) {
  398. callback.call(context, dimNames, axisIndex, this, ecModel);
  399. }, this);
  400. }, this);
  401. },
  402. /**
  403. * @param {string} dimName
  404. * @param {number} axisIndex
  405. * @return {module:echarts/component/dataZoom/AxisProxy} If not found, return null/undefined.
  406. */
  407. getAxisProxy: function (dimName, axisIndex) {
  408. return this._axisProxies[dimName + '_' + axisIndex];
  409. },
  410. /**
  411. * @param {string} dimName
  412. * @param {number} axisIndex
  413. * @return {module:echarts/model/Model} If not found, return null/undefined.
  414. */
  415. getAxisModel: function (dimName, axisIndex) {
  416. var axisProxy = this.getAxisProxy(dimName, axisIndex);
  417. return axisProxy && axisProxy.getAxisModel();
  418. },
  419. /**
  420. * If not specified, set to undefined.
  421. *
  422. * @public
  423. * @param {Object} opt
  424. * @param {number} [opt.start]
  425. * @param {number} [opt.end]
  426. * @param {number} [opt.startValue]
  427. * @param {number} [opt.endValue]
  428. */
  429. setRawRange: function (opt) {
  430. var thisOption = this.option;
  431. var settledOption = this.settledOption;
  432. each([['start', 'startValue'], ['end', 'endValue']], function (names) {
  433. // Consider the pair <start, startValue>:
  434. // If one has value and the other one is `null/undefined`, we both set them
  435. // to `settledOption`. This strategy enables the feature to clear the original
  436. // value in `settledOption` to `null/undefined`.
  437. // But if both of them are `null/undefined`, we do not set them to `settledOption`
  438. // and keep `settledOption` with the original value. This strategy enables users to
  439. // only set <end or endValue> but not set <start or startValue> when calling
  440. // `dispatchAction`.
  441. // The pair <end, endValue> is treated in the same way.
  442. if (opt[names[0]] != null || opt[names[1]] != null) {
  443. thisOption[names[0]] = settledOption[names[0]] = opt[names[0]];
  444. thisOption[names[1]] = settledOption[names[1]] = opt[names[1]];
  445. }
  446. }, this);
  447. updateRangeUse(this, opt);
  448. },
  449. /**
  450. * @public
  451. * @param {Object} opt
  452. * @param {number} [opt.start]
  453. * @param {number} [opt.end]
  454. * @param {number} [opt.startValue]
  455. * @param {number} [opt.endValue]
  456. */
  457. setCalculatedRange: function (opt) {
  458. var option = this.option;
  459. each(['start', 'startValue', 'end', 'endValue'], function (name) {
  460. option[name] = opt[name];
  461. });
  462. },
  463. /**
  464. * @public
  465. * @return {Array.<number>} [startPercent, endPercent]
  466. */
  467. getPercentRange: function () {
  468. var axisProxy = this.findRepresentativeAxisProxy();
  469. if (axisProxy) {
  470. return axisProxy.getDataPercentWindow();
  471. }
  472. },
  473. /**
  474. * @public
  475. * For example, chart.getModel().getComponent('dataZoom').getValueRange('y', 0);
  476. *
  477. * @param {string} [axisDimName]
  478. * @param {number} [axisIndex]
  479. * @return {Array.<number>} [startValue, endValue] value can only be '-' or finite number.
  480. */
  481. getValueRange: function (axisDimName, axisIndex) {
  482. if (axisDimName == null && axisIndex == null) {
  483. var axisProxy = this.findRepresentativeAxisProxy();
  484. if (axisProxy) {
  485. return axisProxy.getDataValueWindow();
  486. }
  487. } else {
  488. return this.getAxisProxy(axisDimName, axisIndex).getDataValueWindow();
  489. }
  490. },
  491. /**
  492. * @public
  493. * @param {module:echarts/model/Model} [axisModel] If axisModel given, find axisProxy
  494. * corresponding to the axisModel
  495. * @return {module:echarts/component/dataZoom/AxisProxy}
  496. */
  497. findRepresentativeAxisProxy: function (axisModel) {
  498. if (axisModel) {
  499. return axisModel.__dzAxisProxy;
  500. } // Find the first hosted axisProxy
  501. var axisProxies = this._axisProxies;
  502. for (var key in axisProxies) {
  503. if (axisProxies.hasOwnProperty(key) && axisProxies[key].hostedBy(this)) {
  504. return axisProxies[key];
  505. }
  506. } // If no hosted axis find not hosted axisProxy.
  507. // Consider this case: dataZoomModel1 and dataZoomModel2 control the same axis,
  508. // and the option.start or option.end settings are different. The percentRange
  509. // should follow axisProxy.
  510. // (We encounter this problem in toolbox data zoom.)
  511. for (var key in axisProxies) {
  512. if (axisProxies.hasOwnProperty(key) && !axisProxies[key].hostedBy(this)) {
  513. return axisProxies[key];
  514. }
  515. }
  516. },
  517. /**
  518. * @return {Array.<string>}
  519. */
  520. getRangePropMode: function () {
  521. return this._rangePropMode.slice();
  522. }
  523. });
  524. /**
  525. * Retrieve the those raw params from option, which will be cached separately.
  526. * becasue they will be overwritten by normalized/calculated values in the main
  527. * process.
  528. */
  529. function retrieveRawOption(option) {
  530. var ret = {};
  531. each(['start', 'end', 'startValue', 'endValue', 'throttle'], function (name) {
  532. option.hasOwnProperty(name) && (ret[name] = option[name]);
  533. });
  534. return ret;
  535. }
  536. function updateRangeUse(dataZoomModel, inputRawOption) {
  537. var rangePropMode = dataZoomModel._rangePropMode;
  538. var rangeModeInOption = dataZoomModel.get('rangeMode');
  539. each([['start', 'startValue'], ['end', 'endValue']], function (names, index) {
  540. var percentSpecified = inputRawOption[names[0]] != null;
  541. var valueSpecified = inputRawOption[names[1]] != null;
  542. if (percentSpecified && !valueSpecified) {
  543. rangePropMode[index] = 'percent';
  544. } else if (!percentSpecified && valueSpecified) {
  545. rangePropMode[index] = 'value';
  546. } else if (rangeModeInOption) {
  547. rangePropMode[index] = rangeModeInOption[index];
  548. } else if (percentSpecified) {
  549. // percentSpecified && valueSpecified
  550. rangePropMode[index] = 'percent';
  551. } // else remain its original setting.
  552. });
  553. }
  554. var _default = DataZoomModel;
  555. module.exports = _default;