/* * 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. */ /** * AUTO-GENERATED FILE. DO NOT MODIFY. */ /* * 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. */ import { __extends } from "tslib"; // FIXME step not support polar import * as zrUtil from 'zrender/lib/core/util.js'; import SymbolDraw from '../helper/SymbolDraw.js'; import SymbolClz from '../helper/Symbol.js'; import lineAnimationDiff from './lineAnimationDiff.js'; import * as graphic from '../../util/graphic.js'; import * as modelUtil from '../../util/model.js'; import { ECPolyline, ECPolygon } from './poly.js'; import ChartView from '../../view/Chart.js'; import { prepareDataCoordInfo, getStackedOnPoint } from './helper.js'; import { createGridClipPath, createPolarClipPath } from '../helper/createClipPathFromCoordSys.js'; import { isCoordinateSystemType } from '../../coord/CoordinateSystem.js'; import { setStatesStylesFromModel, setStatesFlag, toggleHoverEmphasis, SPECIAL_STATES } from '../../util/states.js'; import { setLabelStyle, getLabelStatesModels, labelInner } from '../../label/labelStyle.js'; import { getDefaultLabel, getDefaultInterpolatedLabel } from '../helper/labelHelper.js'; import { getECData } from '../../util/innerStore.js'; import { createFloat32Array } from '../../util/vendor.js'; import { convertToColorString } from '../../util/format.js'; import { lerp } from 'zrender/lib/tool/color.js'; function isPointsSame(points1, points2) { if (points1.length !== points2.length) { return; } for (var i = 0; i < points1.length; i++) { if (points1[i] !== points2[i]) { return; } } return true; } function bboxFromPoints(points) { var minX = Infinity; var minY = Infinity; var maxX = -Infinity; var maxY = -Infinity; for (var i = 0; i < points.length;) { var x = points[i++]; var y = points[i++]; if (!isNaN(x)) { minX = Math.min(x, minX); maxX = Math.max(x, maxX); } if (!isNaN(y)) { minY = Math.min(y, minY); maxY = Math.max(y, maxY); } } return [[minX, minY], [maxX, maxY]]; } function getBoundingDiff(points1, points2) { var _a = bboxFromPoints(points1), min1 = _a[0], max1 = _a[1]; var _b = bboxFromPoints(points2), min2 = _b[0], max2 = _b[1]; // Get a max value from each corner of two boundings. return Math.max(Math.abs(min1[0] - min2[0]), Math.abs(min1[1] - min2[1]), Math.abs(max1[0] - max2[0]), Math.abs(max1[1] - max2[1])); } function getSmooth(smooth) { return zrUtil.isNumber(smooth) ? smooth : smooth ? 0.5 : 0; } function getStackedOnPoints(coordSys, data, dataCoordInfo) { if (!dataCoordInfo.valueDim) { return []; } var len = data.count(); var points = createFloat32Array(len * 2); for (var idx = 0; idx < len; idx++) { var pt = getStackedOnPoint(dataCoordInfo, coordSys, data, idx); points[idx * 2] = pt[0]; points[idx * 2 + 1] = pt[1]; } return points; } function turnPointsIntoStep(points, coordSys, stepTurnAt, connectNulls) { var baseAxis = coordSys.getBaseAxis(); var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1; var stepPoints = []; var i = 0; var stepPt = []; var pt = []; var nextPt = []; var filteredPoints = []; if (connectNulls) { for (i = 0; i < points.length; i += 2) { if (!isNaN(points[i]) && !isNaN(points[i + 1])) { filteredPoints.push(points[i], points[i + 1]); } } points = filteredPoints; } for (i = 0; i < points.length - 2; i += 2) { nextPt[0] = points[i + 2]; nextPt[1] = points[i + 3]; pt[0] = points[i]; pt[1] = points[i + 1]; stepPoints.push(pt[0], pt[1]); switch (stepTurnAt) { case 'end': stepPt[baseIndex] = nextPt[baseIndex]; stepPt[1 - baseIndex] = pt[1 - baseIndex]; stepPoints.push(stepPt[0], stepPt[1]); break; case 'middle': var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2; var stepPt2 = []; stepPt[baseIndex] = stepPt2[baseIndex] = middle; stepPt[1 - baseIndex] = pt[1 - baseIndex]; stepPt2[1 - baseIndex] = nextPt[1 - baseIndex]; stepPoints.push(stepPt[0], stepPt[1]); stepPoints.push(stepPt2[0], stepPt2[1]); break; default: // default is start stepPt[baseIndex] = pt[baseIndex]; stepPt[1 - baseIndex] = nextPt[1 - baseIndex]; stepPoints.push(stepPt[0], stepPt[1]); } } // Last points stepPoints.push(points[i++], points[i++]); return stepPoints; } /** * Clip color stops to edge. Avoid creating too large gradients. * Which may lead to blurry when GPU acceleration is enabled. See #15680 * * The stops has been sorted from small to large. */ function clipColorStops(colorStops, maxSize) { var newColorStops = []; var len = colorStops.length; // coord will always < 0 in prevOutOfRangeColorStop. var prevOutOfRangeColorStop; var prevInRangeColorStop; function lerpStop(stop0, stop1, clippedCoord) { var coord0 = stop0.coord; var p = (clippedCoord - coord0) / (stop1.coord - coord0); var color = lerp(p, [stop0.color, stop1.color]); return { coord: clippedCoord, color: color }; } for (var i = 0; i < len; i++) { var stop_1 = colorStops[i]; var coord = stop_1.coord; if (coord < 0) { prevOutOfRangeColorStop = stop_1; } else if (coord > maxSize) { if (prevInRangeColorStop) { newColorStops.push(lerpStop(prevInRangeColorStop, stop_1, maxSize)); } else if (prevOutOfRangeColorStop) { // If there are two stops and coord range is between these two stops newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0), lerpStop(prevOutOfRangeColorStop, stop_1, maxSize)); } // All following stop will be out of range. So just ignore them. break; } else { if (prevOutOfRangeColorStop) { newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0)); // Reset prevOutOfRangeColorStop = null; } newColorStops.push(stop_1); prevInRangeColorStop = stop_1; } } return newColorStops; } function getVisualGradient(data, coordSys, api) { var visualMetaList = data.getVisual('visualMeta'); if (!visualMetaList || !visualMetaList.length || !data.count()) { // When data.count() is 0, gradient range can not be calculated. return; } if (coordSys.type !== 'cartesian2d') { if (process.env.NODE_ENV !== 'production') { console.warn('Visual map on line style is only supported on cartesian2d.'); } return; } var coordDim; var visualMeta; for (var i = visualMetaList.length - 1; i >= 0; i--) { var dimInfo = data.getDimensionInfo(visualMetaList[i].dimension); coordDim = dimInfo && dimInfo.coordDim; // Can only be x or y if (coordDim === 'x' || coordDim === 'y') { visualMeta = visualMetaList[i]; break; } } if (!visualMeta) { if (process.env.NODE_ENV !== 'production') { console.warn('Visual map on line style only support x or y dimension.'); } return; } // If the area to be rendered is bigger than area defined by LinearGradient, // the canvas spec prescribes that the color of the first stop and the last // stop should be used. But if two stops are added at offset 0, in effect // browsers use the color of the second stop to render area outside // LinearGradient. So we can only infinitesimally extend area defined in // LinearGradient to render `outerColors`. var axis = coordSys.getAxis(coordDim); // dataToCoord mapping may not be linear, but must be monotonic. var colorStops = zrUtil.map(visualMeta.stops, function (stop) { // offset will be calculated later. return { coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)), color: stop.color }; }); var stopLen = colorStops.length; var outerColors = visualMeta.outerColors.slice(); if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) { colorStops.reverse(); outerColors.reverse(); } var colorStopsInRange = clipColorStops(colorStops, coordDim === 'x' ? api.getWidth() : api.getHeight()); var inRangeStopLen = colorStopsInRange.length; if (!inRangeStopLen && stopLen) { // All stops are out of range. All will be the same color. return colorStops[0].coord < 0 ? outerColors[1] ? outerColors[1] : colorStops[stopLen - 1].color : outerColors[0] ? outerColors[0] : colorStops[0].color; } var tinyExtent = 10; // Arbitrary value: 10px var minCoord = colorStopsInRange[0].coord - tinyExtent; var maxCoord = colorStopsInRange[inRangeStopLen - 1].coord + tinyExtent; var coordSpan = maxCoord - minCoord; if (coordSpan < 1e-3) { return 'transparent'; } zrUtil.each(colorStopsInRange, function (stop) { stop.offset = (stop.coord - minCoord) / coordSpan; }); colorStopsInRange.push({ // NOTE: inRangeStopLen may still be 0 if stoplen is zero. offset: inRangeStopLen ? colorStopsInRange[inRangeStopLen - 1].offset : 0.5, color: outerColors[1] || 'transparent' }); colorStopsInRange.unshift({ offset: inRangeStopLen ? colorStopsInRange[0].offset : 0.5, color: outerColors[0] || 'transparent' }); var gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStopsInRange, true); gradient[coordDim] = minCoord; gradient[coordDim + '2'] = maxCoord; return gradient; } function getIsIgnoreFunc(seriesModel, data, coordSys) { var showAllSymbol = seriesModel.get('showAllSymbol'); var isAuto = showAllSymbol === 'auto'; if (showAllSymbol && !isAuto) { return; } var categoryAxis = coordSys.getAxesByScale('ordinal')[0]; if (!categoryAxis) { return; } // Note that category label interval strategy might bring some weird effect // in some scenario: users may wonder why some of the symbols are not // displayed. So we show all symbols as possible as we can. if (isAuto // Simplify the logic, do not determine label overlap here. && canShowAllSymbolForCategory(categoryAxis, data)) { return; } // Otherwise follow the label interval strategy on category axis. var categoryDataDim = data.mapDimension(categoryAxis.dim); var labelMap = {}; zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) { var ordinalNumber = categoryAxis.scale.getRawOrdinalNumber(labelItem.tickValue); labelMap[ordinalNumber] = 1; }); return function (dataIndex) { return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex)); }; } function canShowAllSymbolForCategory(categoryAxis, data) { // In most cases, line is monotonous on category axis, and the label size // is close with each other. So we check the symbol size and some of the // label size alone with the category axis to estimate whether all symbol // can be shown without overlap. var axisExtent = categoryAxis.getExtent(); var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count(); isNaN(availSize) && (availSize = 0); // 0/0 is NaN. // Sampling some points, max 5. var dataLen = data.count(); var step = Math.max(1, Math.round(dataLen / 5)); for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) { if (SymbolClz.getSymbolSize(data, dataIndex // Only for cartesian, where `isHorizontal` exists. )[categoryAxis.isHorizontal() ? 1 : 0] // Empirical number * 1.5 > availSize) { return false; } } return true; } function isPointNull(x, y) { return isNaN(x) || isNaN(y); } function getLastIndexNotNull(points) { var len = points.length / 2; for (; len > 0; len--) { if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) { break; } } return len - 1; } function getPointAtIndex(points, idx) { return [points[idx * 2], points[idx * 2 + 1]]; } function getIndexRange(points, xOrY, dim) { var len = points.length / 2; var dimIdx = dim === 'x' ? 0 : 1; var a; var b; var prevIndex = 0; var nextIndex = -1; for (var i = 0; i < len; i++) { b = points[i * 2 + dimIdx]; if (isNaN(b) || isNaN(points[i * 2 + 1 - dimIdx])) { continue; } if (i === 0) { a = b; continue; } if (a <= xOrY && b >= xOrY || a >= xOrY && b <= xOrY) { nextIndex = i; break; } prevIndex = i; a = b; } return { range: [prevIndex, nextIndex], t: (xOrY - a) / (b - a) }; } function anyStateShowEndLabel(seriesModel) { if (seriesModel.get(['endLabel', 'show'])) { return true; } for (var i = 0; i < SPECIAL_STATES.length; i++) { if (seriesModel.get([SPECIAL_STATES[i], 'endLabel', 'show'])) { return true; } } return false; } function createLineClipPath(lineView, coordSys, hasAnimation, seriesModel) { if (isCoordinateSystemType(coordSys, 'cartesian2d')) { var endLabelModel_1 = seriesModel.getModel('endLabel'); var valueAnimation_1 = endLabelModel_1.get('valueAnimation'); var data_1 = seriesModel.getData(); var labelAnimationRecord_1 = { lastFrameIndex: 0 }; var during = anyStateShowEndLabel(seriesModel) ? function (percent, clipRect) { lineView._endLabelOnDuring(percent, clipRect, data_1, labelAnimationRecord_1, valueAnimation_1, endLabelModel_1, coordSys); } : null; var isHorizontal = coordSys.getBaseAxis().isHorizontal(); var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, function () { var endLabel = lineView._endLabel; if (endLabel && hasAnimation) { if (labelAnimationRecord_1.originalX != null) { endLabel.attr({ x: labelAnimationRecord_1.originalX, y: labelAnimationRecord_1.originalY }); } } }, during); // Expand clip shape to avoid clipping when line value exceeds axis if (!seriesModel.get('clip', true)) { var rectShape = clipPath.shape; var expandSize = Math.max(rectShape.width, rectShape.height); if (isHorizontal) { rectShape.y -= expandSize; rectShape.height += expandSize * 2; } else { rectShape.x -= expandSize; rectShape.width += expandSize * 2; } } // Set to the final frame. To make sure label layout is right. if (during) { during(1, clipPath); } return clipPath; } else { if (process.env.NODE_ENV !== 'production') { if (seriesModel.get(['endLabel', 'show'])) { console.warn('endLabel is not supported for lines in polar systems.'); } } return createPolarClipPath(coordSys, hasAnimation, seriesModel); } } function getEndLabelStateSpecified(endLabelModel, coordSys) { var baseAxis = coordSys.getBaseAxis(); var isHorizontal = baseAxis.isHorizontal(); var isBaseInversed = baseAxis.inverse; var align = isHorizontal ? isBaseInversed ? 'right' : 'left' : 'center'; var verticalAlign = isHorizontal ? 'middle' : isBaseInversed ? 'top' : 'bottom'; return { normal: { align: endLabelModel.get('align') || align, verticalAlign: endLabelModel.get('verticalAlign') || verticalAlign } }; } var LineView = /** @class */function (_super) { __extends(LineView, _super); function LineView() { return _super !== null && _super.apply(this, arguments) || this; } LineView.prototype.init = function () { var lineGroup = new graphic.Group(); var symbolDraw = new SymbolDraw(); this.group.add(symbolDraw.group); this._symbolDraw = symbolDraw; this._lineGroup = lineGroup; }; LineView.prototype.render = function (seriesModel, ecModel, api) { var _this = this; var coordSys = seriesModel.coordinateSystem; var group = this.group; var data = seriesModel.getData(); var lineStyleModel = seriesModel.getModel('lineStyle'); var areaStyleModel = seriesModel.getModel('areaStyle'); var points = data.getLayout('points') || []; var isCoordSysPolar = coordSys.type === 'polar'; var prevCoordSys = this._coordSys; var symbolDraw = this._symbolDraw; var polyline = this._polyline; var polygon = this._polygon; var lineGroup = this._lineGroup; var hasAnimation = !ecModel.ssr && seriesModel.get('animation'); var isAreaChart = !areaStyleModel.isEmpty(); var valueOrigin = areaStyleModel.get('origin'); var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin); var stackedOnPoints = isAreaChart && getStackedOnPoints(coordSys, data, dataCoordInfo); var showSymbol = seriesModel.get('showSymbol'); var connectNulls = seriesModel.get('connectNulls'); var isIgnoreFunc = showSymbol && !isCoordSysPolar && getIsIgnoreFunc(seriesModel, data, coordSys); // Remove temporary symbols var oldData = this._data; oldData && oldData.eachItemGraphicEl(function (el, idx) { if (el.__temp) { group.remove(el); oldData.setItemGraphicEl(idx, null); } }); // Remove previous created symbols if showSymbol changed to false if (!showSymbol) { symbolDraw.remove(); } group.add(lineGroup); // FIXME step not support polar var step = !isCoordSysPolar ? seriesModel.get('step') : false; var clipShapeForSymbol; if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) { clipShapeForSymbol = coordSys.getArea(); // Avoid float number rounding error for symbol on the edge of axis extent. // See #7913 and `test/dataZoom-clip.html`. if (clipShapeForSymbol.width != null) { clipShapeForSymbol.x -= 0.1; clipShapeForSymbol.y -= 0.1; clipShapeForSymbol.width += 0.2; clipShapeForSymbol.height += 0.2; } else if (clipShapeForSymbol.r0) { clipShapeForSymbol.r0 -= 0.5; clipShapeForSymbol.r += 0.5; } } this._clipShapeForSymbol = clipShapeForSymbol; var visualColor = getVisualGradient(data, coordSys, api) || data.getVisual('style')[data.getVisual('drawType')]; // Initialization animation or coordinate system changed if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) { showSymbol && symbolDraw.updateData(data, { isIgnore: isIgnoreFunc, clipShape: clipShapeForSymbol, disableAnimation: true, getSymbolPoint: function (idx) { return [points[idx * 2], points[idx * 2 + 1]]; } }); hasAnimation && this._initSymbolLabelAnimation(data, coordSys, clipShapeForSymbol); if (step) { // TODO If stacked series is not step points = turnPointsIntoStep(points, coordSys, step, connectNulls); if (stackedOnPoints) { stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step, connectNulls); } } polyline = this._newPolyline(points); if (isAreaChart) { polygon = this._newPolygon(points, stackedOnPoints); } // If areaStyle is removed else if (polygon) { lineGroup.remove(polygon); polygon = this._polygon = null; } // NOTE: Must update _endLabel before setClipPath. if (!isCoordSysPolar) { this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor)); } lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel)); } else { if (isAreaChart && !polygon) { // If areaStyle is added polygon = this._newPolygon(points, stackedOnPoints); } else if (polygon && !isAreaChart) { // If areaStyle is removed lineGroup.remove(polygon); polygon = this._polygon = null; } // NOTE: Must update _endLabel before setClipPath. if (!isCoordSysPolar) { this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor)); } // Update clipPath var oldClipPath = lineGroup.getClipPath(); if (oldClipPath) { var newClipPath = createLineClipPath(this, coordSys, false, seriesModel); graphic.initProps(oldClipPath, { shape: newClipPath.shape }, seriesModel); } else { lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel)); } // Always update, or it is wrong in the case turning on legend // because points are not changed. showSymbol && symbolDraw.updateData(data, { isIgnore: isIgnoreFunc, clipShape: clipShapeForSymbol, disableAnimation: true, getSymbolPoint: function (idx) { return [points[idx * 2], points[idx * 2 + 1]]; } }); // In the case data zoom triggered refreshing frequently // Data may not change if line has a category axis. So it should animate nothing. if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) || !isPointsSame(this._points, points)) { if (hasAnimation) { this._doUpdateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls); } else { // Not do it in update with animation if (step) { // TODO If stacked series is not step points = turnPointsIntoStep(points, coordSys, step, connectNulls); if (stackedOnPoints) { stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step, connectNulls); } } polyline.setShape({ points: points }); polygon && polygon.setShape({ points: points, stackedOnPoints: stackedOnPoints }); } } } var emphasisModel = seriesModel.getModel('emphasis'); var focus = emphasisModel.get('focus'); var blurScope = emphasisModel.get('blurScope'); var emphasisDisabled = emphasisModel.get('disabled'); polyline.useStyle(zrUtil.defaults( // Use color in lineStyle first lineStyleModel.getLineStyle(), { fill: 'none', stroke: visualColor, lineJoin: 'bevel' })); setStatesStylesFromModel(polyline, seriesModel, 'lineStyle'); if (polyline.style.lineWidth > 0 && seriesModel.get(['emphasis', 'lineStyle', 'width']) === 'bolder') { var emphasisLineStyle = polyline.getState('emphasis').style; emphasisLineStyle.lineWidth = +polyline.style.lineWidth + 1; } // Needs seriesIndex for focus getECData(polyline).seriesIndex = seriesModel.seriesIndex; toggleHoverEmphasis(polyline, focus, blurScope, emphasisDisabled); var smooth = getSmooth(seriesModel.get('smooth')); var smoothMonotone = seriesModel.get('smoothMonotone'); polyline.setShape({ smooth: smooth, smoothMonotone: smoothMonotone, connectNulls: connectNulls }); if (polygon) { var stackedOnSeries = data.getCalculationInfo('stackedOnSeries'); var stackedOnSmooth = 0; polygon.useStyle(zrUtil.defaults(areaStyleModel.getAreaStyle(), { fill: visualColor, opacity: 0.7, lineJoin: 'bevel', decal: data.getVisual('style').decal })); if (stackedOnSeries) { stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth')); } polygon.setShape({ smooth: smooth, stackedOnSmooth: stackedOnSmooth, smoothMonotone: smoothMonotone, connectNulls: connectNulls }); setStatesStylesFromModel(polygon, seriesModel, 'areaStyle'); // Needs seriesIndex for focus getECData(polygon).seriesIndex = seriesModel.seriesIndex; toggleHoverEmphasis(polygon, focus, blurScope, emphasisDisabled); } var changePolyState = function (toState) { _this._changePolyState(toState); }; data.eachItemGraphicEl(function (el) { // Switch polyline / polygon state if element changed its state. el && (el.onHoverStateChange = changePolyState); }); this._polyline.onHoverStateChange = changePolyState; this._data = data; // Save the coordinate system for transition animation when data changed this._coordSys = coordSys; this._stackedOnPoints = stackedOnPoints; this._points = points; this._step = step; this._valueOrigin = valueOrigin; if (seriesModel.get('triggerLineEvent')) { this.packEventData(seriesModel, polyline); polygon && this.packEventData(seriesModel, polygon); } }; LineView.prototype.packEventData = function (seriesModel, el) { getECData(el).eventData = { componentType: 'series', componentSubType: 'line', componentIndex: seriesModel.componentIndex, seriesIndex: seriesModel.seriesIndex, seriesName: seriesModel.name, seriesType: 'line' }; }; LineView.prototype.highlight = function (seriesModel, ecModel, api, payload) { var data = seriesModel.getData(); var dataIndex = modelUtil.queryDataIndex(data, payload); this._changePolyState('emphasis'); if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) { var points = data.getLayout('points'); var symbol = data.getItemGraphicEl(dataIndex); if (!symbol) { // Create a temporary symbol if it is not exists var x = points[dataIndex * 2]; var y = points[dataIndex * 2 + 1]; if (isNaN(x) || isNaN(y)) { // Null data return; } // fix #11360: shouldn't draw symbol outside clipShapeForSymbol if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) { return; } var zlevel = seriesModel.get('zlevel') || 0; var z = seriesModel.get('z') || 0; symbol = new SymbolClz(data, dataIndex); symbol.x = x; symbol.y = y; symbol.setZ(zlevel, z); // ensure label text of the temporary symbol is in front of line and area polygon var symbolLabel = symbol.getSymbolPath().getTextContent(); if (symbolLabel) { symbolLabel.zlevel = zlevel; symbolLabel.z = z; symbolLabel.z2 = this._polyline.z2 + 1; } symbol.__temp = true; data.setItemGraphicEl(dataIndex, symbol); // Stop scale animation symbol.stopSymbolAnimation(true); this.group.add(symbol); } symbol.highlight(); } else { // Highlight whole series ChartView.prototype.highlight.call(this, seriesModel, ecModel, api, payload); } }; LineView.prototype.downplay = function (seriesModel, ecModel, api, payload) { var data = seriesModel.getData(); var dataIndex = modelUtil.queryDataIndex(data, payload); this._changePolyState('normal'); if (dataIndex != null && dataIndex >= 0) { var symbol = data.getItemGraphicEl(dataIndex); if (symbol) { if (symbol.__temp) { data.setItemGraphicEl(dataIndex, null); this.group.remove(symbol); } else { symbol.downplay(); } } } else { // FIXME // can not downplay completely. // Downplay whole series ChartView.prototype.downplay.call(this, seriesModel, ecModel, api, payload); } }; LineView.prototype._changePolyState = function (toState) { var polygon = this._polygon; setStatesFlag(this._polyline, toState); polygon && setStatesFlag(polygon, toState); }; LineView.prototype._newPolyline = function (points) { var polyline = this._polyline; // Remove previous created polyline if (polyline) { this._lineGroup.remove(polyline); } polyline = new ECPolyline({ shape: { points: points }, segmentIgnoreThreshold: 2, z2: 10 }); this._lineGroup.add(polyline); this._polyline = polyline; return polyline; }; LineView.prototype._newPolygon = function (points, stackedOnPoints) { var polygon = this._polygon; // Remove previous created polygon if (polygon) { this._lineGroup.remove(polygon); } polygon = new ECPolygon({ shape: { points: points, stackedOnPoints: stackedOnPoints }, segmentIgnoreThreshold: 2 }); this._lineGroup.add(polygon); this._polygon = polygon; return polygon; }; LineView.prototype._initSymbolLabelAnimation = function (data, coordSys, clipShape) { var isHorizontalOrRadial; var isCoordSysPolar; var baseAxis = coordSys.getBaseAxis(); var isAxisInverse = baseAxis.inverse; if (coordSys.type === 'cartesian2d') { isHorizontalOrRadial = baseAxis.isHorizontal(); isCoordSysPolar = false; } else if (coordSys.type === 'polar') { isHorizontalOrRadial = baseAxis.dim === 'angle'; isCoordSysPolar = true; } var seriesModel = data.hostModel; var seriesDuration = seriesModel.get('animationDuration'); if (zrUtil.isFunction(seriesDuration)) { seriesDuration = seriesDuration(null); } var seriesDelay = seriesModel.get('animationDelay') || 0; var seriesDelayValue = zrUtil.isFunction(seriesDelay) ? seriesDelay(null) : seriesDelay; data.eachItemGraphicEl(function (symbol, idx) { var el = symbol; if (el) { var point = [symbol.x, symbol.y]; var start = void 0; var end = void 0; var current = void 0; if (clipShape) { if (isCoordSysPolar) { var polarClip = clipShape; var coord = coordSys.pointToCoord(point); if (isHorizontalOrRadial) { start = polarClip.startAngle; end = polarClip.endAngle; current = -coord[1] / 180 * Math.PI; } else { start = polarClip.r0; end = polarClip.r; current = coord[0]; } } else { var gridClip = clipShape; if (isHorizontalOrRadial) { start = gridClip.x; end = gridClip.x + gridClip.width; current = symbol.x; } else { start = gridClip.y + gridClip.height; end = gridClip.y; current = symbol.y; } } } var ratio = end === start ? 0 : (current - start) / (end - start); if (isAxisInverse) { ratio = 1 - ratio; } var delay = zrUtil.isFunction(seriesDelay) ? seriesDelay(idx) : seriesDuration * ratio + seriesDelayValue; var symbolPath = el.getSymbolPath(); var text = symbolPath.getTextContent(); el.attr({ scaleX: 0, scaleY: 0 }); el.animateTo({ scaleX: 1, scaleY: 1 }, { duration: 200, setToFinal: true, delay: delay }); if (text) { text.animateFrom({ style: { opacity: 0 } }, { duration: 300, delay: delay }); } symbolPath.disableLabelAnimation = true; } }); }; LineView.prototype._initOrUpdateEndLabel = function (seriesModel, coordSys, inheritColor) { var endLabelModel = seriesModel.getModel('endLabel'); if (anyStateShowEndLabel(seriesModel)) { var data_2 = seriesModel.getData(); var polyline = this._polyline; // series may be filtered. var points = data_2.getLayout('points'); if (!points) { polyline.removeTextContent(); this._endLabel = null; return; } var endLabel = this._endLabel; if (!endLabel) { endLabel = this._endLabel = new graphic.Text({ z2: 200 // should be higher than item symbol }); endLabel.ignoreClip = true; polyline.setTextContent(this._endLabel); polyline.disableLabelAnimation = true; } // Find last non-NaN data to display data var dataIndex = getLastIndexNotNull(points); if (dataIndex >= 0) { setLabelStyle(polyline, getLabelStatesModels(seriesModel, 'endLabel'), { inheritColor: inheritColor, labelFetcher: seriesModel, labelDataIndex: dataIndex, defaultText: function (dataIndex, opt, interpolatedValue) { return interpolatedValue != null ? getDefaultInterpolatedLabel(data_2, interpolatedValue) : getDefaultLabel(data_2, dataIndex); }, enableTextSetter: true }, getEndLabelStateSpecified(endLabelModel, coordSys)); polyline.textConfig.position = null; } } else if (this._endLabel) { this._polyline.removeTextContent(); this._endLabel = null; } }; LineView.prototype._endLabelOnDuring = function (percent, clipRect, data, animationRecord, valueAnimation, endLabelModel, coordSys) { var endLabel = this._endLabel; var polyline = this._polyline; if (endLabel) { // NOTE: Don't remove percent < 1. percent === 1 means the first frame during render. // The label is not prepared at this time. if (percent < 1 && animationRecord.originalX == null) { animationRecord.originalX = endLabel.x; animationRecord.originalY = endLabel.y; } var points = data.getLayout('points'); var seriesModel = data.hostModel; var connectNulls = seriesModel.get('connectNulls'); var precision = endLabelModel.get('precision'); var distance = endLabelModel.get('distance') || 0; var baseAxis = coordSys.getBaseAxis(); var isHorizontal = baseAxis.isHorizontal(); var isBaseInversed = baseAxis.inverse; var clipShape = clipRect.shape; var xOrY = isBaseInversed ? isHorizontal ? clipShape.x : clipShape.y + clipShape.height : isHorizontal ? clipShape.x + clipShape.width : clipShape.y; var distanceX = (isHorizontal ? distance : 0) * (isBaseInversed ? -1 : 1); var distanceY = (isHorizontal ? 0 : -distance) * (isBaseInversed ? -1 : 1); var dim = isHorizontal ? 'x' : 'y'; var dataIndexRange = getIndexRange(points, xOrY, dim); var indices = dataIndexRange.range; var diff = indices[1] - indices[0]; var value = void 0; if (diff >= 1) { // diff > 1 && connectNulls, which is on the null data. if (diff > 1 && !connectNulls) { var pt = getPointAtIndex(points, indices[0]); endLabel.attr({ x: pt[0] + distanceX, y: pt[1] + distanceY }); valueAnimation && (value = seriesModel.getRawValue(indices[0])); } else { var pt = polyline.getPointOn(xOrY, dim); pt && endLabel.attr({ x: pt[0] + distanceX, y: pt[1] + distanceY }); var startValue = seriesModel.getRawValue(indices[0]); var endValue = seriesModel.getRawValue(indices[1]); valueAnimation && (value = modelUtil.interpolateRawValues(data, precision, startValue, endValue, dataIndexRange.t)); } animationRecord.lastFrameIndex = indices[0]; } else { // If diff <= 0, which is the range is not found(Include NaN) // Choose the first point or last point. var idx = percent === 1 || animationRecord.lastFrameIndex > 0 ? indices[0] : 0; var pt = getPointAtIndex(points, idx); valueAnimation && (value = seriesModel.getRawValue(idx)); endLabel.attr({ x: pt[0] + distanceX, y: pt[1] + distanceY }); } if (valueAnimation) { var inner = labelInner(endLabel); if (typeof inner.setLabelText === 'function') { inner.setLabelText(value); } } } }; /** * @private */ // FIXME Two value axis LineView.prototype._doUpdateAnimation = function (data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls) { var polyline = this._polyline; var polygon = this._polygon; var seriesModel = data.hostModel; var diff = lineAnimationDiff(this._data, data, this._stackedOnPoints, stackedOnPoints, this._coordSys, coordSys, this._valueOrigin, valueOrigin); var current = diff.current; var stackedOnCurrent = diff.stackedOnCurrent; var next = diff.next; var stackedOnNext = diff.stackedOnNext; if (step) { // TODO If stacked series is not step current = turnPointsIntoStep(diff.current, coordSys, step, connectNulls); stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step, connectNulls); next = turnPointsIntoStep(diff.next, coordSys, step, connectNulls); stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step, connectNulls); } // Don't apply animation if diff is large. // For better result and avoid memory explosion problems like // https://github.com/apache/incubator-echarts/issues/12229 if (getBoundingDiff(current, next) > 3000 || polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3000) { polyline.stopAnimation(); polyline.setShape({ points: next }); if (polygon) { polygon.stopAnimation(); polygon.setShape({ points: next, stackedOnPoints: stackedOnNext }); } return; } polyline.shape.__points = diff.current; polyline.shape.points = current; var target = { shape: { points: next } }; // Also animate the original points. // If points reference is changed when turning into step line. if (diff.current !== current) { target.shape.__points = diff.next; } // Stop previous animation. polyline.stopAnimation(); graphic.updateProps(polyline, target, seriesModel); if (polygon) { polygon.setShape({ // Reuse the points with polyline. points: current, stackedOnPoints: stackedOnCurrent }); polygon.stopAnimation(); graphic.updateProps(polygon, { shape: { stackedOnPoints: stackedOnNext } }, seriesModel); // If use attr directly in updateProps. if (polyline.shape.points !== polygon.shape.points) { polygon.shape.points = polyline.shape.points; } } var updatedDataInfo = []; var diffStatus = diff.status; for (var i = 0; i < diffStatus.length; i++) { var cmd = diffStatus[i].cmd; if (cmd === '=') { var el = data.getItemGraphicEl(diffStatus[i].idx1); if (el) { updatedDataInfo.push({ el: el, ptIdx: i // Index of points }); } } } if (polyline.animators && polyline.animators.length) { polyline.animators[0].during(function () { polygon && polygon.dirtyShape(); var points = polyline.shape.__points; for (var i = 0; i < updatedDataInfo.length; i++) { var el = updatedDataInfo[i].el; var offset = updatedDataInfo[i].ptIdx * 2; el.x = points[offset]; el.y = points[offset + 1]; el.markRedraw(); } }); } }; LineView.prototype.remove = function (ecModel) { var group = this.group; var oldData = this._data; this._lineGroup.removeAll(); this._symbolDraw.remove(true); // Remove temporary created elements when highlighting oldData && oldData.eachItemGraphicEl(function (el, idx) { if (el.__temp) { group.remove(el); oldData.setItemGraphicEl(idx, null); } }); this._polyline = this._polygon = this._coordSys = this._points = this._stackedOnPoints = this._endLabel = this._data = null; }; LineView.type = 'line'; return LineView; }(ChartView); export default LineView;