Line.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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. var zrUtil = require("zrender/lib/core/util");
  20. var vector = require("zrender/lib/core/vector");
  21. var symbolUtil = require("../../util/symbol");
  22. var LinePath = require("./LinePath");
  23. var graphic = require("../../util/graphic");
  24. var _number = require("../../util/number");
  25. var round = _number.round;
  26. /*
  27. * Licensed to the Apache Software Foundation (ASF) under one
  28. * or more contributor license agreements. See the NOTICE file
  29. * distributed with this work for additional information
  30. * regarding copyright ownership. The ASF licenses this file
  31. * to you under the Apache License, Version 2.0 (the
  32. * "License"); you may not use this file except in compliance
  33. * with the License. You may obtain a copy of the License at
  34. *
  35. * http://www.apache.org/licenses/LICENSE-2.0
  36. *
  37. * Unless required by applicable law or agreed to in writing,
  38. * software distributed under the License is distributed on an
  39. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  40. * KIND, either express or implied. See the License for the
  41. * specific language governing permissions and limitations
  42. * under the License.
  43. */
  44. /**
  45. * @module echarts/chart/helper/Line
  46. */
  47. var SYMBOL_CATEGORIES = ['fromSymbol', 'toSymbol'];
  48. function makeSymbolTypeKey(symbolCategory) {
  49. return '_' + symbolCategory + 'Type';
  50. }
  51. /**
  52. * @inner
  53. */
  54. function createSymbol(name, lineData, idx) {
  55. var symbolType = lineData.getItemVisual(idx, name);
  56. if (!symbolType || symbolType === 'none') {
  57. return;
  58. }
  59. var color = lineData.getItemVisual(idx, 'color');
  60. var symbolSize = lineData.getItemVisual(idx, name + 'Size');
  61. var symbolRotate = lineData.getItemVisual(idx, name + 'Rotate');
  62. if (!zrUtil.isArray(symbolSize)) {
  63. symbolSize = [symbolSize, symbolSize];
  64. }
  65. var symbolPath = symbolUtil.createSymbol(symbolType, -symbolSize[0] / 2, -symbolSize[1] / 2, symbolSize[0], symbolSize[1], color); // rotate by default if symbolRotate is not specified or NaN
  66. symbolPath.__specifiedRotation = symbolRotate == null || isNaN(symbolRotate) ? void 0 : +symbolRotate * Math.PI / 180 || 0;
  67. symbolPath.name = name;
  68. return symbolPath;
  69. }
  70. function createLine(points) {
  71. var line = new LinePath({
  72. name: 'line',
  73. subPixelOptimize: true
  74. });
  75. setLinePoints(line.shape, points);
  76. return line;
  77. }
  78. function setLinePoints(targetShape, points) {
  79. targetShape.x1 = points[0][0];
  80. targetShape.y1 = points[0][1];
  81. targetShape.x2 = points[1][0];
  82. targetShape.y2 = points[1][1];
  83. targetShape.percent = 1;
  84. var cp1 = points[2];
  85. if (cp1) {
  86. targetShape.cpx1 = cp1[0];
  87. targetShape.cpy1 = cp1[1];
  88. } else {
  89. targetShape.cpx1 = NaN;
  90. targetShape.cpy1 = NaN;
  91. }
  92. }
  93. function updateSymbolAndLabelBeforeLineUpdate() {
  94. var lineGroup = this;
  95. var symbolFrom = lineGroup.childOfName('fromSymbol');
  96. var symbolTo = lineGroup.childOfName('toSymbol');
  97. var label = lineGroup.childOfName('label'); // Quick reject
  98. if (!symbolFrom && !symbolTo && label.ignore) {
  99. return;
  100. }
  101. var invScale = 1;
  102. var parentNode = this.parent;
  103. while (parentNode) {
  104. if (parentNode.scale) {
  105. invScale /= parentNode.scale[0];
  106. }
  107. parentNode = parentNode.parent;
  108. }
  109. var line = lineGroup.childOfName('line'); // If line not changed
  110. // FIXME Parent scale changed
  111. if (!this.__dirty && !line.__dirty) {
  112. return;
  113. }
  114. var percent = line.shape.percent;
  115. var fromPos = line.pointAt(0);
  116. var toPos = line.pointAt(percent);
  117. var d = vector.sub([], toPos, fromPos);
  118. vector.normalize(d, d);
  119. if (symbolFrom) {
  120. symbolFrom.attr('position', fromPos); // Fix #12388
  121. // when symbol is set to be 'arrow' in markLine,
  122. // symbolRotate value will be ignored, and compulsively use tangent angle.
  123. // rotate by default if symbol rotation is not specified
  124. var specifiedRotation = symbolFrom.__specifiedRotation;
  125. if (specifiedRotation == null) {
  126. var tangent = line.tangentAt(0);
  127. symbolFrom.attr('rotation', Math.PI / 2 - Math.atan2(tangent[1], tangent[0]));
  128. } else {
  129. symbolFrom.attr('rotation', specifiedRotation);
  130. }
  131. symbolFrom.attr('scale', [invScale * percent, invScale * percent]);
  132. }
  133. if (symbolTo) {
  134. symbolTo.attr('position', toPos); // Fix #12388
  135. // when symbol is set to be 'arrow' in markLine,
  136. // symbolRotate value will be ignored, and compulsively use tangent angle.
  137. // rotate by default if symbol rotation is not specified
  138. var specifiedRotation = symbolTo.__specifiedRotation;
  139. if (specifiedRotation == null) {
  140. var tangent = line.tangentAt(1);
  141. symbolTo.attr('rotation', -Math.PI / 2 - Math.atan2(tangent[1], tangent[0]));
  142. } else {
  143. symbolTo.attr('rotation', specifiedRotation);
  144. }
  145. symbolTo.attr('scale', [invScale * percent, invScale * percent]);
  146. }
  147. if (!label.ignore) {
  148. label.attr('position', toPos);
  149. var textPosition;
  150. var textAlign;
  151. var textVerticalAlign;
  152. var textOrigin;
  153. var distance = label.__labelDistance;
  154. var distanceX = distance[0] * invScale;
  155. var distanceY = distance[1] * invScale;
  156. var halfPercent = percent / 2;
  157. var tangent = line.tangentAt(halfPercent);
  158. var n = [tangent[1], -tangent[0]];
  159. var cp = line.pointAt(halfPercent);
  160. if (n[1] > 0) {
  161. n[0] = -n[0];
  162. n[1] = -n[1];
  163. }
  164. var dir = tangent[0] < 0 ? -1 : 1;
  165. if (label.__position !== 'start' && label.__position !== 'end') {
  166. var rotation = -Math.atan2(tangent[1], tangent[0]);
  167. if (toPos[0] < fromPos[0]) {
  168. rotation = Math.PI + rotation;
  169. }
  170. label.attr('rotation', rotation);
  171. }
  172. var dy;
  173. switch (label.__position) {
  174. case 'insideStartTop':
  175. case 'insideMiddleTop':
  176. case 'insideEndTop':
  177. case 'middle':
  178. dy = -distanceY;
  179. textVerticalAlign = 'bottom';
  180. break;
  181. case 'insideStartBottom':
  182. case 'insideMiddleBottom':
  183. case 'insideEndBottom':
  184. dy = distanceY;
  185. textVerticalAlign = 'top';
  186. break;
  187. default:
  188. dy = 0;
  189. textVerticalAlign = 'middle';
  190. }
  191. switch (label.__position) {
  192. case 'end':
  193. textPosition = [d[0] * distanceX + toPos[0], d[1] * distanceY + toPos[1]];
  194. textAlign = d[0] > 0.8 ? 'left' : d[0] < -0.8 ? 'right' : 'center';
  195. textVerticalAlign = d[1] > 0.8 ? 'top' : d[1] < -0.8 ? 'bottom' : 'middle';
  196. break;
  197. case 'start':
  198. textPosition = [-d[0] * distanceX + fromPos[0], -d[1] * distanceY + fromPos[1]];
  199. textAlign = d[0] > 0.8 ? 'right' : d[0] < -0.8 ? 'left' : 'center';
  200. textVerticalAlign = d[1] > 0.8 ? 'bottom' : d[1] < -0.8 ? 'top' : 'middle';
  201. break;
  202. case 'insideStartTop':
  203. case 'insideStart':
  204. case 'insideStartBottom':
  205. textPosition = [distanceX * dir + fromPos[0], fromPos[1] + dy];
  206. textAlign = tangent[0] < 0 ? 'right' : 'left';
  207. textOrigin = [-distanceX * dir, -dy];
  208. break;
  209. case 'insideMiddleTop':
  210. case 'insideMiddle':
  211. case 'insideMiddleBottom':
  212. case 'middle':
  213. textPosition = [cp[0], cp[1] + dy];
  214. textAlign = 'center';
  215. textOrigin = [0, -dy];
  216. break;
  217. case 'insideEndTop':
  218. case 'insideEnd':
  219. case 'insideEndBottom':
  220. textPosition = [-distanceX * dir + toPos[0], toPos[1] + dy];
  221. textAlign = tangent[0] >= 0 ? 'right' : 'left';
  222. textOrigin = [distanceX * dir, -dy];
  223. break;
  224. }
  225. label.attr({
  226. style: {
  227. // Use the user specified text align and baseline first
  228. textVerticalAlign: label.__verticalAlign || textVerticalAlign,
  229. textAlign: label.__textAlign || textAlign
  230. },
  231. position: textPosition,
  232. scale: [invScale, invScale],
  233. origin: textOrigin
  234. });
  235. }
  236. }
  237. /**
  238. * @constructor
  239. * @extends {module:zrender/graphic/Group}
  240. * @alias {module:echarts/chart/helper/Line}
  241. */
  242. function Line(lineData, idx, seriesScope) {
  243. graphic.Group.call(this);
  244. this._createLine(lineData, idx, seriesScope);
  245. }
  246. var lineProto = Line.prototype; // Update symbol position and rotation
  247. lineProto.beforeUpdate = updateSymbolAndLabelBeforeLineUpdate;
  248. lineProto._createLine = function (lineData, idx, seriesScope) {
  249. var seriesModel = lineData.hostModel;
  250. var linePoints = lineData.getItemLayout(idx);
  251. var line = createLine(linePoints);
  252. line.shape.percent = 0;
  253. graphic.initProps(line, {
  254. shape: {
  255. percent: 1
  256. }
  257. }, seriesModel, idx);
  258. this.add(line);
  259. var label = new graphic.Text({
  260. name: 'label',
  261. // FIXME
  262. // Temporary solution for `focusNodeAdjacency`.
  263. // line label do not use the opacity of lineStyle.
  264. lineLabelOriginalOpacity: 1
  265. });
  266. this.add(label);
  267. zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
  268. var symbol = createSymbol(symbolCategory, lineData, idx); // symbols must added after line to make sure
  269. // it will be updated after line#update.
  270. // Or symbol position and rotation update in line#beforeUpdate will be one frame slow
  271. this.add(symbol);
  272. this[makeSymbolTypeKey(symbolCategory)] = lineData.getItemVisual(idx, symbolCategory);
  273. }, this);
  274. this._updateCommonStl(lineData, idx, seriesScope);
  275. };
  276. lineProto.updateData = function (lineData, idx, seriesScope) {
  277. var seriesModel = lineData.hostModel;
  278. var line = this.childOfName('line');
  279. var linePoints = lineData.getItemLayout(idx);
  280. var target = {
  281. shape: {}
  282. };
  283. setLinePoints(target.shape, linePoints);
  284. graphic.updateProps(line, target, seriesModel, idx);
  285. zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
  286. var symbolType = lineData.getItemVisual(idx, symbolCategory);
  287. var key = makeSymbolTypeKey(symbolCategory); // Symbol changed
  288. if (this[key] !== symbolType) {
  289. this.remove(this.childOfName(symbolCategory));
  290. var symbol = createSymbol(symbolCategory, lineData, idx);
  291. this.add(symbol);
  292. }
  293. this[key] = symbolType;
  294. }, this);
  295. this._updateCommonStl(lineData, idx, seriesScope);
  296. };
  297. lineProto._updateCommonStl = function (lineData, idx, seriesScope) {
  298. var seriesModel = lineData.hostModel;
  299. var line = this.childOfName('line');
  300. var lineStyle = seriesScope && seriesScope.lineStyle;
  301. var hoverLineStyle = seriesScope && seriesScope.hoverLineStyle;
  302. var labelModel = seriesScope && seriesScope.labelModel;
  303. var hoverLabelModel = seriesScope && seriesScope.hoverLabelModel; // Optimization for large dataset
  304. if (!seriesScope || lineData.hasItemOption) {
  305. var itemModel = lineData.getItemModel(idx);
  306. lineStyle = itemModel.getModel('lineStyle').getLineStyle();
  307. hoverLineStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle();
  308. labelModel = itemModel.getModel('label');
  309. hoverLabelModel = itemModel.getModel('emphasis.label');
  310. }
  311. var visualColor = lineData.getItemVisual(idx, 'color');
  312. var visualOpacity = zrUtil.retrieve3(lineData.getItemVisual(idx, 'opacity'), lineStyle.opacity, 1);
  313. line.useStyle(zrUtil.defaults({
  314. strokeNoScale: true,
  315. fill: 'none',
  316. stroke: visualColor,
  317. opacity: visualOpacity
  318. }, lineStyle));
  319. line.hoverStyle = hoverLineStyle; // Update symbol
  320. zrUtil.each(SYMBOL_CATEGORIES, function (symbolCategory) {
  321. var symbol = this.childOfName(symbolCategory);
  322. if (symbol) {
  323. symbol.setColor(visualColor);
  324. symbol.setStyle({
  325. opacity: visualOpacity
  326. });
  327. }
  328. }, this);
  329. var showLabel = labelModel.getShallow('show');
  330. var hoverShowLabel = hoverLabelModel.getShallow('show');
  331. var label = this.childOfName('label');
  332. var defaultLabelColor;
  333. var baseText; // FIXME: the logic below probably should be merged to `graphic.setLabelStyle`.
  334. if (showLabel || hoverShowLabel) {
  335. defaultLabelColor = visualColor || '#000';
  336. baseText = seriesModel.getFormattedLabel(idx, 'normal', lineData.dataType);
  337. if (baseText == null) {
  338. var rawVal = seriesModel.getRawValue(idx);
  339. baseText = rawVal == null ? lineData.getName(idx) : isFinite(rawVal) ? round(rawVal) : rawVal;
  340. }
  341. }
  342. var normalText = showLabel ? baseText : null;
  343. var emphasisText = hoverShowLabel ? zrUtil.retrieve2(seriesModel.getFormattedLabel(idx, 'emphasis', lineData.dataType), baseText) : null;
  344. var labelStyle = label.style; // Always set `textStyle` even if `normalStyle.text` is null, because default
  345. // values have to be set on `normalStyle`.
  346. if (normalText != null || emphasisText != null) {
  347. graphic.setTextStyle(label.style, labelModel, {
  348. text: normalText
  349. }, {
  350. autoColor: defaultLabelColor
  351. });
  352. label.__textAlign = labelStyle.textAlign;
  353. label.__verticalAlign = labelStyle.textVerticalAlign; // 'start', 'middle', 'end'
  354. label.__position = labelModel.get('position') || 'middle';
  355. var distance = labelModel.get('distance');
  356. if (!zrUtil.isArray(distance)) {
  357. distance = [distance, distance];
  358. }
  359. label.__labelDistance = distance;
  360. }
  361. if (emphasisText != null) {
  362. // Only these properties supported in this emphasis style here.
  363. label.hoverStyle = {
  364. text: emphasisText,
  365. textFill: hoverLabelModel.getTextColor(true),
  366. // For merging hover style to normal style, do not use
  367. // `hoverLabelModel.getFont()` here.
  368. fontStyle: hoverLabelModel.getShallow('fontStyle'),
  369. fontWeight: hoverLabelModel.getShallow('fontWeight'),
  370. fontSize: hoverLabelModel.getShallow('fontSize'),
  371. fontFamily: hoverLabelModel.getShallow('fontFamily')
  372. };
  373. } else {
  374. label.hoverStyle = {
  375. text: null
  376. };
  377. }
  378. label.ignore = !showLabel && !hoverShowLabel;
  379. graphic.setHoverStyle(this);
  380. };
  381. lineProto.highlight = function () {
  382. this.trigger('emphasis');
  383. };
  384. lineProto.downplay = function () {
  385. this.trigger('normal');
  386. };
  387. lineProto.updateLayout = function (lineData, idx) {
  388. this.setLinePoints(lineData.getItemLayout(idx));
  389. };
  390. lineProto.setLinePoints = function (points) {
  391. var linePath = this.childOfName('line');
  392. setLinePoints(linePath.shape, points);
  393. linePath.dirty();
  394. };
  395. zrUtil.inherits(Line, graphic.Group);
  396. var _default = Line;
  397. module.exports = _default;