From: Joris van Zundert Date: Fri, 20 Jan 2012 13:03:49 +0000 (+0100) Subject: Scaling. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=a88e75b1e7858fa3878155c70f473897694162ba;p=scpubgit%2Fstemmatology.git Scaling. --- diff --git a/stemmaweb/root/css/style.css b/stemmaweb/root/css/style.css index 3a04989..11fe773 100644 --- a/stemmaweb/root/css/style.css +++ b/stemmaweb/root/css/style.css @@ -33,7 +33,8 @@ h2 { clear: both; width: 900px; height: 400px; - border: 1px #c6dcf1 solid; + border: 1px #c6dcf1 solid; + overflow: auto; } #stemma_graph { float: left; diff --git a/stemmaweb/root/js/componentload.js b/stemmaweb/root/js/componentload.js index c5e5d3c..97c48c0 100644 --- a/stemmaweb/root/js/componentload.js +++ b/stemmaweb/root/js/componentload.js @@ -1,6 +1,14 @@ function loadTradition( textid ) { - $('#stemma_graph').load( "stemma/" + textid ); - $('#stemma_graph > svg').width('485px'); - $('#variant_graph').load( "variantgraph/" + textid ); - $('#variant_graph > svg').height('300px'); -} \ No newline at end of file + $('#stemma_graph').load( "stemma/" + textid , function() { + var stemma_svg_element = $('#stemma_graph svg').svg().svg('get').root(); + stemma_svg_element.height.baseVal.value = $('#stemma_graph').height(); + }); + $('#variant_graph').load( "variantgraph/" + textid , function() { + var variant_svg_element = $('#variant_graph svg').svg().svg('get').root(); + var svg_height = variant_svg_element.height.baseVal.value; + var svg_width = variant_svg_element.width.baseVal.value; + var container_height = $('#variant_graph').height(); + variant_svg_element.height.baseVal.value = container_height; + variant_svg_element.width.baseVal.value = (svg_width/svg_height * container_height); + }); +} diff --git a/stemmaweb/root/js/jquery.svg.js b/stemmaweb/root/js/jquery.svg.js new file mode 100644 index 0000000..df5530e --- /dev/null +++ b/stemmaweb/root/js/jquery.svg.js @@ -0,0 +1,1339 @@ +/* http://keith-wood.name/svg.html + SVG for jQuery v1.4.3. + Written by Keith Wood (kbwood{at}iinet.com.au) August 2007. + Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and + MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Please attribute the author if you use it. */ + +(function($) { // Hide scope, no $ conflict + +/* SVG manager. + Use the singleton instance of this class, $.svg, + to interact with the SVG functionality. */ +function SVGManager() { + this._settings = []; // Settings to be remembered per SVG object + this._extensions = []; // List of SVG extensions added to SVGWrapper + // for each entry [0] is extension name, [1] is extension class (function) + // the function takes one parameter - the SVGWrapper instance + this.regional = []; // Localisations, indexed by language, '' for default (English) + this.regional[''] = {errorLoadingText: 'Error loading', + notSupportedText: 'This browser does not support SVG'}; + this.local = this.regional['']; // Current localisation + this._uuid = new Date().getTime(); + this._renesis = detectActiveX('RenesisX.RenesisCtrl'); +} + +/* Determine whether a given ActiveX control is available. + @param classId (string) the ID for the ActiveX control + @return (boolean) true if found, false if not */ +function detectActiveX(classId) { + try { + return !!(window.ActiveXObject && new ActiveXObject(classId)); + } + catch (e) { + return false; + } +} + +var PROP_NAME = 'svgwrapper'; + +$.extend(SVGManager.prototype, { + /* Class name added to elements to indicate already configured with SVG. */ + markerClassName: 'hasSVG', + + /* SVG namespace. */ + svgNS: 'http://www.w3.org/2000/svg', + /* XLink namespace. */ + xlinkNS: 'http://www.w3.org/1999/xlink', + + /* SVG wrapper class. */ + _wrapperClass: SVGWrapper, + + /* Camel-case versions of attribute names containing dashes or are reserved words. */ + _attrNames: {class_: 'class', in_: 'in', + alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift', + clipPath: 'clip-path', clipRule: 'clip-rule', + colorInterpolation: 'color-interpolation', + colorInterpolationFilters: 'color-interpolation-filters', + colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline', + enableBackground: 'enable-background', fillOpacity: 'fill-opacity', + fillRule: 'fill-rule', floodColor: 'flood-color', + floodOpacity: 'flood-opacity', fontFamily: 'font-family', + fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust', + fontStretch: 'font-stretch', fontStyle: 'font-style', + fontVariant: 'font-variant', fontWeight: 'font-weight', + glyphOrientationHorizontal: 'glyph-orientation-horizontal', + glyphOrientationVertical: 'glyph-orientation-vertical', + horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x', + imageRendering: 'image-rendering', letterSpacing: 'letter-spacing', + lightingColor: 'lighting-color', markerEnd: 'marker-end', + markerMid: 'marker-mid', markerStart: 'marker-start', + stopColor: 'stop-color', stopOpacity: 'stop-opacity', + strikethroughPosition: 'strikethrough-position', + strikethroughThickness: 'strikethrough-thickness', + strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset', + strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin', + strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity', + strokeWidth: 'stroke-width', textAnchor: 'text-anchor', + textDecoration: 'text-decoration', textRendering: 'text-rendering', + underlinePosition: 'underline-position', underlineThickness: 'underline-thickness', + vertAdvY: 'vert-adv-y', vertOriginY: 'vert-origin-y', + wordSpacing: 'word-spacing', writingMode: 'writing-mode'}, + + /* Add the SVG object to its container. */ + _attachSVG: function(container, settings) { + var svg = (container.namespaceURI == this.svgNS ? container : null); + var container = (svg ? null : container); + if ($(container || svg).hasClass(this.markerClassName)) { + return; + } + if (typeof settings == 'string') { + settings = {loadURL: settings}; + } + else if (typeof settings == 'function') { + settings = {onLoad: settings}; + } + $(container || svg).addClass(this.markerClassName); + try { + if (!svg) { + svg = document.createElementNS(this.svgNS, 'svg'); + svg.setAttribute('version', '1.1'); + svg.setAttribute('width', container.clientWidth); + svg.setAttribute('height', container.clientHeight); + container.appendChild(svg); + } + this._afterLoad(container, svg, settings || {}); + } + catch (e) { + if ($.browser.msie) { + if (!container.id) { + container.id = 'svg' + (this._uuid++); + } + this._settings[container.id] = settings; + container.innerHTML = ''; + } + else { + container.innerHTML = '

' + + this.local.notSupportedText + '

'; + } + } + }, + + /* SVG callback after loading - register SVG root. */ + _registerSVG: function() { + for (var i = 0; i < document.embeds.length; i++) { // Check all + var container = document.embeds[i].parentNode; + if (!$(container).hasClass($.svg.markerClassName) || // Not SVG + $.data(container, PROP_NAME)) { // Already done + continue; + } + var svg = null; + try { + svg = document.embeds[i].getSVGDocument(); + } + catch(e) { + setTimeout($.svg._registerSVG, 250); // Renesis takes longer to load + return; + } + svg = (svg ? svg.documentElement : null); + if (svg) { + $.svg._afterLoad(container, svg); + } + } + }, + + /* Post-processing once loaded. */ + _afterLoad: function(container, svg, settings) { + var settings = settings || this._settings[container.id]; + this._settings[container ? container.id : ''] = null; + var wrapper = new this._wrapperClass(svg, container); + $.data(container || svg, PROP_NAME, wrapper); + try { + if (settings.loadURL) { // Load URL + wrapper.load(settings.loadURL, settings); + } + if (settings.settings) { // Additional settings + wrapper.configure(settings.settings); + } + if (settings.onLoad && !settings.loadURL) { // Onload callback + settings.onLoad.apply(container || svg, [wrapper]); + } + } + catch (e) { + alert(e); + } + }, + + /* Return the SVG wrapper created for a given container. + @param container (string) selector for the container or + (element) the container for the SVG object or + jQuery collection - first entry is the container + @return (SVGWrapper) the corresponding SVG wrapper element, or null if not attached */ + _getSVG: function(container) { + container = (typeof container == 'string' ? $(container)[0] : + (container.jquery ? container[0] : container)); + return $.data(container, PROP_NAME); + }, + + /* Remove the SVG functionality from a div. + @param container (element) the container for the SVG object */ + _destroySVG: function(container) { + var $container = $(container); + if (!$container.hasClass(this.markerClassName)) { + return; + } + $container.removeClass(this.markerClassName); + if (container.namespaceURI != this.svgNS) { + $container.empty(); + } + $.removeData(container, PROP_NAME); + }, + + /* Extend the SVGWrapper object with an embedded class. + The constructor function must take a single parameter that is + a reference to the owning SVG root object. This allows the + extension to access the basic SVG functionality. + @param name (string) the name of the SVGWrapper attribute to access the new class + @param extClass (function) the extension class constructor */ + addExtension: function(name, extClass) { + this._extensions.push([name, extClass]); + } +}); + +/* The main SVG interface, which encapsulates the SVG element. + Obtain a reference from $().svg('get') */ +function SVGWrapper(svg, container) { + this._svg = svg; // The SVG root node + this._container = container; // The containing div + for (var i = 0; i < $.svg._extensions.length; i++) { + var extension = $.svg._extensions[i]; + this[extension[0]] = new extension[1](this); + } +} + +$.extend(SVGWrapper.prototype, { + + /* Retrieve the width of the SVG object. */ + _width: function() { + return (this._container ? this._container.clientWidth : this._svg.width); + }, + + /* Retrieve the height of the SVG object. */ + _height: function() { + return (this._container ? this._container.clientHeight : this._svg.height); + }, + + /* Retrieve the root SVG element. + @return the top-level SVG element */ + root: function() { + return this._svg; + }, + + /* Configure the SVG root. + @param settings (object) additional settings for the root + @param clear (boolean) true to remove existing attributes first, + false to add to what is already there (optional) + @return (SVGWrapper) this root */ + configure: function(settings, clear) { + if (clear) { + for (var i = this._svg.attributes.length - 1; i >= 0; i--) { + var attr = this._svg.attributes.item(i); + if (!(attr.nodeName == 'onload' || attr.nodeName == 'version' || + attr.nodeName.substring(0, 5) == 'xmlns')) { + this._svg.attributes.removeNamedItem(attr.nodeName); + } + } + } + for (var attrName in settings) { + this._svg.setAttribute(attrName, settings[attrName]); + } + return this; + }, + + /* Locate a specific element in the SVG document. + @param id (string) the element's identifier + @return (element) the element reference, or null if not found */ + getElementById: function(id) { + return this._svg.ownerDocument.getElementById(id); + }, + + /* Change the attributes for a SVG node. + @param element (SVG element) the node to change + @param settings (object) the new settings + @return (SVGWrapper) this root */ + change: function(element, settings) { + if (element) { + for (var name in settings) { + if (settings[name] == null) { + element.removeAttribute(name); + } + else { + element.setAttribute(name, settings[name]); + } + } + } + return this; + }, + + /* Check for parent being absent and adjust arguments accordingly. */ + _args: function(values, names, optSettings) { + names.splice(0, 0, 'parent'); + names.splice(names.length, 0, 'settings'); + var args = {}; + var offset = 0; + if (values[0] != null && values[0].jquery) { + values[0] = values[0][0]; + } + if (values[0] != null && !(typeof values[0] == 'object' && values[0].nodeName)) { + args['parent'] = null; + offset = 1; + } + for (var i = 0; i < values.length; i++) { + args[names[i + offset]] = values[i]; + } + if (optSettings) { + $.each(optSettings, function(i, value) { + if (typeof args[value] == 'object') { + args.settings = args[value]; + args[value] = null; + } + }); + } + return args; + }, + + /* Add a title. + @param parent (element or jQuery) the parent node for the new title (optional) + @param text (string) the text of the title + @param settings (object) additional settings for the title (optional) + @return (element) the new title node */ + title: function(parent, text, settings) { + var args = this._args(arguments, ['text']); + var node = this._makeNode(args.parent, 'title', args.settings || {}); + node.appendChild(this._svg.ownerDocument.createTextNode(args.text)); + return node; + }, + + /* Add a description. + @param parent (element or jQuery) the parent node for the new description (optional) + @param text (string) the text of the description + @param settings (object) additional settings for the description (optional) + @return (element) the new description node */ + describe: function(parent, text, settings) { + var args = this._args(arguments, ['text']); + var node = this._makeNode(args.parent, 'desc', args.settings || {}); + node.appendChild(this._svg.ownerDocument.createTextNode(args.text)); + return node; + }, + + /* Add a definitions node. + @param parent (element or jQuery) the parent node for the new definitions (optional) + @param id (string) the ID of this definitions (optional) + @param settings (object) additional settings for the definitions (optional) + @return (element) the new definitions node */ + defs: function(parent, id, settings) { + var args = this._args(arguments, ['id'], ['id']); + return this._makeNode(args.parent, 'defs', $.extend( + (args.id ? {id: args.id} : {}), args.settings || {})); + }, + + /* Add a symbol definition. + @param parent (element or jQuery) the parent node for the new symbol (optional) + @param id (string) the ID of this symbol + @param x1 (number) the left coordinate for this symbol + @param y1 (number) the top coordinate for this symbol + @param width (number) the width of this symbol + @param height (number) the height of this symbol + @param settings (object) additional settings for the symbol (optional) + @return (element) the new symbol node */ + symbol: function(parent, id, x1, y1, width, height, settings) { + var args = this._args(arguments, ['id', 'x1', 'y1', 'width', 'height']); + return this._makeNode(args.parent, 'symbol', $.extend({id: args.id, + viewBox: args.x1 + ' ' + args.y1 + ' ' + args.width + ' ' + args.height}, + args.settings || {})); + }, + + /* Add a marker definition. + @param parent (element or jQuery) the parent node for the new marker (optional) + @param id (string) the ID of this marker + @param refX (number) the x-coordinate for the reference point + @param refY (number) the y-coordinate for the reference point + @param mWidth (number) the marker viewport width + @param mHeight (number) the marker viewport height + @param orient (string or int) 'auto' or angle (degrees) (optional) + @param settings (object) additional settings for the marker (optional) + @return (element) the new marker node */ + marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) { + var args = this._args(arguments, ['id', 'refX', 'refY', + 'mWidth', 'mHeight', 'orient'], ['orient']); + return this._makeNode(args.parent, 'marker', $.extend( + {id: args.id, refX: args.refX, refY: args.refY, markerWidth: args.mWidth, + markerHeight: args.mHeight, orient: args.orient || 'auto'}, args.settings || {})); + }, + + /* Add a style node. + @param parent (element or jQuery) the parent node for the new node (optional) + @param styles (string) the CSS styles + @param settings (object) additional settings for the node (optional) + @return (element) the new style node */ + style: function(parent, styles, settings) { + var args = this._args(arguments, ['styles']); + var node = this._makeNode(args.parent, 'style', $.extend( + {type: 'text/css'}, args.settings || {})); + node.appendChild(this._svg.ownerDocument.createTextNode(args.styles)); + if ($.browser.opera) { + $('head').append(''); + } + return node; + }, + + /* Add a script node. + @param parent (element or jQuery) the parent node for the new node (optional) + @param script (string) the JavaScript code + @param type (string) the MIME type for the code (optional, default 'text/javascript') + @param settings (object) additional settings for the node (optional) + @return (element) the new script node */ + script: function(parent, script, type, settings) { + var args = this._args(arguments, ['script', 'type'], ['type']); + var node = this._makeNode(args.parent, 'script', $.extend( + {type: args.type || 'text/javascript'}, args.settings || {})); + node.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(args.script))); + if (!$.browser.mozilla) { + $.globalEval(args.script); + } + return node; + }, + + /* Add a linear gradient definition. + Specify all of x1, y1, x2, y2 or none of them. + @param parent (element or jQuery) the parent node for the new gradient (optional) + @param id (string) the ID for this gradient + @param stops (string[][]) the gradient stops, each entry is + [0] is offset (0.0-1.0 or 0%-100%), [1] is colour, + [2] is opacity (optional) + @param x1 (number) the x-coordinate of the gradient start (optional) + @param y1 (number) the y-coordinate of the gradient start (optional) + @param x2 (number) the x-coordinate of the gradient end (optional) + @param y2 (number) the y-coordinate of the gradient end (optional) + @param settings (object) additional settings for the gradient (optional) + @return (element) the new gradient node */ + linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) { + var args = this._args(arguments, + ['id', 'stops', 'x1', 'y1', 'x2', 'y2'], ['x1']); + var sets = $.extend({id: args.id}, + (args.x1 != null ? {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2} : {})); + return this._gradient(args.parent, 'linearGradient', + $.extend(sets, args.settings || {}), args.stops); + }, + + /* Add a radial gradient definition. + Specify all of cx, cy, r, fx, fy or none of them. + @param parent (element or jQuery) the parent node for the new gradient (optional) + @param id (string) the ID for this gradient + @param stops (string[][]) the gradient stops, each entry + [0] is offset, [1] is colour, [2] is opacity (optional) + @param cx (number) the x-coordinate of the largest circle centre (optional) + @param cy (number) the y-coordinate of the largest circle centre (optional) + @param r (number) the radius of the largest circle (optional) + @param fx (number) the x-coordinate of the gradient focus (optional) + @param fy (number) the y-coordinate of the gradient focus (optional) + @param settings (object) additional settings for the gradient (optional) + @return (element) the new gradient node */ + radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) { + var args = this._args(arguments, + ['id', 'stops', 'cx', 'cy', 'r', 'fx', 'fy'], ['cx']); + var sets = $.extend({id: args.id}, (args.cx != null ? + {cx: args.cx, cy: args.cy, r: args.r, fx: args.fx, fy: args.fy} : {})); + return this._gradient(args.parent, 'radialGradient', + $.extend(sets, args.settings || {}), args.stops); + }, + + /* Add a gradient node. */ + _gradient: function(parent, name, settings, stops) { + var node = this._makeNode(parent, name, settings); + for (var i = 0; i < stops.length; i++) { + var stop = stops[i]; + this._makeNode(node, 'stop', $.extend( + {offset: stop[0], stopColor: stop[1]}, + (stop[2] != null ? {stopOpacity: stop[2]} : {}))); + } + return node; + }, + + /* Add a pattern definition. + Specify all of vx, vy, xwidth, vheight or none of them. + @param parent (element or jQuery) the parent node for the new pattern (optional) + @param id (string) the ID for this pattern + @param x (number) the x-coordinate for the left edge of the pattern + @param y (number) the y-coordinate for the top edge of the pattern + @param width (number) the width of the pattern + @param height (number) the height of the pattern + @param vx (number) the minimum x-coordinate for view box (optional) + @param vy (number) the minimum y-coordinate for the view box (optional) + @param vwidth (number) the width of the view box (optional) + @param vheight (number) the height of the view box (optional) + @param settings (object) additional settings for the pattern (optional) + @return (element) the new pattern node */ + pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) { + var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height', + 'vx', 'vy', 'vwidth', 'vheight'], ['vx']); + var sets = $.extend({id: args.id, x: args.x, y: args.y, + width: args.width, height: args.height}, (args.vx != null ? + {viewBox: args.vx + ' ' + args.vy + ' ' + args.vwidth + ' ' + args.vheight} : {})); + return this._makeNode(args.parent, 'pattern', $.extend(sets, args.settings || {})); + }, + + /* Add a mask definition. + @param parent (element or jQuery) the parent node for the new mask (optional) + @param id (string) the ID for this mask + @param x (number) the x-coordinate for the left edge of the mask + @param y (number) the y-coordinate for the top edge of the mask + @param width (number) the width of the mask + @param height (number) the height of the mask + @param settings (object) additional settings for the mask (optional) + @return (element) the new mask node */ + mask: function(parent, id, x, y, width, height, settings) { + var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height']); + return this._makeNode(args.parent, 'mask', $.extend( + {id: args.id, x: args.x, y: args.y, width: args.width, height: args.height}, + args.settings || {})); + }, + + /* Create a new path object. + @return (SVGPath) a new path object */ + createPath: function() { + return new SVGPath(); + }, + + /* Create a new text object. + @return (SVGText) a new text object */ + createText: function() { + return new SVGText(); + }, + + /* Add an embedded SVG element. + Specify all of vx, vy, vwidth, vheight or none of them. + @param parent (element or jQuery) the parent node for the new node (optional) + @param x (number) the x-coordinate for the left edge of the node + @param y (number) the y-coordinate for the top edge of the node + @param width (number) the width of the node + @param height (number) the height of the node + @param vx (number) the minimum x-coordinate for view box (optional) + @param vy (number) the minimum y-coordinate for the view box (optional) + @param vwidth (number) the width of the view box (optional) + @param vheight (number) the height of the view box (optional) + @param settings (object) additional settings for the node (optional) + @return (element) the new node */ + svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) { + var args = this._args(arguments, ['x', 'y', 'width', 'height', + 'vx', 'vy', 'vwidth', 'vheight'], ['vx']); + var sets = $.extend({x: args.x, y: args.y, width: args.width, height: args.height}, + (args.vx != null ? {viewBox: args.vx + ' ' + args.vy + ' ' + + args.vwidth + ' ' + args.vheight} : {})); + return this._makeNode(args.parent, 'svg', $.extend(sets, args.settings || {})); + }, + + /* Create a group. + @param parent (element or jQuery) the parent node for the new group (optional) + @param id (string) the ID of this group (optional) + @param settings (object) additional settings for the group (optional) + @return (element) the new group node */ + group: function(parent, id, settings) { + var args = this._args(arguments, ['id'], ['id']); + return this._makeNode(args.parent, 'g', $.extend({id: args.id}, args.settings || {})); + }, + + /* Add a usage reference. + Specify all of x, y, width, height or none of them. + @param parent (element or jQuery) the parent node for the new node (optional) + @param x (number) the x-coordinate for the left edge of the node (optional) + @param y (number) the y-coordinate for the top edge of the node (optional) + @param width (number) the width of the node (optional) + @param height (number) the height of the node (optional) + @param ref (string) the ID of the definition node + @param settings (object) additional settings for the node (optional) + @return (element) the new node */ + use: function(parent, x, y, width, height, ref, settings) { + var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']); + if (typeof args.x == 'string') { + args.ref = args.x; + args.settings = args.y; + args.x = args.y = args.width = args.height = null; + } + var node = this._makeNode(args.parent, 'use', $.extend( + {x: args.x, y: args.y, width: args.width, height: args.height}, + args.settings || {})); + node.setAttributeNS($.svg.xlinkNS, 'href', args.ref); + return node; + }, + + /* Add a link, which applies to all child elements. + @param parent (element or jQuery) the parent node for the new link (optional) + @param ref (string) the target URL + @param settings (object) additional settings for the link (optional) + @return (element) the new link node */ + link: function(parent, ref, settings) { + var args = this._args(arguments, ['ref']); + var node = this._makeNode(args.parent, 'a', args.settings); + node.setAttributeNS($.svg.xlinkNS, 'href', args.ref); + return node; + }, + + /* Add an image. + @param parent (element or jQuery) the parent node for the new image (optional) + @param x (number) the x-coordinate for the left edge of the image + @param y (number) the y-coordinate for the top edge of the image + @param width (number) the width of the image + @param height (number) the height of the image + @param ref (string) the path to the image + @param settings (object) additional settings for the image (optional) + @return (element) the new image node */ + image: function(parent, x, y, width, height, ref, settings) { + var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']); + var node = this._makeNode(args.parent, 'image', $.extend( + {x: args.x, y: args.y, width: args.width, height: args.height}, + args.settings || {})); + node.setAttributeNS($.svg.xlinkNS, 'href', args.ref); + return node; + }, + + /* Draw a path. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param path (string or SVGPath) the path to draw + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + path: function(parent, path, settings) { + var args = this._args(arguments, ['path']); + return this._makeNode(args.parent, 'path', $.extend( + {d: (args.path.path ? args.path.path() : args.path)}, args.settings || {})); + }, + + /* Draw a rectangle. + Specify both of rx and ry or neither. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param x (number) the x-coordinate for the left edge of the rectangle + @param y (number) the y-coordinate for the top edge of the rectangle + @param width (number) the width of the rectangle + @param height (number) the height of the rectangle + @param rx (number) the x-radius of the ellipse for the rounded corners (optional) + @param ry (number) the y-radius of the ellipse for the rounded corners (optional) + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + rect: function(parent, x, y, width, height, rx, ry, settings) { + var args = this._args(arguments, ['x', 'y', 'width', 'height', 'rx', 'ry'], ['rx']); + return this._makeNode(args.parent, 'rect', $.extend( + {x: args.x, y: args.y, width: args.width, height: args.height}, + (args.rx ? {rx: args.rx, ry: args.ry} : {}), args.settings || {})); + }, + + /* Draw a circle. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param cx (number) the x-coordinate for the centre of the circle + @param cy (number) the y-coordinate for the centre of the circle + @param r (number) the radius of the circle + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + circle: function(parent, cx, cy, r, settings) { + var args = this._args(arguments, ['cx', 'cy', 'r']); + return this._makeNode(args.parent, 'circle', $.extend( + {cx: args.cx, cy: args.cy, r: args.r}, args.settings || {})); + }, + + /* Draw an ellipse. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param cx (number) the x-coordinate for the centre of the ellipse + @param cy (number) the y-coordinate for the centre of the ellipse + @param rx (number) the x-radius of the ellipse + @param ry (number) the y-radius of the ellipse + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + ellipse: function(parent, cx, cy, rx, ry, settings) { + var args = this._args(arguments, ['cx', 'cy', 'rx', 'ry']); + return this._makeNode(args.parent, 'ellipse', $.extend( + {cx: args.cx, cy: args.cy, rx: args.rx, ry: args.ry}, args.settings || {})); + }, + + /* Draw a line. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param x1 (number) the x-coordinate for the start of the line + @param y1 (number) the y-coordinate for the start of the line + @param x2 (number) the x-coordinate for the end of the line + @param y2 (number) the y-coordinate for the end of the line + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + line: function(parent, x1, y1, x2, y2, settings) { + var args = this._args(arguments, ['x1', 'y1', 'x2', 'y2']); + return this._makeNode(args.parent, 'line', $.extend( + {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2}, args.settings || {})); + }, + + /* Draw a polygonal line. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param points (number[][]) the x-/y-coordinates for the points on the line + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + polyline: function(parent, points, settings) { + var args = this._args(arguments, ['points']); + return this._poly(args.parent, 'polyline', args.points, args.settings); + }, + + /* Draw a polygonal shape. + @param parent (element or jQuery) the parent node for the new shape (optional) + @param points (number[][]) the x-/y-coordinates for the points on the shape + @param settings (object) additional settings for the shape (optional) + @return (element) the new shape node */ + polygon: function(parent, points, settings) { + var args = this._args(arguments, ['points']); + return this._poly(args.parent, 'polygon', args.points, args.settings); + }, + + /* Draw a polygonal line or shape. */ + _poly: function(parent, name, points, settings) { + var ps = ''; + for (var i = 0; i < points.length; i++) { + ps += points[i].join() + ' '; + } + return this._makeNode(parent, name, $.extend( + {points: $.trim(ps)}, settings || {})); + }, + + /* Draw text. + Specify both of x and y or neither of them. + @param parent (element or jQuery) the parent node for the text (optional) + @param x (number or number[]) the x-coordinate(s) for the text (optional) + @param y (number or number[]) the y-coordinate(s) for the text (optional) + @param value (string) the text content or + (SVGText) text with spans and references + @param settings (object) additional settings for the text (optional) + @return (element) the new text node */ + text: function(parent, x, y, value, settings) { + var args = this._args(arguments, ['x', 'y', 'value']); + if (typeof args.x == 'string' && arguments.length < 4) { + args.value = args.x; + args.settings = args.y; + args.x = args.y = null; + } + return this._text(args.parent, 'text', args.value, $.extend( + {x: (args.x && isArray(args.x) ? args.x.join(' ') : args.x), + y: (args.y && isArray(args.y) ? args.y.join(' ') : args.y)}, + args.settings || {})); + }, + + /* Draw text along a path. + @param parent (element or jQuery) the parent node for the text (optional) + @param path (string) the ID of the path + @param value (string) the text content or + (SVGText) text with spans and references + @param settings (object) additional settings for the text (optional) + @return (element) the new text node */ + textpath: function(parent, path, value, settings) { + var args = this._args(arguments, ['path', 'value']); + var node = this._text(args.parent, 'textPath', args.value, args.settings || {}); + node.setAttributeNS($.svg.xlinkNS, 'href', args.path); + return node; + }, + + /* Draw text. */ + _text: function(parent, name, value, settings) { + var node = this._makeNode(parent, name, settings); + if (typeof value == 'string') { + node.appendChild(node.ownerDocument.createTextNode(value)); + } + else { + for (var i = 0; i < value._parts.length; i++) { + var part = value._parts[i]; + if (part[0] == 'tspan') { + var child = this._makeNode(node, part[0], part[2]); + child.appendChild(node.ownerDocument.createTextNode(part[1])); + node.appendChild(child); + } + else if (part[0] == 'tref') { + var child = this._makeNode(node, part[0], part[2]); + child.setAttributeNS($.svg.xlinkNS, 'href', part[1]); + node.appendChild(child); + } + else if (part[0] == 'textpath') { + var set = $.extend({}, part[2]); + set.href = null; + var child = this._makeNode(node, part[0], set); + child.setAttributeNS($.svg.xlinkNS, 'href', part[2].href); + child.appendChild(node.ownerDocument.createTextNode(part[1])); + node.appendChild(child); + } + else { // straight text + node.appendChild(node.ownerDocument.createTextNode(part[1])); + } + } + } + return node; + }, + + /* Add a custom SVG element. + @param parent (element or jQuery) the parent node for the new element (optional) + @param name (string) the name of the element + @param settings (object) additional settings for the element (optional) + @return (element) the new custom node */ + other: function(parent, name, settings) { + var args = this._args(arguments, ['name']); + return this._makeNode(args.parent, args.name, args.settings || {}); + }, + + /* Create a shape node with the given settings. */ + _makeNode: function(parent, name, settings) { + parent = parent || this._svg; + var node = this._svg.ownerDocument.createElementNS($.svg.svgNS, name); + for (var name in settings) { + var value = settings[name]; + if (value != null && value != null && + (typeof value != 'string' || value != '')) { + node.setAttribute($.svg._attrNames[name] || name, value); + } + } + parent.appendChild(node); + return node; + }, + + /* Add an existing SVG node to the diagram. + @param parent (element or jQuery) the parent node for the new node (optional) + @param node (element) the new node to add or + (string) the jQuery selector for the node or + (jQuery collection) set of nodes to add + @return (SVGWrapper) this wrapper */ + add: function(parent, node) { + var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']); + var svg = this; + args.parent = args.parent || this._svg; + try { + if ($.svg._renesis) { + throw 'Force traversal'; + } + args.parent.appendChild(args.node.cloneNode(true)); + } + catch (e) { + args.node = (args.node.jquery ? args.node : $(args.node)); + args.node.each(function() { + var child = svg._cloneAsSVG(this); + if (child) { + args.parent.appendChild(child); + } + }); + } + return this; + }, + + /* SVG nodes must belong to the SVG namespace, so clone and ensure this is so. */ + _cloneAsSVG: function(node) { + var newNode = null; + if (node.nodeType == 1) { // element + newNode = this._svg.ownerDocument.createElementNS( + $.svg.svgNS, this._checkName(node.nodeName)); + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + if (attr.nodeName != 'xmlns' && attr.nodeValue) { + if (attr.prefix == 'xlink') { + newNode.setAttributeNS($.svg.xlinkNS, attr.localName, attr.nodeValue); + } + else { + newNode.setAttribute(this._checkName(attr.nodeName), attr.nodeValue); + } + } + } + for (var i = 0; i < node.childNodes.length; i++) { + var child = this._cloneAsSVG(node.childNodes[i]); + if (child) { + newNode.appendChild(child); + } + } + } + else if (node.nodeType == 3) { // text + if ($.trim(node.nodeValue)) { + newNode = this._svg.ownerDocument.createTextNode(node.nodeValue); + } + } + else if (node.nodeType == 4) { // CDATA + if ($.trim(node.nodeValue)) { + try { + newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue); + } + catch (e) { + newNode = this._svg.ownerDocument.createTextNode( + node.nodeValue.replace(/&/g, '&'). + replace(//g, '>')); + } + } + } + return newNode; + }, + + /* Node names must be lower case and without SVG namespace prefix. */ + _checkName: function(name) { + name = (name.substring(0, 1) >= 'A' && name.substring(0, 1) <= 'Z' ? + name.toLowerCase() : name); + return (name.substring(0, 4) == 'svg:' ? name.substring(4) : name); + }, + + /* Load an external SVG document. + @param url (string) the location of the SVG document or + the actual SVG content + @param settings (boolean) see addTo below or + (function) see onLoad below or + (object) additional settings for the load with attributes below: + addTo (boolean) true to add to what's already there, + or false to clear the canvas first + changeSize (boolean) true to allow the canvas size to change, + or false to retain the original + onLoad (function) callback after the document has loaded, + 'this' is the container, receives SVG object and + optional error message as a parameter + @return (SVGWrapper) this root */ + load: function(url, settings) { + settings = (typeof settings == 'boolean'? {addTo: settings} : + (typeof settings == 'function'? {onLoad: settings} : settings || {})); + if (!settings.addTo) { + this.clear(false); + } + var size = [this._svg.getAttribute('width'), this._svg.getAttribute('height')]; + var wrapper = this; + // Report a problem with the load + var reportError = function(message) { + message = $.svg.local.errorLoadingText + ': ' + message; + if (settings.onLoad) { + settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper, message]); + } + else { + wrapper.text(null, 10, 20, message); + } + }; + // Create a DOM from SVG content + var loadXML4IE = function(data) { + var xml = new ActiveXObject('Microsoft.XMLDOM'); + xml.validateOnParse = false; + xml.resolveExternals = false; + xml.async = false; + xml.loadXML(data); + if (xml.parseError.errorCode != 0) { + reportError(xml.parseError.reason); + return null; + } + return xml; + }; + // Load the SVG DOM + var loadSVG = function(data) { + if (!data) { + return; + } + if (data.documentElement.nodeName != 'svg') { + var errors = data.getElementsByTagName('parsererror'); + var messages = (errors.length ? errors[0].getElementsByTagName('div') : []); // Safari + reportError(!errors.length ? '???' : + (messages.length ? messages[0] : errors[0]).firstChild.nodeValue); + return; + } + var attrs = {}; + for (var i = 0; i < data.documentElement.attributes.length; i++) { + var attr = data.documentElement.attributes.item(i); + if (!(attr.nodeName == 'version' || attr.nodeName.substring(0, 5) == 'xmlns')) { + attrs[attr.nodeName] = attr.nodeValue; + } + } + wrapper.configure(attrs, true); + var nodes = data.documentElement.childNodes; + for (var i = 0; i < nodes.length; i++) { + try { + if ($.svg._renesis) { + throw 'Force traversal'; + } + wrapper._svg.appendChild(nodes[i].cloneNode(true)); + if (nodes[i].nodeName == 'script') { + $.globalEval(nodes[i].textContent); + } + } + catch (e) { + wrapper.add(null, nodes[i]); + } + } + if (!settings.changeSize) { + wrapper.configure({width: size[0], height: size[1]}); + } + if (settings.onLoad) { + settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper]); + } + }; + if (url.match(''; + } + else { // Element + svgDoc = '<' + node.nodeName; + if (node.attributes) { + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + if (!($.trim(attr.nodeValue) == '' || attr.nodeValue.match(/^\[object/) || + attr.nodeValue.match(/^function/))) { + svgDoc += ' ' + (attr.namespaceURI == $.svg.xlinkNS ? 'xlink:' : '') + + attr.nodeName + '="' + attr.nodeValue + '"'; + } + } + } + if (node.firstChild) { + svgDoc += '>'; + var child = node.firstChild; + while (child) { + svgDoc += this._toSVG(child); + child = child.nextSibling; + } + svgDoc += ''; + } + else { + svgDoc += '/>'; + } + } + return svgDoc; + }, + + /* Escape reserved characters in XML. */ + _escapeXML: function(text) { + text = text.replace(/&/g, '&'); + text = text.replace(//g, '>'); + return text; + } +}); + +/* Helper to generate an SVG path. + Obtain an instance from the SVGWrapper object. + String calls together to generate the path and use its value: + var path = root.createPath(); + root.path(null, path.move(100, 100).line(300, 100).line(200, 300).close(), {fill: 'red'}); + or + root.path(null, path.move(100, 100).line([[300, 100], [200, 300]]).close(), {fill: 'red'}); */ +function SVGPath() { + this._path = ''; +} + +$.extend(SVGPath.prototype, { + /* Prepare to create a new path. + @return (SVGPath) this path */ + reset: function() { + this._path = ''; + return this; + }, + + /* Move the pointer to a position. + @param x (number) x-coordinate to move to or + (number[][]) x-/y-coordinates to move to + @param y (number) y-coordinate to move to (omitted if x is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + move: function(x, y, relative) { + relative = (isArray(x) ? y : relative); + return this._coords((relative ? 'm' : 'M'), x, y); + }, + + /* Draw a line to a position. + @param x (number) x-coordinate to move to or + (number[][]) x-/y-coordinates to move to + @param y (number) y-coordinate to move to (omitted if x is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + line: function(x, y, relative) { + relative = (isArray(x) ? y : relative); + return this._coords((relative ? 'l' : 'L'), x, y); + }, + + /* Draw a horizontal line to a position. + @param x (number) x-coordinate to draw to or + (number[]) x-coordinates to draw to + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + horiz: function(x, relative) { + this._path += (relative ? 'h' : 'H') + (isArray(x) ? x.join(' ') : x); + return this; + }, + + /* Draw a vertical line to a position. + @param y (number) y-coordinate to draw to or + (number[]) y-coordinates to draw to + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + vert: function(y, relative) { + this._path += (relative ? 'v' : 'V') + (isArray(y) ? y.join(' ') : y); + return this; + }, + + /* Draw a cubic Bézier curve. + @param x1 (number) x-coordinate of beginning control point or + (number[][]) x-/y-coordinates of control and end points to draw to + @param y1 (number) y-coordinate of beginning control point (omitted if x1 is array) + @param x2 (number) x-coordinate of ending control point (omitted if x1 is array) + @param y2 (number) y-coordinate of ending control point (omitted if x1 is array) + @param x (number) x-coordinate of curve end (omitted if x1 is array) + @param y (number) y-coordinate of curve end (omitted if x1 is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + curveC: function(x1, y1, x2, y2, x, y, relative) { + relative = (isArray(x1) ? y1 : relative); + return this._coords((relative ? 'c' : 'C'), x1, y1, x2, y2, x, y); + }, + + /* Continue a cubic Bézier curve. + Starting control point is the reflection of the previous end control point. + @param x2 (number) x-coordinate of ending control point or + (number[][]) x-/y-coordinates of control and end points to draw to + @param y2 (number) y-coordinate of ending control point (omitted if x2 is array) + @param x (number) x-coordinate of curve end (omitted if x2 is array) + @param y (number) y-coordinate of curve end (omitted if x2 is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + smoothC: function(x2, y2, x, y, relative) { + relative = (isArray(x2) ? y2 : relative); + return this._coords((relative ? 's' : 'S'), x2, y2, x, y); + }, + + /* Draw a quadratic Bézier curve. + @param x1 (number) x-coordinate of control point or + (number[][]) x-/y-coordinates of control and end points to draw to + @param y1 (number) y-coordinate of control point (omitted if x1 is array) + @param x (number) x-coordinate of curve end (omitted if x1 is array) + @param y (number) y-coordinate of curve end (omitted if x1 is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + curveQ: function(x1, y1, x, y, relative) { + relative = (isArray(x1) ? y1 : relative); + return this._coords((relative ? 'q' : 'Q'), x1, y1, x, y); + }, + + /* Continue a quadratic Bézier curve. + Control point is the reflection of the previous control point. + @param x (number) x-coordinate of curve end or + (number[][]) x-/y-coordinates of points to draw to + @param y (number) y-coordinate of curve end (omitted if x is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + smoothQ: function(x, y, relative) { + relative = (isArray(x) ? y : relative); + return this._coords((relative ? 't' : 'T'), x, y); + }, + + /* Generate a path command with (a list of) coordinates. */ + _coords: function(cmd, x1, y1, x2, y2, x3, y3) { + if (isArray(x1)) { + for (var i = 0; i < x1.length; i++) { + var cs = x1[i]; + this._path += (i == 0 ? cmd : ' ') + cs[0] + ',' + cs[1] + + (cs.length < 4 ? '' : ' ' + cs[2] + ',' + cs[3] + + (cs.length < 6 ? '': ' ' + cs[4] + ',' + cs[5])); + } + } + else { + this._path += cmd + x1 + ',' + y1 + + (x2 == null ? '' : ' ' + x2 + ',' + y2 + + (x3 == null ? '' : ' ' + x3 + ',' + y3)); + } + return this; + }, + + /* Draw an arc to a position. + @param rx (number) x-radius of arc or + (number/boolean[][]) x-/y-coordinates and flags for points to draw to + @param ry (number) y-radius of arc (omitted if rx is array) + @param xRotate (number) x-axis rotation (degrees, clockwise) (omitted if rx is array) + @param large (boolean) true to draw the large part of the arc, + false to draw the small part (omitted if rx is array) + @param clockwise (boolean) true to draw the clockwise arc, + false to draw the anti-clockwise arc (omitted if rx is array) + @param x (number) x-coordinate of arc end (omitted if rx is array) + @param y (number) y-coordinate of arc end (omitted if rx is array) + @param relative (boolean) true for coordinates relative to the current point, + false for coordinates being absolute + @return (SVGPath) this path */ + arc: function(rx, ry, xRotate, large, clockwise, x, y, relative) { + relative = (isArray(rx) ? ry : relative); + this._path += (relative ? 'a' : 'A'); + if (isArray(rx)) { + for (var i = 0; i < rx.length; i++) { + var cs = rx[i]; + this._path += (i == 0 ? '' : ' ') + cs[0] + ',' + cs[1] + ' ' + + cs[2] + ' ' + (cs[3] ? '1' : '0') + ',' + + (cs[4] ? '1' : '0') + ' ' + cs[5] + ',' + cs[6]; + } + } + else { + this._path += rx + ',' + ry + ' ' + xRotate + ' ' + + (large ? '1' : '0') + ',' + (clockwise ? '1' : '0') + ' ' + x + ',' + y; + } + return this; + }, + + /* Close the current path. + @return (SVGPath) this path */ + close: function() { + this._path += 'z'; + return this; + }, + + /* Return the string rendering of the specified path. + @return (string) stringified path */ + path: function() { + return this._path; + } +}); + +SVGPath.prototype.moveTo = SVGPath.prototype.move; +SVGPath.prototype.lineTo = SVGPath.prototype.line; +SVGPath.prototype.horizTo = SVGPath.prototype.horiz; +SVGPath.prototype.vertTo = SVGPath.prototype.vert; +SVGPath.prototype.curveCTo = SVGPath.prototype.curveC; +SVGPath.prototype.smoothCTo = SVGPath.prototype.smoothC; +SVGPath.prototype.curveQTo = SVGPath.prototype.curveQ; +SVGPath.prototype.smoothQTo = SVGPath.prototype.smoothQ; +SVGPath.prototype.arcTo = SVGPath.prototype.arc; + +/* Helper to generate an SVG text object. + Obtain an instance from the SVGWrapper object. + String calls together to generate the text and use its value: + var text = root.createText(); + root.text(null, x, y, text.string('This is '). + span('red', {fill: 'red'}).string('!'), {fill: 'blue'}); */ +function SVGText() { + this._parts = []; // The components of the text object +} + +$.extend(SVGText.prototype, { + /* Prepare to create a new text object. + @return (SVGText) this text */ + reset: function() { + this._parts = []; + return this; + }, + + /* Add a straight string value. + @param value (string) the actual text + @return (SVGText) this text object */ + string: function(value) { + this._parts[this._parts.length] = ['text', value]; + return this; + }, + + /* Add a separate text span that has its own settings. + @param value (string) the actual text + @param settings (object) the settings for this text + @return (SVGText) this text object */ + span: function(value, settings) { + this._parts[this._parts.length] = ['tspan', value, settings]; + return this; + }, + + /* Add a reference to a previously defined text string. + @param id (string) the ID of the actual text + @param settings (object) the settings for this text + @return (SVGText) this text object */ + ref: function(id, settings) { + this._parts[this._parts.length] = ['tref', id, settings]; + return this; + }, + + /* Add text drawn along a path. + @param id (string) the ID of the path + @param value (string) the actual text + @param settings (object) the settings for this text + @return (SVGText) this text object */ + path: function(id, value, settings) { + this._parts[this._parts.length] = ['textpath', value, + $.extend({href: id}, settings || {})]; + return this; + } +}); + +/* Attach the SVG functionality to a jQuery selection. + @param command (string) the command to run (optional, default 'attach') + @param options (object) the new settings to use for these SVG instances + @return jQuery (object) for chaining further calls */ +$.fn.svg = function(options) { + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options == 'string' && options == 'get') { + return $.svg['_' + options + 'SVG'].apply($.svg, [this[0]].concat(otherArgs)); + } + return this.each(function() { + if (typeof options == 'string') { + $.svg['_' + options + 'SVG'].apply($.svg, [this].concat(otherArgs)); + } + else { + $.svg._attachSVG(this, options || {}); + } + }); +}; + +/* Determine whether an object is an array. */ +function isArray(a) { + return (a && a.constructor == Array); +} + +// Singleton primary SVG interface +$.svg = new SVGManager(); + +})(jQuery); diff --git a/stemmaweb/root/js/jquery.svgdom.js b/stemmaweb/root/js/jquery.svgdom.js new file mode 100644 index 0000000..df9ce61 --- /dev/null +++ b/stemmaweb/root/js/jquery.svgdom.js @@ -0,0 +1,332 @@ +/* http://keith-wood.name/svg.html + SVG/jQuery DOM compatibility for jQuery v1.4.3. + Written by Keith Wood (kbwood{at}iinet.com.au) April 2009. + Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and + MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Please attribute the author if you use it. */ + +(function($) { // Hide scope, no $ conflict + +/* Support adding class names to SVG nodes. */ +$.fn.addClass = function(origAddClass) { + return function(classNames) { + classNames = classNames || ''; + return this.each(function() { + if (isSVGElem(this)) { + var node = this; + $.each(classNames.split(/\s+/), function(i, className) { + var classes = (node.className ? node.className.baseVal : node.getAttribute('class')); + if ($.inArray(className, classes.split(/\s+/)) == -1) { + classes += (classes ? ' ' : '') + className; + (node.className ? node.className.baseVal = classes : + node.setAttribute('class', classes)); + } + }); + } + else { + origAddClass.apply($(this), [classNames]); + } + }); + }; +}($.fn.addClass); + +/* Support removing class names from SVG nodes. */ +$.fn.removeClass = function(origRemoveClass) { + return function(classNames) { + classNames = classNames || ''; + return this.each(function() { + if (isSVGElem(this)) { + var node = this; + $.each(classNames.split(/\s+/), function(i, className) { + var classes = (node.className ? node.className.baseVal : node.getAttribute('class')); + classes = $.grep(classes.split(/\s+/), function(n, i) { return n != className; }). + join(' '); + (node.className ? node.className.baseVal = classes : + node.setAttribute('class', classes)); + }); + } + else { + origRemoveClass.apply($(this), [classNames]); + } + }); + }; +}($.fn.removeClass); + +/* Support toggling class names on SVG nodes. */ +$.fn.toggleClass = function(origToggleClass) { + return function(className, state) { + return this.each(function() { + if (isSVGElem(this)) { + if (typeof state !== 'boolean') { + state = !$(this).hasClass(className); + } + $(this)[(state ? 'add' : 'remove') + 'Class'](className); + } + else { + origToggleClass.apply($(this), [className, state]); + } + }); + }; +}($.fn.toggleClass); + +/* Support checking class names on SVG nodes. */ +$.fn.hasClass = function(origHasClass) { + return function(className) { + className = className || ''; + var found = false; + this.each(function() { + if (isSVGElem(this)) { + var classes = (this.className ? this.className.baseVal : + this.getAttribute('class')).split(/\s+/); + found = ($.inArray(className, classes) > -1); + } + else { + found = (origHasClass.apply($(this), [className])); + } + return !found; + }); + return found; + }; +}($.fn.hasClass); + +/* Support attributes on SVG nodes. */ +$.fn.attr = function(origAttr) { + return function(name, value, type) { + if (typeof name === 'string' && value === undefined) { + var val = origAttr.apply(this, [name, value, type]); + if (val && val.baseVal && val.baseVal.numberOfItems != null) { // Transform + value = ''; + val = val.baseVal; + for (var i = 0; i < val.numberOfItems; i++) { + var item = val.getItem(i); + switch (item.type) { + case 1: value += ' matrix(' + item.matrix.a + ',' + item.matrix.b + ',' + + item.matrix.c + ',' + item.matrix.d + ',' + + item.matrix.e + ',' + item.matrix.f + ')'; + break; + case 2: value += ' translate(' + item.matrix.e + ',' + item.matrix.f + ')'; break; + case 3: value += ' scale(' + item.matrix.a + ',' + item.matrix.d + ')'; break; + case 4: value += ' rotate(' + item.angle + ')'; break; // Doesn't handle new origin + case 5: value += ' skewX(' + item.angle + ')'; break; + case 6: value += ' skewY(' + item.angle + ')'; break; + } + } + val = value.substring(1); + } + return (val && val.baseVal ? val.baseVal.valueAsString : val); + } + var options = name; + if (typeof name === 'string') { + options = {}; + options[name] = value; + } + return this.each(function() { + if (isSVGElem(this)) { + for (var n in options) { + var val = ($.isFunction(options[n]) ? options[n]() : options[n]); + (type ? this.style[n] = val : this.setAttribute(n, val)); + } + } + else { + origAttr.apply($(this), [name, value, type]); + } + }); + }; +}($.fn.attr); + +/* Support removing attributes on SVG nodes. */ +$.fn.removeAttr = function(origRemoveAttr) { + return function(name) { + return this.each(function() { + if (isSVGElem(this)) { + (this[name] && this[name].baseVal ? this[name].baseVal.value = '' : + this.setAttribute(name, '')); + } + else { + origRemoveAttr.apply($(this), [name]); + } + }); + }; +}($.fn.removeAttr); + +/* Determine if any nodes are SVG nodes. */ +function anySVG(checkSet) { + for (var i = 0; i < checkSet.length; i++) { + if (checkSet[i].nodeType == 1 && checkSet[i].namespaceURI == $.svg.svgNS) { + return true; + } + } + return false; +} + +/* Update Sizzle selectors. */ + +$.expr.relative['+'] = function(origRelativeNext) { + return function(checkSet, part, isXML) { + origRelativeNext(checkSet, part, isXML || anySVG(checkSet)); + }; +}($.expr.relative['+']); + +$.expr.relative['>'] = function(origRelativeChild) { + return function(checkSet, part, isXML) { + origRelativeChild(checkSet, part, isXML || anySVG(checkSet)); + }; +}($.expr.relative['>']); + +$.expr.relative[''] = function(origRelativeDescendant) { + return function(checkSet, part, isXML) { + origRelativeDescendant(checkSet, part, isXML || anySVG(checkSet)); + }; +}($.expr.relative['']); + +$.expr.relative['~'] = function(origRelativeSiblings) { + return function(checkSet, part, isXML) { + origRelativeSiblings(checkSet, part, isXML || anySVG(checkSet)); + }; +}($.expr.relative['~']); + +$.expr.find.ID = function(origFindId) { + return function(match, context, isXML) { + return (isSVGElem(context) ? + [context.ownerDocument.getElementById(match[1])] : + origFindId(match, context, isXML)); + }; +}($.expr.find.ID); + +var div = document.createElement('div'); +div.appendChild(document.createComment('')); +if (div.getElementsByTagName('*').length > 0) { // Make sure no comments are found + $.expr.find.TAG = function(match, context) { + var results = context.getElementsByTagName(match[1]); + if (match[1] === '*') { // Filter out possible comments + var tmp = []; + for (var i = 0; results[i] || results.item(i); i++) { + if ((results[i] || results.item(i)).nodeType === 1) { + tmp.push(results[i] || results.item(i)); + } + } + results = tmp; + } + return results; + }; +} + +$.expr.preFilter.CLASS = function(match, curLoop, inplace, result, not, isXML) { + match = ' ' + match[1].replace(/\\/g, '') + ' '; + if (isXML) { + return match; + } + for (var i = 0, elem = {}; elem != null; i++) { + elem = curLoop[i]; + if (!elem) { + try { + elem = curLoop.item(i); + } + catch (e) { + // Ignore + } + } + if (elem) { + var className = (!isSVGElem(elem) ? elem.className : + (elem.className ? elem.className.baseVal : '') || elem.getAttribute('class')); + if (not ^ (className && (' ' + className + ' ').indexOf(match) > -1)) { + if (!inplace) + result.push(elem); + } + else if (inplace) { + curLoop[i] = false; + } + } + } + return false; +}; + +$.expr.filter.CLASS = function(elem, match) { + var className = (!isSVGElem(elem) ? elem.className : + (elem.className ? elem.className.baseVal : elem.getAttribute('class'))); + return (' ' + className + ' ').indexOf(match) > -1; +}; + +$.expr.filter.ATTR = function(origFilterAttr) { + return function(elem, match) { + var handler = null; + if (isSVGElem(elem)) { + handler = match[1]; + $.expr.attrHandle[handler] = function(elem){ + var attr = elem.getAttribute(handler); + return attr && attr.baseVal || attr; + }; + } + var filter = origFilterAttr(elem, match); + if (handler) { + $.expr.attrHandle[handler] = null; + } + return filter; + }; +}($.expr.filter.ATTR); + +/* + Change Sizzle initialisation (line 1425) in jQuery v1.3.2 base code... + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] || set.item(i) ); // Here + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] || set.item(i) ); // Here + } + } + } + } + + Change fallback makeArray (line 2076) implementation in jQuery Sizzle... + + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] || array.item(i) ); // Here + } + } +*/ + +/* + Events management requires changes to jQuery v1.3.2 base code... + + In $.event.add (line 2437)... + + if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) { + // Bind the global event handler to the element + try { // Here + elem.addEventListener(type, handle, false); + } + catch(e) { + if (elem.attachEvent) + elem.attachEvent("on" + type, handle); + } + } + + In $.event.remove (line 2521)... + + if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) { + try { // Here + elem.removeEventListener(type, jQuery.data(elem, "handle"), false); + } + catch (e) { + if (elem.detachEvent) + elem.detachEvent("on" + type, jQuery.data(elem, "handle")); + } + } +*/ + +/* Does this node belong to SVG? */ +function isSVGElem(node) { + return (node.nodeType == 1 && node.namespaceURI == $.svg.svgNS); +} + +})(jQuery); diff --git a/stemmaweb/root/src/header.tt b/stemmaweb/root/src/header.tt index 24349eb..02542dd 100644 --- a/stemmaweb/root/src/header.tt +++ b/stemmaweb/root/src/header.tt @@ -6,6 +6,8 @@ + + [% content %] [% pagetitle %]