Path.js 9.0 KB

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