3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
17 * A Surface contains methods to render sprites, get bounding boxes of sprites, add
18 * sprites to the canvas, initialize other graphic components, etc. One of the most used
19 * methods for this class is the `add` method, to add Sprites to the surface.
21 * Most of the Surface methods are abstract and they have a concrete implementation
22 * in VML or SVG engines.
24 * A Surface instance can be accessed as a property of a draw component. For example:
26 * drawComponent.surface.add({
34 * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
35 * class documentation.
39 * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
52 * drawComponent.surface.on({
53 * 'mousemove': function() {
54 * console.log('moving the mouse over the surface');
60 * var drawComponent = Ext.create('Ext.draw.Component', {
63 * renderTo: document.body
64 * }), surface = drawComponent.surface;
102 * group: 'rectangles'
110 * group: 'rectangles'
113 * // Get references to my groups
114 * circles = surface.getGroup('circles');
115 * rectangles = surface.getGroup('rectangles');
117 * // Animate the circles down
127 * // Animate the rectangles across
128 * rectangles.animate({
137 Ext.define('Ext.draw.Surface', {
139 /* Begin Definitions */
142 observable: 'Ext.util.Observable'
145 requires: ['Ext.draw.CompositeSprite'],
146 uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
148 separatorRe: /[, ]+/,
152 * Creates and returns a new concrete Surface instance appropriate for the current environment.
153 * @param {Object} config Initial configuration for the Surface instance
154 * @param {[String]} enginePriority Optional order of implementations to use; the first one that is
155 * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
156 * @return {Object} The created Surface or false.
159 create: function(config, enginePriority) {
160 enginePriority = enginePriority || ['Svg', 'Vml'];
163 len = enginePriority.length,
166 for (; i < len; i++) {
167 if (Ext.supports[enginePriority[i]]) {
168 return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
175 /* End Definitions */
180 "clip-rect": "0 0 1e9 1e9",
184 'dominant-baseline': 'auto',
187 font: '10px "Arial"',
188 "font-family": '"Arial"',
190 "font-style": "normal",
195 href: "http://sencha.com/",
204 "stroke-dasharray": "",
205 "stroke-linecap": "butt",
206 "stroke-linejoin": "butt",
207 "stroke-miterlimit": 0,
212 "text-anchor": "middle",
221 * @cfg {Number} height
222 * The height of this component in pixels (defaults to auto).
223 * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
226 * @cfg {Number} width
227 * The width of this component in pixels (defaults to auto).
228 * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
231 container: undefined,
238 * Creates new Surface.
239 * @param {Object} config (optional) Config object.
241 constructor: function(config) {
243 config = config || {};
244 Ext.apply(me, config);
246 me.domRef = Ext.getDoc().dom;
248 me.customAttributes = {};
261 me.mixins.observable.constructor.call(me);
267 me.render(me.renderTo);
270 me.initBackground(config.background);
273 // @private called to initialize components in the surface
274 // this is dependent on the underlying implementation.
275 initSurface: Ext.emptyFn,
277 // @private called to setup the surface to render an item
278 //this is dependent on the underlying implementation.
279 renderItem: Ext.emptyFn,
282 renderItems: Ext.emptyFn,
285 setViewBox: Ext.emptyFn,
288 * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
292 * drawComponent.surface.addCls(sprite, 'x-visible');
294 * @param {Object} sprite The sprite to add the class to.
295 * @param {String/[String]} className The CSS class to add, or an array of classes
301 * Removes one or more CSS classes from the element.
305 * drawComponent.surface.removeCls(sprite, 'x-visible');
307 * @param {Object} sprite The sprite to remove the class from.
308 * @param {String/[String]} className The CSS class to remove, or an array of classes
311 removeCls: Ext.emptyFn,
314 * Sets CSS style attributes to an element.
318 * drawComponent.surface.setStyle(sprite, {
319 * 'cursor': 'pointer'
322 * @param {Object} sprite The sprite to add, or an array of classes to
323 * @param {Object} styles An Object with CSS styles.
326 setStyle: Ext.emptyFn,
329 initGradients: function() {
330 var gradients = this.gradients;
332 Ext.each(gradients, this.addGradient, this);
337 initItems: function() {
338 var items = this.items;
339 this.items = Ext.create('Ext.draw.CompositeSprite');
340 this.groups = Ext.create('Ext.draw.CompositeSprite');
347 initBackground: function(config) {
351 gradientId, gradient, backgroundSprite;
353 if (config.gradient) {
354 gradient = config.gradient;
355 gradientId = gradient.id;
356 me.addGradient(gradient);
357 me.background = me.add({
363 fill: 'url(#' + gradientId + ')'
365 } else if (config.fill) {
366 me.background = me.add({
374 } else if (config.image) {
375 me.background = me.add({
388 * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
392 * drawComponent.surface.setSize(500, 500);
394 * This method is generally called when also setting the size of the draw Component.
396 * @param {Number} w The new width of the canvas.
397 * @param {Number} h The new height of the canvas.
399 setSize: function(w, h) {
400 if (this.background) {
401 this.background.setAttributes({
410 scrubAttrs: function(sprite) {
416 // Narrow down attributes to the main set
417 if (this.translateAttrs.hasOwnProperty(i)) {
419 attrs[this.translateAttrs[i]] = sattr[i];
420 exclude[this.translateAttrs[i]] = true;
422 else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
431 onClick: function(e) {
432 this.processEvent('click', e);
436 onMouseUp: function(e) {
437 this.processEvent('mouseup', e);
441 onMouseDown: function(e) {
442 this.processEvent('mousedown', e);
446 onMouseOver: function(e) {
447 this.processEvent('mouseover', e);
451 onMouseOut: function(e) {
452 this.processEvent('mouseout', e);
456 onMouseMove: function(e) {
457 this.fireEvent('mousemove', e);
461 onMouseEnter: Ext.emptyFn,
464 onMouseLeave: Ext.emptyFn,
467 * Adds a gradient definition to the Surface. Note that in some surface engines, adding
468 * a gradient via this method will not take effect if the surface has already been rendered.
469 * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
470 * than calling this method, especially if the surface is rendered immediately (e.g. due to
471 * 'renderTo' in its config). For more information on how to create gradients in the Chart
472 * configuration object please refer to {@link Ext.chart.Chart}.
474 * The gradient object to be passed into this method is composed by:
476 * - **id** - string - The unique name of the gradient.
477 * - **angle** - number, optional - The angle of the gradient in degrees.
478 * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
482 * drawComponent.surface.addGradient({
497 addGradient: Ext.emptyFn,
500 * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
501 * passed into this method.
505 * drawComponent.surface.add({
515 var args = Array.prototype.slice.call(arguments),
519 var hasMultipleArgs = args.length > 1;
520 if (hasMultipleArgs || Ext.isArray(args[0])) {
521 var items = hasMultipleArgs ? args : args[0],
525 for (i = 0, ln = items.length; i < ln; i++) {
527 item = this.add(item);
533 sprite = this.prepareItems(args[0], true)[0];
534 this.normalizeSpriteCollection(sprite);
541 * Inserts or moves a given sprite into the correct position in the items
542 * MixedCollection, according to its zIndex. Will be inserted at the end of
543 * an existing series of sprites with the same or lower zIndex. If the sprite
544 * is already positioned within an appropriate zIndex group, it will not be moved.
545 * This ordering can be used by subclasses to assist in rendering the sprites in
546 * the correct order for proper z-index stacking.
547 * @param {Ext.draw.Sprite} sprite
548 * @return {Number} the sprite's new index in the list
550 normalizeSpriteCollection: function(sprite) {
551 var items = this.items,
552 zIndex = sprite.attr.zIndex,
553 idx = items.indexOf(sprite);
555 if (idx < 0 || (idx > 0 && items.getAt(idx - 1).attr.zIndex > zIndex) ||
556 (idx < items.length - 1 && items.getAt(idx + 1).attr.zIndex < zIndex)) {
558 idx = items.findIndexBy(function(otherSprite) {
559 return otherSprite.attr.zIndex > zIndex;
564 items.insert(idx, sprite);
569 onAdd: function(sprite) {
570 var group = sprite.group,
571 draggable = sprite.draggable,
574 groups = [].concat(group);
576 for (i = 0; i < ln; i++) {
578 this.getGroup(group).add(sprite);
583 sprite.initDraggable();
588 * Removes a given sprite from the surface, optionally destroying the sprite in the process.
589 * You can also call the sprite own `remove` method.
593 * drawComponent.surface.remove(sprite);
597 * @param {Ext.draw.Sprite} sprite
598 * @param {Boolean} destroySprite
599 * @return {Number} the sprite's new index in the list
601 remove: function(sprite, destroySprite) {
603 this.items.remove(sprite);
604 this.groups.each(function(item) {
608 if (destroySprite === true) {
615 * Removes all sprites from the surface, optionally destroying the sprites in the process.
619 * drawComponent.surface.removeAll();
621 * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
622 * @return {Number} The sprite's new index in the list.
624 removeAll: function(destroySprites) {
625 var items = this.items.items,
628 for (i = ln - 1; i > -1; i--) {
629 this.remove(items[i], destroySprites);
633 onRemove: Ext.emptyFn,
635 onDestroy: Ext.emptyFn,
638 applyTransformations: function(sprite) {
639 sprite.bbox.transform = 0;
640 this.transform(sprite);
646 if (attr.translation.x != null || attr.translation.y != null) {
647 me.translate(sprite);
650 if (attr.scaling.x != null || attr.scaling.y != null) {
654 if (attr.rotation.degrees != null) {
659 sprite.bbox.transform = 0;
660 this.transform(sprite);
661 sprite.transformations = [];
666 rotate: function (sprite) {
668 deg = sprite.attr.rotation.degrees,
669 centerX = sprite.attr.rotation.x,
670 centerY = sprite.attr.rotation.y;
671 if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
672 bbox = this.getBBox(sprite);
673 centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
674 centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
676 sprite.transformations.push({
685 translate: function(sprite) {
686 var x = sprite.attr.translation.x || 0,
687 y = sprite.attr.translation.y || 0;
688 sprite.transformations.push({
696 scale: function(sprite) {
698 x = sprite.attr.scaling.x || 1,
699 y = sprite.attr.scaling.y || 1,
700 centerX = sprite.attr.scaling.centerX,
701 centerY = sprite.attr.scaling.centerY;
703 if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
704 bbox = this.getBBox(sprite);
705 centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
706 centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
708 sprite.transformations.push({
718 rectPath: function (x, y, w, h, r) {
720 return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
722 return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
726 ellipsePath: function (x, y, rx, ry) {
730 return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
734 getPathpath: function (el) {
739 getPathcircle: function (el) {
741 return this.ellipsePath(a.x, a.y, a.radius, a.radius);
745 getPathellipse: function (el) {
747 return this.ellipsePath(a.x, a.y,
748 a.radiusX || (a.width / 2) || 0,
749 a.radiusY || (a.height / 2) || 0);
753 getPathrect: function (el) {
755 return this.rectPath(a.x, a.y, a.width, a.height, a.r);
759 getPathimage: function (el) {
761 return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
765 getPathtext: function (el) {
766 var bbox = this.getBBoxText(el);
767 return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
770 createGroup: function(id) {
771 var group = this.groups.get(id);
773 group = Ext.create('Ext.draw.CompositeSprite', {
776 group.id = id || Ext.id(null, 'ext-surface-group-');
777 this.groups.add(group);
783 * Returns a new group or an existent group associated with the current surface.
784 * The group returned is a {@link Ext.draw.CompositeSprite} group.
788 * var spriteGroup = drawComponent.surface.getGroup('someGroupId');
790 * @param {String} id The unique identifier of the group.
791 * @return {Object} The {@link Ext.draw.CompositeSprite}.
793 getGroup: function(id) {
794 if (typeof id == "string") {
795 var group = this.groups.get(id);
797 group = this.createGroup(id);
806 prepareItems: function(items, applyDefaults) {
807 items = [].concat(items);
808 // Make sure defaults are applied and item is initialized
810 for (i = 0, ln = items.length; i < ln; i++) {
812 if (!(item instanceof Ext.draw.Sprite)) {
813 // Temporary, just take in configs...
815 items[i] = this.createItem(item);
824 * Changes the text in the sprite element. The sprite must be a `text` sprite.
825 * This method can also be called from {@link Ext.draw.Sprite}.
829 * var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
831 * @param {Object} sprite The Sprite to change the text.
832 * @param {String} text The new text to be set.
835 setText: Ext.emptyFn,
837 //@private Creates an item and appends it to the surface. Called
838 //as an internal method when calling `add`.
839 createItem: Ext.emptyFn,
842 * Retrieves the id of this component.
843 * Will autogenerate an id if one has not already been set.
846 return this.id || (this.id = Ext.id(null, 'ext-surface-'));
850 * Destroys the surface. This is done by removing all components from it and
851 * also removing its reference to a DOM element.
855 * drawComponent.surface.destroy();
857 destroy: function() {