Version 3.18.1
Show:

File: graphics/js/SVGShape.js

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