Version 3.18.1
Show:

File: graphics/js/CanvasGraphic.js

  1. /**
  2. * <a href="http://www.w3.org/TR/html5/the-canvas-element.html">Canvas</a> implementation of the `Graphic` class.
  3. * `CanvasGraphic` is not intended to be used directly. Instead, use the <a href="Graphic.html">`Graphic`</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="Graphic.html">`Graphic`</a>
  6. * class will point to the `CanvasGraphic` class.
  7. *
  8. * @module graphics
  9. * @class CanvasGraphic
  10. * @constructor
  11. */
  12. function CanvasGraphic() {
  13. CanvasGraphic.superclass.constructor.apply(this, arguments);
  14. }
  15. CanvasGraphic.NAME = "canvasGraphic";
  16. CanvasGraphic.ATTRS = {
  17. /**
  18. * Whether or not to render the `Graphic` automatically after to a specified parent node after init. This can be a Node
  19. * instance or a CSS selector string.
  20. *
  21. * @config render
  22. * @type Node | String
  23. */
  24. render: {},
  25. /**
  26. * Unique id for class instance.
  27. *
  28. * @config id
  29. * @type String
  30. */
  31. id: {
  32. valueFn: function()
  33. {
  34. return Y.guid();
  35. },
  36. setter: function(val)
  37. {
  38. var node = this._node;
  39. if(node)
  40. {
  41. node.setAttribute("id", val);
  42. }
  43. return val;
  44. }
  45. },
  46. /**
  47. * Key value pairs in which a shape instance is associated with its id.
  48. *
  49. * @config shapes
  50. * @type Object
  51. * @readOnly
  52. */
  53. shapes: {
  54. readOnly: true,
  55. getter: function()
  56. {
  57. return this._shapes;
  58. }
  59. },
  60. /**
  61. * Object containing size and coordinate data for the content of a Graphic in relation to the graphic instance's position.
  62. *
  63. * @config contentBounds
  64. * @type Object
  65. * @readOnly
  66. */
  67. contentBounds: {
  68. readOnly: true,
  69. getter: function()
  70. {
  71. return this._contentBounds;
  72. }
  73. },
  74. /**
  75. * The outermost html element of the Graphic instance.
  76. *
  77. * @config node
  78. * @type HTMLElement
  79. * @readOnly
  80. */
  81. node: {
  82. readOnly: true,
  83. getter: function()
  84. {
  85. return this._node;
  86. }
  87. },
  88. /**
  89. * Indicates the width of the `Graphic`.
  90. *
  91. * @config width
  92. * @type Number
  93. */
  94. width: {
  95. setter: function(val)
  96. {
  97. if(this._node)
  98. {
  99. this._node.style.width = val + "px";
  100. }
  101. return val;
  102. }
  103. },
  104. /**
  105. * Indicates the height of the `Graphic`.
  106. *
  107. * @config height
  108. * @type Number
  109. */
  110. height: {
  111. setter: function(val)
  112. {
  113. if(this._node)
  114. {
  115. this._node.style.height = val + "px";
  116. }
  117. return val;
  118. }
  119. },
  120. /**
  121. * Determines the sizing of the Graphic.
  122. *
  123. * <dl>
  124. * <dt>sizeContentToGraphic</dt><dd>The Graphic's width and height attributes are, either explicitly set through the
  125. * <code>width</code> and <code>height</code> attributes or are determined by the dimensions of the parent element. The
  126. * content contained in the Graphic will be sized to fit with in the Graphic instance's dimensions. When using this
  127. * setting, the <code>preserveAspectRatio</code> attribute will determine how the contents are sized.</dd>
  128. * <dt>sizeGraphicToContent</dt><dd>(Also accepts a value of true) The Graphic's width and height are determined by the
  129. * size and positioning of the content.</dd>
  130. * <dt>false</dt><dd>The Graphic's width and height attributes are, either explicitly set through the <code>width</code>
  131. * and <code>height</code> attributes or are determined by the dimensions of the parent element. The contents of the
  132. * Graphic instance are not affected by this setting.</dd>
  133. * </dl>
  134. *
  135. *
  136. * @config autoSize
  137. * @type Boolean | String
  138. * @default false
  139. */
  140. autoSize: {
  141. value: false
  142. },
  143. /**
  144. * Determines how content is sized when <code>autoSize</code> is set to <code>sizeContentToGraphic</code>.
  145. *
  146. * <dl>
  147. * <dt>none<dt><dd>Do not force uniform scaling. Scale the graphic content of the given element non-uniformly if necessary
  148. * such that the element's bounding box exactly matches the viewport rectangle.</dd>
  149. * <dt>xMinYMin</dt><dd>Force uniform scaling position along the top left of the Graphic's node.</dd>
  150. * <dt>xMidYMin</dt><dd>Force uniform scaling horizontally centered and positioned at the top of the Graphic's node.<dd>
  151. * <dt>xMaxYMin</dt><dd>Force uniform scaling positioned horizontally from the right and vertically from the top.</dd>
  152. * <dt>xMinYMid</dt>Force uniform scaling positioned horizontally from the left and vertically centered.</dd>
  153. * <dt>xMidYMid (the default)</dt><dd>Force uniform scaling with the content centered.</dd>
  154. * <dt>xMaxYMid</dt><dd>Force uniform scaling positioned horizontally from the right and vertically centered.</dd>
  155. * <dt>xMinYMax</dt><dd>Force uniform scaling positioned horizontally from the left and vertically from the bottom.</dd>
  156. * <dt>xMidYMax</dt><dd>Force uniform scaling horizontally centered and position vertically from the bottom.</dd>
  157. * <dt>xMaxYMax</dt><dd>Force uniform scaling positioned horizontally from the right and vertically from the bottom.</dd>
  158. * </dl>
  159. *
  160. * @config preserveAspectRatio
  161. * @type String
  162. * @default xMidYMid
  163. */
  164. preserveAspectRatio: {
  165. value: "xMidYMid"
  166. },
  167. /**
  168. * The contentBounds will resize to greater values but not smaller values. (for performance)
  169. * When resizing the contentBounds down is desirable, set the resizeDown value to true.
  170. *
  171. * @config resizeDown
  172. * @type Boolean
  173. */
  174. resizeDown: {
  175. value: false
  176. },
  177. /**
  178. * Indicates the x-coordinate for the instance.
  179. *
  180. * @config x
  181. * @type Number
  182. */
  183. x: {
  184. getter: function()
  185. {
  186. return this._x;
  187. },
  188. setter: function(val)
  189. {
  190. this._x = val;
  191. if(this._node)
  192. {
  193. this._node.style.left = val + "px";
  194. }
  195. return val;
  196. }
  197. },
  198. /**
  199. * Indicates the y-coordinate for the instance.
  200. *
  201. * @config y
  202. * @type Number
  203. */
  204. y: {
  205. getter: function()
  206. {
  207. return this._y;
  208. },
  209. setter: function(val)
  210. {
  211. this._y = val;
  212. if(this._node)
  213. {
  214. this._node.style.top = val + "px";
  215. }
  216. return val;
  217. }
  218. },
  219. /**
  220. * Indicates whether or not the instance will automatically redraw after a change is made to a shape.
  221. * This property will get set to false when batching operations.
  222. *
  223. * @config autoDraw
  224. * @type Boolean
  225. * @default true
  226. * @private
  227. */
  228. autoDraw: {
  229. value: true
  230. },
  231. /**
  232. * Indicates whether the `Graphic` and its children are visible.
  233. *
  234. * @config visible
  235. * @type Boolean
  236. */
  237. visible: {
  238. value: true,
  239. setter: function(val)
  240. {
  241. this._toggleVisible(val);
  242. return val;
  243. }
  244. }
  245. };
  246. Y.extend(CanvasGraphic, Y.GraphicBase, {
  247. /**
  248. * Sets the value of an attribute.
  249. *
  250. * @method set
  251. * @param {String|Object} name The name of the attribute. Alternatively, an object of key value pairs can
  252. * be passed in to set multiple attributes at once.
  253. * @param {Any} value The value to set the attribute to. This value is ignored if an object is received as
  254. * the name param.
  255. */
  256. set: function()
  257. {
  258. var host = this,
  259. attr = arguments[0],
  260. redrawAttrs = {
  261. autoDraw: true,
  262. autoSize: true,
  263. preserveAspectRatio: true,
  264. resizeDown: true
  265. },
  266. key,
  267. forceRedraw = false;
  268. AttributeLite.prototype.set.apply(host, arguments);
  269. if(host._state.autoDraw === true && Y.Object.size(this._shapes) > 0)
  270. {
  271. if(Y_LANG.isString && redrawAttrs[attr])
  272. {
  273. forceRedraw = true;
  274. }
  275. else if(Y_LANG.isObject(attr))
  276. {
  277. for(key in redrawAttrs)
  278. {
  279. if(redrawAttrs.hasOwnProperty(key) && attr[key])
  280. {
  281. forceRedraw = true;
  282. break;
  283. }
  284. }
  285. }
  286. }
  287. if(forceRedraw)
  288. {
  289. host._redraw();
  290. }
  291. },
  292. /**
  293. * Storage for `x` attribute.
  294. *
  295. * @property _x
  296. * @type Number
  297. * @private
  298. */
  299. _x: 0,
  300. /**
  301. * Storage for `y` attribute.
  302. *
  303. * @property _y
  304. * @type Number
  305. * @private
  306. */
  307. _y: 0,
  308. /**
  309. * Gets the current position of the graphic instance in page coordinates.
  310. *
  311. * @method getXY
  312. * @return Array The XY position of the shape.
  313. */
  314. getXY: function()
  315. {
  316. var node = this._node,
  317. xy;
  318. if(node)
  319. {
  320. xy = Y.DOM.getXY(node);
  321. }
  322. return xy;
  323. },
  324. /**
  325. * Initializes the class.
  326. *
  327. * @method initializer
  328. * @param {Object} config Optional attributes
  329. * @private
  330. */
  331. initializer: function() {
  332. var render = this.get("render"),
  333. visibility = this.get("visible") ? "visible" : "hidden",
  334. w = this.get("width") || 0,
  335. h = this.get("height") || 0;
  336. this._shapes = {};
  337. this._redrawQueue = {};
  338. this._contentBounds = {
  339. left: 0,
  340. top: 0,
  341. right: 0,
  342. bottom: 0
  343. };
  344. this._node = DOCUMENT.createElement('div');
  345. this._node.style.position = "absolute";
  346. this._node.style.visibility = visibility;
  347. this.set("width", w);
  348. this.set("height", h);
  349. if(render)
  350. {
  351. this.render(render);
  352. }
  353. },
  354. /**
  355. * Adds the graphics node to the dom.
  356. *
  357. * @method render
  358. * @param {HTMLElement} parentNode node in which to render the graphics node into.
  359. */
  360. render: function(render) {
  361. var parentNode = render || DOCUMENT.body,
  362. node = this._node,
  363. w,
  364. h;
  365. if(render instanceof Y.Node)
  366. {
  367. parentNode = render._node;
  368. }
  369. else if(Y.Lang.isString(render))
  370. {
  371. parentNode = Y.Selector.query(render, DOCUMENT.body, true);
  372. }
  373. w = this.get("width") || parseInt(Y.DOM.getComputedStyle(parentNode, "width"), 10);
  374. h = this.get("height") || parseInt(Y.DOM.getComputedStyle(parentNode, "height"), 10);
  375. parentNode.appendChild(node);
  376. node.style.display = "block";
  377. node.style.position = "absolute";
  378. node.style.left = this.get("x") + "px";
  379. node.style.top = this.get("y") + "px";
  380. this.set("width", w);
  381. this.set("height", h);
  382. this.parentNode = parentNode;
  383. return this;
  384. },
  385. /**
  386. * Removes all nodes.
  387. *
  388. * @method destroy
  389. */
  390. destroy: function()
  391. {
  392. this.removeAllShapes();
  393. if(this._node)
  394. {
  395. this._removeChildren(this._node);
  396. if(this._node.parentNode)
  397. {
  398. this._node.parentNode.removeChild(this._node);
  399. }
  400. this._node = null;
  401. }
  402. },
  403. /**
  404. * Generates a shape instance by type.
  405. *
  406. * @method addShape
  407. * @param {Object} cfg attributes for the shape
  408. * @return Shape
  409. */
  410. addShape: function(cfg)
  411. {
  412. cfg.graphic = this;
  413. if(!this.get("visible"))
  414. {
  415. cfg.visible = false;
  416. }
  417. var ShapeClass = this._getShapeClass(cfg.type),
  418. shape = new ShapeClass(cfg);
  419. this._appendShape(shape);
  420. return shape;
  421. },
  422. /**
  423. * Adds a shape instance to the graphic instance.
  424. *
  425. * @method _appendShape
  426. * @param {Shape} shape The shape instance to be added to the graphic.
  427. * @private
  428. */
  429. _appendShape: function(shape)
  430. {
  431. var node = shape.node,
  432. parentNode = this._frag || this._node;
  433. if(this.get("autoDraw"))
  434. {
  435. parentNode.appendChild(node);
  436. }
  437. else
  438. {
  439. this._getDocFrag().appendChild(node);
  440. }
  441. },
  442. /**
  443. * Removes a shape instance from from the graphic instance.
  444. *
  445. * @method removeShape
  446. * @param {Shape|String} shape The instance or id of the shape to be removed.
  447. */
  448. removeShape: function(shape)
  449. {
  450. if(!(shape instanceof CanvasShape))
  451. {
  452. if(Y_LANG.isString(shape))
  453. {
  454. shape = this._shapes[shape];
  455. }
  456. }
  457. if(shape && shape instanceof CanvasShape)
  458. {
  459. shape._destroy();
  460. delete this._shapes[shape.get("id")];
  461. }
  462. if(this.get("autoDraw"))
  463. {
  464. this._redraw();
  465. }
  466. return shape;
  467. },
  468. /**
  469. * Removes all shape instances from the dom.
  470. *
  471. * @method removeAllShapes
  472. */
  473. removeAllShapes: function()
  474. {
  475. var shapes = this._shapes,
  476. i;
  477. for(i in shapes)
  478. {
  479. if(shapes.hasOwnProperty(i))
  480. {
  481. shapes[i].destroy();
  482. }
  483. }
  484. this._shapes = {};
  485. },
  486. /**
  487. * Clears the graphics object.
  488. *
  489. * @method clear
  490. */
  491. clear: function() {
  492. this.removeAllShapes();
  493. },
  494. /**
  495. * Removes all child nodes.
  496. *
  497. * @method _removeChildren
  498. * @param {HTMLElement} node
  499. * @private
  500. */
  501. _removeChildren: function(node)
  502. {
  503. if(node && node.hasChildNodes())
  504. {
  505. var child;
  506. while(node.firstChild)
  507. {
  508. child = node.firstChild;
  509. this._removeChildren(child);
  510. node.removeChild(child);
  511. }
  512. }
  513. },
  514. /**
  515. * Toggles visibility
  516. *
  517. * @method _toggleVisible
  518. * @param {Boolean} val indicates visibilitye
  519. * @private
  520. */
  521. _toggleVisible: function(val)
  522. {
  523. var i,
  524. shapes = this._shapes,
  525. visibility = val ? "visible" : "hidden";
  526. if(shapes)
  527. {
  528. for(i in shapes)
  529. {
  530. if(shapes.hasOwnProperty(i))
  531. {
  532. shapes[i].set("visible", val);
  533. }
  534. }
  535. }
  536. if(this._node)
  537. {
  538. this._node.style.visibility = visibility;
  539. }
  540. },
  541. /**
  542. * Returns a shape class. Used by `addShape`.
  543. *
  544. * @method _getShapeClass
  545. * @param {Shape | String} val Indicates which shape class.
  546. * @return Function
  547. * @private
  548. */
  549. _getShapeClass: function(val)
  550. {
  551. var shape = this._shapeClass[val];
  552. if(shape)
  553. {
  554. return shape;
  555. }
  556. return val;
  557. },
  558. /**
  559. * Look up for shape classes. Used by `addShape` to retrieve a class for instantiation.
  560. *
  561. * @property _shapeClass
  562. * @type Object
  563. * @private
  564. */
  565. _shapeClass: {
  566. circle: Y.CanvasCircle,
  567. rect: Y.CanvasRect,
  568. path: Y.CanvasPath,
  569. ellipse: Y.CanvasEllipse,
  570. pieslice: Y.CanvasPieSlice
  571. },
  572. /**
  573. * Returns a shape based on the id of its dom node.
  574. *
  575. * @method getShapeById
  576. * @param {String} id Dom id of the shape's node attribute.
  577. * @return Shape
  578. */
  579. getShapeById: function(id)
  580. {
  581. var shape = this._shapes[id];
  582. return shape;
  583. },
  584. /**
  585. * Allows for creating multiple shapes in order to batch appending and redraw operations.
  586. *
  587. * @method batch
  588. * @param {Function} method Method to execute.
  589. */
  590. batch: function(method)
  591. {
  592. var autoDraw = this.get("autoDraw");
  593. this.set("autoDraw", false);
  594. method();
  595. this.set("autoDraw", autoDraw);
  596. },
  597. /**
  598. * Returns a document fragment to for attaching shapes.
  599. *
  600. * @method _getDocFrag
  601. * @return DocumentFragment
  602. * @private
  603. */
  604. _getDocFrag: function()
  605. {
  606. if(!this._frag)
  607. {
  608. this._frag = DOCUMENT.createDocumentFragment();
  609. }
  610. return this._frag;
  611. },
  612. /**
  613. * Redraws all shapes.
  614. *
  615. * @method _redraw
  616. * @private
  617. */
  618. _redraw: function()
  619. {
  620. var autoSize = this.get("autoSize"),
  621. preserveAspectRatio = this.get("preserveAspectRatio"),
  622. box = this.get("resizeDown") ? this._getUpdatedContentBounds() : this._contentBounds,
  623. contentWidth,
  624. contentHeight,
  625. w,
  626. h,
  627. xScale,
  628. yScale,
  629. translateX = 0,
  630. translateY = 0,
  631. matrix,
  632. node = this.get("node");
  633. if(autoSize)
  634. {
  635. if(autoSize === "sizeContentToGraphic")
  636. {
  637. contentWidth = box.right - box.left;
  638. contentHeight = box.bottom - box.top;
  639. w = parseFloat(Y_DOM.getComputedStyle(node, "width"));
  640. h = parseFloat(Y_DOM.getComputedStyle(node, "height"));
  641. matrix = new Y.Matrix();
  642. if(preserveAspectRatio === "none")
  643. {
  644. xScale = w/contentWidth;
  645. yScale = h/contentHeight;
  646. }
  647. else
  648. {
  649. if(contentWidth/contentHeight !== w/h)
  650. {
  651. if(contentWidth * h/contentHeight > w)
  652. {
  653. xScale = yScale = w/contentWidth;
  654. translateY = this._calculateTranslate(preserveAspectRatio.slice(5).toLowerCase(), contentHeight * w/contentWidth, h);
  655. }
  656. else
  657. {
  658. xScale = yScale = h/contentHeight;
  659. translateX = this._calculateTranslate(preserveAspectRatio.slice(1, 4).toLowerCase(), contentWidth * h/contentHeight, w);
  660. }
  661. }
  662. }
  663. Y_DOM.setStyle(node, "transformOrigin", "0% 0%");
  664. translateX = translateX - (box.left * xScale);
  665. translateY = translateY - (box.top * yScale);
  666. matrix.translate(translateX, translateY);
  667. matrix.scale(xScale, yScale);
  668. Y_DOM.setStyle(node, "transform", matrix.toCSSText());
  669. }
  670. else
  671. {
  672. this.set("width", box.right);
  673. this.set("height", box.bottom);
  674. }
  675. }
  676. if(this._frag)
  677. {
  678. this._node.appendChild(this._frag);
  679. this._frag = null;
  680. }
  681. },
  682. /**
  683. * Determines the value for either an x or y value to be used for the <code>translate</code> of the Graphic.
  684. *
  685. * @method _calculateTranslate
  686. * @param {String} position The position for placement. Possible values are min, mid and max.
  687. * @param {Number} contentSize The total size of the content.
  688. * @param {Number} boundsSize The total size of the Graphic.
  689. * @return Number
  690. * @private
  691. */
  692. _calculateTranslate: function(position, contentSize, boundsSize)
  693. {
  694. var ratio = boundsSize - contentSize,
  695. coord;
  696. switch(position)
  697. {
  698. case "mid" :
  699. coord = ratio * 0.5;
  700. break;
  701. case "max" :
  702. coord = ratio;
  703. break;
  704. default :
  705. coord = 0;
  706. break;
  707. }
  708. return coord;
  709. },
  710. /**
  711. * Adds a shape to the redraw queue and calculates the contentBounds. Used internally
  712. * by `Shape` instances.
  713. *
  714. * @method addToRedrawQueue
  715. * @param Shape shape The shape instance to add to the queue
  716. * @protected
  717. */
  718. addToRedrawQueue: function(shape)
  719. {
  720. var shapeBox,
  721. box;
  722. this._shapes[shape.get("id")] = shape;
  723. if(!this.get("resizeDown"))
  724. {
  725. shapeBox = shape.getBounds();
  726. box = this._contentBounds;
  727. box.left = box.left < shapeBox.left ? box.left : shapeBox.left;
  728. box.top = box.top < shapeBox.top ? box.top : shapeBox.top;
  729. box.right = box.right > shapeBox.right ? box.right : shapeBox.right;
  730. box.bottom = box.bottom > shapeBox.bottom ? box.bottom : shapeBox.bottom;
  731. this._contentBounds = box;
  732. }
  733. if(this.get("autoDraw"))
  734. {
  735. this._redraw();
  736. }
  737. },
  738. /**
  739. * Recalculates and returns the `contentBounds` for the `Graphic` instance.
  740. *
  741. * @method _getUpdatedContentBounds
  742. * @return {Object}
  743. * @private
  744. */
  745. _getUpdatedContentBounds: function()
  746. {
  747. var bounds,
  748. i,
  749. shape,
  750. queue = this._shapes,
  751. box = {};
  752. for(i in queue)
  753. {
  754. if(queue.hasOwnProperty(i))
  755. {
  756. shape = queue[i];
  757. bounds = shape.getBounds();
  758. box.left = Y_LANG.isNumber(box.left) ? Math.min(box.left, bounds.left) : bounds.left;
  759. box.top = Y_LANG.isNumber(box.top) ? Math.min(box.top, bounds.top) : bounds.top;
  760. box.right = Y_LANG.isNumber(box.right) ? Math.max(box.right, bounds.right) : bounds.right;
  761. box.bottom = Y_LANG.isNumber(box.bottom) ? Math.max(box.bottom, bounds.bottom) : bounds.bottom;
  762. }
  763. }
  764. box.left = Y_LANG.isNumber(box.left) ? box.left : 0;
  765. box.top = Y_LANG.isNumber(box.top) ? box.top : 0;
  766. box.right = Y_LANG.isNumber(box.right) ? box.right : 0;
  767. box.bottom = Y_LANG.isNumber(box.bottom) ? box.bottom : 0;
  768. this._contentBounds = box;
  769. return box;
  770. },
  771. /**
  772. * Inserts shape on the top of the tree.
  773. *
  774. * @method _toFront
  775. * @param {CanvasShape} Shape to add.
  776. * @private
  777. */
  778. _toFront: function(shape)
  779. {
  780. var contentNode = this.get("node");
  781. if(shape instanceof Y.CanvasShape)
  782. {
  783. shape = shape.get("node");
  784. }
  785. if(contentNode && shape)
  786. {
  787. contentNode.appendChild(shape);
  788. }
  789. },
  790. /**
  791. * Inserts shape as the first child of the content node.
  792. *
  793. * @method _toBack
  794. * @param {CanvasShape} Shape to add.
  795. * @private
  796. */
  797. _toBack: function(shape)
  798. {
  799. var contentNode = this.get("node"),
  800. targetNode;
  801. if(shape instanceof Y.CanvasShape)
  802. {
  803. shape = shape.get("node");
  804. }
  805. if(contentNode && shape)
  806. {
  807. targetNode = contentNode.firstChild;
  808. if(targetNode)
  809. {
  810. contentNode.insertBefore(shape, targetNode);
  811. }
  812. else
  813. {
  814. contentNode.appendChild(shape);
  815. }
  816. }
  817. }
  818. });
  819. Y.CanvasGraphic = CanvasGraphic;