481 lines
14 KiB
JavaScript
481 lines
14 KiB
JavaScript
|
|
/*
|
|
* 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 zrUtil = require("zrender/lib/core/util");
|
|
|
|
var vector = require("zrender/lib/core/vector");
|
|
|
|
var symbolUtil = require("../../util/symbol");
|
|
|
|
var LinePath = require("./LinePath");
|
|
|
|
var graphic = require("../../util/graphic");
|
|
|
|
var _number = require("../../util/number");
|
|
|
|
var round = _number.round;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @module echarts/chart/helper/Line
|
|
*/
|
|
var SYMBOL_CATEGORIES = ['fromSymbol', 'toSymbol'];
|
|
|
|
function makeSymbolTypeKey(symbolCategory) {
|
|
return '_' + symbolCategory + 'Type';
|
|
}
|
|
/**
|
|
* @inner
|
|
*/
|
|
|
|
|
|
function createSymbol(name, lineData, idx) {
|
|
var symbolType = lineData.getItemVisual(idx, name);
|
|
|
|
if (!symbolType || symbolType === 'none') {
|
|
return;
|
|
}
|
|
|
|
var color = lineData.getItemVisual(idx, 'color');
|
|
var symbolSize = lineData.getItemVisual(idx, name + 'Size');
|
|
var symbolRotate = lineData.getItemVisual(idx, name + 'Rotate');
|
|
|
|
if (!zrUtil.isArray(symbolSize)) {
|
|
symbolSize = [symbolSize, symbolSize];
|
|
}
|
|
|
|
var symbolPath = symbolUtil.createSymbol(symbolType, -symbolSize[0] / 2, -symbolSize[1] / 2, symbolSize[0], symbolSize[1], color); // rotate by default if symbolRotate is not specified or NaN
|
|
|
|
symbolPath.__specifiedRotation = symbolRotate == null || isNaN(symbolRotate) ? void 0 : +symbolRotate * Math.PI / 180 || 0;
|
|
symbolPath.name = name;
|
|
return symbolPath;
|
|
}
|
|
|
|
function createLine(points) {
|
|
var line = new LinePath({
|
|
name: 'line',
|
|
subPixelOptimize: true
|
|
});
|
|
setLinePoints(line.shape, points);
|
|
return line;
|
|
}
|
|
|
|
function setLinePoints(targetShape, points) {
|
|
targetShape.x1 = points[0][0];
|
|
targetShape.y1 = points[0][1];
|
|
targetShape.x2 = points[1][0];
|
|
targetShape.y2 = points[1][1];
|
|
targetShape.percent = 1;
|
|
var cp1 = points[2];
|
|
|
|
if (cp1) {
|
|
targetShape.cpx1 = cp1[0];
|
|
targetShape.cpy1 = cp1[1];
|
|
} else {
|
|
targetShape.cpx1 = NaN;
|
|
targetShape.cpy1 = NaN;
|
|
}
|
|
}
|
|
|
|
function updateSymbolAndLabelBeforeLineUpdate() {
|
|
var lineGroup = this;
|
|
var symbolFrom = lineGroup.childOfName('fromSymbol');
|
|
var symbolTo = lineGroup.childOfName('toSymbol');
|
|
var label = lineGroup.childOfName('label'); // Quick reject
|
|
|
|
if (!symbolFrom && !symbolTo && label.ignore) {
|
|
return;
|
|
}
|
|
|
|
var invScale = 1;
|
|
var parentNode = this.parent;
|
|
|
|
while (parentNode) {
|
|
if (parentNode.scale) {
|
|
invScale /= parentNode.scale[0];
|
|
}
|
|
|
|
parentNode = parentNode.parent;
|
|
}
|
|
|
|
var line = lineGroup.childOfName('line'); // If line not changed
|
|
// FIXME Parent scale changed
|
|
|
|
if (!this.__dirty && !line.__dirty) {
|
|
return;
|
|
}
|
|
|
|
var percent = line.shape.percent;
|
|
var fromPos = line.pointAt(0);
|
|
var toPos = line.pointAt(percent);
|
|
var d = vector.sub([], toPos, fromPos);
|
|
vector.normalize(d, d);
|
|
|
|
if (symbolFrom) {
|
|
symbolFrom.attr('position', fromPos); // Fix #12388
|
|
// when symbol is set to be 'arrow' in markLine,
|
|
// symbolRotate value will be ignored, and compulsively use tangent angle.
|
|
// rotate by default if symbol rotation is not specified
|
|
|
|
var specifiedRotation = symbolFrom.__specifiedRotation;
|
|
|
|
if (specifiedRotation == null) {
|
|
var tangent = line.tangentAt(0);
|
|
symbolFrom.attr('rotation', Math.PI / 2 - Math.atan2(tangent[1], tangent[0]));
|
|
} else {
|
|
symbolFrom.attr('rotation', specifiedRotation);
|
|
}
|
|
|
|
symbolFrom.attr('scale', [invScale * percent, invScale * percent]);
|
|
}
|
|
|
|
if (symbolTo) {
|
|
symbolTo.attr('position', toPos); // Fix #12388
|
|
// when symbol is set to be 'arrow' in markLine,
|
|
// symbolRotate value will be ignored, and compulsively use tangent angle.
|
|
// rotate by default if symbol rotation is not specified
|
|
|
|
var specifiedRotation = symbolTo.__specifiedRotation;
|
|
|
|
if (specifiedRotation == null) {
|
|
var tangent = line.tangentAt(1);
|
|
symbolTo.attr('rotation', -Math.PI / 2 - Math.atan2(tangent[1], tangent[0]));
|
|
} else {
|
|
symbolTo.attr('rotation', specifiedRotation);
|
|
}
|
|
|
|
symbolTo.attr('scale', [invScale * percent, invScale * percent]);
|
|
}
|
|
|
|
if (!label.ignore) {
|
|
label.attr('position', toPos);
|
|
var textPosition;
|
|
var textAlign;
|
|
var textVerticalAlign;
|
|
var textOrigin;
|
|
var distance = label.__labelDistance;
|
|
var distanceX = distance[0] * invScale;
|
|
var distanceY = distance[1] * invScale;
|
|
var halfPercent = percent / 2;
|
|
var tangent = line.tangentAt(halfPercent);
|
|
var n = [tangent[1], -tangent[0]];
|
|
var cp = line.pointAt(halfPercent);
|
|
|
|
if (n[1] > 0) {
|
|
n[0] = -n[0];
|
|
n[1] = -n[1];
|
|
}
|
|
|
|
var dir = tangent[0] < 0 ? -1 : 1;
|
|
|
|
if (label.__position !== 'start' && label.__position !== 'end') {
|
|
var rotation = -Math.atan2(tangent[1], tangent[0]);
|
|
|
|
if (toPos[0] < fromPos[0]) {
|
|
rotation = Math.PI + rotation;
|
|
}
|
|
|
|
label.attr('rotation', rotation);
|
|
}
|
|
|
|
var dy;
|
|
|
|
switch (label.__position) {
|
|
case 'insideStartTop':
|
|
case 'insideMiddleTop':
|
|
case 'insideEndTop':
|
|
case 'middle':
|
|
dy = -distanceY;
|
|
textVerticalAlign = 'bottom';
|
|
break;
|
|
|
|
case 'insideStartBottom':
|
|
case 'insideMiddleBottom':
|
|
case 'insideEndBottom':
|
|
dy = distanceY;
|
|
textVerticalAlign = 'top';
|
|
break;
|
|
|
|
default:
|
|
dy = 0;
|
|
textVerticalAlign = 'middle';
|
|
}
|
|
|
|
switch (label.__position) {
|
|
case 'end':
|
|
textPosition = [d[0] * distanceX + toPos[0], d[1] * distanceY + toPos[1]];
|
|
textAlign = d[0] > 0.8 ? 'left' : d[0] < -0.8 ? 'right' : 'center';
|
|
textVerticalAlign = d[1] > 0.8 ? 'top' : d[1] < -0.8 ? 'bottom' : 'middle';
|
|
break;
|
|
|
|
case 'start':
|
|
textPosition = [-d[0] * distanceX + fromPos[0], -d[1] * distanceY + fromPos[1]];
|
|
textAlign = d[0] > 0.8 ? 'right' : d[0] < -0.8 ? 'left' : 'center';
|
|
textVerticalAlign = d[1] > 0.8 ? 'bottom' : d[1] < -0.8 ? 'top' : 'middle';
|
|
break;
|
|
|
|
case 'insideStartTop':
|
|
case 'insideStart':
|
|
case 'insideStartBottom':
|
|
textPosition = [distanceX * dir + fromPos[0], fromPos[1] + dy];
|
|
textAlign = tangent[0] < 0 ? 'right' : 'left';
|
|
textOrigin = [-distanceX * dir, -dy];
|
|
break;
|
|
|
|
case 'insideMiddleTop':
|
|
case 'insideMiddle':
|
|
case 'insideMiddleBottom':
|
|
case 'middle':
|
|
textPosition = [cp[0], cp[1] + dy];
|
|
textAlign = 'center';
|
|
textOrigin = [0, -dy];
|
|
break;
|
|
|
|
case 'insideEndTop':
|
|
case 'insideEnd':
|
|
case 'insideEndBottom':
|
|
textPosition = [-distanceX * dir + toPos[0], toPos[1] + dy];
|
|
textAlign = tangent[0] >= 0 ? 'right' : 'left';
|
|
textOrigin = [distanceX * dir, -dy];
|
|
break;
|
|
}
|
|
|
|
label.attr({
|
|
style: {
|
|
// Use the user specified text align and baseline first
|
|
textVerticalAlign: label.__verticalAlign || textVerticalAlign,
|
|
textAlign: label.__textAlign || textAlign
|
|
},
|
|
position: textPosition,
|
|
scale: [invScale, invScale],
|
|
origin: textOrigin
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* @constructor
|
|
* @extends {module:zrender/graphic/Group}
|
|
* @alias {module:echarts/chart/helper/Line}
|
|
*/
|
|
|
|
|
|
function Line(lineData, idx, seriesScope) {
|
|
graphic.Group.call(this);
|
|
|
|
this._createLine(lineData, idx, seriesScope);
|
|
}
|
|
|
|
var lineProto = Line.prototype; // Update symbol position and rotation
|
|
|
|
lineProto.beforeUpdate = updateSymbolAndLabelBeforeLineUpdate;
|
|
|
|
lineProto._createLine = function (lineData, idx, seriesScope) {
|
|
var seriesModel = lineData.hostModel;
|
|
var linePoints = lineData.getItemLayout(idx);
|
|
var line = createLine(linePoints);
|
|
line.shape.percent = 0;
|
|
graphic.initProps(line, {
|
|
shape: {
|
|
percent: 1
|
|
}
|
|
}, seriesModel, idx);
|
|
this.add(line);
|
|
var label = new graphic.Text({
|
|
name: 'label',
|
|
// FIXME
|
|
// Temporary solution for `focusNodeAdjacency`.
|
|
// line label do not use the opacity of lineStyle.
|
|
lineLabelOriginalOpacity: 1
|
|
});
|
|
this.add(label);
|
|
zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
|
|
var symbol = createSymbol(symbolCategory, lineData, idx); // symbols must added after line to make sure
|
|
// it will be updated after line#update.
|
|
// Or symbol position and rotation update in line#beforeUpdate will be one frame slow
|
|
|
|
this.add(symbol);
|
|
this[makeSymbolTypeKey(symbolCategory)] = lineData.getItemVisual(idx, symbolCategory);
|
|
}, this);
|
|
|
|
this._updateCommonStl(lineData, idx, seriesScope);
|
|
};
|
|
|
|
lineProto.updateData = function (lineData, idx, seriesScope) {
|
|
var seriesModel = lineData.hostModel;
|
|
var line = this.childOfName('line');
|
|
var linePoints = lineData.getItemLayout(idx);
|
|
var target = {
|
|
shape: {}
|
|
};
|
|
setLinePoints(target.shape, linePoints);
|
|
graphic.updateProps(line, target, seriesModel, idx);
|
|
zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
|
|
var symbolType = lineData.getItemVisual(idx, symbolCategory);
|
|
var key = makeSymbolTypeKey(symbolCategory); // Symbol changed
|
|
|
|
if (this[key] !== symbolType) {
|
|
this.remove(this.childOfName(symbolCategory));
|
|
var symbol = createSymbol(symbolCategory, lineData, idx);
|
|
this.add(symbol);
|
|
}
|
|
|
|
this[key] = symbolType;
|
|
}, this);
|
|
|
|
this._updateCommonStl(lineData, idx, seriesScope);
|
|
};
|
|
|
|
lineProto._updateCommonStl = function (lineData, idx, seriesScope) {
|
|
var seriesModel = lineData.hostModel;
|
|
var line = this.childOfName('line');
|
|
var lineStyle = seriesScope && seriesScope.lineStyle;
|
|
var hoverLineStyle = seriesScope && seriesScope.hoverLineStyle;
|
|
var labelModel = seriesScope && seriesScope.labelModel;
|
|
var hoverLabelModel = seriesScope && seriesScope.hoverLabelModel; // Optimization for large dataset
|
|
|
|
if (!seriesScope || lineData.hasItemOption) {
|
|
var itemModel = lineData.getItemModel(idx);
|
|
lineStyle = itemModel.getModel('lineStyle').getLineStyle();
|
|
hoverLineStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle();
|
|
labelModel = itemModel.getModel('label');
|
|
hoverLabelModel = itemModel.getModel('emphasis.label');
|
|
}
|
|
|
|
var visualColor = lineData.getItemVisual(idx, 'color');
|
|
var visualOpacity = zrUtil.retrieve3(lineData.getItemVisual(idx, 'opacity'), lineStyle.opacity, 1);
|
|
line.useStyle(zrUtil.defaults({
|
|
strokeNoScale: true,
|
|
fill: 'none',
|
|
stroke: visualColor,
|
|
opacity: visualOpacity
|
|
}, lineStyle));
|
|
line.hoverStyle = hoverLineStyle; // Update symbol
|
|
|
|
zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
|
|
var symbol = this.childOfName(symbolCategory);
|
|
|
|
if (symbol) {
|
|
symbol.setColor(visualColor);
|
|
symbol.setStyle({
|
|
opacity: visualOpacity
|
|
});
|
|
}
|
|
}, this);
|
|
var showLabel = labelModel.getShallow('show');
|
|
var hoverShowLabel = hoverLabelModel.getShallow('show');
|
|
var label = this.childOfName('label');
|
|
var defaultLabelColor;
|
|
var baseText; // FIXME: the logic below probably should be merged to `graphic.setLabelStyle`.
|
|
|
|
if (showLabel || hoverShowLabel) {
|
|
defaultLabelColor = visualColor || '#000';
|
|
baseText = seriesModel.getFormattedLabel(idx, 'normal', lineData.dataType);
|
|
|
|
if (baseText == null) {
|
|
var rawVal = seriesModel.getRawValue(idx);
|
|
baseText = rawVal == null ? lineData.getName(idx) : isFinite(rawVal) ? round(rawVal) : rawVal;
|
|
}
|
|
}
|
|
|
|
var normalText = showLabel ? baseText : null;
|
|
var emphasisText = hoverShowLabel ? zrUtil.retrieve2(seriesModel.getFormattedLabel(idx, 'emphasis', lineData.dataType), baseText) : null;
|
|
var labelStyle = label.style; // Always set `textStyle` even if `normalStyle.text` is null, because default
|
|
// values have to be set on `normalStyle`.
|
|
|
|
if (normalText != null || emphasisText != null) {
|
|
graphic.setTextStyle(label.style, labelModel, {
|
|
text: normalText
|
|
}, {
|
|
autoColor: defaultLabelColor
|
|
});
|
|
label.__textAlign = labelStyle.textAlign;
|
|
label.__verticalAlign = labelStyle.textVerticalAlign; // 'start', 'middle', 'end'
|
|
|
|
label.__position = labelModel.get('position') || 'middle';
|
|
var distance = labelModel.get('distance');
|
|
|
|
if (!zrUtil.isArray(distance)) {
|
|
distance = [distance, distance];
|
|
}
|
|
|
|
label.__labelDistance = distance;
|
|
}
|
|
|
|
if (emphasisText != null) {
|
|
// Only these properties supported in this emphasis style here.
|
|
label.hoverStyle = {
|
|
text: emphasisText,
|
|
textFill: hoverLabelModel.getTextColor(true),
|
|
// For merging hover style to normal style, do not use
|
|
// `hoverLabelModel.getFont()` here.
|
|
fontStyle: hoverLabelModel.getShallow('fontStyle'),
|
|
fontWeight: hoverLabelModel.getShallow('fontWeight'),
|
|
fontSize: hoverLabelModel.getShallow('fontSize'),
|
|
fontFamily: hoverLabelModel.getShallow('fontFamily')
|
|
};
|
|
} else {
|
|
label.hoverStyle = {
|
|
text: null
|
|
};
|
|
}
|
|
|
|
label.ignore = !showLabel && !hoverShowLabel;
|
|
graphic.setHoverStyle(this);
|
|
};
|
|
|
|
lineProto.highlight = function () {
|
|
this.trigger('emphasis');
|
|
};
|
|
|
|
lineProto.downplay = function () {
|
|
this.trigger('normal');
|
|
};
|
|
|
|
lineProto.updateLayout = function (lineData, idx) {
|
|
this.setLinePoints(lineData.getItemLayout(idx));
|
|
};
|
|
|
|
lineProto.setLinePoints = function (points) {
|
|
var linePath = this.childOfName('line');
|
|
setLinePoints(linePath.shape, points);
|
|
linePath.dirty();
|
|
};
|
|
|
|
zrUtil.inherits(Line, graphic.Group);
|
|
var _default = Line;
|
|
module.exports = _default; |