labelGuideHelper.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  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 { Point, Path, Polyline } from '../util/graphic.js';
  41. import PathProxy from 'zrender/lib/core/PathProxy.js';
  42. import { normalizeRadian } from 'zrender/lib/contain/util.js';
  43. import { cubicProjectPoint, quadraticProjectPoint } from 'zrender/lib/core/curve.js';
  44. import { defaults, retrieve2 } from 'zrender/lib/core/util.js';
  45. import { invert } from 'zrender/lib/core/matrix.js';
  46. import * as vector from 'zrender/lib/core/vector.js';
  47. import { DISPLAY_STATES, SPECIAL_STATES } from '../util/states.js';
  48. var PI2 = Math.PI * 2;
  49. var CMD = PathProxy.CMD;
  50. var DEFAULT_SEARCH_SPACE = ['top', 'right', 'bottom', 'left'];
  51. function getCandidateAnchor(pos, distance, rect, outPt, outDir) {
  52. var width = rect.width;
  53. var height = rect.height;
  54. switch (pos) {
  55. case 'top':
  56. outPt.set(rect.x + width / 2, rect.y - distance);
  57. outDir.set(0, -1);
  58. break;
  59. case 'bottom':
  60. outPt.set(rect.x + width / 2, rect.y + height + distance);
  61. outDir.set(0, 1);
  62. break;
  63. case 'left':
  64. outPt.set(rect.x - distance, rect.y + height / 2);
  65. outDir.set(-1, 0);
  66. break;
  67. case 'right':
  68. outPt.set(rect.x + width + distance, rect.y + height / 2);
  69. outDir.set(1, 0);
  70. break;
  71. }
  72. }
  73. function projectPointToArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y, out) {
  74. x -= cx;
  75. y -= cy;
  76. var d = Math.sqrt(x * x + y * y);
  77. x /= d;
  78. y /= d; // Intersect point.
  79. var ox = x * r + cx;
  80. var oy = y * r + cy;
  81. if (Math.abs(startAngle - endAngle) % PI2 < 1e-4) {
  82. // Is a circle
  83. out[0] = ox;
  84. out[1] = oy;
  85. return d - r;
  86. }
  87. if (anticlockwise) {
  88. var tmp = startAngle;
  89. startAngle = normalizeRadian(endAngle);
  90. endAngle = normalizeRadian(tmp);
  91. } else {
  92. startAngle = normalizeRadian(startAngle);
  93. endAngle = normalizeRadian(endAngle);
  94. }
  95. if (startAngle > endAngle) {
  96. endAngle += PI2;
  97. }
  98. var angle = Math.atan2(y, x);
  99. if (angle < 0) {
  100. angle += PI2;
  101. }
  102. if (angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle) {
  103. // Project point is on the arc.
  104. out[0] = ox;
  105. out[1] = oy;
  106. return d - r;
  107. }
  108. var x1 = r * Math.cos(startAngle) + cx;
  109. var y1 = r * Math.sin(startAngle) + cy;
  110. var x2 = r * Math.cos(endAngle) + cx;
  111. var y2 = r * Math.sin(endAngle) + cy;
  112. var d1 = (x1 - x) * (x1 - x) + (y1 - y) * (y1 - y);
  113. var d2 = (x2 - x) * (x2 - x) + (y2 - y) * (y2 - y);
  114. if (d1 < d2) {
  115. out[0] = x1;
  116. out[1] = y1;
  117. return Math.sqrt(d1);
  118. } else {
  119. out[0] = x2;
  120. out[1] = y2;
  121. return Math.sqrt(d2);
  122. }
  123. }
  124. function projectPointToLine(x1, y1, x2, y2, x, y, out, limitToEnds) {
  125. var dx = x - x1;
  126. var dy = y - y1;
  127. var dx1 = x2 - x1;
  128. var dy1 = y2 - y1;
  129. var lineLen = Math.sqrt(dx1 * dx1 + dy1 * dy1);
  130. dx1 /= lineLen;
  131. dy1 /= lineLen; // dot product
  132. var projectedLen = dx * dx1 + dy * dy1;
  133. var t = projectedLen / lineLen;
  134. if (limitToEnds) {
  135. t = Math.min(Math.max(t, 0), 1);
  136. }
  137. t *= lineLen;
  138. var ox = out[0] = x1 + t * dx1;
  139. var oy = out[1] = y1 + t * dy1;
  140. return Math.sqrt((ox - x) * (ox - x) + (oy - y) * (oy - y));
  141. }
  142. function projectPointToRect(x1, y1, width, height, x, y, out) {
  143. if (width < 0) {
  144. x1 = x1 + width;
  145. width = -width;
  146. }
  147. if (height < 0) {
  148. y1 = y1 + height;
  149. height = -height;
  150. }
  151. var x2 = x1 + width;
  152. var y2 = y1 + height;
  153. var ox = out[0] = Math.min(Math.max(x, x1), x2);
  154. var oy = out[1] = Math.min(Math.max(y, y1), y2);
  155. return Math.sqrt((ox - x) * (ox - x) + (oy - y) * (oy - y));
  156. }
  157. var tmpPt = [];
  158. function nearestPointOnRect(pt, rect, out) {
  159. var dist = projectPointToRect(rect.x, rect.y, rect.width, rect.height, pt.x, pt.y, tmpPt);
  160. out.set(tmpPt[0], tmpPt[1]);
  161. return dist;
  162. }
  163. /**
  164. * Calculate min distance corresponding point.
  165. * This method won't evaluate if point is in the path.
  166. */
  167. function nearestPointOnPath(pt, path, out) {
  168. var xi = 0;
  169. var yi = 0;
  170. var x0 = 0;
  171. var y0 = 0;
  172. var x1;
  173. var y1;
  174. var minDist = Infinity;
  175. var data = path.data;
  176. var x = pt.x;
  177. var y = pt.y;
  178. for (var i = 0; i < data.length;) {
  179. var cmd = data[i++];
  180. if (i === 1) {
  181. xi = data[i];
  182. yi = data[i + 1];
  183. x0 = xi;
  184. y0 = yi;
  185. }
  186. var d = minDist;
  187. switch (cmd) {
  188. case CMD.M:
  189. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  190. // 在 closePath 的时候使用
  191. x0 = data[i++];
  192. y0 = data[i++];
  193. xi = x0;
  194. yi = y0;
  195. break;
  196. case CMD.L:
  197. d = projectPointToLine(xi, yi, data[i], data[i + 1], x, y, tmpPt, true);
  198. xi = data[i++];
  199. yi = data[i++];
  200. break;
  201. case CMD.C:
  202. d = cubicProjectPoint(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], x, y, tmpPt);
  203. xi = data[i++];
  204. yi = data[i++];
  205. break;
  206. case CMD.Q:
  207. d = quadraticProjectPoint(xi, yi, data[i++], data[i++], data[i], data[i + 1], x, y, tmpPt);
  208. xi = data[i++];
  209. yi = data[i++];
  210. break;
  211. case CMD.A:
  212. // TODO Arc 判断的开销比较大
  213. var cx = data[i++];
  214. var cy = data[i++];
  215. var rx = data[i++];
  216. var ry = data[i++];
  217. var theta = data[i++];
  218. var dTheta = data[i++]; // TODO Arc 旋转
  219. i += 1;
  220. var anticlockwise = !!(1 - data[i++]);
  221. x1 = Math.cos(theta) * rx + cx;
  222. y1 = Math.sin(theta) * ry + cy; // 不是直接使用 arc 命令
  223. if (i <= 1) {
  224. // 第一个命令起点还未定义
  225. x0 = x1;
  226. y0 = y1;
  227. } // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
  228. var _x = (x - cx) * ry / rx + cx;
  229. d = projectPointToArc(cx, cy, ry, theta, theta + dTheta, anticlockwise, _x, y, tmpPt);
  230. xi = Math.cos(theta + dTheta) * rx + cx;
  231. yi = Math.sin(theta + dTheta) * ry + cy;
  232. break;
  233. case CMD.R:
  234. x0 = xi = data[i++];
  235. y0 = yi = data[i++];
  236. var width = data[i++];
  237. var height = data[i++];
  238. d = projectPointToRect(x0, y0, width, height, x, y, tmpPt);
  239. break;
  240. case CMD.Z:
  241. d = projectPointToLine(xi, yi, x0, y0, x, y, tmpPt, true);
  242. xi = x0;
  243. yi = y0;
  244. break;
  245. }
  246. if (d < minDist) {
  247. minDist = d;
  248. out.set(tmpPt[0], tmpPt[1]);
  249. }
  250. }
  251. return minDist;
  252. } // Temporal varible for intermediate usage.
  253. var pt0 = new Point();
  254. var pt1 = new Point();
  255. var pt2 = new Point();
  256. var dir = new Point();
  257. var dir2 = new Point();
  258. /**
  259. * Calculate a proper guide line based on the label position and graphic element definition
  260. * @param label
  261. * @param labelRect
  262. * @param target
  263. * @param targetRect
  264. */
  265. export function updateLabelLinePoints(target, labelLineModel) {
  266. if (!target) {
  267. return;
  268. }
  269. var labelLine = target.getTextGuideLine();
  270. var label = target.getTextContent(); // Needs to create text guide in each charts.
  271. if (!(label && labelLine)) {
  272. return;
  273. }
  274. var labelGuideConfig = target.textGuideLineConfig || {};
  275. var points = [[0, 0], [0, 0], [0, 0]];
  276. var searchSpace = labelGuideConfig.candidates || DEFAULT_SEARCH_SPACE;
  277. var labelRect = label.getBoundingRect().clone();
  278. labelRect.applyTransform(label.getComputedTransform());
  279. var minDist = Infinity;
  280. var anchorPoint = labelGuideConfig.anchor;
  281. var targetTransform = target.getComputedTransform();
  282. var targetInversedTransform = targetTransform && invert([], targetTransform);
  283. var len = labelLineModel.get('length2') || 0;
  284. if (anchorPoint) {
  285. pt2.copy(anchorPoint);
  286. }
  287. for (var i = 0; i < searchSpace.length; i++) {
  288. var candidate = searchSpace[i];
  289. getCandidateAnchor(candidate, 0, labelRect, pt0, dir);
  290. Point.scaleAndAdd(pt1, pt0, dir, len); // Transform to target coord space.
  291. pt1.transform(targetInversedTransform); // Note: getBoundingRect will ensure the `path` being created.
  292. var boundingRect = target.getBoundingRect();
  293. var dist = anchorPoint ? anchorPoint.distance(pt1) : target instanceof Path ? nearestPointOnPath(pt1, target.path, pt2) : nearestPointOnRect(pt1, boundingRect, pt2); // TODO pt2 is in the path
  294. if (dist < minDist) {
  295. minDist = dist; // Transform back to global space.
  296. pt1.transform(targetTransform);
  297. pt2.transform(targetTransform);
  298. pt2.toArray(points[0]);
  299. pt1.toArray(points[1]);
  300. pt0.toArray(points[2]);
  301. }
  302. }
  303. limitTurnAngle(points, labelLineModel.get('minTurnAngle'));
  304. labelLine.setShape({
  305. points: points
  306. });
  307. } // Temporal variable for the limitTurnAngle function
  308. var tmpArr = [];
  309. var tmpProjPoint = new Point();
  310. /**
  311. * Reduce the line segment attached to the label to limit the turn angle between two segments.
  312. * @param linePoints
  313. * @param minTurnAngle Radian of minimum turn angle. 0 - 180
  314. */
  315. export function limitTurnAngle(linePoints, minTurnAngle) {
  316. if (!(minTurnAngle <= 180 && minTurnAngle > 0)) {
  317. return;
  318. }
  319. minTurnAngle = minTurnAngle / 180 * Math.PI; // The line points can be
  320. // /pt1----pt2 (label)
  321. // /
  322. // pt0/
  323. pt0.fromArray(linePoints[0]);
  324. pt1.fromArray(linePoints[1]);
  325. pt2.fromArray(linePoints[2]);
  326. Point.sub(dir, pt0, pt1);
  327. Point.sub(dir2, pt2, pt1);
  328. var len1 = dir.len();
  329. var len2 = dir2.len();
  330. if (len1 < 1e-3 || len2 < 1e-3) {
  331. return;
  332. }
  333. dir.scale(1 / len1);
  334. dir2.scale(1 / len2);
  335. var angleCos = dir.dot(dir2);
  336. var minTurnAngleCos = Math.cos(minTurnAngle);
  337. if (minTurnAngleCos < angleCos) {
  338. // Smaller than minTurnAngle
  339. // Calculate project point of pt0 on pt1-pt2
  340. var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr, false);
  341. tmpProjPoint.fromArray(tmpArr); // Calculate new projected length with limited minTurnAngle and get the new connect point
  342. tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI - minTurnAngle)); // Limit the new calculated connect point between pt1 and pt2.
  343. var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
  344. if (isNaN(t)) {
  345. return;
  346. }
  347. if (t < 0) {
  348. Point.copy(tmpProjPoint, pt1);
  349. } else if (t > 1) {
  350. Point.copy(tmpProjPoint, pt2);
  351. }
  352. tmpProjPoint.toArray(linePoints[1]);
  353. }
  354. }
  355. /**
  356. * Limit the angle of line and the surface
  357. * @param maxSurfaceAngle Radian of minimum turn angle. 0 - 180. 0 is same direction to normal. 180 is opposite
  358. */
  359. export function limitSurfaceAngle(linePoints, surfaceNormal, maxSurfaceAngle) {
  360. if (!(maxSurfaceAngle <= 180 && maxSurfaceAngle > 0)) {
  361. return;
  362. }
  363. maxSurfaceAngle = maxSurfaceAngle / 180 * Math.PI;
  364. pt0.fromArray(linePoints[0]);
  365. pt1.fromArray(linePoints[1]);
  366. pt2.fromArray(linePoints[2]);
  367. Point.sub(dir, pt1, pt0);
  368. Point.sub(dir2, pt2, pt1);
  369. var len1 = dir.len();
  370. var len2 = dir2.len();
  371. if (len1 < 1e-3 || len2 < 1e-3) {
  372. return;
  373. }
  374. dir.scale(1 / len1);
  375. dir2.scale(1 / len2);
  376. var angleCos = dir.dot(surfaceNormal);
  377. var maxSurfaceAngleCos = Math.cos(maxSurfaceAngle);
  378. if (angleCos < maxSurfaceAngleCos) {
  379. // Calculate project point of pt0 on pt1-pt2
  380. var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr, false);
  381. tmpProjPoint.fromArray(tmpArr);
  382. var HALF_PI = Math.PI / 2;
  383. var angle2 = Math.acos(dir2.dot(surfaceNormal));
  384. var newAngle = HALF_PI + angle2 - maxSurfaceAngle;
  385. if (newAngle >= HALF_PI) {
  386. // parallel
  387. Point.copy(tmpProjPoint, pt2);
  388. } else {
  389. // Calculate new projected length with limited minTurnAngle and get the new connect point
  390. tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI / 2 - newAngle)); // Limit the new calculated connect point between pt1 and pt2.
  391. var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
  392. if (isNaN(t)) {
  393. return;
  394. }
  395. if (t < 0) {
  396. Point.copy(tmpProjPoint, pt1);
  397. } else if (t > 1) {
  398. Point.copy(tmpProjPoint, pt2);
  399. }
  400. }
  401. tmpProjPoint.toArray(linePoints[1]);
  402. }
  403. }
  404. function setLabelLineState(labelLine, ignore, stateName, stateModel) {
  405. var isNormal = stateName === 'normal';
  406. var stateObj = isNormal ? labelLine : labelLine.ensureState(stateName); // Make sure display.
  407. stateObj.ignore = ignore; // Set smooth
  408. var smooth = stateModel.get('smooth');
  409. if (smooth && smooth === true) {
  410. smooth = 0.3;
  411. }
  412. stateObj.shape = stateObj.shape || {};
  413. if (smooth > 0) {
  414. stateObj.shape.smooth = smooth;
  415. }
  416. var styleObj = stateModel.getModel('lineStyle').getLineStyle();
  417. isNormal ? labelLine.useStyle(styleObj) : stateObj.style = styleObj;
  418. }
  419. function buildLabelLinePath(path, shape) {
  420. var smooth = shape.smooth;
  421. var points = shape.points;
  422. if (!points) {
  423. return;
  424. }
  425. path.moveTo(points[0][0], points[0][1]);
  426. if (smooth > 0 && points.length >= 3) {
  427. var len1 = vector.dist(points[0], points[1]);
  428. var len2 = vector.dist(points[1], points[2]);
  429. if (!len1 || !len2) {
  430. path.lineTo(points[1][0], points[1][1]);
  431. path.lineTo(points[2][0], points[2][1]);
  432. return;
  433. }
  434. var moveLen = Math.min(len1, len2) * smooth;
  435. var midPoint0 = vector.lerp([], points[1], points[0], moveLen / len1);
  436. var midPoint2 = vector.lerp([], points[1], points[2], moveLen / len2);
  437. var midPoint1 = vector.lerp([], midPoint0, midPoint2, 0.5);
  438. path.bezierCurveTo(midPoint0[0], midPoint0[1], midPoint0[0], midPoint0[1], midPoint1[0], midPoint1[1]);
  439. path.bezierCurveTo(midPoint2[0], midPoint2[1], midPoint2[0], midPoint2[1], points[2][0], points[2][1]);
  440. } else {
  441. for (var i = 1; i < points.length; i++) {
  442. path.lineTo(points[i][0], points[i][1]);
  443. }
  444. }
  445. }
  446. /**
  447. * Create a label line if necessary and set it's style.
  448. */
  449. export function setLabelLineStyle(targetEl, statesModels, defaultStyle) {
  450. var labelLine = targetEl.getTextGuideLine();
  451. var label = targetEl.getTextContent();
  452. if (!label) {
  453. // Not show label line if there is no label.
  454. if (labelLine) {
  455. targetEl.removeTextGuideLine();
  456. }
  457. return;
  458. }
  459. var normalModel = statesModels.normal;
  460. var showNormal = normalModel.get('show');
  461. var labelIgnoreNormal = label.ignore;
  462. for (var i = 0; i < DISPLAY_STATES.length; i++) {
  463. var stateName = DISPLAY_STATES[i];
  464. var stateModel = statesModels[stateName];
  465. var isNormal = stateName === 'normal';
  466. if (stateModel) {
  467. var stateShow = stateModel.get('show');
  468. var isLabelIgnored = isNormal ? labelIgnoreNormal : retrieve2(label.states[stateName] && label.states[stateName].ignore, labelIgnoreNormal);
  469. if (isLabelIgnored // Not show when label is not shown in this state.
  470. || !retrieve2(stateShow, showNormal) // Use normal state by default if not set.
  471. ) {
  472. var stateObj = isNormal ? labelLine : labelLine && labelLine.states[stateName];
  473. if (stateObj) {
  474. stateObj.ignore = true;
  475. }
  476. continue;
  477. } // Create labelLine if not exists
  478. if (!labelLine) {
  479. labelLine = new Polyline();
  480. targetEl.setTextGuideLine(labelLine); // Reset state of normal because it's new created.
  481. // NOTE: NORMAL should always been the first!
  482. if (!isNormal && (labelIgnoreNormal || !showNormal)) {
  483. setLabelLineState(labelLine, true, 'normal', statesModels.normal);
  484. } // Use same state proxy.
  485. if (targetEl.stateProxy) {
  486. labelLine.stateProxy = targetEl.stateProxy;
  487. }
  488. }
  489. setLabelLineState(labelLine, false, stateName, stateModel);
  490. }
  491. }
  492. if (labelLine) {
  493. defaults(labelLine.style, defaultStyle); // Not fill.
  494. labelLine.style.fill = null;
  495. var showAbove = normalModel.get('showAbove');
  496. var labelLineConfig = targetEl.textGuideLineConfig = targetEl.textGuideLineConfig || {};
  497. labelLineConfig.showAbove = showAbove || false; // Custom the buildPath.
  498. labelLine.buildPath = buildLabelLinePath;
  499. }
  500. }
  501. export function getLabelLineStatesModels(itemModel, labelLineName) {
  502. labelLineName = labelLineName || 'labelLine';
  503. var statesModels = {
  504. normal: itemModel.getModel(labelLineName)
  505. };
  506. for (var i = 0; i < SPECIAL_STATES.length; i++) {
  507. var stateName = SPECIAL_STATES[i];
  508. statesModels[stateName] = itemModel.getModel([stateName, labelLineName]);
  509. }
  510. return statesModels;
  511. }