UNPKG

echarts

Version:

A powerful charting and visualization library for browser

392 lines (328 loc) 13 kB
/** * 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; });