API Docs for: 3.10.3
Show:

File: graphics/js/CanvasShape.js

  1. /**
  2. * <a href="http://www.w3.org/TR/html5/the-canvas-element.html">Canvas</a> implementation of the <a href="Shape.html">`Shape`</a> class.
  3. * `CanvasShape` is not intended to be used directly. Instead, use the <a href="Shape.html">`Shape`</a> class.
  4. * If the browser lacks <a href="http://www.w3.org/TR/SVG/">SVG</a> capabilities but has
  5. * <a href="http://www.w3.org/TR/html5/the-canvas-element.html">Canvas</a> capabilities, the <a href="Shape.html">`Shape`</a>
  6. * class will point to the `CanvasShape` class.
  7. *
  8. * @module graphics
  9. * @class CanvasShape
  10. * @constructor
  11. */
  12. CanvasShape = function()
  13. {
  14. this._transforms = [];
  15. this.matrix = new Y.Matrix();
  16. CanvasShape.superclass.constructor.apply(this, arguments);
  17. };
  18.  
  19. CanvasShape.NAME = "shape";
  20.  
  21. Y.extend(CanvasShape, Y.GraphicBase, Y.mix({
  22. /**
  23. * Init method, invoked during construction.
  24. * Calls `initializer` method.
  25. *
  26. * @method init
  27. * @protected
  28. */
  29. init: function()
  30. {
  31. this.initializer.apply(this, arguments);
  32. },
  33.  
  34. /**
  35. * Initializes the shape
  36. *
  37. * @private
  38. * @method _initialize
  39. */
  40. initializer: function(cfg)
  41. {
  42. var host = this,
  43. graphic = cfg.graphic,
  44. data = this.get("data");
  45. host._initProps();
  46. host.createNode();
  47. host._xcoords = [0];
  48. host._ycoords = [0];
  49. if(graphic)
  50. {
  51. this._setGraphic(graphic);
  52. }
  53. if(data)
  54. {
  55. host._parsePathData(data);
  56. }
  57. host._updateHandler();
  58. },
  59.  
  60. /**
  61. * Set the Graphic instance for the shape.
  62. *
  63. * @method _setGraphic
  64. * @param {Graphic | Node | HTMLElement | String} render This param is used to determine the graphic instance. If it is a
  65. * `Graphic` instance, it will be assigned to the `graphic` attribute. Otherwise, a new Graphic instance will be created
  66. * and rendered into the dom element that the render represents.
  67. * @private
  68. */
  69. _setGraphic: function(render)
  70. {
  71. var graphic;
  72. if(render instanceof Y.CanvasGraphic)
  73. {
  74. this._graphic = render;
  75. }
  76. else
  77. {
  78. render = Y.one(render);
  79. graphic = new Y.CanvasGraphic({
  80. render: render
  81. });
  82. graphic._appendShape(this);
  83. this._graphic = graphic;
  84. }
  85. },
  86.  
  87. /**
  88. * Add a class name to each node.
  89. *
  90. * @method addClass
  91. * @param {String} className the class name to add to the node's class attribute
  92. */
  93. addClass: function(className)
  94. {
  95. var node = Y.one(this.get("node"));
  96. node.addClass(className);
  97. },
  98.  
  99. /**
  100. * Removes a class name from each node.
  101. *
  102. * @method removeClass
  103. * @param {String} className the class name to remove from the node's class attribute
  104. */
  105. removeClass: function(className)
  106. {
  107. var node = Y.one(this.get("node"));
  108. node.removeClass(className);
  109. },
  110.  
  111. /**
  112. * Gets the current position of the node in page coordinates.
  113. *
  114. * @method getXY
  115. * @return Array The XY position of the shape.
  116. */
  117. getXY: function()
  118. {
  119. var graphic = this.get("graphic"),
  120. parentXY = graphic.getXY(),
  121. x = this.get("x"),
  122. y = this.get("y");
  123. return [parentXY[0] + x, parentXY[1] + y];
  124. },
  125.  
  126. /**
  127. * Set the position of the shape in page coordinates, regardless of how the node is positioned.
  128. *
  129. * @method setXY
  130. * @param {Array} Contains X & Y values for new position (coordinates are page-based)
  131. */
  132. setXY: function(xy)
  133. {
  134. var graphic = this.get("graphic"),
  135. parentXY = graphic.getXY(),
  136. x = xy[0] - parentXY[0],
  137. y = xy[1] - parentXY[1];
  138. this._set("x", x);
  139. this._set("y", y);
  140. this._updateNodePosition(x, y);
  141. },
  142.  
  143. /**
  144. * Determines whether the node is an ancestor of another HTML element in the DOM hierarchy.
  145. *
  146. * @method contains
  147. * @param {CanvasShape | HTMLElement} needle The possible node or descendent
  148. * @return Boolean Whether or not this shape is the needle or its ancestor.
  149. */
  150. contains: function(needle)
  151. {
  152. return needle === Y.one(this.node);
  153. },
  154.  
  155. /**
  156. * Test if the supplied node matches the supplied selector.
  157. *
  158. * @method test
  159. * @param {String} selector The CSS selector to test against.
  160. * @return Boolean Wheter or not the shape matches the selector.
  161. */
  162. test: function(selector)
  163. {
  164. return Y.one(this.get("node")).test(selector);
  165. //return Y.Selector.test(this.node, selector);
  166. },
  167.  
  168. /**
  169. * Compares nodes to determine if they match.
  170. * Node instances can be compared to each other and/or HTMLElements.
  171. * @method compareTo
  172. * @param {HTMLElement | Node} refNode The reference node to compare to the node.
  173. * @return {Boolean} True if the nodes match, false if they do not.
  174. */
  175. compareTo: function(refNode) {
  176. var node = this.node;
  177. return node === refNode;
  178. },
  179.  
  180. /**
  181. * Value function for fill attribute
  182. *
  183. * @method _getDefaultFill
  184. * @return Object
  185. * @private
  186. */
  187. _getDefaultFill: function() {
  188. return {
  189. type: "solid",
  190. opacity: 1,
  191. cx: 0.5,
  192. cy: 0.5,
  193. fx: 0.5,
  194. fy: 0.5,
  195. r: 0.5
  196. };
  197. },
  198.  
  199. /**
  200. * Value function for stroke attribute
  201. *
  202. * @method _getDefaultStroke
  203. * @return Object
  204. * @private
  205. */
  206. _getDefaultStroke: function()
  207. {
  208. return {
  209. weight: 1,
  210. dashstyle: "none",
  211. color: "#000",
  212. opacity: 1.0
  213. };
  214. },
  215.  
  216. /**
  217. * Left edge of the path
  218. *
  219. * @property _left
  220. * @type Number
  221. * @private
  222. */
  223. _left: 0,
  224.  
  225. /**
  226. * Right edge of the path
  227. *
  228. * @property _right
  229. * @type Number
  230. * @private
  231. */
  232. _right: 0,
  233.  
  234. /**
  235. * Top edge of the path
  236. *
  237. * @property _top
  238. * @type Number
  239. * @private
  240. */
  241. _top: 0,
  242.  
  243. /**
  244. * Bottom edge of the path
  245. *
  246. * @property _bottom
  247. * @type Number
  248. * @private
  249. */
  250. _bottom: 0,
  251.  
  252. /**
  253. * Creates the dom node for the shape.
  254. *
  255. * @method createNode
  256. * @return HTMLElement
  257. * @private
  258. */
  259. createNode: function()
  260. {
  261. var host = this,
  262. node = Y.config.doc.createElement('canvas'),
  263. id = host.get("id"),
  264. concat = host._camelCaseConcat,
  265. name = host.name;
  266. host._context = node.getContext('2d');
  267. node.setAttribute("overflow", "visible");
  268. node.style.overflow = "visible";
  269. if(!host.get("visible"))
  270. {
  271. node.style.visibility = "hidden";
  272. }
  273. node.setAttribute("id", id);
  274. id = "#" + id;
  275. host.node = node;
  276. host.addClass(
  277. _getClassName(SHAPE) +
  278. " " +
  279. _getClassName(concat(IMPLEMENTATION, SHAPE)) +
  280. " " +
  281. _getClassName(name) +
  282. " " +
  283. _getClassName(concat(IMPLEMENTATION, name))
  284. );
  285. },
  286.  
  287. /**
  288. * Overrides default `on` method. Checks to see if its a dom interaction event. If so,
  289. * return an event attached to the `node` element. If not, return the normal functionality.
  290. *
  291. * @method on
  292. * @param {String} type event type
  293. * @param {Object} callback function
  294. * @private
  295. */
  296. on: function(type, fn)
  297. {
  298. if(Y.Node.DOM_EVENTS[type])
  299. {
  300. return Y.one("#" + this.get("id")).on(type, fn);
  301. }
  302. return Y.on.apply(this, arguments);
  303. },
  304.  
  305. /**
  306. * Adds a stroke to the shape node.
  307. *
  308. * @method _strokeChangeHandler
  309. * @param {Object} stroke Properties of the `stroke` attribute.
  310. * @private
  311. */
  312. _setStrokeProps: function(stroke)
  313. {
  314. var color,
  315. weight,
  316. opacity,
  317. linejoin,
  318. linecap,
  319. dashstyle;
  320. if(stroke)
  321. {
  322. color = stroke.color;
  323. weight = PARSE_FLOAT(stroke.weight);
  324. opacity = PARSE_FLOAT(stroke.opacity);
  325. linejoin = stroke.linejoin || "round";
  326. linecap = stroke.linecap || "butt";
  327. dashstyle = stroke.dashstyle;
  328. this._miterlimit = null;
  329. this._dashstyle = (dashstyle && Y.Lang.isArray(dashstyle) && dashstyle.length > 1) ? dashstyle : null;
  330. this._strokeWeight = weight;
  331.  
  332. if (IS_NUMBER(weight) && weight > 0)
  333. {
  334. this._stroke = 1;
  335. }
  336. else
  337. {
  338. this._stroke = 0;
  339. }
  340. if (IS_NUMBER(opacity)) {
  341. this._strokeStyle = this._toRGBA(color, opacity);
  342. }
  343. else
  344. {
  345. this._strokeStyle = color;
  346. }
  347. this._linecap = linecap;
  348. if(linejoin === "round" || linejoin === "bevel")
  349. {
  350. this._linejoin = linejoin;
  351. }
  352. else
  353. {
  354. linejoin = parseInt(linejoin, 10);
  355. if(IS_NUMBER(linejoin))
  356. {
  357. this._miterlimit = Math.max(linejoin, 1);
  358. this._linejoin = "miter";
  359. }
  360. }
  361. }
  362. else
  363. {
  364. this._stroke = 0;
  365. }
  366. },
  367.  
  368. /**
  369. * Sets the value of an attribute.
  370. *
  371. * @method set
  372. * @param {String|Object} name The name of the attribute. Alternatively, an object of key value pairs can
  373. * be passed in to set multiple attributes at once.
  374. * @param {Any} value The value to set the attribute to. This value is ignored if an object is received as
  375. * the name param.
  376. */
  377. set: function()
  378. {
  379. var host = this;
  380. AttributeLite.prototype.set.apply(host, arguments);
  381. if(host.initialized)
  382. {
  383. host._updateHandler();
  384. }
  385. },
  386.  
  387. /**
  388. * Adds a fill to the shape node.
  389. *
  390. * @method _setFillProps
  391. * @param {Object} fill Properties of the `fill` attribute.
  392. * @private
  393. */
  394. _setFillProps: function(fill)
  395. {
  396. var isNumber = IS_NUMBER,
  397. color,
  398. opacity,
  399. type;
  400. if(fill)
  401. {
  402. color = fill.color;
  403. type = fill.type;
  404. if(type === "linear" || type === "radial")
  405. {
  406. this._fillType = type;
  407. }
  408. else if(color)
  409. {
  410. opacity = fill.opacity;
  411. if (isNumber(opacity))
  412. {
  413. opacity = Math.max(0, Math.min(1, opacity));
  414. color = this._toRGBA(color, opacity);
  415. }
  416. else
  417. {
  418. color = TORGB(color);
  419. }
  420.  
  421. this._fillColor = color;
  422. this._fillType = 'solid';
  423. }
  424. else
  425. {
  426. this._fillColor = null;
  427. }
  428. }
  429. else
  430. {
  431. this._fillType = null;
  432. this._fillColor = null;
  433. }
  434. },
  435.  
  436. /**
  437. * Specifies a 2d translation.
  438. *
  439. * @method translate
  440. * @param {Number} x The value to transate on the x-axis.
  441. * @param {Number} y The value to translate on the y-axis.
  442. */
  443. translate: function(x, y)
  444. {
  445. this._translateX += x;
  446. this._translateY += y;
  447. this._addTransform("translate", arguments);
  448. },
  449.  
  450. /**
  451. * Translates the shape along the x-axis. When translating x and y coordinates,
  452. * use the `translate` method.
  453. *
  454. * @method translateX
  455. * @param {Number} x The value to translate.
  456. */
  457. translateX: function(x)
  458. {
  459. this._translateX += x;
  460. this._addTransform("translateX", arguments);
  461. },
  462.  
  463. /**
  464. * Performs a translate on the y-coordinate. When translating x and y coordinates,
  465. * use the `translate` method.
  466. *
  467. * @method translateY
  468. * @param {Number} y The value to translate.
  469. */
  470. translateY: function(y)
  471. {
  472. this._translateY += y;
  473. this._addTransform("translateY", arguments);
  474. },
  475.  
  476. /**
  477. * Skews the shape around the x-axis and y-axis.
  478. *
  479. * @method skew
  480. * @param {Number} x The value to skew on the x-axis.
  481. * @param {Number} y The value to skew on the y-axis.
  482. */
  483. skew: function()
  484. {
  485. this._addTransform("skew", arguments);
  486. },
  487.  
  488. /**
  489. * Skews the shape around the x-axis.
  490. *
  491. * @method skewX
  492. * @param {Number} x x-coordinate
  493. */
  494. skewX: function()
  495. {
  496. this._addTransform("skewX", arguments);
  497. },
  498.  
  499. /**
  500. * Skews the shape around the y-axis.
  501. *
  502. * @method skewY
  503. * @param {Number} y y-coordinate
  504. */
  505. skewY: function()
  506. {
  507. this._addTransform("skewY", arguments);
  508. },
  509.  
  510. /**
  511. * Rotates the shape clockwise around it transformOrigin.
  512. *
  513. * @method rotate
  514. * @param {Number} deg The degree of the rotation.
  515. */
  516. rotate: function()
  517. {
  518. this._addTransform("rotate", arguments);
  519. },
  520.  
  521. /**
  522. * Specifies a 2d scaling operation.
  523. *
  524. * @method scale
  525. * @param {Number} val
  526. */
  527. scale: function()
  528. {
  529. this._addTransform("scale", arguments);
  530. },
  531.  
  532. /**
  533. * Storage for the transform attribute.
  534. *
  535. * @property _transform
  536. * @type String
  537. * @private
  538. */
  539. _transform: "",
  540.  
  541. /**
  542. * Adds a transform to the shape.
  543. *
  544. * @method _addTransform
  545. * @param {String} type The transform being applied.
  546. * @param {Array} args The arguments for the transform.
  547. * @private
  548. */
  549. _addTransform: function(type, args)
  550. {
  551. args = Y.Array(args);
  552. this._transform = Y_LANG.trim(this._transform + " " + type + "(" + args.join(", ") + ")");
  553. args.unshift(type);
  554. this._transforms.push(args);
  555. if(this.initialized)
  556. {
  557. this._updateTransform();
  558. }
  559. },
  560.  
  561. /**
  562. * Applies all transforms.
  563. *
  564. * @method _updateTransform
  565. * @private
  566. */
  567. _updateTransform: function()
  568. {
  569. var node = this.node,
  570. key,
  571. transform,
  572. transformOrigin = this.get("transformOrigin"),
  573. matrix = this.matrix,
  574. i,
  575. len = this._transforms.length;
  576.  
  577. if(this._transforms && this._transforms.length > 0)
  578. {
  579. for(i = 0; i < len; ++i)
  580. {
  581. key = this._transforms[i].shift();
  582. if(key)
  583. {
  584. matrix[key].apply(matrix, this._transforms[i]);
  585. }
  586. }
  587. transform = matrix.toCSSText();
  588. }
  589.  
  590. this._graphic.addToRedrawQueue(this);
  591. transformOrigin = (100 * transformOrigin[0]) + "% " + (100 * transformOrigin[1]) + "%";
  592. Y_DOM.setStyle(node, "transformOrigin", transformOrigin);
  593. if(transform)
  594. {
  595. Y_DOM.setStyle(node, "transform", transform);
  596. }
  597. this._transforms = [];
  598. },
  599.  
  600. /**
  601. * Updates `Shape` based on attribute changes.
  602. *
  603. * @method _updateHandler
  604. * @private
  605. */
  606. _updateHandler: function()
  607. {
  608. this._draw();
  609. this._updateTransform();
  610. },
  611.  
  612. /**
  613. * Updates the shape.
  614. *
  615. * @method _draw
  616. * @private
  617. */
  618. _draw: function()
  619. {
  620. var node = this.node;
  621. this.clear();
  622. this._closePath();
  623. node.style.left = this.get("x") + "px";
  624. node.style.top = this.get("y") + "px";
  625. },
  626.  
  627. /**
  628. * Completes a shape or drawing
  629. *
  630. * @method _closePath
  631. * @private
  632. */
  633. _closePath: function()
  634. {
  635. if(!this._methods)
  636. {
  637. return;
  638. }
  639. var node = this.get("node"),
  640. w = this._right - this._left,
  641. h = this._bottom - this._top,
  642. context = this._context,
  643. methods = [],
  644. cachedMethods = this._methods.concat(),
  645. i,
  646. j,
  647. method,
  648. args,
  649. argsLen,
  650. len = 0;
  651. this._context.clearRect(0, 0, node.width, node.height);
  652. if(this._methods)
  653. {
  654. len = cachedMethods.length;
  655. if(!len || len < 1)
  656. {
  657. return;
  658. }
  659. for(i = 0; i < len; ++i)
  660. {
  661. methods[i] = cachedMethods[i].concat();
  662. args = methods[i];
  663. argsLen = (args[0] === "quadraticCurveTo" || args[0] === "bezierCurveTo") ? args.length : 3;
  664. for(j = 1; j < argsLen; ++j)
  665. {
  666. if(j % 2 === 0)
  667. {
  668. args[j] = args[j] - this._top;
  669. }
  670. else
  671. {
  672. args[j] = args[j] - this._left;
  673. }
  674. }
  675. }
  676. node.setAttribute("width", Math.min(w, 2000));
  677. node.setAttribute("height", Math.min(2000, h));
  678. context.beginPath();
  679. for(i = 0; i < len; ++i)
  680. {
  681. args = methods[i].concat();
  682. if(args && args.length > 0)
  683. {
  684. method = args.shift();
  685. if(method)
  686. {
  687. if(method === "closePath")
  688. {
  689. context.closePath();
  690. this._strokeAndFill(context);
  691. }
  692. else if(method && method === "lineTo" && this._dashstyle)
  693. {
  694. args.unshift(this._xcoords[i] - this._left, this._ycoords[i] - this._top);
  695. this._drawDashedLine.apply(this, args);
  696. }
  697. else
  698. {
  699. context[method].apply(context, args);
  700. }
  701. }
  702. }
  703. }
  704.  
  705. this._strokeAndFill(context);
  706. this._drawingComplete = true;
  707. this._clearAndUpdateCoords();
  708. this._updateNodePosition();
  709. this._methods = cachedMethods;
  710. }
  711. },
  712.  
  713. /**
  714. * Completes a stroke and/or fill operation on the context.
  715. *
  716. * @method _strokeAndFill
  717. * @param {Context} Reference to the context element of the canvas instance.
  718. * @private
  719. */
  720. _strokeAndFill: function(context)
  721. {
  722. if (this._fillType)
  723. {
  724. if(this._fillType === "linear")
  725. {
  726. context.fillStyle = this._getLinearGradient();
  727. }
  728. else if(this._fillType === "radial")
  729. {
  730. context.fillStyle = this._getRadialGradient();
  731. }
  732. else
  733. {
  734. context.fillStyle = this._fillColor;
  735. }
  736. context.closePath();
  737. context.fill();
  738. }
  739.  
  740. if (this._stroke) {
  741. if(this._strokeWeight)
  742. {
  743. context.lineWidth = this._strokeWeight;
  744. }
  745. context.lineCap = this._linecap;
  746. context.lineJoin = this._linejoin;
  747. if(this._miterlimit)
  748. {
  749. context.miterLimit = this._miterlimit;
  750. }
  751. context.strokeStyle = this._strokeStyle;
  752. context.stroke();
  753. }
  754. },
  755.  
  756. /**
  757. * Draws a dashed line between two points.
  758. *
  759. * @method _drawDashedLine
  760. * @param {Number} xStart The x position of the start of the line
  761. * @param {Number} yStart The y position of the start of the line
  762. * @param {Number} xEnd The x position of the end of the line
  763. * @param {Number} yEnd The y position of the end of the line
  764. * @private
  765. */
  766. _drawDashedLine: function(xStart, yStart, xEnd, yEnd)
  767. {
  768. var context = this._context,
  769. dashsize = this._dashstyle[0],
  770. gapsize = this._dashstyle[1],
  771. segmentLength = dashsize + gapsize,
  772. xDelta = xEnd - xStart,
  773. yDelta = yEnd - yStart,
  774. delta = Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2)),
  775. segmentCount = Math.floor(Math.abs(delta / segmentLength)),
  776. radians = Math.atan2(yDelta, xDelta),
  777. xCurrent = xStart,
  778. yCurrent = yStart,
  779. i;
  780. xDelta = Math.cos(radians) * segmentLength;
  781. yDelta = Math.sin(radians) * segmentLength;
  782.  
  783. for(i = 0; i < segmentCount; ++i)
  784. {
  785. context.moveTo(xCurrent, yCurrent);
  786. context.lineTo(xCurrent + Math.cos(radians) * dashsize, yCurrent + Math.sin(radians) * dashsize);
  787. xCurrent += xDelta;
  788. yCurrent += yDelta;
  789. }
  790.  
  791. context.moveTo(xCurrent, yCurrent);
  792. delta = Math.sqrt((xEnd - xCurrent) * (xEnd - xCurrent) + (yEnd - yCurrent) * (yEnd - yCurrent));
  793.  
  794. if(delta > dashsize)
  795. {
  796. context.lineTo(xCurrent + Math.cos(radians) * dashsize, yCurrent + Math.sin(radians) * dashsize);
  797. }
  798. else if(delta > 0)
  799. {
  800. context.lineTo(xCurrent + Math.cos(radians) * delta, yCurrent + Math.sin(radians) * delta);
  801. }
  802.  
  803. context.moveTo(xEnd, yEnd);
  804. },
  805.  
  806. /**
  807. * Returns the bounds for a shape.
  808. *
  809. * Calculates the a new bounding box from the original corner coordinates (base on size and position) and the transform matrix.
  810. * The calculated bounding box is used by the graphic instance to calculate its viewBox.
  811. *
  812. * @method getBounds
  813. * @return Object
  814. */
  815. getBounds: function()
  816. {
  817. var type = this._type,
  818. w = this.get("width"),
  819. h = this.get("height"),
  820. x = this.get("x"),
  821. y = this.get("y");
  822. if(type === "path")
  823. {
  824. x = x + this._left;
  825. y = y + this._top;
  826. w = this._right - this._left;
  827. h = this._bottom - this._top;
  828. }
  829. return this._getContentRect(w, h, x, y);
  830. },
  831.  
  832. /**
  833. * Calculates the bounding box for the shape.
  834. *
  835. * @method _getContentRect
  836. * @param {Number} w width of the shape
  837. * @param {Number} h height of the shape
  838. * @param {Number} x x-coordinate of the shape
  839. * @param {Number} y y-coordinate of the shape
  840. * @private
  841. */
  842. _getContentRect: function(w, h, x, y)
  843. {
  844. var transformOrigin = this.get("transformOrigin"),
  845. transformX = transformOrigin[0] * w,
  846. transformY = transformOrigin[1] * h,
  847. transforms = this.matrix.getTransformArray(this.get("transform")),
  848. matrix = new Y.Matrix(),
  849. i,
  850. len = transforms.length,
  851. transform,
  852. key,
  853. contentRect;
  854. if(this._type === "path")
  855. {
  856. transformX = transformX + x;
  857. transformY = transformY + y;
  858. }
  859. transformX = !isNaN(transformX) ? transformX : 0;
  860. transformY = !isNaN(transformY) ? transformY : 0;
  861. matrix.translate(transformX, transformY);
  862. for(i = 0; i < len; i = i + 1)
  863. {
  864. transform = transforms[i];
  865. key = transform.shift();
  866. if(key)
  867. {
  868. matrix[key].apply(matrix, transform);
  869. }
  870. }
  871. matrix.translate(-transformX, -transformY);
  872. contentRect = matrix.getContentRect(w, h, x, y);
  873. return contentRect;
  874. },
  875.  
  876. /**
  877. * Places the shape above all other shapes.
  878. *
  879. * @method toFront
  880. */
  881. toFront: function()
  882. {
  883. var graphic = this.get("graphic");
  884. if(graphic)
  885. {
  886. graphic._toFront(this);
  887. }
  888. },
  889.  
  890. /**
  891. * Places the shape underneath all other shapes.
  892. *
  893. * @method toFront
  894. */
  895. toBack: function()
  896. {
  897. var graphic = this.get("graphic");
  898. if(graphic)
  899. {
  900. graphic._toBack(this);
  901. }
  902. },
  903.  
  904. /**
  905. * Parses path data string and call mapped methods.
  906. *
  907. * @method _parsePathData
  908. * @param {String} val The path data
  909. * @private
  910. */
  911. _parsePathData: function(val)
  912. {
  913. var method,
  914. methodSymbol,
  915. args,
  916. commandArray = Y.Lang.trim(val.match(SPLITPATHPATTERN)),
  917. i,
  918. len,
  919. str,
  920. symbolToMethod = this._pathSymbolToMethod;
  921. if(commandArray)
  922. {
  923. this.clear();
  924. len = commandArray.length || 0;
  925. for(i = 0; i < len; i = i + 1)
  926. {
  927. str = commandArray[i];
  928. methodSymbol = str.substr(0, 1);
  929. args = str.substr(1).match(SPLITARGSPATTERN);
  930. method = symbolToMethod[methodSymbol];
  931. if(method)
  932. {
  933. if(args)
  934. {
  935. this[method].apply(this, args);
  936. }
  937. else
  938. {
  939. this[method].apply(this);
  940. }
  941. }
  942. }
  943. this.end();
  944. }
  945. },
  946.  
  947. /**
  948. * Destroys the shape instance.
  949. *
  950. * @method destroy
  951. */
  952. destroy: function()
  953. {
  954. var graphic = this.get("graphic");
  955. if(graphic)
  956. {
  957. graphic.removeShape(this);
  958. }
  959. else
  960. {
  961. this._destroy();
  962. }
  963. },
  964.  
  965. /**
  966. * Implementation for shape destruction
  967. *
  968. * @method destroy
  969. * @protected
  970. */
  971. _destroy: function()
  972. {
  973. if(this.node)
  974. {
  975. Y.one(this.node).remove(true);
  976. this._context = null;
  977. this.node = null;
  978. }
  979. }
  980. }, Y.CanvasDrawing.prototype));
  981.  
  982. CanvasShape.ATTRS = {
  983. /**
  984. * An array of x, y values which indicates the transformOrigin in which to rotate the shape. Valid values range between 0 and 1 representing a
  985. * fraction of the shape's corresponding bounding box dimension. The default value is [0.5, 0.5].
  986. *
  987. * @config transformOrigin
  988. * @type Array
  989. */
  990. transformOrigin: {
  991. valueFn: function()
  992. {
  993. return [0.5, 0.5];
  994. }
  995. },
  996.  
  997. /**
  998. * <p>A string containing, in order, transform operations applied to the shape instance. The `transform` string can contain the following values:
  999. *
  1000. * <dl>
  1001. * <dt>rotate</dt><dd>Rotates the shape clockwise around it transformOrigin.</dd>
  1002. * <dt>translate</dt><dd>Specifies a 2d translation.</dd>
  1003. * <dt>skew</dt><dd>Skews the shape around the x-axis and y-axis.</dd>
  1004. * <dt>scale</dt><dd>Specifies a 2d scaling operation.</dd>
  1005. * <dt>translateX</dt><dd>Translates the shape along the x-axis.</dd>
  1006. * <dt>translateY</dt><dd>Translates the shape along the y-axis.</dd>
  1007. * <dt>skewX</dt><dd>Skews the shape around the x-axis.</dd>
  1008. * <dt>skewY</dt><dd>Skews the shape around the y-axis.</dd>
  1009. * <dt>matrix</dt><dd>Specifies a 2D transformation matrix comprised of the specified six values.</dd>
  1010. * </dl>
  1011. * </p>
  1012. * <p>Applying transforms through the transform attribute will reset the transform matrix and apply a new transform. The shape class also contains
  1013. * corresponding methods for each transform that will apply the transform to the current matrix. The below code illustrates how you might use the
  1014. * `transform` attribute to instantiate a recangle with a rotation of 45 degrees.</p>
  1015. var myRect = new Y.Rect({
  1016. type:"rect",
  1017. width: 50,
  1018. height: 40,
  1019. transform: "rotate(45)"
  1020. };
  1021. * <p>The code below would apply `translate` and `rotate` to an existing shape.</p>
  1022.  
  1023. myRect.set("transform", "translate(40, 50) rotate(45)");
  1024. * @config transform
  1025. * @type String
  1026. */
  1027. transform: {
  1028. setter: function(val)
  1029. {
  1030. this.matrix.init();
  1031. this._transforms = this.matrix.getTransformArray(val);
  1032. this._transform = val;
  1033. return val;
  1034. },
  1035.  
  1036. getter: function()
  1037. {
  1038. return this._transform;
  1039. }
  1040. },
  1041.  
  1042. /**
  1043. * Dom node for the shape
  1044. *
  1045. * @config node
  1046. * @type HTMLElement
  1047. * @readOnly
  1048. */
  1049. node: {
  1050. readOnly: true,
  1051.  
  1052. getter: function()
  1053. {
  1054. return this.node;
  1055. }
  1056. },
  1057.  
  1058. /**
  1059. * Unique id for class instance.
  1060. *
  1061. * @config id
  1062. * @type String
  1063. */
  1064. id: {
  1065. valueFn: function()
  1066. {
  1067. return Y.guid();
  1068. },
  1069.  
  1070. setter: function(val)
  1071. {
  1072. var node = this.node;
  1073. if(node)
  1074. {
  1075. node.setAttribute("id", val);
  1076. }
  1077. return val;
  1078. }
  1079. },
  1080.  
  1081. /**
  1082. * Indicates the width of the shape
  1083. *
  1084. * @config width
  1085. * @type Number
  1086. */
  1087. width: {
  1088. value: 0
  1089. },
  1090.  
  1091. /**
  1092. * Indicates the height of the shape
  1093. *
  1094. * @config height
  1095. * @type Number
  1096. */
  1097. height: {
  1098. value: 0
  1099. },
  1100.  
  1101. /**
  1102. * Indicates the x position of shape.
  1103. *
  1104. * @config x
  1105. * @type Number
  1106. */
  1107. x: {
  1108. value: 0
  1109. },
  1110.  
  1111. /**
  1112. * Indicates the y position of shape.
  1113. *
  1114. * @config y
  1115. * @type Number
  1116. */
  1117. y: {
  1118. value: 0
  1119. },
  1120.  
  1121. /**
  1122. * Indicates whether the shape is visible.
  1123. *
  1124. * @config visible
  1125. * @type Boolean
  1126. */
  1127. visible: {
  1128. value: true,
  1129.  
  1130. setter: function(val){
  1131. var node = this.get("node"),
  1132. visibility = val ? "visible" : "hidden";
  1133. if(node)
  1134. {
  1135. node.style.visibility = visibility;
  1136. }
  1137. return val;
  1138. }
  1139. },
  1140.  
  1141. /**
  1142. * Contains information about the fill of the shape.
  1143. * <dl>
  1144. * <dt>color</dt><dd>The color of the fill.</dd>
  1145. * <dt>opacity</dt><dd>Number between 0 and 1 that indicates the opacity of the fill. The default value is 1.</dd>
  1146. * <dt>type</dt><dd>Type of fill.
  1147. * <dl>
  1148. * <dt>solid</dt><dd>Solid single color fill. (default)</dd>
  1149. * <dt>linear</dt><dd>Linear gradient fill.</dd>
  1150. * <dt>radial</dt><dd>Radial gradient fill.</dd>
  1151. * </dl>
  1152. * </dd>
  1153. * </dl>
  1154. * <p>If a `linear` or `radial` is specified as the fill type. The following additional property is used:
  1155. * <dl>
  1156. * <dt>stops</dt><dd>An array of objects containing the following properties:
  1157. * <dl>
  1158. * <dt>color</dt><dd>The color of the stop.</dd>
  1159. * <dt>opacity</dt><dd>Number between 0 and 1 that indicates the opacity of the stop. The default value is 1.
  1160. * Note: No effect for IE 6 - 8</dd>
  1161. * <dt>offset</dt><dd>Number between 0 and 1 indicating where the color stop is positioned.</dd>
  1162. * </dl>
  1163. * </dd>
  1164. * <p>Linear gradients also have the following property:</p>
  1165. * <dt>rotation</dt><dd>Linear gradients flow left to right by default. The rotation property allows you to change the
  1166. * flow by rotation. (e.g. A rotation of 180 would make the gradient pain from right to left.)</dd>
  1167. * <p>Radial gradients have the following additional properties:</p>
  1168. * <dt>r</dt><dd>Radius of the gradient circle.</dd>
  1169. * <dt>fx</dt><dd>Focal point x-coordinate of the gradient.</dd>
  1170. * <dt>fy</dt><dd>Focal point y-coordinate of the gradient.</dd>
  1171. * </dl>
  1172. * <p>The corresponding `SVGShape` class implements the following additional properties.</p>
  1173. * <dl>
  1174. * <dt>cx</dt><dd>
  1175. * <p>The x-coordinate of the center of the gradient circle. Determines where the color stop begins. The default value 0.5.</p>
  1176. * <p><strong>Note: </strong>Currently, this property is not implemented for corresponding `CanvasShape` and
  1177. * `VMLShape` classes which are used on Android or IE 6 - 8.</p>
  1178. * </dd>
  1179. * <dt>cy</dt><dd>
  1180. * <p>The y-coordinate of the center of the gradient circle. Determines where the color stop begins. The default value 0.5.</p>
  1181. * <p><strong>Note: </strong>Currently, this property is not implemented for corresponding `CanvasShape` and `VMLShape`
  1182. * classes which are used on Android or IE 6 - 8.</p>
  1183. * </dd>
  1184. * </dl>
  1185. * <p>These properties are not currently implemented in `CanvasShape` or `VMLShape`.</p>
  1186. *
  1187. * @config fill
  1188. * @type Object
  1189. */
  1190. fill: {
  1191. valueFn: "_getDefaultFill",
  1192.  
  1193. setter: function(val)
  1194. {
  1195. var fill,
  1196. tmpl = this.get("fill") || this._getDefaultFill();
  1197. fill = (val) ? Y.merge(tmpl, val) : null;
  1198. if(fill && fill.color)
  1199. {
  1200. if(fill.color === undefined || fill.color === "none")
  1201. {
  1202. fill.color = null;
  1203. }
  1204. }
  1205. this._setFillProps(fill);
  1206. return fill;
  1207. }
  1208. },
  1209.  
  1210. /**
  1211. * Contains information about the stroke of the shape.
  1212. * <dl>
  1213. * <dt>color</dt><dd>The color of the stroke.</dd>
  1214. * <dt>weight</dt><dd>Number that indicates the width of the stroke.</dd>
  1215. * <dt>opacity</dt><dd>Number between 0 and 1 that indicates the opacity of the stroke. The default value is 1.</dd>
  1216. * <dt>dashstyle</dt>Indicates whether to draw a dashed stroke. When set to "none", a solid stroke is drawn. When set
  1217. * to an array, the first index indicates the length of the dash. The second index indicates the length of gap.
  1218. * <dt>linecap</dt><dd>Specifies the linecap for the stroke. The following values can be specified:
  1219. * <dl>
  1220. * <dt>butt (default)</dt><dd>Specifies a butt linecap.</dd>
  1221. * <dt>square</dt><dd>Specifies a sqare linecap.</dd>
  1222. * <dt>round</dt><dd>Specifies a round linecap.</dd>
  1223. * </dl>
  1224. * </dd>
  1225. * <dt>linejoin</dt><dd>Specifies a linejoin for the stroke. The following values can be specified:
  1226. * <dl>
  1227. * <dt>round (default)</dt><dd>Specifies that the linejoin will be round.</dd>
  1228. * <dt>bevel</dt><dd>Specifies a bevel for the linejoin.</dd>
  1229. * <dt>miter limit</dt><dd>An integer specifying the miter limit of a miter linejoin. If you want to specify a linejoin
  1230. * of miter, you simply specify the limit as opposed to having separate miter and miter limit values.</dd>
  1231. * </dl>
  1232. * </dd>
  1233. * </dl>
  1234. *
  1235. * @config stroke
  1236. * @type Object
  1237. */
  1238. stroke: {
  1239. valueFn: "_getDefaultStroke",
  1240.  
  1241. setter: function(val)
  1242. {
  1243. var tmpl = this.get("stroke") || this._getDefaultStroke(),
  1244. wt;
  1245. if(val && val.hasOwnProperty("weight"))
  1246. {
  1247. wt = parseInt(val.weight, 10);
  1248. if(!isNaN(wt))
  1249. {
  1250. val.weight = wt;
  1251. }
  1252. }
  1253. val = (val) ? Y.merge(tmpl, val) : null;
  1254. this._setStrokeProps(val);
  1255. return val;
  1256. }
  1257. },
  1258.  
  1259. //Not used. Remove in future.
  1260. autoSize: {
  1261. value: false
  1262. },
  1263.  
  1264. // Only implemented in SVG
  1265. // Determines whether the instance will receive mouse events.
  1266. //
  1267. // @config pointerEvents
  1268. // @type string
  1269. //
  1270. pointerEvents: {
  1271. value: "visiblePainted"
  1272. },
  1273.  
  1274. /**
  1275. * Represents an SVG Path string. This will be parsed and added to shape's API to represent the SVG data across all
  1276. * implementations. Note that when using VML or SVG implementations, part of this content will be added to the DOM using
  1277. * respective VML/SVG attributes. If your content comes from an untrusted source, you will need to ensure that no
  1278. * malicious code is included in that content.
  1279. *
  1280. * @config data
  1281. * @type String
  1282. */
  1283. data: {
  1284. setter: function(val)
  1285. {
  1286. if(this.get("node"))
  1287. {
  1288. this._parsePathData(val);
  1289. }
  1290. return val;
  1291. }
  1292. },
  1293.  
  1294. /**
  1295. * Reference to the container Graphic.
  1296. *
  1297. * @config graphic
  1298. * @type Graphic
  1299. */
  1300. graphic: {
  1301. readOnly: true,
  1302.  
  1303. getter: function()
  1304. {
  1305. return this._graphic;
  1306. }
  1307. }
  1308. };
  1309. Y.CanvasShape = CanvasShape;
  1310.