Path: blob/trunk/third_party/closure/goog/graphics/svggraphics.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.131415/**16* @fileoverview SvgGraphics sub class that uses SVG to draw the graphics.17* @author [email protected] (Erik Arvidsson)18*/1920goog.provide('goog.graphics.SvgGraphics');2122goog.require('goog.Timer');23goog.require('goog.dom');24goog.require('goog.events.EventHandler');25goog.require('goog.events.EventType');26goog.require('goog.graphics.AbstractGraphics');27goog.require('goog.graphics.Font');28goog.require('goog.graphics.LinearGradient');29goog.require('goog.graphics.Path');30goog.require('goog.graphics.SolidFill');31goog.require('goog.graphics.Stroke');32goog.require('goog.graphics.SvgEllipseElement');33goog.require('goog.graphics.SvgGroupElement');34goog.require('goog.graphics.SvgImageElement');35goog.require('goog.graphics.SvgPathElement');36goog.require('goog.graphics.SvgRectElement');37goog.require('goog.graphics.SvgTextElement');38goog.require('goog.math');39goog.require('goog.math.Size');40goog.require('goog.style');41goog.require('goog.userAgent');42434445/**46* A Graphics implementation for drawing using SVG.47* @param {string|number} width The width in pixels. Strings48* expressing percentages of parent with (e.g. '80%') are also accepted.49* @param {string|number} height The height in pixels. Strings50* expressing percentages of parent with (e.g. '80%') are also accepted.51* @param {?number=} opt_coordWidth The coordinate width - if52* omitted or null, defaults to same as width.53* @param {?number=} opt_coordHeight The coordinate height - if54* omitted or null, defaults to same as height.55* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the56* document we want to render in.57* @constructor58* @extends {goog.graphics.AbstractGraphics}59* @deprecated goog.graphics is deprecated. It existed to abstract over browser60* differences before the canvas tag was widely supported. See61* http://en.wikipedia.org/wiki/Canvas_element for details.62* @final63*/64goog.graphics.SvgGraphics = function(65width, height, opt_coordWidth, opt_coordHeight, opt_domHelper) {66goog.graphics.AbstractGraphics.call(67this, width, height, opt_coordWidth, opt_coordHeight, opt_domHelper);6869/**70* Map from def key to id of def root element.71* Defs are global "defines" of svg that are used to share common attributes,72* for example gradients.73* @type {Object}74* @private75*/76this.defs_ = {};7778/**79* Whether to manually implement viewBox by using a coordinate transform.80* As of 1/11/08 this is necessary for Safari 3 but not for the nightly81* WebKit build. Apply to webkit versions < 526. 525 is the82* last version used by Safari 3.1.83* @type {boolean}84* @private85*/86this.useManualViewbox_ =87goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher(526);8889/**90* Event handler.91* @type {goog.events.EventHandler<!goog.graphics.SvgGraphics>}92* @private93*/94this.handler_ = new goog.events.EventHandler(this);95};96goog.inherits(goog.graphics.SvgGraphics, goog.graphics.AbstractGraphics);979899/**100* The SVG namespace URN101* @private102* @type {string}103*/104goog.graphics.SvgGraphics.SVG_NS_ = 'http://www.w3.org/2000/svg';105106107/**108* The name prefix for def entries109* @private110* @type {string}111*/112goog.graphics.SvgGraphics.DEF_ID_PREFIX_ = '_svgdef_';113114115/**116* The next available unique identifier for a def entry.117* This is a static variable, so that when multiple graphics are used in one118* document, the same def id can not be re-defined by another SvgGraphics.119* @type {number}120* @private121*/122goog.graphics.SvgGraphics.nextDefId_ = 0;123124125/**126* Svg element for definitions for other elements, e.g. linear gradients.127* @type {Element}128* @private129*/130goog.graphics.SvgGraphics.prototype.defsElement_;131132133/**134* Creates an SVG element. Used internally and by different SVG classes.135* @param {string} tagName The type of element to create.136* @param {Object=} opt_attributes Map of name-value pairs for attributes.137* @return {!Element} The created element.138* @private139*/140goog.graphics.SvgGraphics.prototype.createSvgElement_ = function(141tagName, opt_attributes) {142var element = this.dom_.getDocument().createElementNS(143goog.graphics.SvgGraphics.SVG_NS_, tagName);144145if (opt_attributes) {146this.setElementAttributes(element, opt_attributes);147}148149return element;150};151152153/**154* Sets properties to an SVG element. Used internally and by different155* SVG elements.156* @param {Element} element The svg element.157* @param {Object} attributes Map of name-value pairs for attributes.158*/159goog.graphics.SvgGraphics.prototype.setElementAttributes = function(160element, attributes) {161for (var key in attributes) {162element.setAttribute(key, attributes[key]);163}164};165166167/**168* Appends an element.169*170* @param {goog.graphics.Element} element The element wrapper.171* @param {goog.graphics.GroupElement=} opt_group The group wrapper element172* to append to. If not specified, appends to the main canvas.173* @private174*/175goog.graphics.SvgGraphics.prototype.append_ = function(element, opt_group) {176var parent = opt_group || this.canvasElement;177parent.getElement().appendChild(element.getElement());178};179180181/**182* Sets the fill of the given element.183* @param {goog.graphics.StrokeAndFillElement} element The element wrapper.184* @param {goog.graphics.Fill?} fill The fill object.185* @override186*/187goog.graphics.SvgGraphics.prototype.setElementFill = function(element, fill) {188var svgElement = element.getElement();189if (fill instanceof goog.graphics.SolidFill) {190svgElement.setAttribute('fill', fill.getColor());191svgElement.setAttribute('fill-opacity', fill.getOpacity());192} else if (fill instanceof goog.graphics.LinearGradient) {193// create a def key which is just a concat of all the relevant fields194var defKey = 'lg-' + fill.getX1() + '-' + fill.getY1() + '-' +195fill.getX2() + '-' + fill.getY2() + '-' + fill.getColor1() + '-' +196fill.getColor2();197// It seems that the SVG version accepts opacity where the VML does not198199var id = this.getDef(defKey);200201if (!id) { // No def for this yet, create it202// Create the gradient def entry (only linear gradient are supported)203var gradient = this.createSvgElement_('linearGradient', {204'x1': fill.getX1(),205'y1': fill.getY1(),206'x2': fill.getX2(),207'y2': fill.getY2(),208'gradientUnits': 'userSpaceOnUse'209});210211var gstyle = 'stop-color:' + fill.getColor1();212if (goog.isNumber(fill.getOpacity1())) {213gstyle += ';stop-opacity:' + fill.getOpacity1();214}215var stop1 =216this.createSvgElement_('stop', {'offset': '0%', 'style': gstyle});217gradient.appendChild(stop1);218219// LinearGradients don't have opacity in VML so implement that before220// enabling the following code.221// if (fill.getOpacity() != null) {222// gstyles += 'opacity:' + fill.getOpacity() + ';'223// }224gstyle = 'stop-color:' + fill.getColor2();225if (goog.isNumber(fill.getOpacity2())) {226gstyle += ';stop-opacity:' + fill.getOpacity2();227}228var stop2 =229this.createSvgElement_('stop', {'offset': '100%', 'style': gstyle});230gradient.appendChild(stop2);231232// LinearGradients don't have opacity in VML so implement that before233// enabling the following code.234// if (fill.getOpacity() != null) {235// gstyles += 'opacity:' + fill.getOpacity() + ';'236// }237238id = this.addDef(defKey, gradient);239}240241// Link element to linearGradient definition242svgElement.setAttribute('fill', 'url(#' + id + ')');243} else {244svgElement.setAttribute('fill', 'none');245}246};247248249/**250* Sets the stroke of the given element.251* @param {goog.graphics.StrokeAndFillElement} element The element wrapper.252* @param {goog.graphics.Stroke?} stroke The stroke object.253* @override254*/255goog.graphics.SvgGraphics.prototype.setElementStroke = function(256element, stroke) {257var svgElement = element.getElement();258if (stroke) {259svgElement.setAttribute('stroke', stroke.getColor());260svgElement.setAttribute('stroke-opacity', stroke.getOpacity());261262var width = stroke.getWidth();263if (goog.isString(width) && width.indexOf('px') != -1) {264svgElement.setAttribute(265'stroke-width', parseFloat(width) / this.getPixelScaleX());266} else {267svgElement.setAttribute('stroke-width', width);268}269} else {270svgElement.setAttribute('stroke', 'none');271}272};273274275/**276* Set the translation and rotation of an element.277*278* If a more general affine transform is needed than this provides279* (e.g. skew and scale) then use setElementAffineTransform.280* @param {goog.graphics.Element} element The element wrapper.281* @param {number} x The x coordinate of the translation transform.282* @param {number} y The y coordinate of the translation transform.283* @param {number} angle The angle of the rotation transform.284* @param {number} centerX The horizontal center of the rotation transform.285* @param {number} centerY The vertical center of the rotation transform.286* @override287*/288goog.graphics.SvgGraphics.prototype.setElementTransform = function(289element, x, y, angle, centerX, centerY) {290element.getElement().setAttribute(291'transform', 'translate(' + x + ',' + y + ') rotate(' + angle + ' ' +292centerX + ' ' + centerY + ')');293};294295296/**297* Set the transformation of an element.298* @param {goog.graphics.Element} element The element wrapper.299* @param {!goog.graphics.AffineTransform} affineTransform The300* transformation applied to this element.301* @override302*/303goog.graphics.SvgGraphics.prototype.setElementAffineTransform = function(304element, affineTransform) {305var t = affineTransform;306var substr = [307t.getScaleX(), t.getShearY(), t.getShearX(), t.getScaleY(),308t.getTranslateX(), t.getTranslateY()309].join(',');310element.getElement().setAttribute('transform', 'matrix(' + substr + ')');311};312313314/**315* Creates the DOM representation of the graphics area.316* @override317*/318goog.graphics.SvgGraphics.prototype.createDom = function() {319// Set up the standard attributes.320var attributes =321{'width': this.width, 'height': this.height, 'overflow': 'hidden'};322323var svgElement = this.createSvgElement_('svg', attributes);324325var groupElement = this.createSvgElement_('g');326327this.defsElement_ = this.createSvgElement_('defs');328this.canvasElement = new goog.graphics.SvgGroupElement(groupElement, this);329330svgElement.appendChild(this.defsElement_);331svgElement.appendChild(groupElement);332333// Use the svgElement as the root element.334this.setElementInternal(svgElement);335336// Set up the coordinate system.337this.setViewBox_();338};339340341/**342* Changes the coordinate system position.343* @param {number} left The coordinate system left bound.344* @param {number} top The coordinate system top bound.345* @override346*/347goog.graphics.SvgGraphics.prototype.setCoordOrigin = function(left, top) {348this.coordLeft = left;349this.coordTop = top;350351this.setViewBox_();352};353354355/**356* Changes the coordinate size.357* @param {number} coordWidth The coordinate width.358* @param {number} coordHeight The coordinate height.359* @override360*/361goog.graphics.SvgGraphics.prototype.setCoordSize = function(362coordWidth, coordHeight) {363goog.graphics.SvgGraphics.superClass_.setCoordSize.apply(this, arguments);364this.setViewBox_();365};366367368/**369* @return {string} The view box string.370* @private371*/372goog.graphics.SvgGraphics.prototype.getViewBox_ = function() {373return this.coordLeft + ' ' + this.coordTop + ' ' +374(this.coordWidth ? this.coordWidth + ' ' + this.coordHeight : '');375};376377378/**379* Sets up the view box.380* @private381*/382goog.graphics.SvgGraphics.prototype.setViewBox_ = function() {383if (this.coordWidth || this.coordLeft || this.coordTop) {384this.getElement().setAttribute('preserveAspectRatio', 'none');385if (this.useManualViewbox_) {386this.updateManualViewBox_();387} else {388this.getElement().setAttribute('viewBox', this.getViewBox_());389}390}391};392393394/**395* Updates the transform of the root element to fake a viewBox. Should only396* be called when useManualViewbox_ is set.397* @private398*/399goog.graphics.SvgGraphics.prototype.updateManualViewBox_ = function() {400if (!this.isInDocument() ||401!(this.coordWidth || this.coordLeft || !this.coordTop)) {402return;403}404405var size = this.getPixelSize();406if (size.width == 0) {407// In Safari, invisible SVG is sometimes shown. Explicitly hide it.408this.getElement().style.visibility = 'hidden';409return;410}411412this.getElement().style.visibility = '';413414var offsetX = -this.coordLeft;415var offsetY = -this.coordTop;416var scaleX = size.width / this.coordWidth;417var scaleY = size.height / this.coordHeight;418419this.canvasElement.getElement().setAttribute(420'transform', 'scale(' + scaleX + ' ' + scaleY + ') ' +421'translate(' + offsetX + ' ' + offsetY + ')');422};423424425/**426* Change the size of the canvas.427* @param {number} pixelWidth The width in pixels.428* @param {number} pixelHeight The height in pixels.429* @override430*/431goog.graphics.SvgGraphics.prototype.setSize = function(432pixelWidth, pixelHeight) {433goog.style.setSize(this.getElement(), pixelWidth, pixelHeight);434};435436437/** @override */438goog.graphics.SvgGraphics.prototype.getPixelSize = function() {439if (!goog.userAgent.GECKO) {440return this.isInDocument() ?441goog.style.getSize(this.getElement()) :442goog.graphics.SvgGraphics.base(this, 'getPixelSize');443}444445// In Gecko, goog.style.getSize does not work for SVG elements. We have to446// compute the size manually if it is percentage based.447var width = this.width;448var height = this.height;449var computeWidth = goog.isString(width) && width.indexOf('%') != -1;450var computeHeight = goog.isString(height) && height.indexOf('%') != -1;451452if (!this.isInDocument() && (computeWidth || computeHeight)) {453return null;454}455456var parent;457var parentSize;458459if (computeWidth) {460parent = /** @type {Element} */ (this.getElement().parentNode);461parentSize = goog.style.getSize(parent);462width = parseFloat(/** @type {string} */ (width)) * parentSize.width / 100;463}464465if (computeHeight) {466parent = parent || /** @type {Element} */ (this.getElement().parentNode);467parentSize = parentSize || goog.style.getSize(parent);468height =469parseFloat(/** @type {string} */ (height)) * parentSize.height / 100;470}471472return new goog.math.Size(473/** @type {number} */ (width),474/** @type {number} */ (height));475};476477478/**479* Remove all drawing elements from the graphics.480* @override481*/482goog.graphics.SvgGraphics.prototype.clear = function() {483this.canvasElement.clear();484goog.dom.removeChildren(this.defsElement_);485this.defs_ = {};486};487488489/**490* Draw an ellipse.491*492* @param {number} cx Center X coordinate.493* @param {number} cy Center Y coordinate.494* @param {number} rx Radius length for the x-axis.495* @param {number} ry Radius length for the y-axis.496* @param {goog.graphics.Stroke?} stroke Stroke object describing the497* stroke.498* @param {goog.graphics.Fill?} fill Fill object describing the fill.499* @param {goog.graphics.GroupElement=} opt_group The group wrapper element500* to append to. If not specified, appends to the main canvas.501*502* @return {!goog.graphics.EllipseElement} The newly created element.503* @override504*/505goog.graphics.SvgGraphics.prototype.drawEllipse = function(506cx, cy, rx, ry, stroke, fill, opt_group) {507var element = this.createSvgElement_(508'ellipse', {'cx': cx, 'cy': cy, 'rx': rx, 'ry': ry});509var wrapper =510new goog.graphics.SvgEllipseElement(element, this, stroke, fill);511this.append_(wrapper, opt_group);512return wrapper;513};514515516/**517* Draw a rectangle.518*519* @param {number} x X coordinate (left).520* @param {number} y Y coordinate (top).521* @param {number} width Width of rectangle.522* @param {number} height Height of rectangle.523* @param {goog.graphics.Stroke?} stroke Stroke object describing the524* stroke.525* @param {goog.graphics.Fill?} fill Fill object describing the fill.526* @param {goog.graphics.GroupElement=} opt_group The group wrapper element527* to append to. If not specified, appends to the main canvas.528*529* @return {!goog.graphics.RectElement} The newly created element.530* @override531*/532goog.graphics.SvgGraphics.prototype.drawRect = function(533x, y, width, height, stroke, fill, opt_group) {534var element = this.createSvgElement_(535'rect', {'x': x, 'y': y, 'width': width, 'height': height});536var wrapper = new goog.graphics.SvgRectElement(element, this, stroke, fill);537this.append_(wrapper, opt_group);538return wrapper;539};540541542/**543* Draw an image.544*545* @param {number} x X coordinate (left).546* @param {number} y Y coordinate (top).547* @param {number} width Width of the image.548* @param {number} height Height of the image.549* @param {string} src The source fo the image.550* @param {goog.graphics.GroupElement=} opt_group The group wrapper element551* to append to. If not specified, appends to the main canvas.552*553* @return {!goog.graphics.ImageElement} The newly created image wrapped in a554* rectangle element.555*/556goog.graphics.SvgGraphics.prototype.drawImage = function(557x, y, width, height, src, opt_group) {558var element = this.createSvgElement_('image', {559'x': x,560'y': y,561'width': width,562'height': height,563'image-rendering': 'optimizeQuality',564'preserveAspectRatio': 'none'565});566element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src);567var wrapper = new goog.graphics.SvgImageElement(element, this);568this.append_(wrapper, opt_group);569return wrapper;570};571572573/**574* Draw a text string vertically centered on a given line.575*576* @param {string} text The text to draw.577* @param {number} x1 X coordinate of start of line.578* @param {number} y1 Y coordinate of start of line.579* @param {number} x2 X coordinate of end of line.580* @param {number} y2 Y coordinate of end of line.581* @param {string} align Horizontal alignment: left (default), center, right.582* @param {goog.graphics.Font} font Font describing the font properties.583* @param {goog.graphics.Stroke?} stroke Stroke object describing the584* stroke.585* @param {goog.graphics.Fill?} fill Fill object describing the fill.586* @param {goog.graphics.GroupElement=} opt_group The group wrapper element587* to append to. If not specified, appends to the main canvas.588*589* @return {!goog.graphics.TextElement} The newly created element.590* @override591*/592goog.graphics.SvgGraphics.prototype.drawTextOnLine = function(593text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {594var angle = Math.round(goog.math.angle(x1, y1, x2, y2));595var dx = x2 - x1;596var dy = y2 - y1;597var lineLength = Math.round(Math.sqrt(dx * dx + dy * dy)); // Length of line598599// SVG baseline is on the glyph's base line. We estimate it as 85% of the600// font height. This is just a rough estimate, but do not have a better way.601var fontSize = font.size;602var attributes = {'font-family': font.family, 'font-size': fontSize};603var baseline = Math.round(fontSize * 0.85);604var textY = Math.round(y1 - (fontSize / 2) + baseline);605var textX = x1;606if (align == 'center') {607textX += Math.round(lineLength / 2);608attributes['text-anchor'] = 'middle';609} else if (align == 'right') {610textX += lineLength;611attributes['text-anchor'] = 'end';612}613attributes['x'] = textX;614attributes['y'] = textY;615if (font.bold) {616attributes['font-weight'] = 'bold';617}618if (font.italic) {619attributes['font-style'] = 'italic';620}621if (angle != 0) {622attributes['transform'] = 'rotate(' + angle + ' ' + x1 + ' ' + y1 + ')';623}624625var element = this.createSvgElement_('text', attributes);626element.appendChild(this.dom_.getDocument().createTextNode(text));627628// Bypass a Firefox-Mac bug where text fill is ignored. If text has no stroke,629// set a stroke, otherwise the text will not be visible.630if (stroke == null && goog.userAgent.GECKO && goog.userAgent.MAC) {631var color = 'black';632// For solid fills, use the fill color633if (fill instanceof goog.graphics.SolidFill) {634color = fill.getColor();635}636stroke = new goog.graphics.Stroke(1, color);637}638639var wrapper = new goog.graphics.SvgTextElement(element, this, stroke, fill);640this.append_(wrapper, opt_group);641return wrapper;642};643644645/**646* Draw a path.647*648* @param {!goog.graphics.Path} path The path object to draw.649* @param {goog.graphics.Stroke?} stroke Stroke object describing the650* stroke.651* @param {goog.graphics.Fill?} fill Fill object describing the fill.652* @param {goog.graphics.GroupElement=} opt_group The group wrapper element653* to append to. If not specified, appends to the main canvas.654*655* @return {!goog.graphics.PathElement} The newly created element.656* @override657*/658goog.graphics.SvgGraphics.prototype.drawPath = function(659path, stroke, fill, opt_group) {660661var element = this.createSvgElement_(662'path', {'d': goog.graphics.SvgGraphics.getSvgPath(path)});663var wrapper = new goog.graphics.SvgPathElement(element, this, stroke, fill);664this.append_(wrapper, opt_group);665return wrapper;666};667668669/**670* Returns a string representation of a logical path suitable for use in671* an SVG element.672*673* @param {goog.graphics.Path} path The logical path.674* @return {string} The SVG path representation.675* @suppress {deprecated} goog.graphics is deprecated.676*/677goog.graphics.SvgGraphics.getSvgPath = function(path) {678var list = [];679path.forEachSegment(function(segment, args) {680switch (segment) {681case goog.graphics.Path.Segment.MOVETO:682list.push('M');683Array.prototype.push.apply(list, args);684break;685case goog.graphics.Path.Segment.LINETO:686list.push('L');687Array.prototype.push.apply(list, args);688break;689case goog.graphics.Path.Segment.CURVETO:690list.push('C');691Array.prototype.push.apply(list, args);692break;693case goog.graphics.Path.Segment.ARCTO:694var extent = args[3];695list.push(696'A', args[0], args[1], 0, Math.abs(extent) > 180 ? 1 : 0,697extent > 0 ? 1 : 0, args[4], args[5]);698break;699case goog.graphics.Path.Segment.CLOSE:700list.push('Z');701break;702}703});704return list.join(' ');705};706707708/**709* Create an empty group of drawing elements.710*711* @param {goog.graphics.GroupElement=} opt_group The group wrapper element712* to append to. If not specified, appends to the main canvas.713*714* @return {!goog.graphics.GroupElement} The newly created group.715* @override716*/717goog.graphics.SvgGraphics.prototype.createGroup = function(opt_group) {718var element = this.createSvgElement_('g');719var parent = opt_group || this.canvasElement;720parent.getElement().appendChild(element);721return new goog.graphics.SvgGroupElement(element, this);722};723724725/**726* Measure and return the width (in pixels) of a given text string.727* Text measurement is needed to make sure a text can fit in the allocated area.728* The way text length is measured is by writing it into a div that is after729* the visible area, measure the div width, and immediately erase the written730* value.731*732* @override733*/734goog.graphics.SvgGraphics.prototype.getTextWidth = function(text, font) {735// TODO(user) Implement736throw new Error("unimplemented method");737};738739740/**741* Adds a definition of an element to the global definitions.742* @param {string} defKey This is a key that should be unique in a way that743* if two definitions are equal the should have the same key.744* @param {Element} defElement DOM element to add as a definition. It must745* have an id attribute set.746* @return {string} The assigned id of the defElement.747*/748goog.graphics.SvgGraphics.prototype.addDef = function(defKey, defElement) {749if (defKey in this.defs_) {750return this.defs_[defKey];751}752var id = goog.graphics.SvgGraphics.DEF_ID_PREFIX_ +753goog.graphics.SvgGraphics.nextDefId_++;754defElement.setAttribute('id', id);755this.defs_[defKey] = id;756757// Add the def defElement of the defs list.758var defs = this.defsElement_;759defs.appendChild(defElement);760return id;761};762763764/**765* Returns the id of a definition element.766* @param {string} defKey This is a key that should be unique in a way that767* if two definitions are equal the should have the same key.768* @return {?string} The id of the found definition element or null if769* not found.770*/771goog.graphics.SvgGraphics.prototype.getDef = function(defKey) {772return defKey in this.defs_ ? this.defs_[defKey] : null;773};774775776/**777* Removes a definition of an elemnt from the global definitions.778* @param {string} defKey This is a key that should be unique in a way that779* if two definitions are equal they should have the same key.780*/781goog.graphics.SvgGraphics.prototype.removeDef = function(defKey) {782var id = this.getDef(defKey);783if (id) {784var element = this.dom_.getElement(id);785this.defsElement_.removeChild(element);786delete this.defs_[defKey];787}788};789790791/** @override */792goog.graphics.SvgGraphics.prototype.enterDocument = function() {793var oldPixelSize = this.getPixelSize();794goog.graphics.SvgGraphics.superClass_.enterDocument.call(this);795796// Dispatch a resize if this is the first time the size value is accurate.797if (!oldPixelSize) {798this.dispatchEvent(goog.events.EventType.RESIZE);799}800801802// For percentage based heights, listen for changes to size.803if (this.useManualViewbox_) {804var width = this.width;805var height = this.height;806807if (typeof width == 'string' && width.indexOf('%') != -1 &&808typeof height == 'string' && height.indexOf('%') != -1) {809// SVG elements don't behave well with respect to size events, so we810// resort to polling.811this.handler_.listen(812goog.graphics.SvgGraphics.getResizeCheckTimer_(), goog.Timer.TICK,813this.updateManualViewBox_);814}815816this.updateManualViewBox_();817}818};819820821/** @override */822goog.graphics.SvgGraphics.prototype.exitDocument = function() {823goog.graphics.SvgGraphics.superClass_.exitDocument.call(this);824825// Stop polling.826if (this.useManualViewbox_) {827this.handler_.unlisten(828goog.graphics.SvgGraphics.getResizeCheckTimer_(), goog.Timer.TICK,829this.updateManualViewBox_);830}831};832833834/**835* Disposes of the component by removing event handlers, detacing DOM nodes from836* the document body, and removing references to them.837* @override838* @protected839*/840goog.graphics.SvgGraphics.prototype.disposeInternal = function() {841delete this.defs_;842delete this.defsElement_;843delete this.canvasElement;844this.handler_.dispose();845delete this.handler_;846goog.graphics.SvgGraphics.superClass_.disposeInternal.call(this);847};848849850/**851* The centralized resize checking timer.852* @type {goog.Timer|undefined}853* @private854*/855goog.graphics.SvgGraphics.resizeCheckTimer_;856857858/**859* @return {goog.Timer} The centralized timer object used for interval timing.860* @private861*/862goog.graphics.SvgGraphics.getResizeCheckTimer_ = function() {863if (!goog.graphics.SvgGraphics.resizeCheckTimer_) {864goog.graphics.SvgGraphics.resizeCheckTimer_ = new goog.Timer(400);865goog.graphics.SvgGraphics.resizeCheckTimer_.start();866}867868return /** @type {goog.Timer} */ (869goog.graphics.SvgGraphics.resizeCheckTimer_);870};871872873/** @override */874goog.graphics.SvgGraphics.prototype.isDomClonable = function() {875return true;876};877878879