Path.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. var Displayable = require("./Displayable");
  2. var zrUtil = require("../core/util");
  3. var PathProxy = require("../core/PathProxy");
  4. var pathContain = require("../contain/path");
  5. var Pattern = require("./Pattern");
  6. var getCanvasPattern = Pattern.prototype.getCanvasPattern;
  7. var abs = Math.abs;
  8. var pathProxyForDraw = new PathProxy(true);
  9. /**
  10. * @alias module:zrender/graphic/Path
  11. * @extends module:zrender/graphic/Displayable
  12. * @constructor
  13. * @param {Object} opts
  14. */
  15. function Path(opts) {
  16. Displayable.call(this, opts);
  17. /**
  18. * @type {module:zrender/core/PathProxy}
  19. * @readOnly
  20. */
  21. this.path = null;
  22. }
  23. Path.prototype = {
  24. constructor: Path,
  25. type: 'path',
  26. __dirtyPath: true,
  27. strokeContainThreshold: 5,
  28. // This item default to be false. But in map series in echarts,
  29. // in order to improve performance, it should be set to true,
  30. // so the shorty segment won't draw.
  31. segmentIgnoreThreshold: 0,
  32. /**
  33. * See `module:zrender/src/graphic/helper/subPixelOptimize`.
  34. * @type {boolean}
  35. */
  36. subPixelOptimize: false,
  37. brush: function (ctx, prevEl) {
  38. var style = this.style;
  39. var path = this.path || pathProxyForDraw;
  40. var hasStroke = style.hasStroke();
  41. var hasFill = style.hasFill();
  42. var fill = style.fill;
  43. var stroke = style.stroke;
  44. var hasFillGradient = hasFill && !!fill.colorStops;
  45. var hasStrokeGradient = hasStroke && !!stroke.colorStops;
  46. var hasFillPattern = hasFill && !!fill.image;
  47. var hasStrokePattern = hasStroke && !!stroke.image;
  48. style.bind(ctx, this, prevEl);
  49. this.setTransform(ctx);
  50. if (this.__dirty) {
  51. var rect; // Update gradient because bounding rect may changed
  52. if (hasFillGradient) {
  53. rect = rect || this.getBoundingRect();
  54. this._fillGradient = style.getGradient(ctx, fill, rect);
  55. }
  56. if (hasStrokeGradient) {
  57. rect = rect || this.getBoundingRect();
  58. this._strokeGradient = style.getGradient(ctx, stroke, rect);
  59. }
  60. } // Use the gradient or pattern
  61. if (hasFillGradient) {
  62. // PENDING If may have affect the state
  63. ctx.fillStyle = this._fillGradient;
  64. } else if (hasFillPattern) {
  65. ctx.fillStyle = getCanvasPattern.call(fill, ctx);
  66. }
  67. if (hasStrokeGradient) {
  68. ctx.strokeStyle = this._strokeGradient;
  69. } else if (hasStrokePattern) {
  70. ctx.strokeStyle = getCanvasPattern.call(stroke, ctx);
  71. }
  72. var lineDash = style.lineDash;
  73. var lineDashOffset = style.lineDashOffset;
  74. var ctxLineDash = !!ctx.setLineDash; // Update path sx, sy
  75. var scale = this.getGlobalScale();
  76. path.setScale(scale[0], scale[1], this.segmentIgnoreThreshold); // Proxy context
  77. // Rebuild path in following 2 cases
  78. // 1. Path is dirty
  79. // 2. Path needs javascript implemented lineDash stroking.
  80. // In this case, lineDash information will not be saved in PathProxy
  81. if (this.__dirtyPath || lineDash && !ctxLineDash && hasStroke) {
  82. path.beginPath(ctx); // Setting line dash before build path
  83. if (lineDash && !ctxLineDash) {
  84. path.setLineDash(lineDash);
  85. path.setLineDashOffset(lineDashOffset);
  86. }
  87. this.buildPath(path, this.shape, false); // Clear path dirty flag
  88. if (this.path) {
  89. this.__dirtyPath = false;
  90. }
  91. } else {
  92. // Replay path building
  93. ctx.beginPath();
  94. this.path.rebuildPath(ctx);
  95. }
  96. if (hasFill) {
  97. if (style.fillOpacity != null) {
  98. var originalGlobalAlpha = ctx.globalAlpha;
  99. ctx.globalAlpha = style.fillOpacity * style.opacity;
  100. path.fill(ctx);
  101. ctx.globalAlpha = originalGlobalAlpha;
  102. } else {
  103. path.fill(ctx);
  104. }
  105. }
  106. if (lineDash && ctxLineDash) {
  107. ctx.setLineDash(lineDash);
  108. ctx.lineDashOffset = lineDashOffset;
  109. }
  110. if (hasStroke) {
  111. if (style.strokeOpacity != null) {
  112. var originalGlobalAlpha = ctx.globalAlpha;
  113. ctx.globalAlpha = style.strokeOpacity * style.opacity;
  114. path.stroke(ctx);
  115. ctx.globalAlpha = originalGlobalAlpha;
  116. } else {
  117. path.stroke(ctx);
  118. }
  119. }
  120. if (lineDash && ctxLineDash) {
  121. // PENDING
  122. // Remove lineDash
  123. ctx.setLineDash([]);
  124. } // Draw rect text
  125. if (style.text != null) {
  126. // Only restore transform when needs draw text.
  127. this.restoreTransform(ctx);
  128. this.drawRectText(ctx, this.getBoundingRect());
  129. }
  130. },
  131. // When bundling path, some shape may decide if use moveTo to begin a new subpath or closePath
  132. // Like in circle
  133. buildPath: function (ctx, shapeCfg, inBundle) {},
  134. createPathProxy: function () {
  135. this.path = new PathProxy();
  136. },
  137. getBoundingRect: function () {
  138. var rect = this._rect;
  139. var style = this.style;
  140. var needsUpdateRect = !rect;
  141. if (needsUpdateRect) {
  142. var path = this.path;
  143. if (!path) {
  144. // Create path on demand.
  145. path = this.path = new PathProxy();
  146. }
  147. if (this.__dirtyPath) {
  148. path.beginPath();
  149. this.buildPath(path, this.shape, false);
  150. }
  151. rect = path.getBoundingRect();
  152. }
  153. this._rect = rect;
  154. if (style.hasStroke()) {
  155. // Needs update rect with stroke lineWidth when
  156. // 1. Element changes scale or lineWidth
  157. // 2. Shape is changed
  158. var rectWithStroke = this._rectWithStroke || (this._rectWithStroke = rect.clone());
  159. if (this.__dirty || needsUpdateRect) {
  160. rectWithStroke.copy(rect); // FIXME Must after updateTransform
  161. var w = style.lineWidth; // PENDING, Min line width is needed when line is horizontal or vertical
  162. var lineScale = style.strokeNoScale ? this.getLineScale() : 1; // Only add extra hover lineWidth when there are no fill
  163. if (!style.hasFill()) {
  164. w = Math.max(w, this.strokeContainThreshold || 4);
  165. } // Consider line width
  166. // Line scale can't be 0;
  167. if (lineScale > 1e-10) {
  168. rectWithStroke.width += w / lineScale;
  169. rectWithStroke.height += w / lineScale;
  170. rectWithStroke.x -= w / lineScale / 2;
  171. rectWithStroke.y -= w / lineScale / 2;
  172. }
  173. } // Return rect with stroke
  174. return rectWithStroke;
  175. }
  176. return rect;
  177. },
  178. contain: function (x, y) {
  179. var localPos = this.transformCoordToLocal(x, y);
  180. var rect = this.getBoundingRect();
  181. var style = this.style;
  182. x = localPos[0];
  183. y = localPos[1];
  184. if (rect.contain(x, y)) {
  185. var pathData = this.path.data;
  186. if (style.hasStroke()) {
  187. var lineWidth = style.lineWidth;
  188. var lineScale = style.strokeNoScale ? this.getLineScale() : 1; // Line scale can't be 0;
  189. if (lineScale > 1e-10) {
  190. // Only add extra hover lineWidth when there are no fill
  191. if (!style.hasFill()) {
  192. lineWidth = Math.max(lineWidth, this.strokeContainThreshold);
  193. }
  194. if (pathContain.containStroke(pathData, lineWidth / lineScale, x, y)) {
  195. return true;
  196. }
  197. }
  198. }
  199. if (style.hasFill()) {
  200. return pathContain.contain(pathData, x, y);
  201. }
  202. }
  203. return false;
  204. },
  205. /**
  206. * @param {boolean} dirtyPath
  207. */
  208. dirty: function (dirtyPath) {
  209. if (dirtyPath == null) {
  210. dirtyPath = true;
  211. } // Only mark dirty, not mark clean
  212. if (dirtyPath) {
  213. this.__dirtyPath = dirtyPath;
  214. this._rect = null;
  215. }
  216. this.__dirty = this.__dirtyText = true;
  217. this.__zr && this.__zr.refresh(); // Used as a clipping path
  218. if (this.__clipTarget) {
  219. this.__clipTarget.dirty();
  220. }
  221. },
  222. /**
  223. * Alias for animate('shape')
  224. * @param {boolean} loop
  225. */
  226. animateShape: function (loop) {
  227. return this.animate('shape', loop);
  228. },
  229. // Overwrite attrKV
  230. attrKV: function (key, value) {
  231. // FIXME
  232. if (key === 'shape') {
  233. this.setShape(value);
  234. this.__dirtyPath = true;
  235. this._rect = null;
  236. } else {
  237. Displayable.prototype.attrKV.call(this, key, value);
  238. }
  239. },
  240. /**
  241. * @param {Object|string} key
  242. * @param {*} value
  243. */
  244. setShape: function (key, value) {
  245. var shape = this.shape; // Path from string may not have shape
  246. if (shape) {
  247. if (zrUtil.isObject(key)) {
  248. for (var name in key) {
  249. if (key.hasOwnProperty(name)) {
  250. shape[name] = key[name];
  251. }
  252. }
  253. } else {
  254. shape[key] = value;
  255. }
  256. this.dirty(true);
  257. }
  258. return this;
  259. },
  260. getLineScale: function () {
  261. var m = this.transform; // Get the line scale.
  262. // Determinant of `m` means how much the area is enlarged by the
  263. // transformation. So its square root can be used as a scale factor
  264. // for width.
  265. return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10 ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1])) : 1;
  266. }
  267. };
  268. /**
  269. * 扩展一个 Path element, 比如星形,圆等。
  270. * Extend a path element
  271. * @param {Object} props
  272. * @param {string} props.type Path type
  273. * @param {Function} props.init Initialize
  274. * @param {Function} props.buildPath Overwrite buildPath method
  275. * @param {Object} [props.style] Extended default style config
  276. * @param {Object} [props.shape] Extended default shape config
  277. */
  278. Path.extend = function (defaults) {
  279. var Sub = function (opts) {
  280. Path.call(this, opts);
  281. if (defaults.style) {
  282. // Extend default style
  283. this.style.extendFrom(defaults.style, false);
  284. } // Extend default shape
  285. var defaultShape = defaults.shape;
  286. if (defaultShape) {
  287. this.shape = this.shape || {};
  288. var thisShape = this.shape;
  289. for (var name in defaultShape) {
  290. if (!thisShape.hasOwnProperty(name) && defaultShape.hasOwnProperty(name)) {
  291. thisShape[name] = defaultShape[name];
  292. }
  293. }
  294. }
  295. defaults.init && defaults.init.call(this, opts);
  296. };
  297. zrUtil.inherits(Sub, Path); // FIXME 不能 extend position, rotation 等引用对象
  298. for (var name in defaults) {
  299. // Extending prototype values and methods
  300. if (name !== 'style' && name !== 'shape') {
  301. Sub.prototype[name] = defaults[name];
  302. }
  303. }
  304. return Sub;
  305. };
  306. zrUtil.inherits(Path, Displayable);
  307. var _default = Path;
  308. module.exports = _default;