| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 | 
/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/var zrUtil = require("zrender/lib/core/util");var numberUtil = require("../../util/number");var helper = require("./helper");var sliderMove = require("../helper/sliderMove");/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/var each = zrUtil.each;var asc = numberUtil.asc;/** * Operate single axis. * One axis can only operated by one axis operator. * Different dataZoomModels may be defined to operate the same axis. * (i.e. 'inside' data zoom and 'slider' data zoom components) * So dataZoomModels share one axisProxy in that case. * * @class */var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) {  /**   * @private   * @type {string}   */  this._dimName = dimName;  /**   * @private   */  this._axisIndex = axisIndex;  /**   * @private   * @type {Array.<number>}   */  this._valueWindow;  /**   * @private   * @type {Array.<number>}   */  this._percentWindow;  /**   * @private   * @type {Array.<number>}   */  this._dataExtent;  /**   * {minSpan, maxSpan, minValueSpan, maxValueSpan}   * @private   * @type {Object}   */  this._minMaxSpan;  /**   * @readOnly   * @type {module: echarts/model/Global}   */  this.ecModel = ecModel;  /**   * @private   * @type {module: echarts/component/dataZoom/DataZoomModel}   */  this._dataZoomModel = dataZoomModel; // /**  //  * @readOnly  //  * @private  //  */  // this.hasSeriesStacked;};AxisProxy.prototype = {  constructor: AxisProxy,  /**   * Whether the axisProxy is hosted by dataZoomModel.   *   * @public   * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel   * @return {boolean}   */  hostedBy: function (dataZoomModel) {    return this._dataZoomModel === dataZoomModel;  },  /**   * @return {Array.<number>} Value can only be NaN or finite value.   */  getDataValueWindow: function () {    return this._valueWindow.slice();  },  /**   * @return {Array.<number>}   */  getDataPercentWindow: function () {    return this._percentWindow.slice();  },  /**   * @public   * @param {number} axisIndex   * @return {Array} seriesModels   */  getTargetSeriesModels: function () {    var seriesModels = [];    var ecModel = this.ecModel;    ecModel.eachSeries(function (seriesModel) {      if (helper.isCoordSupported(seriesModel.get('coordinateSystem'))) {        var dimName = this._dimName;        var axisModel = ecModel.queryComponents({          mainType: dimName + 'Axis',          index: seriesModel.get(dimName + 'AxisIndex'),          id: seriesModel.get(dimName + 'AxisId')        })[0];        if (this._axisIndex === (axisModel && axisModel.componentIndex)) {          seriesModels.push(seriesModel);        }      }    }, this);    return seriesModels;  },  getAxisModel: function () {    return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);  },  getOtherAxisModel: function () {    var axisDim = this._dimName;    var ecModel = this.ecModel;    var axisModel = this.getAxisModel();    var isCartesian = axisDim === 'x' || axisDim === 'y';    var otherAxisDim;    var coordSysIndexName;    if (isCartesian) {      coordSysIndexName = 'gridIndex';      otherAxisDim = axisDim === 'x' ? 'y' : 'x';    } else {      coordSysIndexName = 'polarIndex';      otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';    }    var foundOtherAxisModel;    ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {      if ((otherAxisModel.get(coordSysIndexName) || 0) === (axisModel.get(coordSysIndexName) || 0)) {        foundOtherAxisModel = otherAxisModel;      }    });    return foundOtherAxisModel;  },  getMinMaxSpan: function () {    return zrUtil.clone(this._minMaxSpan);  },  /**   * Only calculate by given range and this._dataExtent, do not change anything.   *   * @param {Object} opt   * @param {number} [opt.start]   * @param {number} [opt.end]   * @param {number} [opt.startValue]   * @param {number} [opt.endValue]   */  calculateDataWindow: function (opt) {    var dataExtent = this._dataExtent;    var axisModel = this.getAxisModel();    var scale = axisModel.axis.scale;    var rangePropMode = this._dataZoomModel.getRangePropMode();    var percentExtent = [0, 100];    var percentWindow = [];    var valueWindow = [];    var hasPropModeValue;    each(['start', 'end'], function (prop, idx) {      var boundPercent = opt[prop];      var boundValue = opt[prop + 'Value']; // Notice: dataZoom is based either on `percentProp` ('start', 'end') or      // on `valueProp` ('startValue', 'endValue'). (They are based on the data extent      // but not min/max of axis, which will be calculated by data window then).      // The former one is suitable for cases that a dataZoom component controls multiple      // axes with different unit or extent, and the latter one is suitable for accurate      // zoom by pixel (e.g., in dataZoomSelect).      // we use `getRangePropMode()` to mark which prop is used. `rangePropMode` is updated      // only when setOption or dispatchAction, otherwise it remains its original value.      // (Why not only record `percentProp` and always map to `valueProp`? Because      // the map `valueProp` -> `percentProp` -> `valueProp` probably not the original      // `valueProp`. consider two axes constrolled by one dataZoom. They have different      // data extent. All of values that are overflow the `dataExtent` will be calculated      // to percent '100%').      if (rangePropMode[idx] === 'percent') {        boundPercent == null && (boundPercent = percentExtent[idx]); // Use scale.parse to math round for category or time axis.        boundValue = scale.parse(numberUtil.linearMap(boundPercent, percentExtent, dataExtent));      } else {        hasPropModeValue = true;        boundValue = boundValue == null ? dataExtent[idx] : scale.parse(boundValue); // Calculating `percent` from `value` may be not accurate, because        // This calculation can not be inversed, because all of values that        // are overflow the `dataExtent` will be calculated to percent '100%'        boundPercent = numberUtil.linearMap(boundValue, dataExtent, percentExtent);      } // valueWindow[idx] = round(boundValue);      // percentWindow[idx] = round(boundPercent);      valueWindow[idx] = boundValue;      percentWindow[idx] = boundPercent;    });    asc(valueWindow);    asc(percentWindow); // The windows from user calling of `dispatchAction` might be out of the extent,    // or do not obey the `min/maxSpan`, `min/maxValueSpan`. But we dont restrict window    // by `zoomLock` here, because we see `zoomLock` just as a interaction constraint,    // where API is able to initialize/modify the window size even though `zoomLock`    // specified.    var spans = this._minMaxSpan;    hasPropModeValue ? restrictSet(valueWindow, percentWindow, dataExtent, percentExtent, false) : restrictSet(percentWindow, valueWindow, percentExtent, dataExtent, true);    function restrictSet(fromWindow, toWindow, fromExtent, toExtent, toValue) {      var suffix = toValue ? 'Span' : 'ValueSpan';      sliderMove(0, fromWindow, fromExtent, 'all', spans['min' + suffix], spans['max' + suffix]);      for (var i = 0; i < 2; i++) {        toWindow[i] = numberUtil.linearMap(fromWindow[i], fromExtent, toExtent, true);        toValue && (toWindow[i] = scale.parse(toWindow[i]));      }    }    return {      valueWindow: valueWindow,      percentWindow: percentWindow    };  },  /**   * Notice: reset should not be called before series.restoreData() called,   * so it is recommanded to be called in "process stage" but not "model init   * stage".   *   * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel   */  reset: function (dataZoomModel) {    if (dataZoomModel !== this._dataZoomModel) {      return;    }    var targetSeries = this.getTargetSeriesModels(); // Culculate data window and data extent, and record them.    this._dataExtent = calculateDataExtent(this, this._dimName, targetSeries); // this.hasSeriesStacked = false;    // each(targetSeries, function (series) {    // var data = series.getData();    // var dataDim = data.mapDimension(this._dimName);    // var stackedDimension = data.getCalculationInfo('stackedDimension');    // if (stackedDimension && stackedDimension === dataDim) {    // this.hasSeriesStacked = true;    // }    // }, this);    // `calculateDataWindow` uses min/maxSpan.    setMinMaxSpan(this);    var dataWindow = this.calculateDataWindow(dataZoomModel.settledOption);    this._valueWindow = dataWindow.valueWindow;    this._percentWindow = dataWindow.percentWindow; // Update axis setting then.    setAxisModel(this);  },  /**   * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel   */  restore: function (dataZoomModel) {    if (dataZoomModel !== this._dataZoomModel) {      return;    }    this._valueWindow = this._percentWindow = null;    setAxisModel(this, true);  },  /**   * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel   */  filterData: function (dataZoomModel, api) {    if (dataZoomModel !== this._dataZoomModel) {      return;    }    var axisDim = this._dimName;    var seriesModels = this.getTargetSeriesModels();    var filterMode = dataZoomModel.get('filterMode');    var valueWindow = this._valueWindow;    if (filterMode === 'none') {      return;    } // FIXME    // Toolbox may has dataZoom injected. And if there are stacked bar chart    // with NaN data, NaN will be filtered and stack will be wrong.    // So we need to force the mode to be set empty.    // In fect, it is not a big deal that do not support filterMode-'filter'    // when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis    // selection" some day, which might need "adapt to data extent on the    // otherAxis", which is disabled by filterMode-'empty'.    // But currently, stack has been fixed to based on value but not index,    // so this is not an issue any more.    // var otherAxisModel = this.getOtherAxisModel();    // if (dataZoomModel.get('$fromToolbox')    //     && otherAxisModel    //     && otherAxisModel.hasSeriesStacked    // ) {    //     filterMode = 'empty';    // }    // TODO    // filterMode 'weakFilter' and 'empty' is not optimized for huge data yet.    each(seriesModels, function (seriesModel) {      var seriesData = seriesModel.getData();      var dataDims = seriesData.mapDimension(axisDim, true);      if (!dataDims.length) {        return;      }      if (filterMode === 'weakFilter') {        seriesData.filterSelf(function (dataIndex) {          var leftOut;          var rightOut;          var hasValue;          for (var i = 0; i < dataDims.length; i++) {            var value = seriesData.get(dataDims[i], dataIndex);            var thisHasValue = !isNaN(value);            var thisLeftOut = value < valueWindow[0];            var thisRightOut = value > valueWindow[1];            if (thisHasValue && !thisLeftOut && !thisRightOut) {              return true;            }            thisHasValue && (hasValue = true);            thisLeftOut && (leftOut = true);            thisRightOut && (rightOut = true);          } // If both left out and right out, do not filter.          return hasValue && leftOut && rightOut;        });      } else {        each(dataDims, function (dim) {          if (filterMode === 'empty') {            seriesModel.setData(seriesData = seriesData.map(dim, function (value) {              return !isInWindow(value) ? NaN : value;            }));          } else {            var range = {};            range[dim] = valueWindow; // console.time('select');            seriesData.selectRange(range); // console.timeEnd('select');          }        });      }      each(dataDims, function (dim) {        seriesData.setApproximateExtent(valueWindow, dim);      });    });    function isInWindow(value) {      return value >= valueWindow[0] && value <= valueWindow[1];    }  }};function calculateDataExtent(axisProxy, axisDim, seriesModels) {  var dataExtent = [Infinity, -Infinity];  each(seriesModels, function (seriesModel) {    var seriesData = seriesModel.getData();    if (seriesData) {      each(seriesData.mapDimension(axisDim, true), function (dim) {        var seriesExtent = seriesData.getApproximateExtent(dim);        seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);        seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);      });    }  });  if (dataExtent[1] < dataExtent[0]) {    dataExtent = [NaN, NaN];  } // It is important to get "consistent" extent when more then one axes is  // controlled by a `dataZoom`, otherwise those axes will not be synchronized  // when zooming. But it is difficult to know what is "consistent", considering  // axes have different type or even different meanings (For example, two  // time axes are used to compare data of the same date in different years).  // So basically dataZoom just obtains extent by series.data (in category axis  // extent can be obtained from axis.data).  // Nevertheless, user can set min/max/scale on axes to make extent of axes  // consistent.  fixExtentByAxis(axisProxy, dataExtent);  return dataExtent;}function fixExtentByAxis(axisProxy, dataExtent) {  var axisModel = axisProxy.getAxisModel();  var min = axisModel.getMin(true); // For category axis, if min/max/scale are not set, extent is determined  // by axis.data by default.  var isCategoryAxis = axisModel.get('type') === 'category';  var axisDataLen = isCategoryAxis && axisModel.getCategories().length;  if (min != null && min !== 'dataMin' && typeof min !== 'function') {    dataExtent[0] = min;  } else if (isCategoryAxis) {    dataExtent[0] = axisDataLen > 0 ? 0 : NaN;  }  var max = axisModel.getMax(true);  if (max != null && max !== 'dataMax' && typeof max !== 'function') {    dataExtent[1] = max;  } else if (isCategoryAxis) {    dataExtent[1] = axisDataLen > 0 ? axisDataLen - 1 : NaN;  }  if (!axisModel.get('scale', true)) {    dataExtent[0] > 0 && (dataExtent[0] = 0);    dataExtent[1] < 0 && (dataExtent[1] = 0);  } // For value axis, if min/max/scale are not set, we just use the extent obtained  // by series data, which may be a little different from the extent calculated by  // `axisHelper.getScaleExtent`. But the different just affects the experience a  // little when zooming. So it will not be fixed until some users require it strongly.  return dataExtent;}function setAxisModel(axisProxy, isRestore) {  var axisModel = axisProxy.getAxisModel();  var percentWindow = axisProxy._percentWindow;  var valueWindow = axisProxy._valueWindow;  if (!percentWindow) {    return;  } // [0, 500]: arbitrary value, guess axis extent.  var precision = numberUtil.getPixelPrecision(valueWindow, [0, 500]);  precision = Math.min(precision, 20); // isRestore or isFull  var useOrigin = isRestore || percentWindow[0] === 0 && percentWindow[1] === 100;  axisModel.setRange(useOrigin ? null : +valueWindow[0].toFixed(precision), useOrigin ? null : +valueWindow[1].toFixed(precision));}function setMinMaxSpan(axisProxy) {  var minMaxSpan = axisProxy._minMaxSpan = {};  var dataZoomModel = axisProxy._dataZoomModel;  var dataExtent = axisProxy._dataExtent;  each(['min', 'max'], function (minMax) {    var percentSpan = dataZoomModel.get(minMax + 'Span');    var valueSpan = dataZoomModel.get(minMax + 'ValueSpan');    valueSpan != null && (valueSpan = axisProxy.getAxisModel().axis.scale.parse(valueSpan)); // minValueSpan and maxValueSpan has higher priority than minSpan and maxSpan    if (valueSpan != null) {      percentSpan = numberUtil.linearMap(dataExtent[0] + valueSpan, dataExtent, [0, 100], true);    } else if (percentSpan != null) {      valueSpan = numberUtil.linearMap(percentSpan, [0, 100], dataExtent, true) - dataExtent[0];    }    minMaxSpan[minMax + 'Span'] = percentSpan;    minMaxSpan[minMax + 'ValueSpan'] = valueSpan;  });}var _default = AxisProxy;module.exports = _default;
 |