path.js 9.4 KB

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