ThemeRiverSeries.js 8.74 KB

/*
* 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 SeriesModel = require("../../model/Series");

var createDimensions = require("../../data/helper/createDimensions");

var _dimensionHelper = require("../../data/helper/dimensionHelper");

var getDimensionTypeByAxis = _dimensionHelper.getDimensionTypeByAxis;

var List = require("../../data/List");

var zrUtil = require("zrender/lib/core/util");

var _model = require("../../util/model");

var groupData = _model.groupData;

var _format = require("../../util/format");

var encodeHTML = _format.encodeHTML;

/*
* 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.
*/

/**
 * @file  Define the themeRiver view's series model
 * @author Deqing Li(annong035@gmail.com)
 */
var DATA_NAME_INDEX = 2;
var ThemeRiverSeries = SeriesModel.extend({
  type: 'series.themeRiver',
  dependencies: ['singleAxis'],

  /**
   * @readOnly
   * @type {module:zrender/core/util#HashMap}
   */
  nameMap: null,

  /**
   * @override
   */
  init: function (option) {
    // eslint-disable-next-line
    ThemeRiverSeries.superApply(this, 'init', arguments); // Put this function here is for the sake of consistency of code style.
    // Enable legend selection for each data item
    // Use a function instead of direct access because data reference may changed

    this.legendDataProvider = function () {
      return this.getRawData();
    };
  },

  /**
   * If there is no value of a certain point in the time for some event,set it value to 0.
   *
   * @param {Array} data  initial data in the option
   * @return {Array}
   */
  fixData: function (data) {
    var rawDataLength = data.length; // grouped data by name

    var groupResult = groupData(data, function (item) {
      return item[2];
    });
    var layData = [];
    groupResult.buckets.each(function (items, key) {
      layData.push({
        name: key,
        dataList: items
      });
    });
    var layerNum = layData.length;
    var largestLayer = -1;
    var index = -1;

    for (var i = 0; i < layerNum; ++i) {
      var len = layData[i].dataList.length;

      if (len > largestLayer) {
        largestLayer = len;
        index = i;
      }
    }

    for (var k = 0; k < layerNum; ++k) {
      if (k === index) {
        continue;
      }

      var name = layData[k].name;

      for (var j = 0; j < largestLayer; ++j) {
        var timeValue = layData[index].dataList[j][0];
        var length = layData[k].dataList.length;
        var keyIndex = -1;

        for (var l = 0; l < length; ++l) {
          var value = layData[k].dataList[l][0];

          if (value === timeValue) {
            keyIndex = l;
            break;
          }
        }

        if (keyIndex === -1) {
          data[rawDataLength] = [];
          data[rawDataLength][0] = timeValue;
          data[rawDataLength][1] = 0;
          data[rawDataLength][2] = name;
          rawDataLength++;
        }
      }
    }

    return data;
  },

  /**
   * @override
   * @param  {Object} option  the initial option that user gived
   * @param  {module:echarts/model/Model} ecModel  the model object for themeRiver option
   * @return {module:echarts/data/List}
   */
  getInitialData: function (option, ecModel) {
    var singleAxisModel = ecModel.queryComponents({
      mainType: 'singleAxis',
      index: this.get('singleAxisIndex'),
      id: this.get('singleAxisId')
    })[0];
    var axisType = singleAxisModel.get('type'); // filter the data item with the value of label is undefined

    var filterData = zrUtil.filter(option.data, function (dataItem) {
      return dataItem[2] !== undefined;
    }); // ??? TODO design a stage to transfer data for themeRiver and lines?

    var data = this.fixData(filterData || []);
    var nameList = [];
    var nameMap = this.nameMap = zrUtil.createHashMap();
    var count = 0;

    for (var i = 0; i < data.length; ++i) {
      nameList.push(data[i][DATA_NAME_INDEX]);

      if (!nameMap.get(data[i][DATA_NAME_INDEX])) {
        nameMap.set(data[i][DATA_NAME_INDEX], count);
        count++;
      }
    }

    var dimensionsInfo = createDimensions(data, {
      coordDimensions: ['single'],
      dimensionsDefine: [{
        name: 'time',
        type: getDimensionTypeByAxis(axisType)
      }, {
        name: 'value',
        type: 'float'
      }, {
        name: 'name',
        type: 'ordinal'
      }],
      encodeDefine: {
        single: 0,
        value: 1,
        itemName: 2
      }
    });
    var list = new List(dimensionsInfo, this);
    list.initData(data);
    return list;
  },

  /**
   * The raw data is divided into multiple layers and each layer
   *     has same name.
   *
   * @return {Array.<Array.<number>>}
   */
  getLayerSeries: function () {
    var data = this.getData();
    var lenCount = data.count();
    var indexArr = [];

    for (var i = 0; i < lenCount; ++i) {
      indexArr[i] = i;
    }

    var timeDim = data.mapDimension('single'); // data group by name

    var groupResult = groupData(indexArr, function (index) {
      return data.get('name', index);
    });
    var layerSeries = [];
    groupResult.buckets.each(function (items, key) {
      items.sort(function (index1, index2) {
        return data.get(timeDim, index1) - data.get(timeDim, index2);
      });
      layerSeries.push({
        name: key,
        indices: items
      });
    });
    return layerSeries;
  },

  /**
   * Get data indices for show tooltip content
   *
   * @param {Array.<string>|string} dim  single coordinate dimension
   * @param {number} value axis value
   * @param {module:echarts/coord/single/SingleAxis} baseAxis  single Axis used
   *     the themeRiver.
   * @return {Object} {dataIndices, nestestValue}
   */
  getAxisTooltipData: function (dim, value, baseAxis) {
    if (!zrUtil.isArray(dim)) {
      dim = dim ? [dim] : [];
    }

    var data = this.getData();
    var layerSeries = this.getLayerSeries();
    var indices = [];
    var layerNum = layerSeries.length;
    var nestestValue;

    for (var i = 0; i < layerNum; ++i) {
      var minDist = Number.MAX_VALUE;
      var nearestIdx = -1;
      var pointNum = layerSeries[i].indices.length;

      for (var j = 0; j < pointNum; ++j) {
        var theValue = data.get(dim[0], layerSeries[i].indices[j]);
        var dist = Math.abs(theValue - value);

        if (dist <= minDist) {
          nestestValue = theValue;
          minDist = dist;
          nearestIdx = layerSeries[i].indices[j];
        }
      }

      indices.push(nearestIdx);
    }

    return {
      dataIndices: indices,
      nestestValue: nestestValue
    };
  },

  /**
   * @override
   * @param {number} dataIndex  index of data
   */
  formatTooltip: function (dataIndex) {
    var data = this.getData();
    var htmlName = data.getName(dataIndex);
    var htmlValue = data.get(data.mapDimension('value'), dataIndex);

    if (isNaN(htmlValue) || htmlValue == null) {
      htmlValue = '-';
    }

    return encodeHTML(htmlName + ' : ' + htmlValue);
  },
  defaultOption: {
    zlevel: 0,
    z: 2,
    coordinateSystem: 'singleAxis',
    // gap in axis's orthogonal orientation
    boundaryGap: ['10%', '10%'],
    // legendHoverLink: true,
    singleAxisIndex: 0,
    animationEasing: 'linear',
    label: {
      margin: 4,
      show: true,
      position: 'left',
      color: '#000',
      fontSize: 11
    },
    emphasis: {
      label: {
        show: true
      }
    }
  }
});
var _default = ThemeRiverSeries;
module.exports = _default;