LineView.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { __extends } from "tslib"; // FIXME step not support polar
  41. import * as zrUtil from 'zrender/lib/core/util';
  42. import SymbolDraw from '../helper/SymbolDraw';
  43. import SymbolClz from '../helper/Symbol';
  44. import lineAnimationDiff from './lineAnimationDiff';
  45. import * as graphic from '../../util/graphic';
  46. import * as modelUtil from '../../util/model';
  47. import { ECPolyline, ECPolygon } from './poly';
  48. import ChartView from '../../view/Chart';
  49. import { prepareDataCoordInfo, getStackedOnPoint } from './helper';
  50. import { createGridClipPath, createPolarClipPath } from '../helper/createClipPathFromCoordSys';
  51. import { isCoordinateSystemType } from '../../coord/CoordinateSystem';
  52. import { setStatesStylesFromModel, setStatesFlag, enableHoverEmphasis, SPECIAL_STATES } from '../../util/states';
  53. import { setLabelStyle, getLabelStatesModels, labelInner } from '../../label/labelStyle';
  54. import { getDefaultLabel, getDefaultInterpolatedLabel } from '../helper/labelHelper';
  55. import { getECData } from '../../util/innerStore';
  56. import { createFloat32Array } from '../../util/vendor';
  57. import { convertToColorString } from '../../util/format';
  58. import { lerp } from 'zrender/lib/tool/color';
  59. function isPointsSame(points1, points2) {
  60. if (points1.length !== points2.length) {
  61. return;
  62. }
  63. for (var i = 0; i < points1.length; i++) {
  64. if (points1[i] !== points2[i]) {
  65. return;
  66. }
  67. }
  68. return true;
  69. }
  70. function bboxFromPoints(points) {
  71. var minX = Infinity;
  72. var minY = Infinity;
  73. var maxX = -Infinity;
  74. var maxY = -Infinity;
  75. for (var i = 0; i < points.length;) {
  76. var x = points[i++];
  77. var y = points[i++];
  78. if (!isNaN(x)) {
  79. minX = Math.min(x, minX);
  80. maxX = Math.max(x, maxX);
  81. }
  82. if (!isNaN(y)) {
  83. minY = Math.min(y, minY);
  84. maxY = Math.max(y, maxY);
  85. }
  86. }
  87. return [[minX, minY], [maxX, maxY]];
  88. }
  89. function getBoundingDiff(points1, points2) {
  90. var _a = bboxFromPoints(points1),
  91. min1 = _a[0],
  92. max1 = _a[1];
  93. var _b = bboxFromPoints(points2),
  94. min2 = _b[0],
  95. max2 = _b[1]; // Get a max value from each corner of two boundings.
  96. 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]));
  97. }
  98. function getSmooth(smooth) {
  99. return typeof smooth === 'number' ? smooth : smooth ? 0.5 : 0;
  100. }
  101. function getStackedOnPoints(coordSys, data, dataCoordInfo) {
  102. if (!dataCoordInfo.valueDim) {
  103. return [];
  104. }
  105. var len = data.count();
  106. var points = createFloat32Array(len * 2);
  107. for (var idx = 0; idx < len; idx++) {
  108. var pt = getStackedOnPoint(dataCoordInfo, coordSys, data, idx);
  109. points[idx * 2] = pt[0];
  110. points[idx * 2 + 1] = pt[1];
  111. }
  112. return points;
  113. }
  114. function turnPointsIntoStep(points, coordSys, stepTurnAt) {
  115. var baseAxis = coordSys.getBaseAxis();
  116. var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
  117. var stepPoints = [];
  118. var i = 0;
  119. var stepPt = [];
  120. var pt = [];
  121. var nextPt = [];
  122. for (; i < points.length - 2; i += 2) {
  123. nextPt[0] = points[i + 2];
  124. nextPt[1] = points[i + 3];
  125. pt[0] = points[i];
  126. pt[1] = points[i + 1];
  127. stepPoints.push(pt[0], pt[1]);
  128. switch (stepTurnAt) {
  129. case 'end':
  130. stepPt[baseIndex] = nextPt[baseIndex];
  131. stepPt[1 - baseIndex] = pt[1 - baseIndex];
  132. stepPoints.push(stepPt[0], stepPt[1]);
  133. break;
  134. case 'middle':
  135. var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
  136. var stepPt2 = [];
  137. stepPt[baseIndex] = stepPt2[baseIndex] = middle;
  138. stepPt[1 - baseIndex] = pt[1 - baseIndex];
  139. stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
  140. stepPoints.push(stepPt[0], stepPt[1]);
  141. stepPoints.push(stepPt2[0], stepPt2[1]);
  142. break;
  143. default:
  144. // default is start
  145. stepPt[baseIndex] = pt[baseIndex];
  146. stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
  147. stepPoints.push(stepPt[0], stepPt[1]);
  148. }
  149. } // Last points
  150. stepPoints.push(points[i++], points[i++]);
  151. return stepPoints;
  152. }
  153. /**
  154. * Clip color stops to edge. Avoid creating too large gradients.
  155. * Which may lead to blurry when GPU acceleration is enabled. See #15680
  156. *
  157. * The stops has been sorted from small to large.
  158. */
  159. function clipColorStops(colorStops, maxSize) {
  160. var newColorStops = [];
  161. var len = colorStops.length; // coord will always < 0 in prevOutOfRangeColorStop.
  162. var prevOutOfRangeColorStop;
  163. var prevInRangeColorStop;
  164. function lerpStop(stop0, stop1, clippedCoord) {
  165. var coord0 = stop0.coord;
  166. var p = (clippedCoord - coord0) / (stop1.coord - coord0);
  167. var color = lerp(p, [stop0.color, stop1.color]);
  168. return {
  169. coord: clippedCoord,
  170. color: color
  171. };
  172. }
  173. for (var i = 0; i < len; i++) {
  174. var stop_1 = colorStops[i];
  175. var coord = stop_1.coord;
  176. if (coord < 0) {
  177. prevOutOfRangeColorStop = stop_1;
  178. } else if (coord > maxSize) {
  179. if (prevInRangeColorStop) {
  180. newColorStops.push(lerpStop(prevInRangeColorStop, stop_1, maxSize));
  181. } // All following stop will be out of range. So just ignore them.
  182. break;
  183. } else {
  184. if (prevOutOfRangeColorStop) {
  185. newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0)); // Reset
  186. prevOutOfRangeColorStop = null;
  187. }
  188. newColorStops.push(stop_1);
  189. prevInRangeColorStop = stop_1;
  190. }
  191. }
  192. return newColorStops;
  193. }
  194. function getVisualGradient(data, coordSys, api) {
  195. var visualMetaList = data.getVisual('visualMeta');
  196. if (!visualMetaList || !visualMetaList.length || !data.count()) {
  197. // When data.count() is 0, gradient range can not be calculated.
  198. return;
  199. }
  200. if (coordSys.type !== 'cartesian2d') {
  201. if (process.env.NODE_ENV !== 'production') {
  202. console.warn('Visual map on line style is only supported on cartesian2d.');
  203. }
  204. return;
  205. }
  206. var coordDim;
  207. var visualMeta;
  208. for (var i = visualMetaList.length - 1; i >= 0; i--) {
  209. var dimInfo = data.getDimensionInfo(visualMetaList[i].dimension);
  210. coordDim = dimInfo && dimInfo.coordDim; // Can only be x or y
  211. if (coordDim === 'x' || coordDim === 'y') {
  212. visualMeta = visualMetaList[i];
  213. break;
  214. }
  215. }
  216. if (!visualMeta) {
  217. if (process.env.NODE_ENV !== 'production') {
  218. console.warn('Visual map on line style only support x or y dimension.');
  219. }
  220. return;
  221. } // If the area to be rendered is bigger than area defined by LinearGradient,
  222. // the canvas spec prescribes that the color of the first stop and the last
  223. // stop should be used. But if two stops are added at offset 0, in effect
  224. // browsers use the color of the second stop to render area outside
  225. // LinearGradient. So we can only infinitesimally extend area defined in
  226. // LinearGradient to render `outerColors`.
  227. var axis = coordSys.getAxis(coordDim); // dataToCoord mapping may not be linear, but must be monotonic.
  228. var colorStops = zrUtil.map(visualMeta.stops, function (stop) {
  229. // offset will be calculated later.
  230. return {
  231. coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
  232. color: stop.color
  233. };
  234. });
  235. var stopLen = colorStops.length;
  236. var outerColors = visualMeta.outerColors.slice();
  237. if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
  238. colorStops.reverse();
  239. outerColors.reverse();
  240. }
  241. var colorStopsInRange = clipColorStops(colorStops, coordDim === 'x' ? api.getWidth() : api.getHeight());
  242. var inRangeStopLen = colorStopsInRange.length;
  243. if (!inRangeStopLen && stopLen) {
  244. // All stops are out of range. All will be the same color.
  245. return colorStops[0].coord < 0 ? outerColors[1] ? outerColors[1] : colorStops[stopLen - 1].color : outerColors[0] ? outerColors[0] : colorStops[0].color;
  246. }
  247. var tinyExtent = 0; // Arbitrary value: 10px
  248. var minCoord = colorStopsInRange[0].coord - tinyExtent;
  249. var maxCoord = colorStopsInRange[inRangeStopLen - 1].coord + tinyExtent;
  250. var coordSpan = maxCoord - minCoord;
  251. if (coordSpan < 1e-3) {
  252. return 'transparent';
  253. }
  254. zrUtil.each(colorStopsInRange, function (stop) {
  255. stop.offset = (stop.coord - minCoord) / coordSpan;
  256. });
  257. colorStopsInRange.push({
  258. // NOTE: inRangeStopLen may still be 0 if stoplen is zero.
  259. offset: inRangeStopLen ? colorStopsInRange[inRangeStopLen - 1].offset : 0.5,
  260. color: outerColors[1] || 'transparent'
  261. });
  262. colorStopsInRange.unshift({
  263. offset: inRangeStopLen ? colorStopsInRange[0].offset : 0.5,
  264. color: outerColors[0] || 'transparent'
  265. });
  266. var gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStopsInRange, true);
  267. gradient[coordDim] = minCoord;
  268. gradient[coordDim + '2'] = maxCoord;
  269. return gradient;
  270. }
  271. function getIsIgnoreFunc(seriesModel, data, coordSys) {
  272. var showAllSymbol = seriesModel.get('showAllSymbol');
  273. var isAuto = showAllSymbol === 'auto';
  274. if (showAllSymbol && !isAuto) {
  275. return;
  276. }
  277. var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
  278. if (!categoryAxis) {
  279. return;
  280. } // Note that category label interval strategy might bring some weird effect
  281. // in some scenario: users may wonder why some of the symbols are not
  282. // displayed. So we show all symbols as possible as we can.
  283. if (isAuto // Simplify the logic, do not determine label overlap here.
  284. && canShowAllSymbolForCategory(categoryAxis, data)) {
  285. return;
  286. } // Otherwise follow the label interval strategy on category axis.
  287. var categoryDataDim = data.mapDimension(categoryAxis.dim);
  288. var labelMap = {};
  289. zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
  290. var ordinalNumber = categoryAxis.scale.getRawOrdinalNumber(labelItem.tickValue);
  291. labelMap[ordinalNumber] = 1;
  292. });
  293. return function (dataIndex) {
  294. return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
  295. };
  296. }
  297. function canShowAllSymbolForCategory(categoryAxis, data) {
  298. // In mose cases, line is monotonous on category axis, and the label size
  299. // is close with each other. So we check the symbol size and some of the
  300. // label size alone with the category axis to estimate whether all symbol
  301. // can be shown without overlap.
  302. var axisExtent = categoryAxis.getExtent();
  303. var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count();
  304. isNaN(availSize) && (availSize = 0); // 0/0 is NaN.
  305. // Sampling some points, max 5.
  306. var dataLen = data.count();
  307. var step = Math.max(1, Math.round(dataLen / 5));
  308. for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) {
  309. if (SymbolClz.getSymbolSize(data, dataIndex // Only for cartesian, where `isHorizontal` exists.
  310. )[categoryAxis.isHorizontal() ? 1 : 0] // Empirical number
  311. * 1.5 > availSize) {
  312. return false;
  313. }
  314. }
  315. return true;
  316. }
  317. function isPointNull(x, y) {
  318. return isNaN(x) || isNaN(y);
  319. }
  320. function getLastIndexNotNull(points) {
  321. var len = points.length / 2;
  322. for (; len > 0; len--) {
  323. if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) {
  324. break;
  325. }
  326. }
  327. return len - 1;
  328. }
  329. function getPointAtIndex(points, idx) {
  330. return [points[idx * 2], points[idx * 2 + 1]];
  331. }
  332. function getIndexRange(points, xOrY, dim) {
  333. var len = points.length / 2;
  334. var dimIdx = dim === 'x' ? 0 : 1;
  335. var a;
  336. var b;
  337. var prevIndex = 0;
  338. var nextIndex = -1;
  339. for (var i = 0; i < len; i++) {
  340. b = points[i * 2 + dimIdx];
  341. if (isNaN(b) || isNaN(points[i * 2 + 1 - dimIdx])) {
  342. continue;
  343. }
  344. if (i === 0) {
  345. a = b;
  346. continue;
  347. }
  348. if (a <= xOrY && b >= xOrY || a >= xOrY && b <= xOrY) {
  349. nextIndex = i;
  350. break;
  351. }
  352. prevIndex = i;
  353. a = b;
  354. }
  355. return {
  356. range: [prevIndex, nextIndex],
  357. t: (xOrY - a) / (b - a)
  358. };
  359. }
  360. function anyStateShowEndLabel(seriesModel) {
  361. if (seriesModel.get(['endLabel', 'show'])) {
  362. return true;
  363. }
  364. for (var i = 0; i < SPECIAL_STATES.length; i++) {
  365. if (seriesModel.get([SPECIAL_STATES[i], 'endLabel', 'show'])) {
  366. return true;
  367. }
  368. }
  369. return false;
  370. }
  371. function createLineClipPath(lineView, coordSys, hasAnimation, seriesModel) {
  372. if (isCoordinateSystemType(coordSys, 'cartesian2d')) {
  373. var endLabelModel_1 = seriesModel.getModel('endLabel');
  374. var valueAnimation_1 = endLabelModel_1.get('valueAnimation');
  375. var data_1 = seriesModel.getData();
  376. var labelAnimationRecord_1 = {
  377. lastFrameIndex: 0
  378. };
  379. var during = anyStateShowEndLabel(seriesModel) ? function (percent, clipRect) {
  380. lineView._endLabelOnDuring(percent, clipRect, data_1, labelAnimationRecord_1, valueAnimation_1, endLabelModel_1, coordSys);
  381. } : null;
  382. var isHorizontal = coordSys.getBaseAxis().isHorizontal();
  383. var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, function () {
  384. var endLabel = lineView._endLabel;
  385. if (endLabel && hasAnimation) {
  386. if (labelAnimationRecord_1.originalX != null) {
  387. endLabel.attr({
  388. x: labelAnimationRecord_1.originalX,
  389. y: labelAnimationRecord_1.originalY
  390. });
  391. }
  392. }
  393. }, during); // Expand clip shape to avoid clipping when line value exceeds axis
  394. if (!seriesModel.get('clip', true)) {
  395. var rectShape = clipPath.shape;
  396. var expandSize = Math.max(rectShape.width, rectShape.height);
  397. if (isHorizontal) {
  398. rectShape.y -= expandSize;
  399. rectShape.height += expandSize * 2;
  400. } else {
  401. rectShape.x -= expandSize;
  402. rectShape.width += expandSize * 2;
  403. }
  404. } // Set to the final frame. To make sure label layout is right.
  405. if (during) {
  406. during(1, clipPath);
  407. }
  408. return clipPath;
  409. } else {
  410. if (process.env.NODE_ENV !== 'production') {
  411. if (seriesModel.get(['endLabel', 'show'])) {
  412. console.warn('endLabel is not supported for lines in polar systems.');
  413. }
  414. }
  415. return createPolarClipPath(coordSys, hasAnimation, seriesModel);
  416. }
  417. }
  418. function getEndLabelStateSpecified(endLabelModel, coordSys) {
  419. var baseAxis = coordSys.getBaseAxis();
  420. var isHorizontal = baseAxis.isHorizontal();
  421. var isBaseInversed = baseAxis.inverse;
  422. var align = isHorizontal ? isBaseInversed ? 'right' : 'left' : 'center';
  423. var verticalAlign = isHorizontal ? 'middle' : isBaseInversed ? 'top' : 'bottom';
  424. return {
  425. normal: {
  426. align: endLabelModel.get('align') || align,
  427. verticalAlign: endLabelModel.get('verticalAlign') || verticalAlign
  428. }
  429. };
  430. }
  431. var LineView =
  432. /** @class */
  433. function (_super) {
  434. __extends(LineView, _super);
  435. function LineView() {
  436. return _super !== null && _super.apply(this, arguments) || this;
  437. }
  438. LineView.prototype.init = function () {
  439. var lineGroup = new graphic.Group();
  440. var symbolDraw = new SymbolDraw();
  441. this.group.add(symbolDraw.group);
  442. this._symbolDraw = symbolDraw;
  443. this._lineGroup = lineGroup;
  444. };
  445. LineView.prototype.render = function (seriesModel, ecModel, api) {
  446. var _this = this;
  447. var coordSys = seriesModel.coordinateSystem;
  448. var group = this.group;
  449. var data = seriesModel.getData();
  450. var lineStyleModel = seriesModel.getModel('lineStyle');
  451. var areaStyleModel = seriesModel.getModel('areaStyle');
  452. var points = data.getLayout('points') || [];
  453. var isCoordSysPolar = coordSys.type === 'polar';
  454. var prevCoordSys = this._coordSys;
  455. var symbolDraw = this._symbolDraw;
  456. var polyline = this._polyline;
  457. var polygon = this._polygon;
  458. var lineGroup = this._lineGroup;
  459. var hasAnimation = seriesModel.get('animation');
  460. var isAreaChart = !areaStyleModel.isEmpty();
  461. var valueOrigin = areaStyleModel.get('origin');
  462. var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
  463. var stackedOnPoints = isAreaChart && getStackedOnPoints(coordSys, data, dataCoordInfo);
  464. var showSymbol = seriesModel.get('showSymbol');
  465. var isIgnoreFunc = showSymbol && !isCoordSysPolar && getIsIgnoreFunc(seriesModel, data, coordSys); // Remove temporary symbols
  466. var oldData = this._data;
  467. oldData && oldData.eachItemGraphicEl(function (el, idx) {
  468. if (el.__temp) {
  469. group.remove(el);
  470. oldData.setItemGraphicEl(idx, null);
  471. }
  472. }); // Remove previous created symbols if showSymbol changed to false
  473. if (!showSymbol) {
  474. symbolDraw.remove();
  475. }
  476. group.add(lineGroup); // FIXME step not support polar
  477. var step = !isCoordSysPolar ? seriesModel.get('step') : false;
  478. var clipShapeForSymbol;
  479. if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
  480. clipShapeForSymbol = coordSys.getArea(); // Avoid float number rounding error for symbol on the edge of axis extent.
  481. // See #7913 and `test/dataZoom-clip.html`.
  482. if (clipShapeForSymbol.width != null) {
  483. clipShapeForSymbol.x -= 0.1;
  484. clipShapeForSymbol.y -= 0.1;
  485. clipShapeForSymbol.width += 0.2;
  486. clipShapeForSymbol.height += 0.2;
  487. } else if (clipShapeForSymbol.r0) {
  488. clipShapeForSymbol.r0 -= 0.5;
  489. clipShapeForSymbol.r += 0.5;
  490. }
  491. }
  492. this._clipShapeForSymbol = clipShapeForSymbol;
  493. var visualColor = getVisualGradient(data, coordSys, api) || data.getVisual('style')[data.getVisual('drawType')]; // Initialization animation or coordinate system changed
  494. if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) {
  495. showSymbol && symbolDraw.updateData(data, {
  496. isIgnore: isIgnoreFunc,
  497. clipShape: clipShapeForSymbol,
  498. disableAnimation: true,
  499. getSymbolPoint: function (idx) {
  500. return [points[idx * 2], points[idx * 2 + 1]];
  501. }
  502. });
  503. hasAnimation && this._initSymbolLabelAnimation(data, coordSys, clipShapeForSymbol);
  504. if (step) {
  505. // TODO If stacked series is not step
  506. points = turnPointsIntoStep(points, coordSys, step);
  507. if (stackedOnPoints) {
  508. stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
  509. }
  510. }
  511. polyline = this._newPolyline(points);
  512. if (isAreaChart) {
  513. polygon = this._newPolygon(points, stackedOnPoints);
  514. } // NOTE: Must update _endLabel before setClipPath.
  515. if (!isCoordSysPolar) {
  516. this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor));
  517. }
  518. lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel));
  519. } else {
  520. if (isAreaChart && !polygon) {
  521. // If areaStyle is added
  522. polygon = this._newPolygon(points, stackedOnPoints);
  523. } else if (polygon && !isAreaChart) {
  524. // If areaStyle is removed
  525. lineGroup.remove(polygon);
  526. polygon = this._polygon = null;
  527. } // NOTE: Must update _endLabel before setClipPath.
  528. if (!isCoordSysPolar) {
  529. this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor));
  530. } // Update clipPath
  531. var oldClipPath = lineGroup.getClipPath();
  532. if (oldClipPath) {
  533. var newClipPath = createLineClipPath(this, coordSys, false, seriesModel);
  534. graphic.initProps(oldClipPath, {
  535. shape: newClipPath.shape
  536. }, seriesModel);
  537. } else {
  538. lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel));
  539. } // Always update, or it is wrong in the case turning on legend
  540. // because points are not changed
  541. showSymbol && symbolDraw.updateData(data, {
  542. isIgnore: isIgnoreFunc,
  543. clipShape: clipShapeForSymbol,
  544. disableAnimation: true,
  545. getSymbolPoint: function (idx) {
  546. return [points[idx * 2], points[idx * 2 + 1]];
  547. }
  548. }); // In the case data zoom triggerred refreshing frequently
  549. // Data may not change if line has a category axis. So it should animate nothing
  550. if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) || !isPointsSame(this._points, points)) {
  551. if (hasAnimation) {
  552. this._doUpdateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin);
  553. } else {
  554. // Not do it in update with animation
  555. if (step) {
  556. // TODO If stacked series is not step
  557. points = turnPointsIntoStep(points, coordSys, step);
  558. if (stackedOnPoints) {
  559. stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
  560. }
  561. }
  562. polyline.setShape({
  563. points: points
  564. });
  565. polygon && polygon.setShape({
  566. points: points,
  567. stackedOnPoints: stackedOnPoints
  568. });
  569. }
  570. }
  571. }
  572. var focus = seriesModel.get(['emphasis', 'focus']);
  573. var blurScope = seriesModel.get(['emphasis', 'blurScope']);
  574. polyline.useStyle(zrUtil.defaults( // Use color in lineStyle first
  575. lineStyleModel.getLineStyle(), {
  576. fill: 'none',
  577. stroke: visualColor,
  578. lineJoin: 'bevel'
  579. }));
  580. setStatesStylesFromModel(polyline, seriesModel, 'lineStyle');
  581. if (polyline.style.lineWidth > 0 && seriesModel.get(['emphasis', 'lineStyle', 'width']) === 'bolder') {
  582. var emphasisLineStyle = polyline.getState('emphasis').style;
  583. emphasisLineStyle.lineWidth = +polyline.style.lineWidth + 1;
  584. } // Needs seriesIndex for focus
  585. getECData(polyline).seriesIndex = seriesModel.seriesIndex;
  586. enableHoverEmphasis(polyline, focus, blurScope);
  587. var smooth = getSmooth(seriesModel.get('smooth'));
  588. var smoothMonotone = seriesModel.get('smoothMonotone');
  589. var connectNulls = seriesModel.get('connectNulls');
  590. polyline.setShape({
  591. smooth: smooth,
  592. smoothMonotone: smoothMonotone,
  593. connectNulls: connectNulls
  594. });
  595. if (polygon) {
  596. var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
  597. var stackedOnSmooth = 0;
  598. polygon.useStyle(zrUtil.defaults(areaStyleModel.getAreaStyle(), {
  599. fill: visualColor,
  600. opacity: 0.7,
  601. lineJoin: 'bevel',
  602. decal: data.getVisual('style').decal
  603. }));
  604. if (stackedOnSeries) {
  605. stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
  606. }
  607. polygon.setShape({
  608. smooth: smooth,
  609. stackedOnSmooth: stackedOnSmooth,
  610. smoothMonotone: smoothMonotone,
  611. connectNulls: connectNulls
  612. });
  613. setStatesStylesFromModel(polygon, seriesModel, 'areaStyle'); // Needs seriesIndex for focus
  614. getECData(polygon).seriesIndex = seriesModel.seriesIndex;
  615. enableHoverEmphasis(polygon, focus, blurScope);
  616. }
  617. var changePolyState = function (toState) {
  618. _this._changePolyState(toState);
  619. };
  620. data.eachItemGraphicEl(function (el) {
  621. // Switch polyline / polygon state if element changed its state.
  622. el && (el.onHoverStateChange = changePolyState);
  623. });
  624. this._polyline.onHoverStateChange = changePolyState;
  625. this._data = data; // Save the coordinate system for transition animation when data changed
  626. this._coordSys = coordSys;
  627. this._stackedOnPoints = stackedOnPoints;
  628. this._points = points;
  629. this._step = step;
  630. this._valueOrigin = valueOrigin;
  631. };
  632. LineView.prototype.dispose = function () {};
  633. LineView.prototype.highlight = function (seriesModel, ecModel, api, payload) {
  634. var data = seriesModel.getData();
  635. var dataIndex = modelUtil.queryDataIndex(data, payload);
  636. this._changePolyState('emphasis');
  637. if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
  638. var points = data.getLayout('points');
  639. var symbol = data.getItemGraphicEl(dataIndex);
  640. if (!symbol) {
  641. // Create a temporary symbol if it is not exists
  642. var x = points[dataIndex * 2];
  643. var y = points[dataIndex * 2 + 1];
  644. if (isNaN(x) || isNaN(y)) {
  645. // Null data
  646. return;
  647. } // fix #11360: should't draw symbol outside clipShapeForSymbol
  648. if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) {
  649. return;
  650. }
  651. var zlevel = seriesModel.get('zlevel');
  652. var z = seriesModel.get('z');
  653. symbol = new SymbolClz(data, dataIndex);
  654. symbol.x = x;
  655. symbol.y = y;
  656. symbol.setZ(zlevel, z); // ensure label text of the temporary symbol is in front of line and area polygon
  657. var symbolLabel = symbol.getSymbolPath().getTextContent();
  658. if (symbolLabel) {
  659. symbolLabel.zlevel = zlevel;
  660. symbolLabel.z = z;
  661. symbolLabel.z2 = this._polyline.z2 + 1;
  662. }
  663. symbol.__temp = true;
  664. data.setItemGraphicEl(dataIndex, symbol); // Stop scale animation
  665. symbol.stopSymbolAnimation(true);
  666. this.group.add(symbol);
  667. }
  668. symbol.highlight();
  669. } else {
  670. // Highlight whole series
  671. ChartView.prototype.highlight.call(this, seriesModel, ecModel, api, payload);
  672. }
  673. };
  674. LineView.prototype.downplay = function (seriesModel, ecModel, api, payload) {
  675. var data = seriesModel.getData();
  676. var dataIndex = modelUtil.queryDataIndex(data, payload);
  677. this._changePolyState('normal');
  678. if (dataIndex != null && dataIndex >= 0) {
  679. var symbol = data.getItemGraphicEl(dataIndex);
  680. if (symbol) {
  681. if (symbol.__temp) {
  682. data.setItemGraphicEl(dataIndex, null);
  683. this.group.remove(symbol);
  684. } else {
  685. symbol.downplay();
  686. }
  687. }
  688. } else {
  689. // FIXME
  690. // can not downplay completely.
  691. // Downplay whole series
  692. ChartView.prototype.downplay.call(this, seriesModel, ecModel, api, payload);
  693. }
  694. };
  695. LineView.prototype._changePolyState = function (toState) {
  696. var polygon = this._polygon;
  697. setStatesFlag(this._polyline, toState);
  698. polygon && setStatesFlag(polygon, toState);
  699. };
  700. LineView.prototype._newPolyline = function (points) {
  701. var polyline = this._polyline; // Remove previous created polyline
  702. if (polyline) {
  703. this._lineGroup.remove(polyline);
  704. }
  705. polyline = new ECPolyline({
  706. shape: {
  707. points: points
  708. },
  709. segmentIgnoreThreshold: 2,
  710. z2: 10
  711. });
  712. this._lineGroup.add(polyline);
  713. this._polyline = polyline;
  714. return polyline;
  715. };
  716. LineView.prototype._newPolygon = function (points, stackedOnPoints) {
  717. var polygon = this._polygon; // Remove previous created polygon
  718. if (polygon) {
  719. this._lineGroup.remove(polygon);
  720. }
  721. polygon = new ECPolygon({
  722. shape: {
  723. points: points,
  724. stackedOnPoints: stackedOnPoints
  725. },
  726. segmentIgnoreThreshold: 2
  727. });
  728. this._lineGroup.add(polygon);
  729. this._polygon = polygon;
  730. return polygon;
  731. };
  732. LineView.prototype._initSymbolLabelAnimation = function (data, coordSys, clipShape) {
  733. var isHorizontalOrRadial;
  734. var isCoordSysPolar;
  735. var baseAxis = coordSys.getBaseAxis();
  736. var isAxisInverse = baseAxis.inverse;
  737. if (coordSys.type === 'cartesian2d') {
  738. isHorizontalOrRadial = baseAxis.isHorizontal();
  739. isCoordSysPolar = false;
  740. } else if (coordSys.type === 'polar') {
  741. isHorizontalOrRadial = baseAxis.dim === 'angle';
  742. isCoordSysPolar = true;
  743. }
  744. var seriesModel = data.hostModel;
  745. var seriesDuration = seriesModel.get('animationDuration');
  746. if (typeof seriesDuration === 'function') {
  747. seriesDuration = seriesDuration(null);
  748. }
  749. var seriesDalay = seriesModel.get('animationDelay') || 0;
  750. var seriesDalayValue = typeof seriesDalay === 'function' ? seriesDalay(null) : seriesDalay;
  751. data.eachItemGraphicEl(function (symbol, idx) {
  752. var el = symbol;
  753. if (el) {
  754. var point = [symbol.x, symbol.y];
  755. var start = void 0;
  756. var end = void 0;
  757. var current = void 0;
  758. if (clipShape) {
  759. if (isCoordSysPolar) {
  760. var polarClip = clipShape;
  761. var coord = coordSys.pointToCoord(point);
  762. if (isHorizontalOrRadial) {
  763. start = polarClip.startAngle;
  764. end = polarClip.endAngle;
  765. current = -coord[1] / 180 * Math.PI;
  766. } else {
  767. start = polarClip.r0;
  768. end = polarClip.r;
  769. current = coord[0];
  770. }
  771. } else {
  772. var gridClip = clipShape;
  773. if (isHorizontalOrRadial) {
  774. start = gridClip.x;
  775. end = gridClip.x + gridClip.width;
  776. current = symbol.x;
  777. } else {
  778. start = gridClip.y + gridClip.height;
  779. end = gridClip.y;
  780. current = symbol.y;
  781. }
  782. }
  783. }
  784. var ratio = end === start ? 0 : (current - start) / (end - start);
  785. if (isAxisInverse) {
  786. ratio = 1 - ratio;
  787. }
  788. var delay = typeof seriesDalay === 'function' ? seriesDalay(idx) : seriesDuration * ratio + seriesDalayValue;
  789. var symbolPath = el.getSymbolPath();
  790. var text = symbolPath.getTextContent();
  791. el.attr({
  792. scaleX: 0,
  793. scaleY: 0
  794. });
  795. el.animateTo({
  796. scaleX: 1,
  797. scaleY: 1
  798. }, {
  799. duration: 200,
  800. setToFinal: true,
  801. delay: delay
  802. });
  803. if (text) {
  804. text.animateFrom({
  805. style: {
  806. opacity: 0
  807. }
  808. }, {
  809. duration: 300,
  810. delay: delay
  811. });
  812. }
  813. symbolPath.disableLabelAnimation = true;
  814. }
  815. });
  816. };
  817. LineView.prototype._initOrUpdateEndLabel = function (seriesModel, coordSys, inheritColor) {
  818. var endLabelModel = seriesModel.getModel('endLabel');
  819. if (anyStateShowEndLabel(seriesModel)) {
  820. var data_2 = seriesModel.getData();
  821. var polyline = this._polyline;
  822. var endLabel = this._endLabel;
  823. if (!endLabel) {
  824. endLabel = this._endLabel = new graphic.Text({
  825. z2: 200 // should be higher than item symbol
  826. });
  827. endLabel.ignoreClip = true;
  828. polyline.setTextContent(this._endLabel);
  829. polyline.disableLabelAnimation = true;
  830. } // Find last non-NaN data to display data
  831. var dataIndex = getLastIndexNotNull(data_2.getLayout('points'));
  832. if (dataIndex >= 0) {
  833. setLabelStyle(polyline, getLabelStatesModels(seriesModel, 'endLabel'), {
  834. inheritColor: inheritColor,
  835. labelFetcher: seriesModel,
  836. labelDataIndex: dataIndex,
  837. defaultText: function (dataIndex, opt, interpolatedValue) {
  838. return interpolatedValue != null ? getDefaultInterpolatedLabel(data_2, interpolatedValue) : getDefaultLabel(data_2, dataIndex);
  839. },
  840. enableTextSetter: true
  841. }, getEndLabelStateSpecified(endLabelModel, coordSys));
  842. polyline.textConfig.position = null;
  843. }
  844. } else if (this._endLabel) {
  845. this._polyline.removeTextContent();
  846. this._endLabel = null;
  847. }
  848. };
  849. LineView.prototype._endLabelOnDuring = function (percent, clipRect, data, animationRecord, valueAnimation, endLabelModel, coordSys) {
  850. var endLabel = this._endLabel;
  851. var polyline = this._polyline;
  852. if (endLabel) {
  853. // NOTE: Don't remove percent < 1. percent === 1 means the first frame during render.
  854. // The label is not prepared at this time.
  855. if (percent < 1 && animationRecord.originalX == null) {
  856. animationRecord.originalX = endLabel.x;
  857. animationRecord.originalY = endLabel.y;
  858. }
  859. var points = data.getLayout('points');
  860. var seriesModel = data.hostModel;
  861. var connectNulls = seriesModel.get('connectNulls');
  862. var precision = endLabelModel.get('precision');
  863. var distance = endLabelModel.get('distance') || 0;
  864. var baseAxis = coordSys.getBaseAxis();
  865. var isHorizontal = baseAxis.isHorizontal();
  866. var isBaseInversed = baseAxis.inverse;
  867. var clipShape = clipRect.shape;
  868. var xOrY = isBaseInversed ? isHorizontal ? clipShape.x : clipShape.y + clipShape.height : isHorizontal ? clipShape.x + clipShape.width : clipShape.y;
  869. var distanceX = (isHorizontal ? distance : 0) * (isBaseInversed ? -1 : 1);
  870. var distanceY = (isHorizontal ? 0 : -distance) * (isBaseInversed ? -1 : 1);
  871. var dim = isHorizontal ? 'x' : 'y';
  872. var dataIndexRange = getIndexRange(points, xOrY, dim);
  873. var indices = dataIndexRange.range;
  874. var diff = indices[1] - indices[0];
  875. var value = void 0;
  876. if (diff >= 1) {
  877. // diff > 1 && connectNulls, which is on the null data.
  878. if (diff > 1 && !connectNulls) {
  879. var pt = getPointAtIndex(points, indices[0]);
  880. endLabel.attr({
  881. x: pt[0] + distanceX,
  882. y: pt[1] + distanceY
  883. });
  884. valueAnimation && (value = seriesModel.getRawValue(indices[0]));
  885. } else {
  886. var pt = polyline.getPointOn(xOrY, dim);
  887. pt && endLabel.attr({
  888. x: pt[0] + distanceX,
  889. y: pt[1] + distanceY
  890. });
  891. var startValue = seriesModel.getRawValue(indices[0]);
  892. var endValue = seriesModel.getRawValue(indices[1]);
  893. valueAnimation && (value = modelUtil.interpolateRawValues(data, precision, startValue, endValue, dataIndexRange.t));
  894. }
  895. animationRecord.lastFrameIndex = indices[0];
  896. } else {
  897. // If diff <= 0, which is the range is not found(Include NaN)
  898. // Choose the first point or last point.
  899. var idx = percent === 1 || animationRecord.lastFrameIndex > 0 ? indices[0] : 0;
  900. var pt = getPointAtIndex(points, idx);
  901. valueAnimation && (value = seriesModel.getRawValue(idx));
  902. endLabel.attr({
  903. x: pt[0] + distanceX,
  904. y: pt[1] + distanceY
  905. });
  906. }
  907. if (valueAnimation) {
  908. labelInner(endLabel).setLabelText(value);
  909. }
  910. }
  911. };
  912. /**
  913. * @private
  914. */
  915. // FIXME Two value axis
  916. LineView.prototype._doUpdateAnimation = function (data, stackedOnPoints, coordSys, api, step, valueOrigin) {
  917. var polyline = this._polyline;
  918. var polygon = this._polygon;
  919. var seriesModel = data.hostModel;
  920. var diff = lineAnimationDiff(this._data, data, this._stackedOnPoints, stackedOnPoints, this._coordSys, coordSys, this._valueOrigin, valueOrigin);
  921. var current = diff.current;
  922. var stackedOnCurrent = diff.stackedOnCurrent;
  923. var next = diff.next;
  924. var stackedOnNext = diff.stackedOnNext;
  925. if (step) {
  926. // TODO If stacked series is not step
  927. current = turnPointsIntoStep(diff.current, coordSys, step);
  928. stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step);
  929. next = turnPointsIntoStep(diff.next, coordSys, step);
  930. stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step);
  931. } // Don't apply animation if diff is large.
  932. // For better result and avoid memory explosion problems like
  933. // https://github.com/apache/incubator-echarts/issues/12229
  934. if (getBoundingDiff(current, next) > 3000 || polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3000) {
  935. polyline.stopAnimation();
  936. polyline.setShape({
  937. points: next
  938. });
  939. if (polygon) {
  940. polygon.stopAnimation();
  941. polygon.setShape({
  942. points: next,
  943. stackedOnPoints: stackedOnNext
  944. });
  945. }
  946. return;
  947. }
  948. polyline.shape.__points = diff.current;
  949. polyline.shape.points = current;
  950. var target = {
  951. shape: {
  952. points: next
  953. }
  954. }; // Also animate the original points.
  955. // If points reference is changed when turning into step line.
  956. if (diff.current !== current) {
  957. target.shape.__points = diff.next;
  958. } // Stop previous animation.
  959. polyline.stopAnimation();
  960. graphic.updateProps(polyline, target, seriesModel);
  961. if (polygon) {
  962. polygon.setShape({
  963. // Reuse the points with polyline.
  964. points: current,
  965. stackedOnPoints: stackedOnCurrent
  966. });
  967. polygon.stopAnimation();
  968. graphic.updateProps(polygon, {
  969. shape: {
  970. stackedOnPoints: stackedOnNext
  971. }
  972. }, seriesModel); // If use attr directly in updateProps.
  973. if (polyline.shape.points !== polygon.shape.points) {
  974. polygon.shape.points = polyline.shape.points;
  975. }
  976. }
  977. var updatedDataInfo = [];
  978. var diffStatus = diff.status;
  979. for (var i = 0; i < diffStatus.length; i++) {
  980. var cmd = diffStatus[i].cmd;
  981. if (cmd === '=') {
  982. var el = data.getItemGraphicEl(diffStatus[i].idx1);
  983. if (el) {
  984. updatedDataInfo.push({
  985. el: el,
  986. ptIdx: i // Index of points
  987. });
  988. }
  989. }
  990. }
  991. if (polyline.animators && polyline.animators.length) {
  992. polyline.animators[0].during(function () {
  993. polygon && polygon.dirtyShape();
  994. var points = polyline.shape.__points;
  995. for (var i = 0; i < updatedDataInfo.length; i++) {
  996. var el = updatedDataInfo[i].el;
  997. var offset = updatedDataInfo[i].ptIdx * 2;
  998. el.x = points[offset];
  999. el.y = points[offset + 1];
  1000. el.markRedraw();
  1001. }
  1002. });
  1003. }
  1004. };
  1005. LineView.prototype.remove = function (ecModel) {
  1006. var group = this.group;
  1007. var oldData = this._data;
  1008. this._lineGroup.removeAll();
  1009. this._symbolDraw.remove(true); // Remove temporary created elements when highlighting
  1010. oldData && oldData.eachItemGraphicEl(function (el, idx) {
  1011. if (el.__temp) {
  1012. group.remove(el);
  1013. oldData.setItemGraphicEl(idx, null);
  1014. }
  1015. });
  1016. this._polyline = this._polygon = this._coordSys = this._points = this._stackedOnPoints = this._endLabel = this._data = null;
  1017. };
  1018. LineView.type = 'line';
  1019. return LineView;
  1020. }(ChartView);
  1021. export default LineView;