path.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. var Path = require("../graphic/Path");
  2. var PathProxy = require("../core/PathProxy");
  3. var transformPath = require("./transformPath");
  4. // command chars
  5. // var cc = [
  6. // 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z',
  7. // 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A'
  8. // ];
  9. var mathSqrt = Math.sqrt;
  10. var mathSin = Math.sin;
  11. var mathCos = Math.cos;
  12. var PI = Math.PI;
  13. var vMag = function (v) {
  14. return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  15. };
  16. var vRatio = function (u, v) {
  17. return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
  18. };
  19. var vAngle = function (u, v) {
  20. return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
  21. };
  22. function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) {
  23. var psi = psiDeg * (PI / 180.0);
  24. var xp = mathCos(psi) * (x1 - x2) / 2.0 + mathSin(psi) * (y1 - y2) / 2.0;
  25. var yp = -1 * mathSin(psi) * (x1 - x2) / 2.0 + mathCos(psi) * (y1 - y2) / 2.0;
  26. var lambda = xp * xp / (rx * rx) + yp * yp / (ry * ry);
  27. if (lambda > 1) {
  28. rx *= mathSqrt(lambda);
  29. ry *= mathSqrt(lambda);
  30. }
  31. var f = (fa === fs ? -1 : 1) * mathSqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / (rx * rx * (yp * yp) + ry * ry * (xp * xp))) || 0;
  32. var cxp = f * rx * yp / ry;
  33. var cyp = f * -ry * xp / rx;
  34. var cx = (x1 + x2) / 2.0 + mathCos(psi) * cxp - mathSin(psi) * cyp;
  35. var cy = (y1 + y2) / 2.0 + mathSin(psi) * cxp + mathCos(psi) * cyp;
  36. var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
  37. var u = [(xp - cxp) / rx, (yp - cyp) / ry];
  38. var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
  39. var dTheta = vAngle(u, v);
  40. if (vRatio(u, v) <= -1) {
  41. dTheta = PI;
  42. }
  43. if (vRatio(u, v) >= 1) {
  44. dTheta = 0;
  45. }
  46. if (fs === 0 && dTheta > 0) {
  47. dTheta = dTheta - 2 * PI;
  48. }
  49. if (fs === 1 && dTheta < 0) {
  50. dTheta = dTheta + 2 * PI;
  51. }
  52. path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs);
  53. }
  54. var commandReg = /([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig; // Consider case:
  55. // (1) delimiter can be comma or space, where continuous commas
  56. // or spaces should be seen as one comma.
  57. // (2) value can be like:
  58. // '2e-4', 'l.5.9' (ignore 0), 'M-10-10', 'l-2.43e-1,34.9983',
  59. // 'l-.5E1,54', '121-23-44-11' (no delimiter)
  60. var numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g; // var valueSplitReg = /[\s,]+/;
  61. function createPathProxyFromString(data) {
  62. if (!data) {
  63. return new PathProxy();
  64. } // var data = data.replace(/-/g, ' -')
  65. // .replace(/ /g, ' ')
  66. // .replace(/ /g, ',')
  67. // .replace(/,,/g, ',');
  68. // var n;
  69. // create pipes so that we can split the data
  70. // for (n = 0; n < cc.length; n++) {
  71. // cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
  72. // }
  73. // data = data.replace(/-/g, ',-');
  74. // create array
  75. // var arr = cs.split('|');
  76. // init context point
  77. var cpx = 0;
  78. var cpy = 0;
  79. var subpathX = cpx;
  80. var subpathY = cpy;
  81. var prevCmd;
  82. var path = new PathProxy();
  83. var CMD = PathProxy.CMD; // commandReg.lastIndex = 0;
  84. // var cmdResult;
  85. // while ((cmdResult = commandReg.exec(data)) != null) {
  86. // var cmdStr = cmdResult[1];
  87. // var cmdContent = cmdResult[2];
  88. var cmdList = data.match(commandReg);
  89. for (var l = 0; l < cmdList.length; l++) {
  90. var cmdText = cmdList[l];
  91. var cmdStr = cmdText.charAt(0);
  92. var cmd; // String#split is faster a little bit than String#replace or RegExp#exec.
  93. // var p = cmdContent.split(valueSplitReg);
  94. // var pLen = 0;
  95. // for (var i = 0; i < p.length; i++) {
  96. // // '' and other invalid str => NaN
  97. // var val = parseFloat(p[i]);
  98. // !isNaN(val) && (p[pLen++] = val);
  99. // }
  100. var p = cmdText.match(numberReg) || [];
  101. var pLen = p.length;
  102. for (var i = 0; i < pLen; i++) {
  103. p[i] = parseFloat(p[i]);
  104. }
  105. var off = 0;
  106. while (off < pLen) {
  107. var ctlPtx;
  108. var ctlPty;
  109. var rx;
  110. var ry;
  111. var psi;
  112. var fa;
  113. var fs;
  114. var x1 = cpx;
  115. var y1 = cpy; // convert l, H, h, V, and v to L
  116. switch (cmdStr) {
  117. case 'l':
  118. cpx += p[off++];
  119. cpy += p[off++];
  120. cmd = CMD.L;
  121. path.addData(cmd, cpx, cpy);
  122. break;
  123. case 'L':
  124. cpx = p[off++];
  125. cpy = p[off++];
  126. cmd = CMD.L;
  127. path.addData(cmd, cpx, cpy);
  128. break;
  129. case 'm':
  130. cpx += p[off++];
  131. cpy += p[off++];
  132. cmd = CMD.M;
  133. path.addData(cmd, cpx, cpy);
  134. subpathX = cpx;
  135. subpathY = cpy;
  136. cmdStr = 'l';
  137. break;
  138. case 'M':
  139. cpx = p[off++];
  140. cpy = p[off++];
  141. cmd = CMD.M;
  142. path.addData(cmd, cpx, cpy);
  143. subpathX = cpx;
  144. subpathY = cpy;
  145. cmdStr = 'L';
  146. break;
  147. case 'h':
  148. cpx += p[off++];
  149. cmd = CMD.L;
  150. path.addData(cmd, cpx, cpy);
  151. break;
  152. case 'H':
  153. cpx = p[off++];
  154. cmd = CMD.L;
  155. path.addData(cmd, cpx, cpy);
  156. break;
  157. case 'v':
  158. cpy += p[off++];
  159. cmd = CMD.L;
  160. path.addData(cmd, cpx, cpy);
  161. break;
  162. case 'V':
  163. cpy = p[off++];
  164. cmd = CMD.L;
  165. path.addData(cmd, cpx, cpy);
  166. break;
  167. case 'C':
  168. cmd = CMD.C;
  169. path.addData(cmd, p[off++], p[off++], p[off++], p[off++], p[off++], p[off++]);
  170. cpx = p[off - 2];
  171. cpy = p[off - 1];
  172. break;
  173. case 'c':
  174. cmd = CMD.C;
  175. path.addData(cmd, p[off++] + cpx, p[off++] + cpy, p[off++] + cpx, p[off++] + cpy, p[off++] + cpx, p[off++] + cpy);
  176. cpx += p[off - 2];
  177. cpy += p[off - 1];
  178. break;
  179. case 'S':
  180. ctlPtx = cpx;
  181. ctlPty = cpy;
  182. var len = path.len();
  183. var pathData = path.data;
  184. if (prevCmd === CMD.C) {
  185. ctlPtx += cpx - pathData[len - 4];
  186. ctlPty += cpy - pathData[len - 3];
  187. }
  188. cmd = CMD.C;
  189. x1 = p[off++];
  190. y1 = p[off++];
  191. cpx = p[off++];
  192. cpy = p[off++];
  193. path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
  194. break;
  195. case 's':
  196. ctlPtx = cpx;
  197. ctlPty = cpy;
  198. var len = path.len();
  199. var pathData = path.data;
  200. if (prevCmd === CMD.C) {
  201. ctlPtx += cpx - pathData[len - 4];
  202. ctlPty += cpy - pathData[len - 3];
  203. }
  204. cmd = CMD.C;
  205. x1 = cpx + p[off++];
  206. y1 = cpy + p[off++];
  207. cpx += p[off++];
  208. cpy += p[off++];
  209. path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
  210. break;
  211. case 'Q':
  212. x1 = p[off++];
  213. y1 = p[off++];
  214. cpx = p[off++];
  215. cpy = p[off++];
  216. cmd = CMD.Q;
  217. path.addData(cmd, x1, y1, cpx, cpy);
  218. break;
  219. case 'q':
  220. x1 = p[off++] + cpx;
  221. y1 = p[off++] + cpy;
  222. cpx += p[off++];
  223. cpy += p[off++];
  224. cmd = CMD.Q;
  225. path.addData(cmd, x1, y1, cpx, cpy);
  226. break;
  227. case 'T':
  228. ctlPtx = cpx;
  229. ctlPty = cpy;
  230. var len = path.len();
  231. var pathData = path.data;
  232. if (prevCmd === CMD.Q) {
  233. ctlPtx += cpx - pathData[len - 4];
  234. ctlPty += cpy - pathData[len - 3];
  235. }
  236. cpx = p[off++];
  237. cpy = p[off++];
  238. cmd = CMD.Q;
  239. path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
  240. break;
  241. case 't':
  242. ctlPtx = cpx;
  243. ctlPty = cpy;
  244. var len = path.len();
  245. var pathData = path.data;
  246. if (prevCmd === CMD.Q) {
  247. ctlPtx += cpx - pathData[len - 4];
  248. ctlPty += cpy - pathData[len - 3];
  249. }
  250. cpx += p[off++];
  251. cpy += p[off++];
  252. cmd = CMD.Q;
  253. path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
  254. break;
  255. case 'A':
  256. rx = p[off++];
  257. ry = p[off++];
  258. psi = p[off++];
  259. fa = p[off++];
  260. fs = p[off++];
  261. x1 = cpx, y1 = cpy;
  262. cpx = p[off++];
  263. cpy = p[off++];
  264. cmd = CMD.A;
  265. processArc(x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path);
  266. break;
  267. case 'a':
  268. rx = p[off++];
  269. ry = p[off++];
  270. psi = p[off++];
  271. fa = p[off++];
  272. fs = p[off++];
  273. x1 = cpx, y1 = cpy;
  274. cpx += p[off++];
  275. cpy += p[off++];
  276. cmd = CMD.A;
  277. processArc(x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path);
  278. break;
  279. }
  280. }
  281. if (cmdStr === 'z' || cmdStr === 'Z') {
  282. cmd = CMD.Z;
  283. path.addData(cmd); // z may be in the middle of the path.
  284. cpx = subpathX;
  285. cpy = subpathY;
  286. }
  287. prevCmd = cmd;
  288. }
  289. path.toStatic();
  290. return path;
  291. } // TODO Optimize double memory cost problem
  292. function createPathOptions(str, opts) {
  293. var pathProxy = createPathProxyFromString(str);
  294. opts = opts || {};
  295. opts.buildPath = function (path) {
  296. if (path.setData) {
  297. path.setData(pathProxy.data); // Svg and vml renderer don't have context
  298. var ctx = path.getContext();
  299. if (ctx) {
  300. path.rebuildPath(ctx);
  301. }
  302. } else {
  303. var ctx = path;
  304. pathProxy.rebuildPath(ctx);
  305. }
  306. };
  307. opts.applyTransform = function (m) {
  308. transformPath(pathProxy, m);
  309. this.dirty(true);
  310. };
  311. return opts;
  312. }
  313. /**
  314. * Create a Path object from path string data
  315. * http://www.w3.org/TR/SVG/paths.html#PathData
  316. * @param {Object} opts Other options
  317. */
  318. function createFromString(str, opts) {
  319. return new Path(createPathOptions(str, opts));
  320. }
  321. /**
  322. * Create a Path class from path string data
  323. * @param {string} str
  324. * @param {Object} opts Other options
  325. */
  326. function extendFromString(str, opts) {
  327. return Path.extend(createPathOptions(str, opts));
  328. }
  329. /**
  330. * Merge multiple paths
  331. */
  332. // TODO Apply transform
  333. // TODO stroke dash
  334. // TODO Optimize double memory cost problem
  335. function mergePath(pathEls, opts) {
  336. var pathList = [];
  337. var len = pathEls.length;
  338. for (var i = 0; i < len; i++) {
  339. var pathEl = pathEls[i];
  340. if (!pathEl.path) {
  341. pathEl.createPathProxy();
  342. }
  343. if (pathEl.__dirtyPath) {
  344. pathEl.buildPath(pathEl.path, pathEl.shape, true);
  345. }
  346. pathList.push(pathEl.path);
  347. }
  348. var pathBundle = new Path(opts); // Need path proxy.
  349. pathBundle.createPathProxy();
  350. pathBundle.buildPath = function (path) {
  351. path.appendPath(pathList); // Svg and vml renderer don't have context
  352. var ctx = path.getContext();
  353. if (ctx) {
  354. path.rebuildPath(ctx);
  355. }
  356. };
  357. return pathBundle;
  358. }
  359. exports.createFromString = createFromString;
  360. exports.extendFromString = extendFromString;
  361. exports.mergePath = mergePath;