| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739 | 
/** 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 _config = require("../../config");var __DEV__ = _config.__DEV__;var zrUtil = require("zrender/lib/core/util");var _bbox = require("zrender/lib/core/bbox");var fromPoints = _bbox.fromPoints;var SymbolDraw = require("../helper/SymbolDraw");var SymbolClz = require("../helper/Symbol");var lineAnimationDiff = require("./lineAnimationDiff");var graphic = require("../../util/graphic");var modelUtil = require("../../util/model");var _poly = require("./poly");var Polyline = _poly.Polyline;var Polygon = _poly.Polygon;var ChartView = require("../../view/Chart");var _helper = require("./helper");var prepareDataCoordInfo = _helper.prepareDataCoordInfo;var getStackedOnPoint = _helper.getStackedOnPoint;var _createClipPathFromCoordSys = require("../helper/createClipPathFromCoordSys");var createGridClipPath = _createClipPathFromCoordSys.createGridClipPath;var createPolarClipPath = _createClipPathFromCoordSys.createPolarClipPath;/** 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.*/// FIXME step not support polarfunction isPointsSame(points1, points2) {  if (points1.length !== points2.length) {    return;  }  for (var i = 0; i < points1.length; i++) {    var p1 = points1[i];    var p2 = points2[i];    if (p1[0] !== p2[0] || p1[1] !== p2[1]) {      return;    }  }  return true;}function getBoundingDiff(points1, points2) {  var min1 = [];  var max1 = [];  var min2 = [];  var max2 = [];  fromPoints(points1, min1, max1);  fromPoints(points2, min2, max2); // 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 typeof smooth === 'number' ? smooth : smooth ? 0.5 : 0;}/** * @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys * @param {module:echarts/data/List} data * @param {Object} dataCoordInfo * @param {Array.<Array.<number>>} points */function getStackedOnPoints(coordSys, data, dataCoordInfo) {  if (!dataCoordInfo.valueDim) {    return [];  }  var points = [];  for (var idx = 0, len = data.count(); idx < len; idx++) {    points.push(getStackedOnPoint(dataCoordInfo, coordSys, data, idx));  }  return points;}function turnPointsIntoStep(points, coordSys, stepTurnAt) {  var baseAxis = coordSys.getBaseAxis();  var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;  var stepPoints = [];  for (var i = 0; i < points.length - 1; i++) {    var nextPt = points[i + 1];    var pt = points[i];    stepPoints.push(pt);    var stepPt = [];    switch (stepTurnAt) {      case 'end':        stepPt[baseIndex] = nextPt[baseIndex];        stepPt[1 - baseIndex] = pt[1 - baseIndex]; // default is start        stepPoints.push(stepPt);        break;      case 'middle':        // default is start        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);        stepPoints.push(stepPt2);        break;      default:        stepPt[baseIndex] = pt[baseIndex];        stepPt[1 - baseIndex] = nextPt[1 - baseIndex]; // default is start        stepPoints.push(stepPt);    }  } // Last points  points[i] && stepPoints.push(points[i]);  return stepPoints;}function getVisualGradient(data, coordSys) {  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') {    return;  }  var coordDim;  var visualMeta;  for (var i = visualMetaList.length - 1; i >= 0; i--) {    var dimIndex = visualMetaList[i].dimension;    var dimName = data.dimensions[dimIndex];    var dimInfo = data.getDimensionInfo(dimName);    coordDim = dimInfo && dimInfo.coordDim; // Can only be x or y    if (coordDim === 'x' || coordDim === 'y') {      visualMeta = visualMetaList[i];      break;    }  }  if (!visualMeta) {    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); // dataToCoor mapping may not be linear, but must be monotonic.  var colorStops = zrUtil.map(visualMeta.stops, function (stop) {    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 tinyExtent = 10; // Arbitrary value: 10px  var minCoord = colorStops[0].coord - tinyExtent;  var maxCoord = colorStops[stopLen - 1].coord + tinyExtent;  var coordSpan = maxCoord - minCoord;  if (coordSpan < 1e-3) {    return 'transparent';  }  zrUtil.each(colorStops, function (stop) {    stop.offset = (stop.coord - minCoord) / coordSpan;  });  colorStops.push({    offset: stopLen ? colorStops[stopLen - 1].offset : 0.5,    color: outerColors[1] || 'transparent'  });  colorStops.unshift({    // notice colorStops.length have been changed.    offset: stopLen ? colorStops[0].offset : 0.5,    color: outerColors[0] || 'transparent'  }); // zrUtil.each(colorStops, function (colorStop) {  //     // Make sure each offset has rounded px to avoid not sharp edge  //     colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start);  // });  var gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStops, 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) {    labelMap[labelItem.tickValue] = 1;  });  return function (dataIndex) {    return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));  };}function canShowAllSymbolForCategory(categoryAxis, data) {  // In mose 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 createLineClipPath(coordSys, hasAnimation, seriesModel) {  if (coordSys.type === 'cartesian2d') {    var isHorizontal = coordSys.getBaseAxis().isHorizontal();    var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel); // 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;      }    }    return clipPath;  } else {    return createPolarClipPath(coordSys, hasAnimation, seriesModel);  }}var _default = ChartView.extend({  type: 'line',  init: function () {    var lineGroup = new graphic.Group();    var symbolDraw = new SymbolDraw();    this.group.add(symbolDraw.group);    this._symbolDraw = symbolDraw;    this._lineGroup = lineGroup;  },  render: function (seriesModel, ecModel, api) {    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.mapArray(data.getItemLayout);    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 = seriesModel.get('animation');    var isAreaChart = !areaStyleModel.isEmpty();    var valueOrigin = areaStyleModel.get('origin');    var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);    var stackedOnPoints = getStackedOnPoints(coordSys, data, dataCoordInfo);    var showSymbol = seriesModel.get('showSymbol');    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');    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.r1 += 0.5;      }    }    this._clipShapeForSymbol = clipShapeForSymbol; // Initialization animation or coordinate system changed    if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) {      showSymbol && symbolDraw.updateData(data, {        isIgnore: isIgnoreFunc,        clipShape: clipShapeForSymbol      });      if (step) {        // TODO If stacked series is not step        points = turnPointsIntoStep(points, coordSys, step);        stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);      }      polyline = this._newPolyline(points, coordSys, hasAnimation);      if (isAreaChart) {        polygon = this._newPolygon(points, stackedOnPoints, coordSys, hasAnimation);      }      lineGroup.setClipPath(createLineClipPath(coordSys, true, seriesModel));    } else {      if (isAreaChart && !polygon) {        // If areaStyle is added        polygon = this._newPolygon(points, stackedOnPoints, coordSys, hasAnimation);      } else if (polygon && !isAreaChart) {        // If areaStyle is removed        lineGroup.remove(polygon);        polygon = this._polygon = null;      } // Update clipPath      lineGroup.setClipPath(createLineClipPath(coordSys, false, 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      }); // Stop symbol animation and sync with line points      // FIXME performance?      data.eachItemGraphicEl(function (el) {        el.stopAnimation(true);      }); // In the case data zoom triggerred 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._updateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin);        } else {          // Not do it in update with animation          if (step) {            // TODO If stacked series is not step            points = turnPointsIntoStep(points, coordSys, step);            stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);          }          polyline.setShape({            points: points          });          polygon && polygon.setShape({            points: points,            stackedOnPoints: stackedOnPoints          });        }      }    }    var visualColor = getVisualGradient(data, coordSys) || data.getVisual('color');    polyline.useStyle(zrUtil.defaults( // Use color in lineStyle first    lineStyleModel.getLineStyle(), {      fill: 'none',      stroke: visualColor,      lineJoin: 'bevel'    }));    var smooth = seriesModel.get('smooth');    smooth = getSmooth(seriesModel.get('smooth'));    polyline.setShape({      smooth: smooth,      smoothMonotone: seriesModel.get('smoothMonotone'),      connectNulls: seriesModel.get('connectNulls')    });    if (polygon) {      var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');      var stackedOnSmooth = 0;      polygon.useStyle(zrUtil.defaults(areaStyleModel.getAreaStyle(), {        fill: visualColor,        opacity: 0.7,        lineJoin: 'bevel'      }));      if (stackedOnSeries) {        stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));      }      polygon.setShape({        smooth: smooth,        stackedOnSmooth: stackedOnSmooth,        smoothMonotone: seriesModel.get('smoothMonotone'),        connectNulls: seriesModel.get('connectNulls')      });    }    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;  },  dispose: function () {},  highlight: function (seriesModel, ecModel, api, payload) {    var data = seriesModel.getData();    var dataIndex = modelUtil.queryDataIndex(data, payload);    if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {      var symbol = data.getItemGraphicEl(dataIndex);      if (!symbol) {        // Create a temporary symbol if it is not exists        var pt = data.getItemLayout(dataIndex);        if (!pt) {          // Null data          return;        } // fix #11360: should't draw symbol outside clipShapeForSymbol        if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(pt[0], pt[1])) {          return;        }        symbol = new SymbolClz(data, dataIndex);        symbol.position = pt;        symbol.setZ(seriesModel.get('zlevel'), seriesModel.get('z'));        symbol.ignore = isNaN(pt[0]) || isNaN(pt[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);    }  },  downplay: function (seriesModel, ecModel, api, payload) {    var data = seriesModel.getData();    var dataIndex = modelUtil.queryDataIndex(data, payload);    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);    }  },  /**   * @param {module:zrender/container/Group} group   * @param {Array.<Array.<number>>} points   * @private   */  _newPolyline: function (points) {    var polyline = this._polyline; // Remove previous created polyline    if (polyline) {      this._lineGroup.remove(polyline);    }    polyline = new Polyline({      shape: {        points: points      },      silent: true,      z2: 10    });    this._lineGroup.add(polyline);    this._polyline = polyline;    return polyline;  },  /**   * @param {module:zrender/container/Group} group   * @param {Array.<Array.<number>>} stackedOnPoints   * @param {Array.<Array.<number>>} points   * @private   */  _newPolygon: function (points, stackedOnPoints) {    var polygon = this._polygon; // Remove previous created polygon    if (polygon) {      this._lineGroup.remove(polygon);    }    polygon = new Polygon({      shape: {        points: points,        stackedOnPoints: stackedOnPoints      },      silent: true    });    this._lineGroup.add(polygon);    this._polygon = polygon;    return polygon;  },  /**   * @private   */  // FIXME Two value axis  _updateAnimation: function (data, stackedOnPoints, coordSys, api, step, valueOrigin) {    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);      stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step);      next = turnPointsIntoStep(diff.next, coordSys, step);      stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step);    } // 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.setShape({        points: next      });      if (polygon) {        polygon.setShape({          points: next,          stackedOnPoints: stackedOnNext        });      }      return;    } // `diff.current` is subset of `current` (which should be ensured by    // turnPointsIntoStep), so points in `__points` can be updated when    // points in `current` are update during animation.    polyline.shape.__points = diff.current;    polyline.shape.points = current;    graphic.updateProps(polyline, {      shape: {        points: next      }    }, seriesModel);    if (polygon) {      polygon.setShape({        points: current,        stackedOnPoints: stackedOnCurrent      });      graphic.updateProps(polygon, {        shape: {          points: next,          stackedOnPoints: stackedOnNext        }      }, seriesModel);    }    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 () {        for (var i = 0; i < updatedDataInfo.length; i++) {          var el = updatedDataInfo[i].el;          el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]);        }      });    }  },  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._data = null;  }});module.exports = _default;
 |