echarts
Version:
A powerful charting and visualization library for browser
392 lines (328 loc) • 13 kB
JavaScript
/**
* 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;
});