text: /**
   * Parallel Coordinates
   * <https://en.wikipedia.org/wiki/Parallel_coordinates>
   */
  define(function(require) {
  
      var layout = require('../../util/layout');
      var axisHelper = require('../../coord/axisHelper');
      var zrUtil = require('zrender/core/util');
      var ParallelAxis = require('./ParallelAxis');
      var graphic = require('../../util/graphic');
      var matrix = require('zrender/core/matrix');
  
      var each = zrUtil.each;
  
      var PI = Math.PI;
  
      function Parallel(parallelModel, ecModel, api) {
  
          /**
           * key: dimension
           * @type {Object.<string, module:echarts/coord/parallel/Axis>}
           * @private
           */
          this._axesMap = {};
  
          /**
           * key: dimension
           * value: {position: [], rotation, }
           * @type {Object.<string, Object>}
           * @private
           */
          this._axesLayout = {};
  
          /**
           * Always follow axis order.
           * @type {Array.<string>}
           * @readOnly
           */
          this.dimensions = parallelModel.dimensions;
  
          /**
           * @type {module:zrender/core/BoundingRect}
           */
          this._rect;
  
          /**
           * @type {module:echarts/coord/parallel/ParallelModel}
           */
          this._model = parallelModel;
  
          this._init(parallelModel, ecModel, api);
      }
  
      Parallel.prototype = {
  
          type: 'parallel',
  
          constructor: Parallel,
  
          /**
           * Initialize cartesian coordinate systems
           * @private
           */
          _init: function (parallelModel, ecModel, api) {
  
              var dimensions = parallelModel.dimensions;
              var parallelAxisIndex = parallelModel.parallelAxisIndex;
  
              each(dimensions, function (dim, idx) {
  
                  var axisIndex = parallelAxisIndex[idx];
                  var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
  
                  var axis = this._axesMap[dim] = new ParallelAxis(
                      dim,
                      axisHelper.createScaleByModel(axisModel),
                      [0, 0],
                      axisModel.get('type'),
                      axisIndex
                  );
  
                  var isCategory = axis.type === 'category';
                  axis.onBand = isCategory && axisModel.get('boundaryGap');
                  axis.inverse = axisModel.get('inverse');
  
                  // Inject axis into axisModel
                  axisModel.axis = axis;
  
                  // Inject axisModel into axis
                  axis.model = axisModel;
              }, this);
          },
  
          /**
           * Update axis scale after data processed
           * @param  {module:echarts/model/Global} ecModel
           * @param  {module:echarts/ExtensionAPI} api
           */
          update: function (ecModel, api) {
              this._updateAxesFromSeries(this._model, ecModel);
          },
  
          /**
           * Update properties from series
           * @private
           */
          _updateAxesFromSeries: function (parallelModel, ecModel) {
              ecModel.eachSeries(function (seriesModel) {
  
                  if (!parallelModel.contains(seriesModel, ecModel)) {
                      return;
                  }
  
                  var data = seriesModel.getData();
  
                  each(this.dimensions, function (dim) {
                      var axis = this._axesMap[dim];
                      axis.scale.unionExtent(data.getDataExtent(dim));
                      axisHelper.niceScaleExtent(axis, axis.model);
                  }, this);
              }, this);
          },
  
          /**
           * Resize the parallel coordinate system.
           * @param {module:echarts/coord/parallel/ParallelModel} parallelModel
           * @param {module:echarts/ExtensionAPI} api
           */
          resize: function (parallelModel, api) {
              this._rect = layout.getLayoutRect(
                  parallelModel.getBoxLayoutParams(),
                  {
                      width: api.getWidth(),
                      height: api.getHeight()
                  }
              );
  
              this._layoutAxes(parallelModel);
          },
  
          /**
           * @return {module:zrender/core/BoundingRect}
           */
          getRect: function () {
              return this._rect;
          },
  
          /**
           * @private
           */
          _layoutAxes: function (parallelModel) {
              var rect = this._rect;
              var layout = parallelModel.get('layout');
              var axes = this._axesMap;
              var dimensions = this.dimensions;
  
              var size = [rect.width, rect.height];
              var sizeIdx = layout === 'horizontal' ? 0 : 1;
              var layoutLength = size[sizeIdx];
              var axisLength = size[1 - sizeIdx];
              var axisExtent = [0, axisLength];
  
              each(axes, function (axis) {
                  var idx = axis.inverse ? 1 : 0;
                  axis.setExtent(axisExtent[idx], axisExtent[1 - idx]);
              });
  
              var axisExpandable = parallelModel.get('axisExpandable');
              var axisExpandWidth = parallelModel.get('axisExpandWidth');
              var axisExpandCenter = parallelModel.get('axisExpandCenter');
              var axisExpandCount = parallelModel.get('axisExpandCount') || 0;
              var axisExpandWindow;
  
              if (axisExpandCenter != null) {
                  // Clamp
                  var left = Math.max(0, Math.floor(axisExpandCenter - (axisExpandCount - 1) / 2));
                  var right = left + axisExpandCount - 1;
                  if (right >= dimensions.length) {
                      right = dimensions.length - 1;
                      left = Math.max(0, Math.floor(right - axisExpandCount + 1));
                  }
                  axisExpandWindow = [left, right];
              }
  
              var calcPos = (axisExpandable && axisExpandWindow && axisExpandWidth)
                  ? function (axisIndex, layoutLength, axisCount) {
                      var peekIntervalCount = axisExpandWindow[1] - axisExpandWindow[0];
                      var otherWidth = (
                          layoutLength - axisExpandWidth * peekIntervalCount
                      ) / (axisCount - 1 - peekIntervalCount);
  
                      var position;
  
                      if (axisIndex < axisExpandWindow[0]) {
                          position = (axisIndex - 1) * otherWidth;
                      }
                      else if (axisIndex <= axisExpandWindow[1]) {
                          position = axisExpandWindow[0] * otherWidth
                              + (axisIndex - axisExpandWindow[0]) * axisExpandWidth;
                      }
                      else if (axisIndex === axisCount - 1) {
                          position = layoutLength;
                      }
                      else {
                          position = axisExpandWindow[0] * otherWidth
                              + peekIntervalCount * axisExpandWidth
                              + (axisIndex - axisExpandWindow[1]) * otherWidth;
                      }
  
                      return {
                          position: position,
                          axisNameAvailableWidth: (
                              axisExpandWindow[0] < axisIndex && axisIndex < axisExpandWindow[1]
                          ) ? axisExpandWidth : otherWidth
                      };
                  }
                  : function (axisIndex, layoutLength, axisCount) {
                      var step = layoutLength / (axisCount - 1);
                      return {
                          position: step * axisIndex,
                          axisNameAvailableWidth: step
                      };
                  };
  
              each(dimensions, function (dim, idx) {
                  var posInfo = calcPos(idx, layoutLength, dimensions.length);
  
                  var positionTable = {
                      horizontal: {
                          x: posInfo.position,
                          y: axisLength
                      },
                      vertical: {
                          x: 0,
                          y: posInfo.position
                      }
                  };
                  var rotationTable = {
                      horizontal: PI / 2,
                      vertical: 0
                  };
  
                  var position = [
                      positionTable[layout].x + rect.x,
                      positionTable[layout].y + rect.y
                  ];
  
                  var rotation = rotationTable[layout];
                  var transform = matrix.create();
                  matrix.rotate(transform, transform, rotation);
                  matrix.translate(transform, transform, position);
  
                  // TODO
                  // tick等排布信息。
  
                  // TODO
                  // 根据axis order 更新 dimensions顺序。
  
                  this._axesLayout[dim] = {
                      position: position,
                      rotation: rotation,
                      transform: transform,
                      axisNameAvailableWidth: posInfo.axisNameAvailableWidth,
                      tickDirection: 1,
                      labelDirection: 1,
                      axisExpandWindow: axisExpandWindow
                  };
              }, this);
          },
  
          /**
           * Get axis by dim.
           * @param {string} dim
           * @return {module:echarts/coord/parallel/ParallelAxis} [description]
           */
          getAxis: function (dim) {
              return this._axesMap[dim];
          },
  
          /**
           * Convert a dim value of a single item of series data to Point.
           * @param {*} value
           * @param {string} dim
           * @return {Array}
           */
          dataToPoint: function (value, dim) {
              return this.axisCoordToPoint(
                  this._axesMap[dim].dataToCoord(value),
                  dim
              );
          },
  
          /**
           * Travel data for one time, get activeState of each data item.
           * @param {module:echarts/data/List} data
           * @param {Functio} cb param: {string} activeState 'active' or 'inactive' or 'normal'
           *                            {number} dataIndex
           * @param {Object} context
           */
          eachActiveState: function (data, callback, context) {
              var dimensions = this.dimensions;
              var axesMap = this._axesMap;
              var hasActiveSet = this.hasAxisbrushed();
  
              for (var i = 0, len = data.count(); i < len; i++) {
                  var values = data.getValues(dimensions, i);
                  var activeState;
  
                  if (!hasActiveSet) {
                      activeState = 'normal';
                  }
                  else {
                      activeState = 'active';
                      for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
                          var dimName = dimensions[j];
                          var state = axesMap[dimName].model.getActiveState(values[j], j);
  
                          if (state === 'inactive') {
                              activeState = 'inactive';
                              break;
                          }
                      }
                  }
  
                  callback.call(context, activeState, i);
              }
          },
  
          /**
           * Whether has any activeSet.
           * @return {boolean}
           */
          hasAxisbrushed: function () {
              var dimensions = this.dimensions;
              var axesMap = this._axesMap;
              var hasActiveSet = false;
  
              for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
                  if (axesMap[dimensions[j]].model.getActiveState() !== 'normal') {
                      hasActiveSet = true;
                  }
              }
  
              return hasActiveSet;
          },
  
          /**
           * Convert coords of each axis to Point.
           *  Return point. For example: [10, 20]
           * @param {Array.<number>} coords
           * @param {string} dim
           * @return {Array.<number>}
           */
          axisCoordToPoint: function (coord, dim) {
              var axisLayout = this._axesLayout[dim];
              return graphic.applyTransform([coord, 0], axisLayout.transform);
          },
  
          /**
           * Get axis layout.
           */
          getAxisLayout: function (dim) {
              return zrUtil.clone(this._axesLayout[dim]);
          },
  
          findClosestAxisDim: function (point) {
              var axisDim;
              var minDist = Infinity;
  
              zrUtil.each(this._axesLayout, function (axisLayout, dim) {
                  var localPoint = graphic.applyTransform(point, axisLayout.transform, true);
                  var extent = this._axesMap[dim].getExtent();
  
                  if (localPoint[0] < extent[0] || localPoint[0] > extent[1]) {
                      return;
                  }
  
                  var dist = Math.abs(localPoint[1]);
                  if (dist < minDist) {
                      minDist = dist;
                      axisDim = dim;
                  }
              }, this);
  
              return axisDim;
          }
  
      };
  
      return Parallel;
  });