| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 | 
/** 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 _config = require("../config");var __DEV__ = _config.__DEV__;var zrUtil = require("zrender/lib/core/util");var env = require("zrender/lib/core/env");var _format = require("../util/format");var formatTime = _format.formatTime;var encodeHTML = _format.encodeHTML;var addCommas = _format.addCommas;var getTooltipMarker = _format.getTooltipMarker;var modelUtil = require("../util/model");var ComponentModel = require("./Component");var colorPaletteMixin = require("./mixin/colorPalette");var dataFormatMixin = require("../model/mixin/dataFormat");var _layout = require("../util/layout");var getLayoutParams = _layout.getLayoutParams;var mergeLayoutParam = _layout.mergeLayoutParam;var _task = require("../stream/task");var createTask = _task.createTask;var _sourceHelper = require("../data/helper/sourceHelper");var prepareSource = _sourceHelper.prepareSource;var getSource = _sourceHelper.getSource;var _dataProvider = require("../data/helper/dataProvider");var retrieveRawValue = _dataProvider.retrieveRawValue;/** 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 inner = modelUtil.makeInner();var SeriesModel = ComponentModel.extend({  type: 'series.__base__',  /**   * @readOnly   */  seriesIndex: 0,  // coodinateSystem will be injected in the echarts/CoordinateSystem  coordinateSystem: null,  /**   * @type {Object}   * @protected   */  defaultOption: null,  /**   * legend visual provider to the legend component   * @type {Object}   */  // PENDING  legendVisualProvider: null,  /**   * Access path of color for visual   */  visualColorAccessPath: 'itemStyle.color',  /**   * Access path of borderColor for visual   */  visualBorderColorAccessPath: 'itemStyle.borderColor',  /**   * Support merge layout params.   * Only support 'box' now (left/right/top/bottom/width/height).   * @type {string|Object} Object can be {ignoreSize: true}   * @readOnly   */  layoutMode: null,  init: function (option, parentModel, ecModel, extraOpt) {    /**     * @type {number}     * @readOnly     */    this.seriesIndex = this.componentIndex;    this.dataTask = createTask({      count: dataTaskCount,      reset: dataTaskReset    });    this.dataTask.context = {      model: this    };    this.mergeDefaultAndTheme(option, ecModel);    prepareSource(this);    var data = this.getInitialData(option, ecModel);    wrapData(data, this);    this.dataTask.context.data = data;    /**     * @type {module:echarts/data/List|module:echarts/data/Tree|module:echarts/data/Graph}     * @private     */    inner(this).dataBeforeProcessed = data; // If we reverse the order (make data firstly, and then make    // dataBeforeProcessed by cloneShallow), cloneShallow will    // cause data.graph.data !== data when using    // module:echarts/data/Graph or module:echarts/data/Tree.    // See module:echarts/data/helper/linkList    // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model    // init or merge stage, because the data can be restored. So we do not `restoreData`    // and `setData` here, which forbids calling `seriesModel.getData()` in this stage.    // Call `seriesModel.getRawData()` instead.    // this.restoreData();    autoSeriesName(this);  },  /**   * Util for merge default and theme to option   * @param  {Object} option   * @param  {module:echarts/model/Global} ecModel   */  mergeDefaultAndTheme: function (option, ecModel) {    var layoutMode = this.layoutMode;    var inputPositionParams = layoutMode ? getLayoutParams(option) : {}; // Backward compat: using subType on theme.    // But if name duplicate between series subType    // (for example: parallel) add component mainType,    // add suffix 'Series'.    var themeSubType = this.subType;    if (ComponentModel.hasClass(themeSubType)) {      themeSubType += 'Series';    }    zrUtil.merge(option, ecModel.getTheme().get(this.subType));    zrUtil.merge(option, this.getDefaultOption()); // Default label emphasis `show`    modelUtil.defaultEmphasis(option, 'label', ['show']);    this.fillDataTextStyle(option.data);    if (layoutMode) {      mergeLayoutParam(option, inputPositionParams, layoutMode);    }  },  mergeOption: function (newSeriesOption, ecModel) {    // this.settingTask.dirty();    newSeriesOption = zrUtil.merge(this.option, newSeriesOption, true);    this.fillDataTextStyle(newSeriesOption.data);    var layoutMode = this.layoutMode;    if (layoutMode) {      mergeLayoutParam(this.option, newSeriesOption, layoutMode);    }    prepareSource(this);    var data = this.getInitialData(newSeriesOption, ecModel);    wrapData(data, this);    this.dataTask.dirty();    this.dataTask.context.data = data;    inner(this).dataBeforeProcessed = data;    autoSeriesName(this);  },  fillDataTextStyle: function (data) {    // Default data label emphasis `show`    // FIXME Tree structure data ?    // FIXME Performance ?    if (data && !zrUtil.isTypedArray(data)) {      var props = ['show'];      for (var i = 0; i < data.length; i++) {        if (data[i] && data[i].label) {          modelUtil.defaultEmphasis(data[i], 'label', props);        }      }    }  },  /**   * Init a data structure from data related option in series   * Must be overwritten   */  getInitialData: function () {},  /**   * Append data to list   * @param {Object} params   * @param {Array|TypedArray} params.data   */  appendData: function (params) {    // FIXME ???    // (1) If data from dataset, forbidden append.    // (2) support append data of dataset.    var data = this.getRawData();    data.appendData(params.data);  },  /**   * Consider some method like `filter`, `map` need make new data,   * We should make sure that `seriesModel.getData()` get correct   * data in the stream procedure. So we fetch data from upstream   * each time `task.perform` called.   * @param {string} [dataType]   * @return {module:echarts/data/List}   */  getData: function (dataType) {    var task = getCurrentTask(this);    if (task) {      var data = task.context.data;      return dataType == null ? data : data.getLinkedData(dataType);    } else {      // When series is not alive (that may happen when click toolbox      // restore or setOption with not merge mode), series data may      // be still need to judge animation or something when graphic      // elements want to know whether fade out.      return inner(this).data;    }  },  /**   * @param {module:echarts/data/List} data   */  setData: function (data) {    var task = getCurrentTask(this);    if (task) {      var context = task.context; // Consider case: filter, data sample.      if (context.data !== data && task.modifyOutputEnd) {        task.setOutputEnd(data.count());      }      context.outputData = data; // Caution: setData should update context.data,      // Because getData may be called multiply in a      // single stage and expect to get the data just      // set. (For example, AxisProxy, x y both call      // getData and setDate sequentially).      // So the context.data should be fetched from      // upstream each time when a stage starts to be      // performed.      if (task !== this.dataTask) {        context.data = data;      }    }    inner(this).data = data;  },  /**   * @see {module:echarts/data/helper/sourceHelper#getSource}   * @return {module:echarts/data/Source} source   */  getSource: function () {    return getSource(this);  },  /**   * Get data before processed   * @return {module:echarts/data/List}   */  getRawData: function () {    return inner(this).dataBeforeProcessed;  },  /**   * Get base axis if has coordinate system and has axis.   * By default use coordSys.getBaseAxis();   * Can be overrided for some chart.   * @return {type} description   */  getBaseAxis: function () {    var coordSys = this.coordinateSystem;    return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();  },  // FIXME  /**   * Default tooltip formatter   *   * @param {number} dataIndex   * @param {boolean} [multipleSeries=false]   * @param {number} [dataType]   * @param {string} [renderMode='html'] valid values: 'html' and 'richText'.   *                                     'html' is used for rendering tooltip in extra DOM form, and the result   *                                     string is used as DOM HTML content.   *                                     'richText' is used for rendering tooltip in rich text form, for those where   *                                     DOM operation is not supported.   * @return {Object} formatted tooltip with `html` and `markers`   */  formatTooltip: function (dataIndex, multipleSeries, dataType, renderMode) {    var series = this;    renderMode = renderMode || 'html';    var newLine = renderMode === 'html' ? '<br/>' : '\n';    var isRichText = renderMode === 'richText';    var markers = {};    var markerId = 0;    function formatArrayValue(value) {      // ??? TODO refactor these logic.      // check: category-no-encode-has-axis-data in dataset.html      var vertially = zrUtil.reduce(value, function (vertially, val, idx) {        var dimItem = data.getDimensionInfo(idx);        return vertially |= dimItem && dimItem.tooltip !== false && dimItem.displayName != null;      }, 0);      var result = [];      tooltipDims.length ? zrUtil.each(tooltipDims, function (dim) {        setEachItem(retrieveRawValue(data, dataIndex, dim), dim);      }) // By default, all dims is used on tooltip.      : zrUtil.each(value, setEachItem);      function setEachItem(val, dim) {        var dimInfo = data.getDimensionInfo(dim); // If `dimInfo.tooltip` is not set, show tooltip.        if (!dimInfo || dimInfo.otherDims.tooltip === false) {          return;        }        var dimType = dimInfo.type;        var markName = 'sub' + series.seriesIndex + 'at' + markerId;        var dimHead = getTooltipMarker({          color: color,          type: 'subItem',          renderMode: renderMode,          markerId: markName        });        var dimHeadStr = typeof dimHead === 'string' ? dimHead : dimHead.content;        var valStr = (vertially ? dimHeadStr + encodeHTML(dimInfo.displayName || '-') + ': ' : '') + // FIXME should not format time for raw data?        encodeHTML(dimType === 'ordinal' ? val + '' : dimType === 'time' ? multipleSeries ? '' : formatTime('yyyy/MM/dd hh:mm:ss', val) : addCommas(val));        valStr && result.push(valStr);        if (isRichText) {          markers[markName] = color;          ++markerId;        }      }      var newLine = vertially ? isRichText ? '\n' : '<br/>' : '';      var content = newLine + result.join(newLine || ', ');      return {        renderMode: renderMode,        content: content,        style: markers      };    }    function formatSingleValue(val) {      // return encodeHTML(addCommas(val));      return {        renderMode: renderMode,        content: encodeHTML(addCommas(val)),        style: markers      };    }    var data = this.getData();    var tooltipDims = data.mapDimension('defaultedTooltip', true);    var tooltipDimLen = tooltipDims.length;    var value = this.getRawValue(dataIndex);    var isValueArr = zrUtil.isArray(value);    var color = data.getItemVisual(dataIndex, 'color');    if (zrUtil.isObject(color) && color.colorStops) {      color = (color.colorStops[0] || {}).color;    }    color = color || 'transparent'; // Complicated rule for pretty tooltip.    var formattedValue = tooltipDimLen > 1 || isValueArr && !tooltipDimLen ? formatArrayValue(value) : tooltipDimLen ? formatSingleValue(retrieveRawValue(data, dataIndex, tooltipDims[0])) : formatSingleValue(isValueArr ? value[0] : value);    var content = formattedValue.content;    var markName = series.seriesIndex + 'at' + markerId;    var colorEl = getTooltipMarker({      color: color,      type: 'item',      renderMode: renderMode,      markerId: markName    });    markers[markName] = color;    ++markerId;    var name = data.getName(dataIndex);    var seriesName = this.name;    if (!modelUtil.isNameSpecified(this)) {      seriesName = '';    }    seriesName = seriesName ? encodeHTML(seriesName) + (!multipleSeries ? newLine : ': ') : '';    var colorStr = typeof colorEl === 'string' ? colorEl : colorEl.content;    var html = !multipleSeries ? seriesName + colorStr + (name ? encodeHTML(name) + ': ' + content : content) : colorStr + seriesName + content;    return {      html: html,      markers: markers    };  },  /**   * @return {boolean}   */  isAnimationEnabled: function () {    if (env.node) {      return false;    }    var animationEnabled = this.getShallow('animation');    if (animationEnabled) {      if (this.getData().count() > this.getShallow('animationThreshold')) {        animationEnabled = false;      }    }    return animationEnabled;  },  restoreData: function () {    this.dataTask.dirty();  },  getColorFromPalette: function (name, scope, requestColorNum) {    var ecModel = this.ecModel; // PENDING    var color = colorPaletteMixin.getColorFromPalette.call(this, name, scope, requestColorNum);    if (!color) {      color = ecModel.getColorFromPalette(name, scope, requestColorNum);    }    return color;  },  /**   * Use `data.mapDimension(coordDim, true)` instead.   * @deprecated   */  coordDimToDataDim: function (coordDim) {    return this.getRawData().mapDimension(coordDim, true);  },  /**   * Get progressive rendering count each step   * @return {number}   */  getProgressive: function () {    return this.get('progressive');  },  /**   * Get progressive rendering count each step   * @return {number}   */  getProgressiveThreshold: function () {    return this.get('progressiveThreshold');  },  /**   * Get data indices for show tooltip content. See tooltip.   * @abstract   * @param {Array.<string>|string} dim   * @param {Array.<number>} value   * @param {module:echarts/coord/single/SingleAxis} baseAxis   * @return {Object} {dataIndices, nestestValue}.   */  getAxisTooltipData: null,  /**   * See tooltip.   * @abstract   * @param {number} dataIndex   * @return {Array.<number>} Point of tooltip. null/undefined can be returned.   */  getTooltipPosition: null,  /**   * @see {module:echarts/stream/Scheduler}   */  pipeTask: null,  /**   * Convinient for override in extended class.   * @protected   * @type {Function}   */  preventIncremental: null,  /**   * @public   * @readOnly   * @type {Object}   */  pipelineContext: null});zrUtil.mixin(SeriesModel, dataFormatMixin);zrUtil.mixin(SeriesModel, colorPaletteMixin);/** * MUST be called after `prepareSource` called * Here we need to make auto series, especially for auto legend. But we * do not modify series.name in option to avoid side effects. */function autoSeriesName(seriesModel) {  // User specified name has higher priority, otherwise it may cause  // series can not be queried unexpectedly.  var name = seriesModel.name;  if (!modelUtil.isNameSpecified(seriesModel)) {    seriesModel.name = getSeriesAutoName(seriesModel) || name;  }}function getSeriesAutoName(seriesModel) {  var data = seriesModel.getRawData();  var dataDims = data.mapDimension('seriesName', true);  var nameArr = [];  zrUtil.each(dataDims, function (dataDim) {    var dimInfo = data.getDimensionInfo(dataDim);    dimInfo.displayName && nameArr.push(dimInfo.displayName);  });  return nameArr.join(' ');}function dataTaskCount(context) {  return context.model.getRawData().count();}function dataTaskReset(context) {  var seriesModel = context.model;  seriesModel.setData(seriesModel.getRawData().cloneShallow());  return dataTaskProgress;}function dataTaskProgress(param, context) {  // Avoid repead cloneShallow when data just created in reset.  if (context.outputData && param.end > context.outputData.count()) {    context.model.getRawData().cloneShallow(context.outputData);  }} // TODO refactorfunction wrapData(data, seriesModel) {  zrUtil.each(data.CHANGABLE_METHODS, function (methodName) {    data.wrapMethod(methodName, zrUtil.curry(onDataSelfChange, seriesModel));  });}function onDataSelfChange(seriesModel) {  var task = getCurrentTask(seriesModel);  if (task) {    // Consider case: filter, selectRange    task.setOutputEnd(this.count());  }}function getCurrentTask(seriesModel) {  var scheduler = (seriesModel.ecModel || {}).scheduler;  var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid);  if (pipeline) {    // When pipline finished, the currrentTask keep the last    // task (renderTask).    var task = pipeline.currentTask;    if (task) {      var agentStubMap = task.agentStubMap;      if (agentStubMap) {        task = agentStubMap.get(seriesModel.uid);      }    }    return task;  }}var _default = SeriesModel;module.exports = _default;
 |