1 /* http://keith-wood.name/svg.html
3 Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
4 Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
5 MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
6 Please attribute the author if you use it. */
8 (function($) { // Hide scope, no $ conflict
11 Use the singleton instance of this class, $.svg,
12 to interact with the SVG functionality. */
13 function SVGManager() {
14 this._settings = []; // Settings to be remembered per SVG object
15 this._extensions = []; // List of SVG extensions added to SVGWrapper
16 // for each entry [0] is extension name, [1] is extension class (function)
17 // the function takes one parameter - the SVGWrapper instance
18 this.regional = []; // Localisations, indexed by language, '' for default (English)
19 this.regional[''] = {errorLoadingText: 'Error loading',
20 notSupportedText: 'This browser does not support SVG'};
21 this.local = this.regional['']; // Current localisation
22 this._uuid = new Date().getTime();
23 this._renesis = detectActiveX('RenesisX.RenesisCtrl');
26 /* Determine whether a given ActiveX control is available.
27 @param classId (string) the ID for the ActiveX control
28 @return (boolean) true if found, false if not */
29 function detectActiveX(classId) {
31 return !!(window.ActiveXObject && new ActiveXObject(classId));
38 var PROP_NAME = 'svgwrapper';
40 $.extend(SVGManager.prototype, {
41 /* Class name added to elements to indicate already configured with SVG. */
42 markerClassName: 'hasSVG',
45 svgNS: 'http://www.w3.org/2000/svg',
46 /* XLink namespace. */
47 xlinkNS: 'http://www.w3.org/1999/xlink',
49 /* SVG wrapper class. */
50 _wrapperClass: SVGWrapper,
52 /* Camel-case versions of attribute names containing dashes or are reserved words. */
53 _attrNames: {class_: 'class', in_: 'in',
54 alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift',
55 clipPath: 'clip-path', clipRule: 'clip-rule',
56 colorInterpolation: 'color-interpolation',
57 colorInterpolationFilters: 'color-interpolation-filters',
58 colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline',
59 enableBackground: 'enable-background', fillOpacity: 'fill-opacity',
60 fillRule: 'fill-rule', floodColor: 'flood-color',
61 floodOpacity: 'flood-opacity', fontFamily: 'font-family',
62 fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust',
63 fontStretch: 'font-stretch', fontStyle: 'font-style',
64 fontVariant: 'font-variant', fontWeight: 'font-weight',
65 glyphOrientationHorizontal: 'glyph-orientation-horizontal',
66 glyphOrientationVertical: 'glyph-orientation-vertical',
67 horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x',
68 imageRendering: 'image-rendering', letterSpacing: 'letter-spacing',
69 lightingColor: 'lighting-color', markerEnd: 'marker-end',
70 markerMid: 'marker-mid', markerStart: 'marker-start',
71 stopColor: 'stop-color', stopOpacity: 'stop-opacity',
72 strikethroughPosition: 'strikethrough-position',
73 strikethroughThickness: 'strikethrough-thickness',
74 strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset',
75 strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin',
76 strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity',
77 strokeWidth: 'stroke-width', textAnchor: 'text-anchor',
78 textDecoration: 'text-decoration', textRendering: 'text-rendering',
79 underlinePosition: 'underline-position', underlineThickness: 'underline-thickness',
80 vertAdvY: 'vert-adv-y', vertOriginY: 'vert-origin-y',
81 wordSpacing: 'word-spacing', writingMode: 'writing-mode'},
83 /* Add the SVG object to its container. */
84 _attachSVG: function(container, settings) {
85 var svg = (container.namespaceURI == this.svgNS ? container : null);
86 var container = (svg ? null : container);
87 if ($(container || svg).hasClass(this.markerClassName)) {
90 if (typeof settings == 'string') {
91 settings = {loadURL: settings};
93 else if (typeof settings == 'function') {
94 settings = {onLoad: settings};
96 $(container || svg).addClass(this.markerClassName);
99 svg = document.createElementNS(this.svgNS, 'svg');
100 svg.setAttribute('version', '1.1');
101 svg.setAttribute('width', container.clientWidth);
102 svg.setAttribute('height', container.clientHeight);
103 container.appendChild(svg);
105 this._afterLoad(container, svg, settings || {});
108 if ($.browser.msie) {
110 container.id = 'svg' + (this._uuid++);
112 this._settings[container.id] = settings;
113 container.innerHTML = '<embed type="image/svg+xml" width="100%" ' +
114 'height="100%" src="' + (settings.initPath || '') + 'blank.svg"/>';
117 container.innerHTML = '<p class="svg_error">' +
118 this.local.notSupportedText + '</p>';
123 /* SVG callback after loading - register SVG root. */
124 _registerSVG: function() {
125 for (var i = 0; i < document.embeds.length; i++) { // Check all
126 var container = document.embeds[i].parentNode;
127 if (!$(container).hasClass($.svg.markerClassName) || // Not SVG
128 $.data(container, PROP_NAME)) { // Already done
133 svg = document.embeds[i].getSVGDocument();
136 setTimeout($.svg._registerSVG, 250); // Renesis takes longer to load
139 svg = (svg ? svg.documentElement : null);
141 $.svg._afterLoad(container, svg);
146 /* Post-processing once loaded. */
147 _afterLoad: function(container, svg, settings) {
148 var settings = settings || this._settings[container.id];
149 this._settings[container ? container.id : ''] = null;
150 var wrapper = new this._wrapperClass(svg, container);
151 $.data(container || svg, PROP_NAME, wrapper);
153 if (settings.loadURL) { // Load URL
154 wrapper.load(settings.loadURL, settings);
156 if (settings.settings) { // Additional settings
157 wrapper.configure(settings.settings);
159 if (settings.onLoad && !settings.loadURL) { // Onload callback
160 settings.onLoad.apply(container || svg, [wrapper]);
168 /* Return the SVG wrapper created for a given container.
169 @param container (string) selector for the container or
170 (element) the container for the SVG object or
171 jQuery collection - first entry is the container
172 @return (SVGWrapper) the corresponding SVG wrapper element, or null if not attached */
173 _getSVG: function(container) {
174 container = (typeof container == 'string' ? $(container)[0] :
175 (container.jquery ? container[0] : container));
176 return $.data(container, PROP_NAME);
179 /* Remove the SVG functionality from a div.
180 @param container (element) the container for the SVG object */
181 _destroySVG: function(container) {
182 var $container = $(container);
183 if (!$container.hasClass(this.markerClassName)) {
186 $container.removeClass(this.markerClassName);
187 if (container.namespaceURI != this.svgNS) {
190 $.removeData(container, PROP_NAME);
193 /* Extend the SVGWrapper object with an embedded class.
194 The constructor function must take a single parameter that is
195 a reference to the owning SVG root object. This allows the
196 extension to access the basic SVG functionality.
197 @param name (string) the name of the SVGWrapper attribute to access the new class
198 @param extClass (function) the extension class constructor */
199 addExtension: function(name, extClass) {
200 this._extensions.push([name, extClass]);
204 /* The main SVG interface, which encapsulates the SVG element.
205 Obtain a reference from $().svg('get') */
206 function SVGWrapper(svg, container) {
207 this._svg = svg; // The SVG root node
208 this._container = container; // The containing div
209 for (var i = 0; i < $.svg._extensions.length; i++) {
210 var extension = $.svg._extensions[i];
211 this[extension[0]] = new extension[1](this);
215 $.extend(SVGWrapper.prototype, {
217 /* Retrieve the width of the SVG object. */
219 return (this._container ? this._container.clientWidth : this._svg.width);
222 /* Retrieve the height of the SVG object. */
223 _height: function() {
224 return (this._container ? this._container.clientHeight : this._svg.height);
227 /* Retrieve the root SVG element.
228 @return the top-level SVG element */
233 /* Configure the SVG root.
234 @param settings (object) additional settings for the root
235 @param clear (boolean) true to remove existing attributes first,
236 false to add to what is already there (optional)
237 @return (SVGWrapper) this root */
238 configure: function(settings, clear) {
240 for (var i = this._svg.attributes.length - 1; i >= 0; i--) {
241 var attr = this._svg.attributes.item(i);
242 if (!(attr.nodeName == 'onload' || attr.nodeName == 'version' ||
243 attr.nodeName.substring(0, 5) == 'xmlns')) {
244 this._svg.attributes.removeNamedItem(attr.nodeName);
248 for (var attrName in settings) {
249 this._svg.setAttribute(attrName, settings[attrName]);
254 /* Locate a specific element in the SVG document.
255 @param id (string) the element's identifier
256 @return (element) the element reference, or null if not found */
257 getElementById: function(id) {
258 return this._svg.ownerDocument.getElementById(id);
261 /* Change the attributes for a SVG node.
262 @param element (SVG element) the node to change
263 @param settings (object) the new settings
264 @return (SVGWrapper) this root */
265 change: function(element, settings) {
267 for (var name in settings) {
268 if (settings[name] == null) {
269 element.removeAttribute(name);
272 element.setAttribute(name, settings[name]);
279 /* Check for parent being absent and adjust arguments accordingly. */
280 _args: function(values, names, optSettings) {
281 names.splice(0, 0, 'parent');
282 names.splice(names.length, 0, 'settings');
285 if (values[0] != null && values[0].jquery) {
286 values[0] = values[0][0];
288 if (values[0] != null && !(typeof values[0] == 'object' && values[0].nodeName)) {
289 args['parent'] = null;
292 for (var i = 0; i < values.length; i++) {
293 args[names[i + offset]] = values[i];
296 $.each(optSettings, function(i, value) {
297 if (typeof args[value] == 'object') {
298 args.settings = args[value];
307 @param parent (element or jQuery) the parent node for the new title (optional)
308 @param text (string) the text of the title
309 @param settings (object) additional settings for the title (optional)
310 @return (element) the new title node */
311 title: function(parent, text, settings) {
312 var args = this._args(arguments, ['text']);
313 var node = this._makeNode(args.parent, 'title', args.settings || {});
314 node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
318 /* Add a description.
319 @param parent (element or jQuery) the parent node for the new description (optional)
320 @param text (string) the text of the description
321 @param settings (object) additional settings for the description (optional)
322 @return (element) the new description node */
323 describe: function(parent, text, settings) {
324 var args = this._args(arguments, ['text']);
325 var node = this._makeNode(args.parent, 'desc', args.settings || {});
326 node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
330 /* Add a definitions node.
331 @param parent (element or jQuery) the parent node for the new definitions (optional)
332 @param id (string) the ID of this definitions (optional)
333 @param settings (object) additional settings for the definitions (optional)
334 @return (element) the new definitions node */
335 defs: function(parent, id, settings) {
336 var args = this._args(arguments, ['id'], ['id']);
337 return this._makeNode(args.parent, 'defs', $.extend(
338 (args.id ? {id: args.id} : {}), args.settings || {}));
341 /* Add a symbol definition.
342 @param parent (element or jQuery) the parent node for the new symbol (optional)
343 @param id (string) the ID of this symbol
344 @param x1 (number) the left coordinate for this symbol
345 @param y1 (number) the top coordinate for this symbol
346 @param width (number) the width of this symbol
347 @param height (number) the height of this symbol
348 @param settings (object) additional settings for the symbol (optional)
349 @return (element) the new symbol node */
350 symbol: function(parent, id, x1, y1, width, height, settings) {
351 var args = this._args(arguments, ['id', 'x1', 'y1', 'width', 'height']);
352 return this._makeNode(args.parent, 'symbol', $.extend({id: args.id,
353 viewBox: args.x1 + ' ' + args.y1 + ' ' + args.width + ' ' + args.height},
354 args.settings || {}));
357 /* Add a marker definition.
358 @param parent (element or jQuery) the parent node for the new marker (optional)
359 @param id (string) the ID of this marker
360 @param refX (number) the x-coordinate for the reference point
361 @param refY (number) the y-coordinate for the reference point
362 @param mWidth (number) the marker viewport width
363 @param mHeight (number) the marker viewport height
364 @param orient (string or int) 'auto' or angle (degrees) (optional)
365 @param settings (object) additional settings for the marker (optional)
366 @return (element) the new marker node */
367 marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) {
368 var args = this._args(arguments, ['id', 'refX', 'refY',
369 'mWidth', 'mHeight', 'orient'], ['orient']);
370 return this._makeNode(args.parent, 'marker', $.extend(
371 {id: args.id, refX: args.refX, refY: args.refY, markerWidth: args.mWidth,
372 markerHeight: args.mHeight, orient: args.orient || 'auto'}, args.settings || {}));
376 @param parent (element or jQuery) the parent node for the new node (optional)
377 @param styles (string) the CSS styles
378 @param settings (object) additional settings for the node (optional)
379 @return (element) the new style node */
380 style: function(parent, styles, settings) {
381 var args = this._args(arguments, ['styles']);
382 var node = this._makeNode(args.parent, 'style', $.extend(
383 {type: 'text/css'}, args.settings || {}));
384 node.appendChild(this._svg.ownerDocument.createTextNode(args.styles));
385 if ($.browser.opera) {
386 $('head').append('<style type="text/css">' + args.styles + '</style>');
391 /* Add a script node.
392 @param parent (element or jQuery) the parent node for the new node (optional)
393 @param script (string) the JavaScript code
394 @param type (string) the MIME type for the code (optional, default 'text/javascript')
395 @param settings (object) additional settings for the node (optional)
396 @return (element) the new script node */
397 script: function(parent, script, type, settings) {
398 var args = this._args(arguments, ['script', 'type'], ['type']);
399 var node = this._makeNode(args.parent, 'script', $.extend(
400 {type: args.type || 'text/javascript'}, args.settings || {}));
401 node.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(args.script)));
402 if (!$.browser.mozilla) {
403 $.globalEval(args.script);
408 /* Add a linear gradient definition.
409 Specify all of x1, y1, x2, y2 or none of them.
410 @param parent (element or jQuery) the parent node for the new gradient (optional)
411 @param id (string) the ID for this gradient
412 @param stops (string[][]) the gradient stops, each entry is
413 [0] is offset (0.0-1.0 or 0%-100%), [1] is colour,
414 [2] is opacity (optional)
415 @param x1 (number) the x-coordinate of the gradient start (optional)
416 @param y1 (number) the y-coordinate of the gradient start (optional)
417 @param x2 (number) the x-coordinate of the gradient end (optional)
418 @param y2 (number) the y-coordinate of the gradient end (optional)
419 @param settings (object) additional settings for the gradient (optional)
420 @return (element) the new gradient node */
421 linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) {
422 var args = this._args(arguments,
423 ['id', 'stops', 'x1', 'y1', 'x2', 'y2'], ['x1']);
424 var sets = $.extend({id: args.id},
425 (args.x1 != null ? {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2} : {}));
426 return this._gradient(args.parent, 'linearGradient',
427 $.extend(sets, args.settings || {}), args.stops);
430 /* Add a radial gradient definition.
431 Specify all of cx, cy, r, fx, fy or none of them.
432 @param parent (element or jQuery) the parent node for the new gradient (optional)
433 @param id (string) the ID for this gradient
434 @param stops (string[][]) the gradient stops, each entry
435 [0] is offset, [1] is colour, [2] is opacity (optional)
436 @param cx (number) the x-coordinate of the largest circle centre (optional)
437 @param cy (number) the y-coordinate of the largest circle centre (optional)
438 @param r (number) the radius of the largest circle (optional)
439 @param fx (number) the x-coordinate of the gradient focus (optional)
440 @param fy (number) the y-coordinate of the gradient focus (optional)
441 @param settings (object) additional settings for the gradient (optional)
442 @return (element) the new gradient node */
443 radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) {
444 var args = this._args(arguments,
445 ['id', 'stops', 'cx', 'cy', 'r', 'fx', 'fy'], ['cx']);
446 var sets = $.extend({id: args.id}, (args.cx != null ?
447 {cx: args.cx, cy: args.cy, r: args.r, fx: args.fx, fy: args.fy} : {}));
448 return this._gradient(args.parent, 'radialGradient',
449 $.extend(sets, args.settings || {}), args.stops);
452 /* Add a gradient node. */
453 _gradient: function(parent, name, settings, stops) {
454 var node = this._makeNode(parent, name, settings);
455 for (var i = 0; i < stops.length; i++) {
457 this._makeNode(node, 'stop', $.extend(
458 {offset: stop[0], stopColor: stop[1]},
459 (stop[2] != null ? {stopOpacity: stop[2]} : {})));
464 /* Add a pattern definition.
465 Specify all of vx, vy, xwidth, vheight or none of them.
466 @param parent (element or jQuery) the parent node for the new pattern (optional)
467 @param id (string) the ID for this pattern
468 @param x (number) the x-coordinate for the left edge of the pattern
469 @param y (number) the y-coordinate for the top edge of the pattern
470 @param width (number) the width of the pattern
471 @param height (number) the height of the pattern
472 @param vx (number) the minimum x-coordinate for view box (optional)
473 @param vy (number) the minimum y-coordinate for the view box (optional)
474 @param vwidth (number) the width of the view box (optional)
475 @param vheight (number) the height of the view box (optional)
476 @param settings (object) additional settings for the pattern (optional)
477 @return (element) the new pattern node */
478 pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) {
479 var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height',
480 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
481 var sets = $.extend({id: args.id, x: args.x, y: args.y,
482 width: args.width, height: args.height}, (args.vx != null ?
483 {viewBox: args.vx + ' ' + args.vy + ' ' + args.vwidth + ' ' + args.vheight} : {}));
484 return this._makeNode(args.parent, 'pattern', $.extend(sets, args.settings || {}));
487 /* Add a mask definition.
488 @param parent (element or jQuery) the parent node for the new mask (optional)
489 @param id (string) the ID for this mask
490 @param x (number) the x-coordinate for the left edge of the mask
491 @param y (number) the y-coordinate for the top edge of the mask
492 @param width (number) the width of the mask
493 @param height (number) the height of the mask
494 @param settings (object) additional settings for the mask (optional)
495 @return (element) the new mask node */
496 mask: function(parent, id, x, y, width, height, settings) {
497 var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height']);
498 return this._makeNode(args.parent, 'mask', $.extend(
499 {id: args.id, x: args.x, y: args.y, width: args.width, height: args.height},
500 args.settings || {}));
503 /* Create a new path object.
504 @return (SVGPath) a new path object */
505 createPath: function() {
506 return new SVGPath();
509 /* Create a new text object.
510 @return (SVGText) a new text object */
511 createText: function() {
512 return new SVGText();
515 /* Add an embedded SVG element.
516 Specify all of vx, vy, vwidth, vheight or none of them.
517 @param parent (element or jQuery) the parent node for the new node (optional)
518 @param x (number) the x-coordinate for the left edge of the node
519 @param y (number) the y-coordinate for the top edge of the node
520 @param width (number) the width of the node
521 @param height (number) the height of the node
522 @param vx (number) the minimum x-coordinate for view box (optional)
523 @param vy (number) the minimum y-coordinate for the view box (optional)
524 @param vwidth (number) the width of the view box (optional)
525 @param vheight (number) the height of the view box (optional)
526 @param settings (object) additional settings for the node (optional)
527 @return (element) the new node */
528 svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) {
529 var args = this._args(arguments, ['x', 'y', 'width', 'height',
530 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
531 var sets = $.extend({x: args.x, y: args.y, width: args.width, height: args.height},
532 (args.vx != null ? {viewBox: args.vx + ' ' + args.vy + ' ' +
533 args.vwidth + ' ' + args.vheight} : {}));
534 return this._makeNode(args.parent, 'svg', $.extend(sets, args.settings || {}));
538 @param parent (element or jQuery) the parent node for the new group (optional)
539 @param id (string) the ID of this group (optional)
540 @param settings (object) additional settings for the group (optional)
541 @return (element) the new group node */
542 group: function(parent, id, settings) {
543 var args = this._args(arguments, ['id'], ['id']);
544 return this._makeNode(args.parent, 'g', $.extend({id: args.id}, args.settings || {}));
547 /* Add a usage reference.
548 Specify all of x, y, width, height or none of them.
549 @param parent (element or jQuery) the parent node for the new node (optional)
550 @param x (number) the x-coordinate for the left edge of the node (optional)
551 @param y (number) the y-coordinate for the top edge of the node (optional)
552 @param width (number) the width of the node (optional)
553 @param height (number) the height of the node (optional)
554 @param ref (string) the ID of the definition node
555 @param settings (object) additional settings for the node (optional)
556 @return (element) the new node */
557 use: function(parent, x, y, width, height, ref, settings) {
558 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
559 if (typeof args.x == 'string') {
561 args.settings = args.y;
562 args.x = args.y = args.width = args.height = null;
564 var node = this._makeNode(args.parent, 'use', $.extend(
565 {x: args.x, y: args.y, width: args.width, height: args.height},
566 args.settings || {}));
567 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
571 /* Add a link, which applies to all child elements.
572 @param parent (element or jQuery) the parent node for the new link (optional)
573 @param ref (string) the target URL
574 @param settings (object) additional settings for the link (optional)
575 @return (element) the new link node */
576 link: function(parent, ref, settings) {
577 var args = this._args(arguments, ['ref']);
578 var node = this._makeNode(args.parent, 'a', args.settings);
579 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
584 @param parent (element or jQuery) the parent node for the new image (optional)
585 @param x (number) the x-coordinate for the left edge of the image
586 @param y (number) the y-coordinate for the top edge of the image
587 @param width (number) the width of the image
588 @param height (number) the height of the image
589 @param ref (string) the path to the image
590 @param settings (object) additional settings for the image (optional)
591 @return (element) the new image node */
592 image: function(parent, x, y, width, height, ref, settings) {
593 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
594 var node = this._makeNode(args.parent, 'image', $.extend(
595 {x: args.x, y: args.y, width: args.width, height: args.height},
596 args.settings || {}));
597 node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
602 @param parent (element or jQuery) the parent node for the new shape (optional)
603 @param path (string or SVGPath) the path to draw
604 @param settings (object) additional settings for the shape (optional)
605 @return (element) the new shape node */
606 path: function(parent, path, settings) {
607 var args = this._args(arguments, ['path']);
608 return this._makeNode(args.parent, 'path', $.extend(
609 {d: (args.path.path ? args.path.path() : args.path)}, args.settings || {}));
613 Specify both of rx and ry or neither.
614 @param parent (element or jQuery) the parent node for the new shape (optional)
615 @param x (number) the x-coordinate for the left edge of the rectangle
616 @param y (number) the y-coordinate for the top edge of the rectangle
617 @param width (number) the width of the rectangle
618 @param height (number) the height of the rectangle
619 @param rx (number) the x-radius of the ellipse for the rounded corners (optional)
620 @param ry (number) the y-radius of the ellipse for the rounded corners (optional)
621 @param settings (object) additional settings for the shape (optional)
622 @return (element) the new shape node */
623 rect: function(parent, x, y, width, height, rx, ry, settings) {
624 var args = this._args(arguments, ['x', 'y', 'width', 'height', 'rx', 'ry'], ['rx']);
625 return this._makeNode(args.parent, 'rect', $.extend(
626 {x: args.x, y: args.y, width: args.width, height: args.height},
627 (args.rx ? {rx: args.rx, ry: args.ry} : {}), args.settings || {}));
631 @param parent (element or jQuery) the parent node for the new shape (optional)
632 @param cx (number) the x-coordinate for the centre of the circle
633 @param cy (number) the y-coordinate for the centre of the circle
634 @param r (number) the radius of the circle
635 @param settings (object) additional settings for the shape (optional)
636 @return (element) the new shape node */
637 circle: function(parent, cx, cy, r, settings) {
638 var args = this._args(arguments, ['cx', 'cy', 'r']);
639 return this._makeNode(args.parent, 'circle', $.extend(
640 {cx: args.cx, cy: args.cy, r: args.r}, args.settings || {}));
644 @param parent (element or jQuery) the parent node for the new shape (optional)
645 @param cx (number) the x-coordinate for the centre of the ellipse
646 @param cy (number) the y-coordinate for the centre of the ellipse
647 @param rx (number) the x-radius of the ellipse
648 @param ry (number) the y-radius of the ellipse
649 @param settings (object) additional settings for the shape (optional)
650 @return (element) the new shape node */
651 ellipse: function(parent, cx, cy, rx, ry, settings) {
652 var args = this._args(arguments, ['cx', 'cy', 'rx', 'ry']);
653 return this._makeNode(args.parent, 'ellipse', $.extend(
654 {cx: args.cx, cy: args.cy, rx: args.rx, ry: args.ry}, args.settings || {}));
658 @param parent (element or jQuery) the parent node for the new shape (optional)
659 @param x1 (number) the x-coordinate for the start of the line
660 @param y1 (number) the y-coordinate for the start of the line
661 @param x2 (number) the x-coordinate for the end of the line
662 @param y2 (number) the y-coordinate for the end of the line
663 @param settings (object) additional settings for the shape (optional)
664 @return (element) the new shape node */
665 line: function(parent, x1, y1, x2, y2, settings) {
666 var args = this._args(arguments, ['x1', 'y1', 'x2', 'y2']);
667 return this._makeNode(args.parent, 'line', $.extend(
668 {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2}, args.settings || {}));
671 /* Draw a polygonal line.
672 @param parent (element or jQuery) the parent node for the new shape (optional)
673 @param points (number[][]) the x-/y-coordinates for the points on the line
674 @param settings (object) additional settings for the shape (optional)
675 @return (element) the new shape node */
676 polyline: function(parent, points, settings) {
677 var args = this._args(arguments, ['points']);
678 return this._poly(args.parent, 'polyline', args.points, args.settings);
681 /* Draw a polygonal shape.
682 @param parent (element or jQuery) the parent node for the new shape (optional)
683 @param points (number[][]) the x-/y-coordinates for the points on the shape
684 @param settings (object) additional settings for the shape (optional)
685 @return (element) the new shape node */
686 polygon: function(parent, points, settings) {
687 var args = this._args(arguments, ['points']);
688 return this._poly(args.parent, 'polygon', args.points, args.settings);
691 /* Draw a polygonal line or shape. */
692 _poly: function(parent, name, points, settings) {
694 for (var i = 0; i < points.length; i++) {
695 ps += points[i].join() + ' ';
697 return this._makeNode(parent, name, $.extend(
698 {points: $.trim(ps)}, settings || {}));
702 Specify both of x and y or neither of them.
703 @param parent (element or jQuery) the parent node for the text (optional)
704 @param x (number or number[]) the x-coordinate(s) for the text (optional)
705 @param y (number or number[]) the y-coordinate(s) for the text (optional)
706 @param value (string) the text content or
707 (SVGText) text with spans and references
708 @param settings (object) additional settings for the text (optional)
709 @return (element) the new text node */
710 text: function(parent, x, y, value, settings) {
711 var args = this._args(arguments, ['x', 'y', 'value']);
712 if (typeof args.x == 'string' && arguments.length < 4) {
714 args.settings = args.y;
715 args.x = args.y = null;
717 return this._text(args.parent, 'text', args.value, $.extend(
718 {x: (args.x && isArray(args.x) ? args.x.join(' ') : args.x),
719 y: (args.y && isArray(args.y) ? args.y.join(' ') : args.y)},
720 args.settings || {}));
723 /* Draw text along a path.
724 @param parent (element or jQuery) the parent node for the text (optional)
725 @param path (string) the ID of the path
726 @param value (string) the text content or
727 (SVGText) text with spans and references
728 @param settings (object) additional settings for the text (optional)
729 @return (element) the new text node */
730 textpath: function(parent, path, value, settings) {
731 var args = this._args(arguments, ['path', 'value']);
732 var node = this._text(args.parent, 'textPath', args.value, args.settings || {});
733 node.setAttributeNS($.svg.xlinkNS, 'href', args.path);
738 _text: function(parent, name, value, settings) {
739 var node = this._makeNode(parent, name, settings);
740 if (typeof value == 'string') {
741 node.appendChild(node.ownerDocument.createTextNode(value));
744 for (var i = 0; i < value._parts.length; i++) {
745 var part = value._parts[i];
746 if (part[0] == 'tspan') {
747 var child = this._makeNode(node, part[0], part[2]);
748 child.appendChild(node.ownerDocument.createTextNode(part[1]));
749 node.appendChild(child);
751 else if (part[0] == 'tref') {
752 var child = this._makeNode(node, part[0], part[2]);
753 child.setAttributeNS($.svg.xlinkNS, 'href', part[1]);
754 node.appendChild(child);
756 else if (part[0] == 'textpath') {
757 var set = $.extend({}, part[2]);
759 var child = this._makeNode(node, part[0], set);
760 child.setAttributeNS($.svg.xlinkNS, 'href', part[2].href);
761 child.appendChild(node.ownerDocument.createTextNode(part[1]));
762 node.appendChild(child);
764 else { // straight text
765 node.appendChild(node.ownerDocument.createTextNode(part[1]));
772 /* Add a custom SVG element.
773 @param parent (element or jQuery) the parent node for the new element (optional)
774 @param name (string) the name of the element
775 @param settings (object) additional settings for the element (optional)
776 @return (element) the new custom node */
777 other: function(parent, name, settings) {
778 var args = this._args(arguments, ['name']);
779 return this._makeNode(args.parent, args.name, args.settings || {});
782 /* Create a shape node with the given settings. */
783 _makeNode: function(parent, name, settings) {
784 parent = parent || this._svg;
785 var node = this._svg.ownerDocument.createElementNS($.svg.svgNS, name);
786 for (var name in settings) {
787 var value = settings[name];
788 if (value != null && value != null &&
789 (typeof value != 'string' || value != '')) {
790 node.setAttribute($.svg._attrNames[name] || name, value);
793 parent.appendChild(node);
797 /* Add an existing SVG node to the diagram.
798 @param parent (element or jQuery) the parent node for the new node (optional)
799 @param node (element) the new node to add or
800 (string) the jQuery selector for the node or
801 (jQuery collection) set of nodes to add
802 @return (SVGWrapper) this wrapper */
803 add: function(parent, node) {
804 var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
806 args.parent = args.parent || this._svg;
808 if ($.svg._renesis) {
809 throw 'Force traversal';
811 args.parent.appendChild(args.node.cloneNode(true));
814 args.node = (args.node.jquery ? args.node : $(args.node));
815 args.node.each(function() {
816 var child = svg._cloneAsSVG(this);
818 args.parent.appendChild(child);
825 /* SVG nodes must belong to the SVG namespace, so clone and ensure this is so. */
826 _cloneAsSVG: function(node) {
828 if (node.nodeType == 1) { // element
829 newNode = this._svg.ownerDocument.createElementNS(
830 $.svg.svgNS, this._checkName(node.nodeName));
831 for (var i = 0; i < node.attributes.length; i++) {
832 var attr = node.attributes.item(i);
833 if (attr.nodeName != 'xmlns' && attr.nodeValue) {
834 if (attr.prefix == 'xlink') {
835 newNode.setAttributeNS($.svg.xlinkNS, attr.localName, attr.nodeValue);
838 newNode.setAttribute(this._checkName(attr.nodeName), attr.nodeValue);
842 for (var i = 0; i < node.childNodes.length; i++) {
843 var child = this._cloneAsSVG(node.childNodes[i]);
845 newNode.appendChild(child);
849 else if (node.nodeType == 3) { // text
850 if ($.trim(node.nodeValue)) {
851 newNode = this._svg.ownerDocument.createTextNode(node.nodeValue);
854 else if (node.nodeType == 4) { // CDATA
855 if ($.trim(node.nodeValue)) {
857 newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue);
860 newNode = this._svg.ownerDocument.createTextNode(
861 node.nodeValue.replace(/&/g, '&').
862 replace(/</g, '<').replace(/>/g, '>'));
869 /* Node names must be lower case and without SVG namespace prefix. */
870 _checkName: function(name) {
871 name = (name.substring(0, 1) >= 'A' && name.substring(0, 1) <= 'Z' ?
872 name.toLowerCase() : name);
873 return (name.substring(0, 4) == 'svg:' ? name.substring(4) : name);
876 /* Load an external SVG document.
877 @param url (string) the location of the SVG document or
878 the actual SVG content
879 @param settings (boolean) see addTo below or
880 (function) see onLoad below or
881 (object) additional settings for the load with attributes below:
882 addTo (boolean) true to add to what's already there,
883 or false to clear the canvas first
884 changeSize (boolean) true to allow the canvas size to change,
885 or false to retain the original
886 onLoad (function) callback after the document has loaded,
887 'this' is the container, receives SVG object and
888 optional error message as a parameter
889 @return (SVGWrapper) this root */
890 load: function(url, settings) {
891 settings = (typeof settings == 'boolean'? {addTo: settings} :
892 (typeof settings == 'function'? {onLoad: settings} : settings || {}));
893 if (!settings.addTo) {
896 var size = [this._svg.getAttribute('width'), this._svg.getAttribute('height')];
898 // Report a problem with the load
899 var reportError = function(message) {
900 message = $.svg.local.errorLoadingText + ': ' + message;
901 if (settings.onLoad) {
902 settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper, message]);
905 wrapper.text(null, 10, 20, message);
908 // Create a DOM from SVG content
909 var loadXML4IE = function(data) {
910 var xml = new ActiveXObject('Microsoft.XMLDOM');
911 xml.validateOnParse = false;
912 xml.resolveExternals = false;
915 if (xml.parseError.errorCode != 0) {
916 reportError(xml.parseError.reason);
922 var loadSVG = function(data) {
926 if (data.documentElement.nodeName != 'svg') {
927 var errors = data.getElementsByTagName('parsererror');
928 var messages = (errors.length ? errors[0].getElementsByTagName('div') : []); // Safari
929 reportError(!errors.length ? '???' :
930 (messages.length ? messages[0] : errors[0]).firstChild.nodeValue);
934 for (var i = 0; i < data.documentElement.attributes.length; i++) {
935 var attr = data.documentElement.attributes.item(i);
936 if (!(attr.nodeName == 'version' || attr.nodeName.substring(0, 5) == 'xmlns')) {
937 attrs[attr.nodeName] = attr.nodeValue;
940 wrapper.configure(attrs, true);
941 var nodes = data.documentElement.childNodes;
942 for (var i = 0; i < nodes.length; i++) {
944 if ($.svg._renesis) {
945 throw 'Force traversal';
947 wrapper._svg.appendChild(nodes[i].cloneNode(true));
948 if (nodes[i].nodeName == 'script') {
949 $.globalEval(nodes[i].textContent);
953 wrapper.add(null, nodes[i]);
956 if (!settings.changeSize) {
957 wrapper.configure({width: size[0], height: size[1]});
959 if (settings.onLoad) {
960 settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper]);
963 if (url.match('<svg')) { // Inline SVG
964 loadSVG($.browser.msie ? loadXML4IE(url) :
965 new DOMParser().parseFromString(url, 'text/xml'));
968 $.ajax({url: url, dataType: ($.browser.msie ? 'text' : 'xml'),
969 success: function(xml) {
970 loadSVG($.browser.msie ? loadXML4IE(xml) : xml);
971 }, error: function(http, message, exc) {
972 reportError(message + (exc ? ' ' + exc.message : ''));
978 /* Delete a specified node.
979 @param node (element or jQuery) the drawing node to remove
980 @return (SVGWrapper) this root */
981 remove: function(node) {
982 node = (node.jquery ? node[0] : node);
983 node.parentNode.removeChild(node);
987 /* Delete everything in the current document.
988 @param attrsToo (boolean) true to clear any root attributes as well,
989 false to leave them (optional)
990 @return (SVGWrapper) this root */
991 clear: function(attrsToo) {
993 this.configure({}, true);
995 while (this._svg.firstChild) {
996 this._svg.removeChild(this._svg.firstChild);
1001 /* Serialise the current diagram into an SVG text document.
1002 @param node (SVG element) the starting node (optional)
1003 @return (string) the SVG as text */
1004 toSVG: function(node) {
1005 node = node || this._svg;
1006 return (typeof XMLSerializer == 'undefined' ? this._toSVG(node) :
1007 new XMLSerializer().serializeToString(node));
1010 /* Serialise one node in the SVG hierarchy. */
1011 _toSVG: function(node) {
1016 if (node.nodeType == 3) { // Text
1017 svgDoc = node.nodeValue;
1019 else if (node.nodeType == 4) { // CDATA
1020 svgDoc = '<![CDATA[' + node.nodeValue + ']]>';
1023 svgDoc = '<' + node.nodeName;
1024 if (node.attributes) {
1025 for (var i = 0; i < node.attributes.length; i++) {
1026 var attr = node.attributes.item(i);
1027 if (!($.trim(attr.nodeValue) == '' || attr.nodeValue.match(/^\[object/) ||
1028 attr.nodeValue.match(/^function/))) {
1029 svgDoc += ' ' + (attr.namespaceURI == $.svg.xlinkNS ? 'xlink:' : '') +
1030 attr.nodeName + '="' + attr.nodeValue + '"';
1034 if (node.firstChild) {
1036 var child = node.firstChild;
1038 svgDoc += this._toSVG(child);
1039 child = child.nextSibling;
1041 svgDoc += '</' + node.nodeName + '>';
1050 /* Escape reserved characters in XML. */
1051 _escapeXML: function(text) {
1052 text = text.replace(/&/g, '&');
1053 text = text.replace(/</g, '<');
1054 text = text.replace(/>/g, '>');
1059 /* Helper to generate an SVG path.
1060 Obtain an instance from the SVGWrapper object.
1061 String calls together to generate the path and use its value:
1062 var path = root.createPath();
1063 root.path(null, path.move(100, 100).line(300, 100).line(200, 300).close(), {fill: 'red'});
1065 root.path(null, path.move(100, 100).line([[300, 100], [200, 300]]).close(), {fill: 'red'}); */
1066 function SVGPath() {
1070 $.extend(SVGPath.prototype, {
1071 /* Prepare to create a new path.
1072 @return (SVGPath) this path */
1078 /* Move the pointer to a position.
1079 @param x (number) x-coordinate to move to or
1080 (number[][]) x-/y-coordinates to move to
1081 @param y (number) y-coordinate to move to (omitted if x is array)
1082 @param relative (boolean) true for coordinates relative to the current point,
1083 false for coordinates being absolute
1084 @return (SVGPath) this path */
1085 move: function(x, y, relative) {
1086 relative = (isArray(x) ? y : relative);
1087 return this._coords((relative ? 'm' : 'M'), x, y);
1090 /* Draw a line to a position.
1091 @param x (number) x-coordinate to move to or
1092 (number[][]) x-/y-coordinates to move to
1093 @param y (number) y-coordinate to move to (omitted if x is array)
1094 @param relative (boolean) true for coordinates relative to the current point,
1095 false for coordinates being absolute
1096 @return (SVGPath) this path */
1097 line: function(x, y, relative) {
1098 relative = (isArray(x) ? y : relative);
1099 return this._coords((relative ? 'l' : 'L'), x, y);
1102 /* Draw a horizontal line to a position.
1103 @param x (number) x-coordinate to draw to or
1104 (number[]) x-coordinates to draw to
1105 @param relative (boolean) true for coordinates relative to the current point,
1106 false for coordinates being absolute
1107 @return (SVGPath) this path */
1108 horiz: function(x, relative) {
1109 this._path += (relative ? 'h' : 'H') + (isArray(x) ? x.join(' ') : x);
1113 /* Draw a vertical line to a position.
1114 @param y (number) y-coordinate to draw to or
1115 (number[]) y-coordinates to draw to
1116 @param relative (boolean) true for coordinates relative to the current point,
1117 false for coordinates being absolute
1118 @return (SVGPath) this path */
1119 vert: function(y, relative) {
1120 this._path += (relative ? 'v' : 'V') + (isArray(y) ? y.join(' ') : y);
1124 /* Draw a cubic Bézier curve.
1125 @param x1 (number) x-coordinate of beginning control point or
1126 (number[][]) x-/y-coordinates of control and end points to draw to
1127 @param y1 (number) y-coordinate of beginning control point (omitted if x1 is array)
1128 @param x2 (number) x-coordinate of ending control point (omitted if x1 is array)
1129 @param y2 (number) y-coordinate of ending control point (omitted if x1 is array)
1130 @param x (number) x-coordinate of curve end (omitted if x1 is array)
1131 @param y (number) y-coordinate of curve end (omitted if x1 is array)
1132 @param relative (boolean) true for coordinates relative to the current point,
1133 false for coordinates being absolute
1134 @return (SVGPath) this path */
1135 curveC: function(x1, y1, x2, y2, x, y, relative) {
1136 relative = (isArray(x1) ? y1 : relative);
1137 return this._coords((relative ? 'c' : 'C'), x1, y1, x2, y2, x, y);
1140 /* Continue a cubic Bézier curve.
1141 Starting control point is the reflection of the previous end control point.
1142 @param x2 (number) x-coordinate of ending control point or
1143 (number[][]) x-/y-coordinates of control and end points to draw to
1144 @param y2 (number) y-coordinate of ending control point (omitted if x2 is array)
1145 @param x (number) x-coordinate of curve end (omitted if x2 is array)
1146 @param y (number) y-coordinate of curve end (omitted if x2 is array)
1147 @param relative (boolean) true for coordinates relative to the current point,
1148 false for coordinates being absolute
1149 @return (SVGPath) this path */
1150 smoothC: function(x2, y2, x, y, relative) {
1151 relative = (isArray(x2) ? y2 : relative);
1152 return this._coords((relative ? 's' : 'S'), x2, y2, x, y);
1155 /* Draw a quadratic Bézier curve.
1156 @param x1 (number) x-coordinate of control point or
1157 (number[][]) x-/y-coordinates of control and end points to draw to
1158 @param y1 (number) y-coordinate of control point (omitted if x1 is array)
1159 @param x (number) x-coordinate of curve end (omitted if x1 is array)
1160 @param y (number) y-coordinate of curve end (omitted if x1 is array)
1161 @param relative (boolean) true for coordinates relative to the current point,
1162 false for coordinates being absolute
1163 @return (SVGPath) this path */
1164 curveQ: function(x1, y1, x, y, relative) {
1165 relative = (isArray(x1) ? y1 : relative);
1166 return this._coords((relative ? 'q' : 'Q'), x1, y1, x, y);
1169 /* Continue a quadratic Bézier curve.
1170 Control point is the reflection of the previous control point.
1171 @param x (number) x-coordinate of curve end or
1172 (number[][]) x-/y-coordinates of points to draw to
1173 @param y (number) y-coordinate of curve end (omitted if x is array)
1174 @param relative (boolean) true for coordinates relative to the current point,
1175 false for coordinates being absolute
1176 @return (SVGPath) this path */
1177 smoothQ: function(x, y, relative) {
1178 relative = (isArray(x) ? y : relative);
1179 return this._coords((relative ? 't' : 'T'), x, y);
1182 /* Generate a path command with (a list of) coordinates. */
1183 _coords: function(cmd, x1, y1, x2, y2, x3, y3) {
1185 for (var i = 0; i < x1.length; i++) {
1187 this._path += (i == 0 ? cmd : ' ') + cs[0] + ',' + cs[1] +
1188 (cs.length < 4 ? '' : ' ' + cs[2] + ',' + cs[3] +
1189 (cs.length < 6 ? '': ' ' + cs[4] + ',' + cs[5]));
1193 this._path += cmd + x1 + ',' + y1 +
1194 (x2 == null ? '' : ' ' + x2 + ',' + y2 +
1195 (x3 == null ? '' : ' ' + x3 + ',' + y3));
1200 /* Draw an arc to a position.
1201 @param rx (number) x-radius of arc or
1202 (number/boolean[][]) x-/y-coordinates and flags for points to draw to
1203 @param ry (number) y-radius of arc (omitted if rx is array)
1204 @param xRotate (number) x-axis rotation (degrees, clockwise) (omitted if rx is array)
1205 @param large (boolean) true to draw the large part of the arc,
1206 false to draw the small part (omitted if rx is array)
1207 @param clockwise (boolean) true to draw the clockwise arc,
1208 false to draw the anti-clockwise arc (omitted if rx is array)
1209 @param x (number) x-coordinate of arc end (omitted if rx is array)
1210 @param y (number) y-coordinate of arc end (omitted if rx is array)
1211 @param relative (boolean) true for coordinates relative to the current point,
1212 false for coordinates being absolute
1213 @return (SVGPath) this path */
1214 arc: function(rx, ry, xRotate, large, clockwise, x, y, relative) {
1215 relative = (isArray(rx) ? ry : relative);
1216 this._path += (relative ? 'a' : 'A');
1218 for (var i = 0; i < rx.length; i++) {
1220 this._path += (i == 0 ? '' : ' ') + cs[0] + ',' + cs[1] + ' ' +
1221 cs[2] + ' ' + (cs[3] ? '1' : '0') + ',' +
1222 (cs[4] ? '1' : '0') + ' ' + cs[5] + ',' + cs[6];
1226 this._path += rx + ',' + ry + ' ' + xRotate + ' ' +
1227 (large ? '1' : '0') + ',' + (clockwise ? '1' : '0') + ' ' + x + ',' + y;
1232 /* Close the current path.
1233 @return (SVGPath) this path */
1239 /* Return the string rendering of the specified path.
1240 @return (string) stringified path */
1246 SVGPath.prototype.moveTo = SVGPath.prototype.move;
1247 SVGPath.prototype.lineTo = SVGPath.prototype.line;
1248 SVGPath.prototype.horizTo = SVGPath.prototype.horiz;
1249 SVGPath.prototype.vertTo = SVGPath.prototype.vert;
1250 SVGPath.prototype.curveCTo = SVGPath.prototype.curveC;
1251 SVGPath.prototype.smoothCTo = SVGPath.prototype.smoothC;
1252 SVGPath.prototype.curveQTo = SVGPath.prototype.curveQ;
1253 SVGPath.prototype.smoothQTo = SVGPath.prototype.smoothQ;
1254 SVGPath.prototype.arcTo = SVGPath.prototype.arc;
1256 /* Helper to generate an SVG text object.
1257 Obtain an instance from the SVGWrapper object.
1258 String calls together to generate the text and use its value:
1259 var text = root.createText();
1260 root.text(null, x, y, text.string('This is ').
1261 span('red', {fill: 'red'}).string('!'), {fill: 'blue'}); */
1262 function SVGText() {
1263 this._parts = []; // The components of the text object
1266 $.extend(SVGText.prototype, {
1267 /* Prepare to create a new text object.
1268 @return (SVGText) this text */
1274 /* Add a straight string value.
1275 @param value (string) the actual text
1276 @return (SVGText) this text object */
1277 string: function(value) {
1278 this._parts[this._parts.length] = ['text', value];
1282 /* Add a separate text span that has its own settings.
1283 @param value (string) the actual text
1284 @param settings (object) the settings for this text
1285 @return (SVGText) this text object */
1286 span: function(value, settings) {
1287 this._parts[this._parts.length] = ['tspan', value, settings];
1291 /* Add a reference to a previously defined text string.
1292 @param id (string) the ID of the actual text
1293 @param settings (object) the settings for this text
1294 @return (SVGText) this text object */
1295 ref: function(id, settings) {
1296 this._parts[this._parts.length] = ['tref', id, settings];
1300 /* Add text drawn along a path.
1301 @param id (string) the ID of the path
1302 @param value (string) the actual text
1303 @param settings (object) the settings for this text
1304 @return (SVGText) this text object */
1305 path: function(id, value, settings) {
1306 this._parts[this._parts.length] = ['textpath', value,
1307 $.extend({href: id}, settings || {})];
1312 /* Attach the SVG functionality to a jQuery selection.
1313 @param command (string) the command to run (optional, default 'attach')
1314 @param options (object) the new settings to use for these SVG instances
1315 @return jQuery (object) for chaining further calls */
1316 $.fn.svg = function(options) {
1317 var otherArgs = Array.prototype.slice.call(arguments, 1);
1318 if (typeof options == 'string' && options == 'get') {
1319 return $.svg['_' + options + 'SVG'].apply($.svg, [this[0]].concat(otherArgs));
1321 return this.each(function() {
1322 if (typeof options == 'string') {
1323 $.svg['_' + options + 'SVG'].apply($.svg, [this].concat(otherArgs));
1326 $.svg._attachSVG(this, options || {});
1331 /* Determine whether an object is an array. */
1332 function isArray(a) {
1333 return (a && a.constructor == Array);
1336 // Singleton primary SVG interface
1337 $.svg = new SVGManager();