diff --git a/wui/src/app/controllers/graph_controller.rb b/wui/src/app/controllers/graph_controller.rb new file mode 100644 index 0000000..8f3b699 --- /dev/null +++ b/wui/src/app/controllers/graph_controller.rb @@ -0,0 +1,44 @@ +class GraphController < ApplicationController + #This is static test data to show how we would format whatever we get back from the + #data api. We can pass that api: + #* node id + #* type of data we want back (things like summary, memory, storage, etc.) + #* timeframe we are interested in. This one goes into 'timepoints' + # and probably would call some rails helpers to format the date info however we want + + def graph + if params[:type] =="Memory" + graph_object = { + :timepoints => [], + :dataset => [{ + :name =>'IE', + :values => [86.64], + :fill => 'lightblue', + :stroke => 'blue', + :strokeWidth => 3 + } + ] + } + else + graph_object = { + :timepoints => ["April 1", "April 2","April 3","April 4"], + :dataset => [{ + :name =>'Peak', + :values => [95.97, 91.80, 88.16, 86.64], + :fill => 'lightblue', + :stroke => 'blue', + :strokeWidth => 3 + }, + { + :name =>'Average', + :values => [3.39, 2.83, 1.61, 0.00], + :fill => 'pink', + :stroke => 'red', + :strokeWidth => 3 + } + ] + } + end + render :json => graph_object + end +end diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index 6e9b30b..bae80a4 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -72,6 +72,10 @@ class HardwareController < ApplicationController def show_hosts show end + + def show_graphs + show + end def hosts_json json_list(@pool.hosts, diff --git a/wui/src/app/helpers/graph_helper.rb b/wui/src/app/helpers/graph_helper.rb new file mode 100644 index 0000000..ea7d3d5 --- /dev/null +++ b/wui/src/app/helpers/graph_helper.rb @@ -0,0 +1,3 @@ +module GraphHelper + +end diff --git a/wui/src/app/views/hardware/show_graphs.rhtml b/wui/src/app/views/hardware/show_graphs.rhtml new file mode 100644 index 0000000..41e5656 --- /dev/null +++ b/wui/src/app/views/hardware/show_graphs.rhtml @@ -0,0 +1,12 @@ +
+ + + <%= javascript_include_tag "jquery-svg/custom_test.js" -%> + <%= render :partial => '/layouts/graph', + :locals => {:div_id => "bar_graph",:chartType => "bar",:dataType =>"Memory"} %> + <%= render :partial => '/layouts/graph', + :locals => {:div_id => "cool_graph",:chartType => "line",:dataType =>"summary"} %> +
\ No newline at end of file diff --git a/wui/src/app/views/layouts/_graph.rhtml b/wui/src/app/views/layouts/_graph.rhtml new file mode 100644 index 0000000..af1faf6 --- /dev/null +++ b/wui/src/app/views/layouts/_graph.rhtml @@ -0,0 +1,12 @@ + +
\ No newline at end of file diff --git a/wui/src/app/views/layouts/_navigation_tabs.rhtml b/wui/src/app/views/layouts/_navigation_tabs.rhtml index e820f46..a3b94e4 100644 --- a/wui/src/app/views/layouts/_navigation_tabs.rhtml +++ b/wui/src/app/views/layouts/_navigation_tabs.rhtml @@ -6,6 +6,7 @@ + <% elsif controller.controller_name == "resources" and @vm_resource_pool != nil %> diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml index 4009750..85800df 100644 --- a/wui/src/app/views/layouts/redux.rhtml +++ b/wui/src/app/views/layouts/redux.rhtml @@ -6,6 +6,7 @@ <%= yield :title -%> + <%# remove me %> <%= stylesheet_link_tag 'layout' %> <%= stylesheet_link_tag 'components' %> <%= stylesheet_link_tag '/javascripts/jquery-treeview/ovirt.treeview.css' %> @@ -14,7 +15,9 @@ <%= javascript_include_tag "jquery-treeview/jquery.treeview.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.async.js" -%> <%= javascript_include_tag "flexigrid.js" -%> - <%= yield :scripts -%> + <%= javascript_include_tag "jquery-svg/jquery.svg.pack.js" -%> + + <%= javascript_include_tag "jquery-svg/jquery.svggraph.pack.js" -%> - <%= yield :scripts -%> + <%= yield :scripts -%> diff --git a/wui/src/public/javascripts/jquery-svg/blank.svg b/wui/src/public/javascripts/jquery-svg/blank.svg new file mode 100644 index 0000000..730d3d5 --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/blank.svg @@ -0,0 +1,3 @@ + + + diff --git a/wui/src/public/javascripts/jquery-svg/custom_test.js b/wui/src/public/javascripts/jquery-svg/custom_test.js new file mode 100644 index 0000000..decfee9 --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/custom_test.js @@ -0,0 +1,67 @@ + //var chartArea = [[0.1, 0.1, 0.95, 0.9], [0.2, 0.1, 0.95, 0.9], + // [0.1, 0.1, 0.8, 0.9], [0.1, 0.25, 0.9, 0.9], [0.1, 0.1, 0.9, 0.8]]; + //var legendArea = [[0.0, 0.0, 0.0, 0.0], [0.005, 0.1, 0.125, 0.5], + // [0.875, 0.1, 0.995, 0.5], [0.2, 0.1, 0.8, 0.2], [0.2, 0.9, 0.8, 0.995]]; + //var fills = [['lightblue', 'url(#fadeBlue)'], ['pink', 'url(#fadeRed)'], + // ['lightgreen', 'url(#fadeGreen)']]; + function buildGraph(options) { + var settings = $.extend({ + svg_container: "", + graph_url: "test", + id: 1, + dataType: "summary", + chartType: "line", //tell svg plugin if we want line, bar, or pie graphs + chartTitle: "", + timeframe: "7 days", + xTitle: "", + yTitle: "" + },options||{}); + var svg = svgManager.getSVGFor(settings.svg_container); + var params = { + id:settings.id, //id of the node to get graph data for + type:settings.dataType, /*this will be what we pass in to tell + *the data api what kind of data to return*/ + timeframe:settings.timeframe, //what time period do we want back from the data api? + isJSON:true}; /*while this is not used right now, there is the chance we will want to + call the data api before rendering the page, this is a flag to allow that*/ + $.getJSON(settings.graph_url, params, function(response) { + var defs = svg.defs(); + var legendPos = 1; +/* +* Everything else that is commented out from here down is experimental stuff that we shouldn't need right away, +* but may want to play with as we make this look nicer. +*/ + //svg.linearGradient(defs, 'fadeBlue', [[0, 'lightblue'], [1, 'blue']]); + //svg.linearGradient(defs, 'fadeRed', [[0, 'pink'], [1, 'red']]); + //svg.linearGradient(defs, 'fadeGreen', [[0, 'lightgreen'], [1, 'green']]); + svg.graph.noDraw().title(settings.chartTitle,10). + chartFormat('lightyellow', 'gray'). + //gridlines({stroke: 'gray', stroke_dasharray: '2,2'}, 'gray'). + status(setStatus); + $(response.dataset).each(function(){ + svg.graph.noDraw(). + addSeries(this.name, this.values, this.fill, this.stroke, this.strokeWidth); + + }); + svg.graph.xAxis.title(settings.xTitle).scale(0, 3); + if (response.timepoints.length > 0){ + svg.graph.xAxis.ticks(1, 0).labels(response.timepoints); + } + svg.graph.yAxis.title(settings.yTitle).scale(-5, 105).ticks(10, 5); + //svg.graph.legend.settings({fill: 'lightgoldenrodyellow', stroke: 'gray'}); + svg.graph.legend.show(legendPos).area([0.0, 0.0, 0.0, 0.0]); + //for (var i = 0; i < 3; i++) { + // svg.graph.series()[i].format((fills[i])[0]); + //} + svg.graph.noDraw().//chartArea(chartArea[legendPos]). + chartType(settings.chartType, {explode: [2], explodeDist: 10}).redraw(); + + }); +} +/*callback function for when you mouse over the data. + *I hope for this eventually to highlight the datapoint and show any additional information + *the design may call for. + **/ +function setStatus(value) { + //alert(value); +} \ No newline at end of file diff --git a/wui/src/public/javascripts/jquery-svg/jquery.svg.css b/wui/src/public/javascripts/jquery-svg/jquery.svg.css new file mode 100644 index 0000000..26ae31f --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/jquery.svg.css @@ -0,0 +1,9 @@ +/* http://home.iprimus.com.au/kbwood/jquery/svg.html + SVG for jQuery v1.0.0. + Written by Keith Wood (kbwood@iprimus.com.au) August 2007. + Under the Creative Commons Licence http://creativecommons.org/licenses/by/3.0/ + Share or Remix it but please Attribute the author. */ + +svg\:svg { display: none; } + +.svg_error { color: red; font-weight: bold; } diff --git a/wui/src/public/javascripts/jquery-svg/jquery.svg.js b/wui/src/public/javascripts/jquery-svg/jquery.svg.js new file mode 100644 index 0000000..dea8794 --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/jquery.svg.js @@ -0,0 +1,1101 @@ +/* http://keith-wood.name/svg.html + SVG for jQuery v1.0.1. + Written by Keith Wood (kbwood@iprimus.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. */ + +var svgManager = null; + +(function($) { // Hide scope, no $ conflict + +/* SVG manager. + Use the singleton instance of this class, svgManager, + to interact with the SVG functionality. */ +function SVGManager() { + this._nextId = 0; // Next ID for a SVG root + this._svgs = []; // List of SVG roots indexed by ID + this._settings = []; // Settings to be remembered per SVG object: + // [0] is width, [1] is height, [2] is URL to load initially, [3] is callback function + this._extensions = []; // List of SVG extensions added to SVGRoot + // for each entry [0] is extension name, [1] is extension class (function) + // the function takes one parameter - the SVGRoot instance + this.regional = []; // Localisations, indexed by language, '' for default (English) + this.regional[''] = {notSupportedText: 'This browser does not support SVG', + errorLoadingText: 'Error loading'}; + this.local = this.regional['']; // Current localisation +} + +$.extend(SVGManager.prototype, { + /* SVG namespace. */ + svgNS: 'http://www.w3.org/2000/svg', + /* XLink namespace. */ + xlinkNS: 'http://www.w3.org/1999/xlink', + + /* SVG root class. */ + _rootClass: SVGRoot, + + /* Add the SVG object to its container. */ + _connectSVG: function(container, loadURL, onLoad, settings) { + var id = this._nextId++; + container._svgId = id; + var svg = null; + if ($.browser.msie) { + container.innerHTML = ''; + this._settings[id] = [container, loadURL, settings, onLoad]; + } + else if (document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG","1.1") || + document.implementation.hasFeature("org.w3c.svg", "1.1")) { + 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(id, svg, [container, loadURL, settings, onLoad]); + } + else { + container.innerHTML = '

' + this.local.notSupportedText + '

'; + } + return id; + }, + + /* SVG callback after loading - register SVG root. */ + _registerSVG: function() { + for (var i = 0; i < document.embeds.length; i++) { // Check all + var id = document.embeds[i].parentNode._svgId; + var svg = document.embeds[i].getSVGDocument(); + svg = (svg ? svg.documentElement : null); + if (id != null && svg && !this._svgs[id]) { // Valid and not already done + this._afterLoad(id, svg); + } + } + }, + + /* Post-processing once loaded. */ + _afterLoad: function(id, svg, settings) { + var settings = settings || this._settings[id]; + var root = this._svgs[id] = new this._rootClass(svg, settings[0]); + if (settings[1]) { // Load URL + root.load(settings[1]); + } + if (settings[2]) { // Additional settings + root.configure(settings[2]); + } + if (settings[3]) { // Onload callback + settings[3](settings[0]); + } + }, + + /* Return the SVG root 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 the corresponding SVG root element, or null if not attached */ + getSVGFor: function(container) { + container = (typeof container == 'string' ? $(container)[0] : + (container.jquery ? container[0] : container)); + return this._svgs[container._svgId]; + }, + + /* Extend the SVGRoot 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 SVGRoot attribute to access the new class + @param extClass function - the extension class constructor */ + addExtension: function(name, extClass) { + this._extensions[this._extensions.length] = [name, extClass]; + } +}); + + + +/* The main SVG interface, which encapsulates the SVG element. + Obtain a reference from svgManager.getSVGFor(). */ +function SVGRoot(svg, container) { + this._svg = svg; // The SVG root node + this._container = container; // The containing div + for (var i = 0; i < svgManager._extensions.length; i++) { + var extension = svgManager._extensions[i]; + this[extension[0]] = new extension[1](this); + } +} + +$.extend(SVGRoot.prototype, { + + /* Retrieve the width of the SVG object. */ + _width: function() { + return this._container.clientWidth; + }, + + /* Retrieve the height of the SVG object. */ + _height: function() { + return this._container.clientHeight; + }, + + /* 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 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 the element's identifier + @return the element reference, or null if not found */ + getElementById: function(id) { + return this._svg.getElementById(id); + }, + + /* Add a title. + @param parent element - the parent node for the new title + @param text string - the text of the title + @param settings object - additional settings for the title (optional) + @return the new title node */ + title: function(parent, text, settings) { + var node = this._makeNode(parent, 'title', settings || {}); + node.appendChild(this._svg.ownerDocument.createTextNode(text)); + return node; + }, + + /* Add a description. + @param parent element - the parent node for the new description + @param text string - the text of the description + @param settings object - additional settings for the description (optional) + @return the new description node */ + describe: function(parent, text, settings) { + var node = this._makeNode(parent, 'desc', settings || {}); + node.appendChild(this._svg.ownerDocument.createTextNode(text)); + return node; + }, + + /* Add a definitions node. + @param parent element - the parent node for the new definitions + @param id string - the ID of this definitions (optional) + @param settings object - additional settings for the definitions (optional) + @return the new definitions node */ + defs: function(parent, id, settings) { + if (typeof id != 'string') { + settings = id; + id = null; + } + return this._makeNode(parent, 'defs', $.extend( + (id ? {id: id} : {}), settings || {})); + }, + + /* Add a symbol definition. + @param parent element - the parent node for the new symbol + @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 x2 number - the right coordinate for this symbol + @param y2 number - the bottom coordinate for this symbol + @param settings object - additional settings for the symbol (optional) + @return the new symbol node */ + symbol: function(parent, id, x1, y1, x2, y2, settings) { + return this._makeNode(parent, 'symbol', $.extend( + {id: id, viewBox: x1 + ' ' + y1 + ' ' + x2 + ' ' + y2}, settings || {})); + }, + + /* Add a marker definition. + @param parent element - the parent node for the new marker + @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 the new marker node */ + marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) { + if (typeof orient == 'object') { + settings = orient; + orient = null; + } + return this._makeNode(parent, 'marker', $.extend( + {id: id, refX: refX, refY: refY, markerWidth: mWidth, + markerHeight: mHeight, orient: orient || 'auto'}, settings || {})); + }, + + /* Add a style node. + @param parent element - the parent node for the new node + @param styles string - the CSS styles + @param settings object - additional settings for the node (optional) + @return the new style node */ + style: function(parent, styles, settings) { + var node = this._makeNode(parent, 'style', $.extend( + {type: 'text/css'}, settings || {})); + node.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(styles))); + return node; + }, + + /* Add a script node. + @param parent element - the parent node for the new node + @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 the new script node */ + script: function(parent, script, type, settings) { + if (typeof type == 'object') { + settings = type; + type = null; + } + var node = this._makeNode(parent, 'script', $.extend( + {type: type || 'text/javascript'}, settings || {})); + node.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(script))); + return node; + }, + + /* Add a linear gradient definition. + Specify all of x1, y1, x2, y2 or none of them. + @param parent element - the parent node for the new gradient + @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 the new gradient node */ + linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) { + if (typeof x1 == 'object') { + settings = x1; + x1 = null; + } + var sets = $.extend({id: id}, + (x1 != null ? {x1: x1, y1: y1, x2: x2, y2: y2} : {})); + return this._gradient(parent, 'linearGradient', + $.extend(sets, settings || {}), stops); + }, + + /* Add a radial gradient definition. + Specify all of cx, cy, r, fx, fy or none of them. + @param parent element - the parent node for the new gradient + @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 the new gradient node */ + radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) { + if (typeof cx == 'object') { + settings = cx; + cx = null; + } + var sets = $.extend({id: id}, + (cx != null ? {cx: cx, cy: cy, r: r, fx: fx, fy: fy} : {})); + return this._gradient(parent, 'radialGradient', + $.extend(sets, settings || {}), 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], stop_color: stop[1]}, + (stop[2] != null ? {stop_opacity: stop[2]} : {}))); + } + return node; + }, + + /* Add a pattern definition. + Specify all of vx, vy, xwidth, vheight or none of them. + @param parent element - the parent node for the new pattern + @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 the new pattern node */ + pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) { + if (typeof vx == 'object') { + settings = vx; + vx = null; + } + var sets = $.extend({id: id, x: x, y: y, width: width, height: height}, + (vx != null ? {viewBox: vx + ' ' + vy + ' ' + vwidth + ' ' + vheight} : {})); + return this._makeNode(parent, 'pattern', $.extend(sets, settings || {})); + }, + + /* Add a mask definition. + @param parent element - the parent node for the new mask + @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 the new mask node */ + mask: function(parent, id, x, y, width, height, settings) { + return this._makeNode(parent, 'mask', $.extend( + {id: id, x: x, y: y, width: width, height: height}, settings || {})); + }, + + /* Create a new path object. + @return a new path object */ + createPath: function() { + return new SVGPath(); + }, + + /* Create a new text object. + @return 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 - the parent node for the new node + @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 the new node */ + svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) { + if (typeof vx == 'object') { + settings = vx; + vx = null; + } + var sets = $.extend({x: x, y: y, width: width, height: height}, + (vx != null ? {viewBox: vx + ' ' + vy + ' ' + vwidth + ' ' + vheight} : {})); + return this._makeNode(parent, 'svg', $.extend(sets, settings || {})); + }, + + /* Create a group. + @param parent element - the parent node for the new group + @param id string - the ID of this group (optional) + @param settings object - additional settings for the group (optional) + @return the new group node */ + group: function(parent, id, settings) { + if (typeof id == 'object') { + settings = id; + id = null; + } + return this._makeNode(parent, 'g', $.extend({id: id}, settings || {})); + }, + + /* Add a usage reference. + Specify all of x, y, width, height or none of them. + @param parent element - the parent node for the new node + @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 the new node */ + use: function(parent, x, y, width, height, ref, settings) { + if (typeof x == 'string') { + ref = x; + settings = y; + x = y = width = height = null; + } + var node = this._makeNode(parent, 'use', $.extend( + {x: x, y: y, width: width, height: height}, settings || {})); + node.setAttributeNS(svgManager.xlinkNS, 'href', ref); + return node; + }, + + /* Add a link, which applies to all child elements. + @param parent element - the parent node for the new link + @param ref string - the target URL + @param settings object - additional settings for the link (optional) + @return the new link node */ + link: function(parent, ref, settings) { + var node = this._makeNode(parent, 'a', settings); + node.setAttributeNS(svgManager.xlinkNS, 'href', ref); + return node; + }, + + /* Add an image. + @param parent element - the parent node for the new image + @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 the new image node */ + image: function(parent, x, y, width, height, ref, settings) { + var node = this._makeNode(parent, 'image', $.extend( + {x: x, y: y, width: width, height: height}, settings || {})); + node.setAttributeNS(svgManager.xlinkNS, 'href', ref); + return node; + }, + + /* Draw a path. + @param parent element - the parent node for the new shape + @param path string or SVGPath - the path to draw + @param settings object - additional settings for the shape (optional) + @return the new shape node */ + path: function(parent, path, settings) { + return this._makeNode(parent, 'path', $.extend( + {d: (path.path ? path.path() : path)}, settings || {})); + }, + + /* Draw a rectangle. + @param parent element - the parent node for the new shape + @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 settings object - additional settings for the shape (optional) + @return the new shape node */ + rect: function(parent, x, y, width, height, settings) { + return this._makeNode(parent, 'rect', $.extend( + {x: x, y: y, width: width, height: height}, settings || {})); + }, + + /* Draw a rounded rectangle. + @param parent element - the parent node for the new shape + @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 + @param ry number - the y-radius of the ellipse for the rounded corners + @param settings object - additional settings for the shape (optional) + @return the new shape node */ + roundrect: function(parent, x, y, width, height, rx, ry, settings) { + return this._makeNode(parent, 'rect', $.extend( + {x: x, y: y, width: width, height: height, rx: rx, ry: ry}, settings || {})); + }, + + /* Draw a circle. + @param parent element - the parent node for the new shape + @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 the new shape node */ + circle: function(parent, cx, cy, r, settings) { + return this._makeNode(parent, 'circle', $.extend( + {cx: cx, cy: cy, r: r}, settings || {})); + }, + + /* Draw an ellipse. + @param parent element - the parent node for the new shape + @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 the new shape node */ + ellipse: function(parent, cx, cy, rx, ry, settings) { + return this._makeNode(parent, 'ellipse', $.extend( + {cx: cx, cy: cy, rx: rx, ry: ry}, settings || {})); + }, + + /* Draw a line. + @param parent element - the parent node for the new shape + @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 the new shape node */ + line: function(parent, x1, y1, x2, y2, settings) { + return this._makeNode(parent, 'line', $.extend( + {x1: x1, y1: y1, x2: x2, y2: y2}, settings || {})); + }, + + /* Draw a polygonal line. + @param parent element - the parent node for the new shape + @param points number[][] - the x-/y-coordinates for the points on the line + @param settings object - additional settings for the shape (optional) + @return the new shape node */ + polyline: function(parent, points, settings) { + return this._poly(parent, 'polyline', points, settings); + }, + + /* Draw a polygonal shape. + @param parent element - the parent node for the new shape + @param points number[][] - the x-/y-coordinates for the points on the shape + @param settings object - additional settings for the shape (optional) + @return the new shape node */ + polygon: function(parent, points, settings) { + return this._poly(parent, 'polygon', points, 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: ps}, settings || {})); + }, + + /* Draw text. + Specify both of x and y or neither of them. + @param parent element - the parent node for the text + @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 the new text node */ + text: function(parent, x, y, value, settings) { + if (typeof x == 'string' && arguments.length < 4) { + value = x; + settings = y; + x = y = null; + } + return this._text(parent, 'text', value, $.extend( + {x: (x && isArray(x) ? x.join(' ') : x), y: (y && isArray(y) ? y.join(' ') : y)}, + settings || {})); + }, + + /* Draw text along a path. + @param parent element - the parent node for the text + @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 the new text node */ + textpath: function(parent, path, value, settings) { + var node = this._text(parent, 'textPath', value, settings || {}); + node.setAttributeNS(svgManager.xlinkNS, 'href', 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(svgManager.xlinkNS, 'href', part[1]); + node.appendChild(child); + } + else if (part[0] == 'textpath') { + var pathId = part[2].href; + part[2].href = null; + var child = this._makeNode(node, part[0], part[2]); + child.setAttributeNS(svgManager.xlinkNS, 'href', pathId); + 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 - the parent node for the new element + @param name string - the name of the element + @param settings object - additional settings for the element (optional) + @return the new title node */ + other: function(parent, name, settings) { + return this._makeNode(parent, other, settings || {}); + }, + + /* Create a shape node with the given settings. */ + _makeNode: function(parent, name, settings) { + parent = parent || this._svg; + var node = this._svg.ownerDocument.createElementNS(svgManager.svgNS, name); + for (var name in settings) { + var value = settings[name]; + if (value != null && value != null && + (typeof value != 'string' || value != '')) { + node.setAttribute(this._fromJSName(name), value); + } + } + parent.appendChild(node); + return node; + }, + + /* JavaScript identifiers can't use '-', so convert from '_'. */ + _fromJSName: function(name) { + return name.replace(/^_/, '').replace(/_/g, '-'); + }, + + /* Add an existing SVG node to the diagram. + @param parent element - the parent node for the new node + @param node element - the new node to add or + string - the jQuery selector for the node or + jQuery collection - set of nodes to add */ + add: function(parent, node) { + var svg = this; + parent = parent || this._svg; + node = (node.jquery ? node : $(node)); + node.each(function() { + var child = svg._cloneAsSVG(this); + if (child) { + parent.appendChild(child); + } + }); + }, + + /* 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( + node.namespaceURI || svgManager.svgNS, this._checkName(node.nodeName)); + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + if (attr.nodeName != 'xmlns') { + if (attr.prefix == 'xlink') { + newNode.setAttributeNS(svgManager.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)) { + newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue); + } + } + 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 + @param addTo boolean - true to add to what's already there, or false to clear the canvas first */ + load: function(url, addTo) { + if (!addTo) { + this.clear(true); + } + var root = this; + var http = $.ajax({url: url, dataType: 'xml', success: function(data) { + if ($.browser.msie) { // Doesn't load properly! + data.loadXML(http.responseText); + if (data.parseError.errorCode != 0) { + root.text(null, 10, 20, svgManager.local.errorLoadingText + ': ' + + data.parseError.reason); + 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; + } + } + root.configure(attrs, true); + var nodes = data.documentElement.childNodes; + for (var i = 0; i < nodes.length; i++) { + root.add(null, nodes[i]); + } + }, error: function(http, message, exc) { + root.text(null, 10, 20, svgManager.local.errorLoadingText + ': ' + + message + (exc ? ' ' + exc.message : '')); + }}); + }, + + /* Delete a specified node. + @param node element - the drawing node to remove */ + remove: function(node) { + node.parentNode.removeChild(node); + }, + + /* Delete everything in the current document. + @param attrsToo boolean - true to clear any root attributes as well, + false to leave them (optional) */ + clear: function(attrsToo) { + if (attrsToo) { + this.configure({}, true); + } + while (this._svg.firstChild) { + this._svg.removeChild(this._svg.firstChild); + } + }, + + /* Serialise the current diagram into an SVG text document. + @return the SVG as text */ + toSVG: function() { + return this._toSVG(this._svg); + }, + + /* Serialise one node in the SVG hierarchy. */ + _toSVG: function(node) { + var svgDoc = ''; + if (!node) { + return svgDoc; + } + if (node.nodeType == 3) { // Text + svgDoc = node.nodeValue; + } + else if (node.nodeType == 4) { // CDATA + svgDoc = ''; + } + 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 == svgManager.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 SVGRoot object. + String calls together to generate the path and use its value: + var path = root.createPath(); + root.path(null, path.moveTo(100, 100).lineTo(300, 100).lineTo(200, 300).close(), {fill: 'red'}); + or + root.path(null, path.moveTo(100, 100).lineTo([[300, 100], [200, 300]]).close(), {fill: 'red'}); */ +function SVGPath() { + this._path = ''; +} + +$.extend(SVGPath.prototype, { + /* Prepare to create a new path. + @return 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 this path */ + moveTo: 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 this path */ + lineTo: 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 + @param relative boolean - true for coordinates relative to the current point, + false for coordinates being absolute + @return this path */ + horizTo: function(x, relative) { + this._path += (relative ? 'h' : 'H') + x; + return this; + }, + + /* Draw a vertical line to a position. + @param y number - y-coordinate to draw to + @param relative boolean - true for coordinates relative to the current point, + false for coordinates being absolute + @return this path */ + vertTo: function(y, relative) { + this._path += (relative ? 'v' : 'V') + 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 this path */ + curveCTo: 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 this path */ + smoothCTo: 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 this path */ + curveQTo: 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 this path */ + smoothQTo: 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 this path */ + arcTo: 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 this path */ + close: function() { + this._path += 'z'; + return this; + }, + + /* Return the string rendering of the specified path. + @return stringified path */ + path: function() { + return this._path; + } +}); + +/* Helper to generate an SVG text object. + Obtain an instance from the SVGRoot 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 this text */ + reset: function() { + this._parts = []; + return this; + }, + + /* Add a straight string value. + @param value string - the actual text + @return 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 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 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 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 loadURL string - the URL of the initial document to load (optional) + @param onLoad function - a callback functional invoked following loading (optional) + @param settings object - the new settings to use for this SVG instance (optional) + @return jQuery object - for chaining further calls */ +$.fn.svg = function(loadURL, onLoad, settings) { + if (typeof loadURL == 'function') { + settings = onLoad; + onLoad = loadURL; + loadURL = null; + } + if (loadURL && typeof loadURL == 'object') { + settings = loadURL; + loadURL = onLoad = null; + } + if (onLoad && typeof onLoad == 'object') { + settings = onLoad; + onLoad = null; + } + return this.each(function() { + svgManager._connectSVG(this, loadURL, onLoad, settings); + }); +}; + +/* Determine whether an object is an array. */ +function isArray(a) { + return (a.constructor && a.constructor.toString().match(/\Array\(\)/)); +} + +// Singleton primary SVG interface +svgManager = new SVGManager(); + +})(jQuery); diff --git a/wui/src/public/javascripts/jquery-svg/jquery.svg.min.js b/wui/src/public/javascripts/jquery-svg/jquery.svg.min.js new file mode 100644 index 0000000..e0b75fe --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/jquery.svg.min.js @@ -0,0 +1,7 @@ +/* http://keith-wood.name/svg.html + SVG for jQuery v1.0.1. + Written by Keith Wood (kbwood@iprimus.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. */ +var svgManager=null;(function($){function SVGManager(){this._nextId=0;this._svgs=[];this._settings=[];this._extensions=[];this.regional=[];this.regional['']={notSupportedText:'This browser does not support SVG',errorLoadingText:'Error loading'};this.local=this.regional['']}$.extend(SVGManager.prototype,{svgNS:'http://www.w3.org/2000/svg',xlinkNS:'http://www.w3.org/1999/xlink',_rootClass:SVGRoot,_connectSVG:function(a,b,c,d){var e=this._nextId++;a._svgId=e;var f=null;if($.browser.msie){a.innerHTML='';this._settings[e]=[a,b,d,c]}else if(document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG","1.1")||document.implementation.hasFeature("org.w3c.svg","1.1")){f=document.createElementNS(this.svgNS,'svg');f.setAttribute('version','1.1');f.setAttribute('width',a.clientWidth);f.setAttribute('height',a.clientHeight);a.appendChild(f);this._afterLoad(e,f,[a,b,d,c])}else{a.innerHTML='

'+this.local.notSupportedText+'

'}return e},_registerSVG:function(){for(var i=0;i=0;i--){var c=this._svg.attributes.item(i);if(!(c.nodeName=='onload'||c.nodeName=='version'||c.nodeName.substring(0,5)=='xmlns')){this._svg.attributes.removeNamedItem(c.nodeName)}}}for(var d in a){this._svg.setAttribute(d,a[d])}return this},getElementById:function(a){return this._svg.getElementById(a)},title:function(a,b,c){var d=this._makeNode(a,'title',c||{});d.appendChild(this._svg.ownerDocument.createTextNode(b));return d},describe:function(a,b,c){var d=this._makeNode(a,'desc',c||{});d.appendChild(this._svg.ownerDocument.createTextNode(b));return d},defs:function(a,b,c){if(typeof b!='string'){c=b;b=null}return this._makeNode(a,'defs',$.extend((b?{id:b}:{}),c||{}))},symbol:function(a,b,c,d,e,f,g){return this._makeNode(a,'symbol',$.extend({id:b,viewBox:c+' '+d+' '+e+' '+f},g||{}))},marker:function(a,b,c,d,e,f,g,h){if(typeof g=='object'){h=g;g=null}return this._makeNode(a,'marker',$.extend({id:b,refX:c,refY:d,markerWidth:e,markerHeight:f,orient:g||'auto'},h||{}))},style:function(a,b,c){var d=this._makeNode(a,'style',$.extend({type:'text/css'},c||{}));d.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(b)));return d},script:function(a,b,c,d){if(typeof c=='object'){d=c;c=null}var e=this._makeNode(a,'script',$.extend({type:c||'text/javascript'},d||{}));e.appendChild(this._svg.ownerDocument.createTextNode(this._escapeXML(b)));return e},linearGradient:function(a,b,c,d,e,f,g,h){if(typeof d=='object'){h=d;d=null}var i=$.extend({id:b},(d!=null?{x1:d,y1:e,x2:f,y2:g}:{}));return this._gradient(a,'linearGradient',$.extend(i,h||{}),c)},radialGradient:function(a,b,c,d,e,r,f,g,h){if(typeof d=='object'){h=d;d=null}var i=$.extend({id:b},(d!=null?{cx:d,cy:e,r:r,fx:f,fy:g}:{}));return this._gradient(a,'radialGradient',$.extend(i,h||{}),c)},_gradient:function(a,b,c,d){var e=this._makeNode(a,b,c);for(var i=0;i='A'&&a.substring(0,1)<='Z'?a.toLowerCase():a);return(a.substring(0,4)=='svg:'?a.substring(4):a)},load:function(e,f){if(!f){this.clear(true)}var g=this;var h=$.ajax({url:e,dataType:'xml',success:function(a){if($.browser.msie){a.loadXML(h.responseText);if(a.parseError.errorCode!=0){g.text(null,10,20,svgManager.local.errorLoadingText+': '+a.parseError.reason);return}}var b={};for(var i=0;i'}else{b='<'+a.nodeName;if(a.attributes){for(var i=0;i';var d=a.firstChild;while(d){b+=this._toSVG(d);d=d.nextSibling}b+=''}else{b+='/>'}}return b},_escapeXML:function(a){a=a.replace(/&/g,'&');a=a.replace(//g,'>');return a}});function SVGPath(){this._path=''}$.extend(SVGPath.prototype,{reset:function(){this._path='';return this},moveTo:function(x,y,a){a=(isArray(x)?y:a);return this._coords((a?'m':'M'),x,y)},lineTo:function(x,y,a){a=(isArray(x)?y:a);return this._coords((a?'l':'L'),x,y)},horizTo:function(x,a){this._path+=(a?'h':'H')+x;return this},vertTo:function(y,a){this._path+=(a?'v':'V')+y;return this},curveCTo:function(a,b,c,d,x,y,e){e=(isArray(a)?b:e);return this._coords((e?'c':'C'),a,b,c,d,x,y)},smoothCTo:function(a,b,x,y,c){c=(isArray(a)?b:c);return this._coords((c?'s':'S'),a,b,x,y)},curveQTo:function(a,b,x,y,c){c=(isArray(a)?b:c);return this._coords((c?'q':'Q'),a,b,x,y)},smoothQTo:function(x,y,a){a=(isArray(x)?y:a);return this._coords((a?'t':'T'),x,y)},_coords:function(a,b,c,d,e,f,g){if(isArray(b)){for(var i=0;i35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('k E=u;(8($){8 1E(){7.2S=0;7.1p=[];7.1L=[];7.1i=[];7.1z=[];7.1z[\'\']={2b:\'33 25 40 3W 3N 2z\',1N:\'3E 3A\'};7.1l=7.1z[\'\']}$.o(1E.1n,{1m:\'1w://1T.1u.1o/4i/Y\',14:\'1w://1T.1u.1o/3X/1Z\',2G:1Y,2L:8(a,b,c,d){k e=7.2S++;a.1I=e;k f=u;n($.25.2j){a.2g=\'<3s 1y="1A/Y+2V" 11="\'+a.1K+\'" 13="\'+a.1S+\'" 38="35.Y"/>\';7.1L[e]=[a,b,d,c]}J n(1d.2T.2P("1w://1T.1u.1o/4h/4b/48#2z","1.1")||1d.2T.2P("1o.3Y.Y","1.1")){f=1d.24(7.1m,\'Y\');f.1c(\'21\',\'1.1\');f.1c(\'11\',a.1K);f.1c(\'13\',a.1S);a.G(f);7.1V(e,f,[a,b,d,c])}J{a.2g=\'

\'+7.1l.2b+\'

\'}9 e},3L:8(){I(k i=0;i<1d.1P.B;i++){k a=1d.1P[i].2x.1I;k b=1d.1P[i].3G();b=(b?b.1j:u);n(a!=u&&b&&!7.1p[a]){7.1V(a,b)}}},1V:8(a,b,c){k c=c||7.1L[a];k d=7.1p[a]=1f 7.2G(b,c[0]);n(c[1]){d.2r(c[1])}n(c[2]){d.1r(c[2])}n(c[3]){c[3](c[0])}},3B:8(a){a=(F a==\'17\'?$(a)[0]:(a.2m?a[0]:a));9 7.1p[a.1I]},3y:8(a,b){7.1i[7.1i.B]=[a,b]}});8 1Y(a,b){7.D=a;7.1F=b;I(k i=0;i=0;i--){k c=7.D.W.1q(i);n(!(c.K==\'3q\'||c.K==\'21\'||c.K.1b(0,5)==\'1G\')){7.D.W.3j(c.K)}}}I(k d 2d a){7.D.1c(d,a[d])}9 7},2o:8(a){9 7.D.2o(a)},2q:8(a,b,c){k d=7.w(a,\'2q\',c||{});d.G(7.D.P.15(b));9 d},3c:8(a,b,c){k d=7.w(a,\'3a\',c||{});d.G(7.D.P.15(b));9 d},2D:8(a,b,c){n(F b!=\'17\'){c=b;b=u}9 7.w(a,\'2D\',$.o((b?{16:b}:{}),c||{}))},2F:8(a,b,c,d,e,f,g){9 7.w(a,\'2F\',$.o({16:b,1U:c+\' \'+d+\' \'+e+\' \'+f},g||{}))},2U:8(a,b,c,d,e,f,g,h){n(F g==\'X\'){h=g;g=u}9 7.w(a,\'2U\',$.o({16:b,30:c,2Y:d,2X:e,2W:f,4g:g||\'4c\'},h||{}))},2Q:8(a,b,c){k d=7.w(a,\'2Q\',$.o({1y:\'1a/46\'},c||{}));d.G(7.D.P.15(7.26(b)));9 d},2O:8(a,b,c,d){n(F c==\'X\'){d=c;c=u}k e=7.w(a,\'2O\',$.o({1y:c||\'1a/41\'},d||{}));e.G(7.D.P.15(7.26(b)));9 e},2N:8(a,b,c,d,e,f,g,h){n(F d==\'X\'){h=d;d=u}k i=$.o({16:b},(d!=u?{2M:d,2K:e,2J:f,2I:g}:{}));9 7.22(a,\'2N\',$.o(i,h||{}),c)},2H:8(a,b,c,d,e,r,f,g,h){n(F d==\'X\'){h=d;d=u}k i=$.o({16:b},(d!=u?{1X:d,1W:e,r:r,3V:f,3U:g}:{}));9 7.22(a,\'2H\',$.o(i,h||{}),c)},22:8(a,b,c,d){k e=7.w(a,b,c);I(k i=0;i=\'A\'&&a.1b(0,1)<=\'Z\'?a.3p():a);9(a.1b(0,4)==\'Y:\'?a.1b(4):a)},2r:8(e,f){n(!f){7.2e(1B)}k g=7;k h=$.3o({3n:e,3m:\'2V\',3l:8(a){n($.25.2j){a.3k(h.3x);n(a.2B.3i!=0){g.1a(u,10,20,E.1l.1N+\': \'+a.2B.3h);9}}k b={};I(k i=0;i\'}J{b=\'<\'+a.K;n(a.W){I(k i=0;i\';k d=a.1k;2c(d){b+=7.1v(d);d=d.39}b+=\'\'}J{b+=\'/>\'}}9 b},26:8(a){a=a.1g(/&/g,\'&3O;\');a=a.1g(//g,\'&36;\');9 a}});8 1R(){7.U=\'\'}$.o(1R.1n,{29:8(){7.U=\'\';9 7},34:8(x,y,a){a=(O(x)?y:a);9 7.18((a?\'m\':\'M\'),x,y)},32:8(x,y,a){a=(O(x)?y:a);9 7.18((a?\'l\':\'L\'),x,y)},3Z:8(x,a){7.U+=(a?\'h\':\'H\')+x;9 7},31:8(y,a){7.U+=(a?\'v\':\'V\')+y;9 7},2Z:8(a,b,c,d,x,y,e){e=(O(a)?b:e);9 7.18((e?\'c\':\'C\'),a,b,c,d,x,y)},45:8(a,b,x,y,c){c=(O(a)?b:c);9 7.18((c?\'s\':\'S\'),a,b,x,y)},47:8(a,b,x,y,c){c=(O(a)?b:c);9 7.18((c?\'q\':\'Q\'),a,b,x,y)},4f:8(x,y,a){a=(O(x)?y:a);9 7.18((a?\'t\':\'T\'),x,y)},18:8(a,b,c,d,e,f,g){n(O(b)){I(k i=0;i35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(7($){C.2d(\'1B\',H);$.5(C.2a.N,{J:7(a,b,x,y,c,d,e){m 8.l(a,\'J\',$.5({1s:b,x:x,y:y,E:c,F:d},e||{}))}});7 H(a){8.9=a}$.5(H.N,{1d:7(a,b,c,d,e){m 8.9.l(a,\'1a\',$.5({n:b,X:c,U:d},e||{}))},T:7(a,b,x,y,z,c){m 8.9.l(a,\'29\',$.5({n:b,x:x,y:y,z:z},c||{}))},1T:7(a,b,x,y,z,c,d,e,f){q(s c==\'r\'){f=c;c=p}o g=$.5({n:b,x:x,y:y,z:z},(c!=p?{1w:c,1r:d,1p:e}:{}));m 8.9.l(a,\'1o\',$.5(g,f||{}))},1l:7(a,b,c,d,e,f){m 8.9.l(a,\'1k\',$.5({n:b,1i:c,t:d,I:e},f||{}))},1c:7(a,b,c,d,e,f){q(M(e)){o g=\'\';w(o i=0;i 1 in pixels, <= 1 as proportion + this._chartFormat = {fill: 'none', stroke: 'black'}; // The formatting for the chart area + this._gridlines = []; // The formatting of the x- and y-gridlines + this._series = []; // The series to be plotted, each is an object + this._onstatus = null; // The callback function for status updates + this._chartGroup = this._root.group(null, 'graph'); // The main group for the graph + + this.xAxis = new SVGGraphAxis(); // The main x-axis + this.xAxis.title('', 40); + this.yAxis = new SVGGraphAxis(); // The main y-axis + this.yAxis.title('', 40); + this.x2Axis = null; // The secondary x-axis + this.y2Axis = null; // The secondary y-axis + this.legend = new SVGGraphLegend(); // The chart legend +} + +$.extend(SVGGraph.prototype, { + + /* Useful indexes. */ + X: 0, + Y: 1, + W: 2, + H: 3, + L: 0, + T: 1, + R: 2, + B: 3, + + /* Standard percentage axis. */ + _percentageAxis: new SVGGraphAxis(svgGraphing.region.percentageText, 0, 100, 10, 0), + + /* Set or retrieve the type of chart to be rendered. + See svgGraphing.getChartTypes() for the list of available types. + @param id string - the ID of the chart type + @param options object - additional settings for this chart type (optional) + @return SVGGraph - this graph object or + string - the chart type (if no parameters) */ + chartType: function(id, options) { + if (arguments.length == 0) { + return this._chartType; + } + var chartType = svgGraphing._chartTypes[id]; + if (chartType) { + this._chartType = chartType; + this._chartOptions = $.extend({}, options || {}); + } + this._drawGraph(); + return this; + }, + + /* Set or retrieve additional options for the particular chart type. + @param options object - the extra options + @return SVGGraph - this graph object or + object - the chart options (if no parameters) */ + chartOptions: function(options) { + if (arguments.length == 0) { + return this._chartOptions; + } + this._chartOptions = $.extend({}, options); + this._drawGraph(); + return this; + }, + + /* Set or retrieve the background of the graph chart. + @param fill string - how to fill the chart background + @param stroke string - the colour of the outline (optional) + @param settings object - additional formatting for the chart background (optional) + @return SVGGraph - this graph object or + object - the chart format (if no parameters) */ + chartFormat: function(fill, stroke, settings) { + if (arguments.length == 0) { + return this._chartFormat; + } + if (typeof stroke == 'object') { + settings = stroke; + stroke = null; + } + this._chartFormat = $.extend($.extend({fill: fill}, + (stroke ? {stroke: stroke} : {})), settings || {}); + this._drawGraph(); + return this; + }, + + /* Set or retrieve the main chart area. + @param left number - > 1 is pixels, <= 1 is proportion of width or + number[4] - for left, top, right, bottom + @param top number - > 1 is pixels, <= 1 is proportion of height + @param right number - > 1 is pixels, <= 1 is proportion of width + @param bottom number - > 1 is pixels, <= 1 is proportion of height + @return SVGGraph - this graph object or + number[4] - the chart area: left, top, right, bottom (if no parameters) */ + chartArea: function(left, top, right, bottom) { + if (arguments.length == 0) { + return this._area; + } + this._area = (isArray(left) ? left : [left, top, right, bottom]); + this._drawGraph(); + return this; + }, + + /* Set or retrieve the gridlines formatting for the graph chart. + @param xSettings string - the colour of the gridlines along the x-axis, or + object - formatting for the gridlines along the x-axis, or + null for none + @param ySettings string - the colour of the gridlines along the y-axis, or + object - formatting for the gridlines along the y-axis, or + null for none + @return SVGGraph - this graph object or + object[2] - the gridlines formatting (if no parameters) */ + gridlines: function(xSettings, ySettings) { + if (arguments.length == 0) { + return this._gridlines; + } + this._gridlines = [(typeof xSettings == 'string' ? {stroke: xSettings} : xSettings), + (typeof ySettings == 'string' ? {stroke: ySettings} : ySettings)]; + this._drawGraph(); + return this; + }, + + /* Set or retrieve the title of the graph and its formatting. + @param value string - the title + @param offset number - the vertical positioning of the title + > 1 is pixels, <= 1 is proportion of width (optional) + @param settings object - formatting for the title (optional) + @return SVGGraph - this graph object or + object - value, offset, and settings for the title (if no parameters) */ + title: function(value, offset, settings) { + if (arguments.length == 0) { + return this._title; + } + if (typeof offset == 'object') { + settings = offset; + offset = null; + } + this._title = {value: value, offset: offset || this._title.offset, + settings: $.extend({text_anchor: 'middle'}, settings || {})}; + this._drawGraph(); + return this; + }, + + /* Add a series of values to be plotted on the graph. + @param name string - the name of this series + @param values number[] - the values to be plotted + @param fill string - how the plotted values are filled + @param stroke string - the colour of the plotted lines + @param strokeWidth number - the width of the plotted lines (optional) + @param settings object - additional settings for the plotted values (optional) + @return SVGGraph - this graph object */ + addSeries: function(name, values, fill, stroke, strokeWidth, settings) { + if (typeof strokeWidth == 'object') { + settings = strokeWidth; + strokeWidth = null; + } + this._series[this._series.length] = + new SVGGraphSeries(name, values, fill, stroke, strokeWidth, settings || {}); + this._drawGraph(); + return this; + }, + + /* Retrieve the series wrappers. + @return SVGGraphSeries[] - the list of series */ + series: function() { + return this._series; + }, + + /* Suppress drawing of the graph until redraw() is called. + @return SVGGraph - this graph object */ + noDraw: function() { + this._drawNow = false; + return this; + }, + + /* Redraw the entire graph with the current settings and values. + @return SVGGraph - this graph object */ + redraw: function() { + this._drawNow = true; + this._drawGraph(); + return this; + }, + + /* Set the callback function for status updates. + @param onstatus function - the callback function + @return SVGGraph - this graph object */ + status: function(onstatus) { + this._onstatus = onstatus; + return this; + }, + + /* Actually draw the graph (if allowed) based on the graph type set. */ + _drawGraph: function() { + if (!this._drawNow) { + return; + } + while (this._chartGroup.firstChild) { + this._chartGroup.removeChild(this._chartGroup.firstChild); + } + if (!this._chartGroup.parent) { + this._root._svg.appendChild(this._chartGroup); + } + this._chartType.drawGraph(this); + }, + + /* Draw the graph title - centred. */ + _drawTitle: function() { + this._root.text(this._chartGroup, this._root._width() / 2, this._title.offset, + this._title.value, this._title.settings); + }, + + /* Calculate the actual dimensions of the chart area. + @param area number[4] - the area values to evaluate (optional) + @return an array of dimension values: left, top, width, height */ + _getDims: function(area) { + area = area || this._area; + var left = (area[this.L] > 1 ? area[this.L] : + this._root._width() * area[this.L]); + var top = (area[this.T] > 1 ? area[this.T] : + this._root._height() * area[this.T]); + var width = (area[this.R] > 1 ? area[this.R] : + this._root._width() * area[this.R]) - left; + var height = (area[this.B] > 1 ? area[this.B] : + this._root._height() * area[this.B]) - top; + return [left, top, width, height]; + }, + + /* Draw the chart background, including gridlines. + @param noXGrid boolean - true to suppress the x-gridlines, false to draw them (optional) + @param noYGrid boolean - true to suppress the y-gridlines, false to draw them (optional) + @return the background group element */ + _drawChartBackground: function(noXGrid, noYGrid) { + var bg = this._root.group(this._chartGroup, 'background'); + var dims = this._getDims(); + this._root.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._chartFormat); + if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) { + this._drawGridlines(bg, this.yAxis, true, dims, this._gridlines[0]); + } + if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) { + this._drawGridlines(bg, this.xAxis, false, dims, this._gridlines[1]); + } + return bg; + }, + + /* Draw one set of gridlines. + @param bg element - the background group element + @param axis SVGGraphAxis - the axis definition + @param horiz boolean - true if horizontal, false if vertical + @param dims number[] - the left, top, width, height of the chart area + @param format object - additional settings for the gridlines */ + _drawGridlines: function(bg, axis, horiz, dims, format) { + var g = this._root.group(bg, format); + var scale = (horiz ? dims[this.H] : dims[this.W]) / (axis._scale.max - axis._scale.min); + var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major; + major = (major < axis._scale.min ? major + axis._ticks.major : major); + while (major <= axis._scale.max) { + var v = (horiz ? axis._scale.max - major : major - axis._scale.min) * scale + + (horiz ? dims[this.Y] : dims[this.X]); + this._root.line(g, (horiz ? dims[this.X] : v), (horiz ? v : dims[this.Y]), + (horiz ? dims[this.X] + dims[this.W] : v), (horiz ? v : dims[this.Y] + dims[this.H])); + major += axis._ticks.major; + } + }, + + /* Draw the axes in their standard configuration. + @param noX boolean - true to suppress the x-axes, false to draw it (optional) */ + _drawAxes: function(noX) { + var dims = this._getDims(); + if (this.xAxis && !noX) { + if (this.xAxis._title) { + this._root.text(this._chartGroup, dims[this.X] + dims[this.W] / 2, + dims[this.Y] + dims[this.H] + this.xAxis._titleOffset, this.xAxis._title); + } + this._drawAxis(this.xAxis, 'xAxis', dims[this.X], dims[this.Y] + dims[this.H], + dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]); + } + if (this.yAxis) { + if (this.yAxis._title) { + this._root.text(this._chartGroup, 0, 0, this.yAxis._title, {text_anchor: 'middle', + transform: 'translate(' + (dims[this.X] - this.yAxis._titleOffset) + ',' + + (dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}); + } + this._drawAxis(this.yAxis, 'yAxis', dims[this.X], dims[this.Y], + dims[this.X], dims[this.Y] + dims[this.H]); + } + if (this.x2Axis && !noX) { + if (this.x2Axis._title) { + this._root.text(this._chartGroup, dims[this.X] + dims[this.W] / 2, + dims[this.X] - this.x2Axis._titleOffset, this.x2Axis._title); + } + this._drawAxis(this.x2Axis, 'x2Axis', dims[this.X], dims[this.Y], + dims[this.X] + dims[this.W], dims[this.Y]); + } + if (this.y2Axis) { + if (this.y2Axis._title) { + this._root.text(this._chartGroup, 0, 0, this.y2Axis._title, {text_anchor: 'middle', + transform: 'translate(' + (dims[this.X] + dims[this.W] + this.y2Axis._titleOffset) + + ',' + (dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}); + } + this._drawAxis(this.y2Axis, 'y2Axis', dims[this.X] + dims[this.W], dims[this.Y], + dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]); + } + }, + + /* Draw an axis and its tick marks. + @param axis SVGGraphAxis - the axis definition + @param id string - the identifier for the axis group element + @param x1 number - starting x-coodinate for the axis + @param y1 number - starting y-coodinate for the axis + @param x2 number - ending x-coodinate for the axis + @param y2 number - ending y-coodinate for the axis */ + _drawAxis: function(axis, id, x1, y1, x2, y2) { + var horiz = (y1 == y2); + var gl = this._root.group(this._chartGroup, id, axis._lineFormat); + var gt = this._root.group(this._chartGroup, id + 'Labels', + $.extend({text_anchor: (horiz ? 'middle' : 'end')}, axis._labelFormat)); + this._root.line(gl, x1, y1, x2, y2); + if (axis._ticks.major) { + var bottomRight = (x2 > (this._root._width() / 2) && + y2 > (this._root._height() / 2)); + var scale = (horiz ? x2 - x1 : y2 - y1) / (axis._scale.max - axis._scale.min); + var size = axis._ticks.size; + var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major; + major = (major < axis._scale.min ? major + axis._ticks.major : major); + var minor = (!axis._ticks.minor ? axis._scale.max + 1 : + Math.floor(axis._scale.min / axis._ticks.minor) * axis._ticks.minor); + minor = (minor < axis._scale.min ? minor + axis._ticks.minor : minor); + var offsets = this._getTickOffsets(axis, bottomRight); + while (major <= axis._scale.max || minor <= axis._scale.max) { + var cur = Math.min(major, minor); + var len = (cur == major ? size : size / 2); + var v = (horiz ? x1 : y1) + + (horiz ? cur - axis._scale.min : axis._scale.max - cur) * scale; + this._root.line(gl, (horiz ? v : x1 + len * offsets[0]), + (horiz ? y1 + len * offsets[0] : v), + (horiz ? v : x1 + len * offsets[1]), + (horiz ? y1 + len * offsets[1] : v)); + if (cur == major) { + this._root.text(gt, (horiz ? v : x1 - size), (horiz ? y1 + 2 * size : v), + (axis._labels ? axis._labels[cur] : '' + cur)); + } + major += (cur == major ? axis._ticks.major : 0); + minor += (cur == minor ? axis._ticks.minor : 0); + } + } + }, + + /* Calculate offsets based on axis and tick positions. + @param axis SVGGraphAxis - the axis definition + @param bottomRight boolean - true if this axis is appearing on the bottom or + right of the chart area, false if to the top or left + @return the array of offset multipliers (-1..+1) */ + _getTickOffsets: function(axis, bottomRight) { + return [(axis._ticks.position == (bottomRight ? 'in' : 'out') || + axis._ticks.position == 'both' ? -1 : 0), + (axis._ticks.position == (bottomRight ? 'out' : 'in') || + axis._ticks.position == 'both' ? +1 : 0), ]; + }, + + /* Retrieve the standard percentage axis. + @return percentage axis */ + _getPercentageAxis: function() { + this._percentageAxis._title = svgGraphing.region.percentageText; + return this._percentageAxis; + }, + + /* Calculate the column totals across all the series. */ + _getTotals: function() { + var totals = []; + var numVal = (this._series.length ? this._series[0]._values.length : 0); + for (var i = 0; i < numVal; i++) { + totals[i] = 0; + for (var j = 0; j < this._series.length; j++) { + totals[i] += this._series[j]._values[i]; + } + } + return totals; + }, + + /* Draw the chart legend. */ + _drawLegend: function() { + if (!this.legend._show) { + return; + } + var g = this._root.group(this._chartGroup, 'legend'); + var dims = this._getDims(this.legend._area); + this._root.rect(g, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], + this.legend._bgSettings); + var horiz = dims[this.W] > dims[this.H]; + var numSer = this._series.length; + var offset = (horiz ? dims[this.W] : dims[this.H]) / numSer; + var xBase = dims[this.X] + 5; + var yBase = dims[this.Y] + (horiz ? dims[this.H] / 2 : offset / 2); + for (var i = 0; i < numSer; i++) { + var series = this._series[i]; + this._root.rect(g, xBase + (horiz ? i * offset : 0), + yBase + (horiz ? 0 : i * offset) - this.legend._sampleSize, + this.legend._sampleSize, this.legend._sampleSize, + {fill: series._fill, stroke: series._stroke, stroke_width: 1}); + this._root.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5, + yBase + (horiz ? 0 : i * offset), series._name, this.legend._textSettings); + } + }, + + /* Show the current value status on hover. */ + _showStatus: function(value) { + var onStatus = (!this._onstatus ? '' : + this._onstatus.toString().replace(/function (.*)\([\s\S]*/m, '$1')); + return (!this._onstatus ? {} : + {onmouseover: 'window.parent.' + onStatus + '(\'' + value + '\');', + onmouseout: 'window.parent.' + onStatus + '(\'\');'}); + } +}); + +/* Details about each graph axis. + @param title string - the title of the axis + @param min number - the minimum value displayed on this axis + @param max number - the maximum value displayed on this axis + @param major number - the distance between major ticks + @param minor number - the distance between minor ticks (optional) + @return the new axis object */ +function SVGGraphAxis(title, min, max, major, minor) { + /* Title of this axis. */ + this._title = title || ''; + /* Formatting settings for the title. */ + this._titleFormat = {}; + /* The offset for positioning the title. */ + this._titleOffset = 0; + /* List of labels for this axis - one per possible value across all series. */ + this._labels = null; + /* Formatting settings for the labels. */ + this._labelFormat = {}; + /* Formatting settings for the axis lines. */ + this._lineFormat = {stroke: 'black'}; + /* Tick mark options. */ + this._ticks = {major: major || 10, minor: minor || 0, size: 10, position: 'out'}; + /* Axis scale settings. */ + this._scale = {min: min || 0, max: max || 100}; + /* Where this axis crosses the other one. */ + this._crossAt = 0; +} + +$.extend(SVGGraphAxis.prototype, { + + /* Set or retrieve the scale for this axis. + @param min number - the minimum value shown + @param max number - the maximum value shown + @return SVGGraphAxis - this axis object or + object - min and max values (if no parameters) */ + scale: function(min, max) { + if (arguments.length == 0) { + return this._scale; + } + this._scale.min = min; + this._scale.max = max; + return this; + }, + + /* Set or retrieve the ticks for this axis. + @param major number - the distance between major ticks + @param minor number - the distance between minor ticks + @param size number - the length of the major ticks (minor are half) (optional) + @param position string - the location of the ticks: + 'in', 'out', 'both' (optional) + @return SVGGraphAxis - this axis object or + object - major, minor, size, and position values (if no parameters) */ + ticks: function(major, minor, size, position) { + if (arguments.length == 0) { + return this._ticks; + } + if (typeof size == 'string') { + position = size; + size = null; + } + this._ticks.major = major; + this._ticks.minor = minor; + this._ticks.size = size || 10; + this._ticks.position = position || 'out'; + return this; + }, + + /* Set or retrieve the title for this axis. + @param title string - the title text + @param offset number - the distance to offset the title position (optional) + @param format object - formatting settings for the title (optional) + @return SVGGraphAxis - this axis object or + object - title, offset, and format values (if no parameters) */ + title: function(title, offset, format) { + if (arguments.length == 0) { + return {title: this._title, offset: this._titleOffset, format: this._titleFormat}; + } + if (typeof offset == 'object') { + format = offset; + offset = null; + } + this._title = title; + if (offset != null) { + this._titleOffset = offset; + } + if (format) { + this._titleFormat = format; + } + return this; + }, + + /* Set or retrieve the labels for this axis. + @param labels string[] - the text for each entry + @param format object - formatting settings for the labels (optional) + @return SVGGraphAxis - this axis object or + object - labels and format values (if no parameters) */ + labels: function(labels, format) { + if (arguments.length == 0) { + return {labels: this._labels, format: this._labelFormat}; + } + this._labels = labels; + if (format) { + this._labelFormat = format; + } + return this; + }, + + /* Set or retrieve the line formatting for this axis. + @param colour string - the line's colour + @param width number - the line's width (optional) + @param settings object - additional formatting settings for the line (optional) + @return SVGGraphAxis - this axis object or + object - line formatting values (if no parameters) */ + line: function(colour, width, settings) { + if (arguments.length == 0) { + return this._lineFormat; + } + if (typeof width == 'object') { + settings = width; + width = null; + } + $.extend(this._lineFormat, {stroke: colour, stroke_width: width || 1}); + $.extend(this._lineFormat, settings || {}); + return this; + } +}); + +var defaultSeriesFill = 'green'; +var defaultSeriesStroke = 'black'; + +/* Details about each graph series. + @param name string - the name of this series + @param values number[] - the list of values to be plotted + @param fill string - how the series should be displayed + @param stroke string - the colour of the (out)line for the series + @param strokeWidth number - the width of the (out)line for the series + @param settings object - additional formatting settings + @return the new series object */ +function SVGGraphSeries(name, values, fill, stroke, strokeWidth, settings) { + /* The name of this series. */ + this._name = name || ''; + /* The list of values for this series. */ + this._values = values || []; + /* Which axis this series applies to: 1 = primary, 2 = secondary. */ + this._axis = 1; + /* How the series is plotted. */ + this._fill = fill || defaultSeriesFill; + /* The colour for the (out)line. */ + this._stroke = stroke || defaultSeriesStroke; + /* The (out)line width. */ + this._strokeWidth = strokeWidth || 1; + /* Additional formatting settings for the series. */ + this._settings = settings || {}; +} + +$.extend(SVGGraphSeries.prototype, { + + /* Set or retrieve the name for this series. + @param name string - the series' name + @return SVGGraphSeries - this series object or + string - the series name (if no parameters) */ + name: function(name) { + if (arguments.length == 0) { + return this._name; + } + this._name = name; + return this; + }, + + /* Set or retrieve the values for this series. + @param name string - the series' name (optional) + @param values number[] - the values to be graphed + @return SVGGraphSeries - this series object or + number[] - the series values (if no parameters) */ + values: function(name, values) { + if (arguments.length == 0) { + return this._values; + } + if (isArray(name)) { + valus = name; + name = null; + } + this._name = name || this._name; + this._values = values; + return this; + }, + + /* Set or retrieve the formatting for this series. + @param fill string - how the values are filled when plotted + @param stroke string - the (out)line colour + @param strokeWidth number - the line's width (optional) + @param settings object - additional formatting settings for the series (optional) + @return SVGGraphSeries - this series object or + object - formatting settings (if no parameters) */ + format: function(fill, stroke, strokeWidth, settings) { + if (arguments.length == 0) { + return $.extend({fill: this._fill, stroke: this._stroke, + stroke_width: this._strokeWidth}, this._settings); + } + if (typeof strokeWidth == 'object') { + settings = strokeWidth; + strokeWidth = null; + } + this._fill = fill || defaultSeriesFill; + this._stroke = stroke || this._stroke; + this._strokeWidth = strokeWidth || this._strokeWidth; + $.extend(this._settings, settings || {}); + return this; + } +}); + +/* Details about the graph legend. + @param bgSettings object - additional formatting settings for the legend background (optional) + @param textSettings object - additional formatting settings for the legend text (optional) + @return the new legend object */ +function SVGGraphLegend(bgSettings, textSettings) { + this._show = true; // Show the legend? + this._area = [0.9, 0.1, 1.0, 0.9]; // The legend area: left, top, right, bottom, + // > 1 in pixels, <= 1 as proportion + this._sampleSize = 15; // Size of sample box + this._bgSettings = bgSettings || {stroke: 'gray'}; // Additional formatting settings for the legend background + this._textSettings = textSettings || {}; // Additional formatting settings for the text +} + +$.extend(SVGGraphLegend.prototype, { + + /* Set or retrieve whether the legend should be shown. + @param show boolean - true to display it, false to hide it + @return SVGGraphLegend - this legend object or + boolean - show the legend? (if no parameters) */ + show: function(show) { + if (arguments.length == 0) { + return this._show; + } + this._show = show; + return this; + }, + + /* Set or retrieve the legend area. + @param left number - > 1 is pixels, <= 1 is proportion of width or + number[4] - for left, top, right, bottom + @param top number - > 1 is pixels, <= 1 is proportion of height + @param right number - > 1 is pixels, <= 1 is proportion of width + @param bottom number - > 1 is pixels, <= 1 is proportion of height + @return SVGGraphLegend - this legend object or + number[4] - the legend area: left, top, right, bottom (if no parameters) */ + area: function(left, top, right, bottom) { + if (arguments.length == 0) { + return this._area; + } + this._area = (isArray(left) ? left : [left, top, right, bottom]); + return this; + }, + + /* Set or retrieve additional settings for the legend area. + @param sampleSize number - the size of the sample box to display (optional) + @param bgSettings object - additional formatting settings for the legend background + @param textSettings object - additional formatting settings for the legend text (optional) + @return SVGGraphLegend - this legend object or + object - bgSettings and textSettings for the legend (if no parameters) */ + settings: function(sampleSize, bgSettings, textSettings) { + if (arguments.length == 0) { + return {sampleSize: this._sampleSize, bgSettings: this._bgSettings, + textSettings: this._textSettings}; + } + if (typeof sampleSize == 'object') { + textSettings = bgSettings; + bgSettings = sampleSize; + sampleSize = null; + } + if (sampleSize) { + this._sampleSize = sampleSize; + } + this._bgSettings = bgSettings; + if (textSettings) { + this._textSettings = textSettings; + } + return this; + } +}); + +//============================================================================== + +/* Round a number to a given number of decimal points. */ +function roundNumber(num, dec) { + return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); +} + +var barOptions = ['barWidth (number) - the width of each bar', + 'barGap (number) - the gap between sets of bars']; + +//------------------------------------------------------------------------------ + +/* Draw a standard grouped column bar chart. */ +function SVGColumnChart() { +} + +$.extend(SVGColumnChart.prototype, { + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Basic column chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare sets of values as vertical bars with grouped categories.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return barOptions; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + graph._drawChartBackground(true); + var barWidth = graph._chartOptions.barWidth || 10; + var barGap = graph._chartOptions.barGap || 10; + var numSer = graph._series.length; + var numVal = (numSer ? (graph._series[0])._values.length : 0); + var dims = graph._getDims(); + var xScale = dims[graph.W] / ((numSer * barWidth + barGap) * numVal + barGap); + var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min); + this._chart = graph._root.group(graph._chartGroup, 'chart'); + for (var i = 0; i < numSer; i++) { + this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale); + } + graph._drawTitle(); + graph._drawAxes(true); + this._drawXAxis(graph, numSer, numVal, barWidth, barGap, dims, xScale); + graph._drawLegend(); + }, + + /* Plot an individual series. */ + _drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) { + var series = graph._series[cur]; + var g = graph._root.group(this._chart, 'series' + cur, + $.extend({stroke: series._stroke, + stroke_width: series._strokeWidth}, series._settings || {})); + for (var i = 0; i < series._values.length; i++) { + graph._root.rect(g, + dims[graph.X] + xScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)), + dims[graph.Y] + yScale * (graph.yAxis._scale.max - series._values[i]), + xScale * barWidth, yScale * series._values[i], $.extend({fill: series._fill}, + graph._showStatus(series._name + ' ' + series._values[i]))); + } + }, + + /* Draw the x-axis and its ticks. */ + _drawXAxis: function(graph, numSer, numVal, barWidth, barGap, dims, xScale) { + var axis = graph.xAxis; + if (axis._title) { + graph._root.text(graph._chartGroup, dims[graph.X] + dims[graph.W] / 2, + dims[graph.Y] + dims[graph.H] + axis._titleOffset, + axis._title, {text_anchor: 'middle'}); + } + var gl = graph._root.group(graph._chartGroup, 'xAxis', axis._lineFormat); + var gt = graph._root.group(graph._chartGroup, 'xAxisLabels', + $.extend({text_anchor: 'middle'}, axis._labelFormat)); + graph._root.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H], + dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]); + if (axis._ticks.major) { + var offsets = graph._getTickOffsets(axis, true); + for (var i = 1; i < numVal; i++) { + var x = dims[graph.X] + xScale * (barGap / 2 + i * (numSer * barWidth + barGap)); + graph._root.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size, + x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size); + } + for (var i = 0; i < numVal; i++) { + var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap)); + graph._root.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size, + (axis._labels ? axis._labels[i] : '' + i)); + } + } + } +}); + +//------------------------------------------------------------------------------ + +/* Draw a stacked column bar chart. */ +function SVGStackedColumnChart() { +} + +$.extend(SVGStackedColumnChart.prototype, { + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Stacked column chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare sets of values as vertical bars showing ' + + 'relative contributions to the whole for each category.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return barOptions; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + var bg = graph._drawChartBackground(true, true); + var dims = graph._getDims(); + if (graph._gridlines[0] && graph.xAxis._ticks.major) { + graph._drawGridlines(bg, graph._getPercentageAxis(), true, dims, graph._gridlines[0]); + } + var barWidth = graph._chartOptions.barWidth || 10; + var barGap = graph._chartOptions.barGap || 10; + var numSer = graph._series.length; + var numVal = (numSer ? (graph._series[0])._values.length : 0); + var xScale = dims[graph.W] / ((barWidth + barGap) * numVal + barGap); + var yScale = dims[graph.H]; + this._chart = graph._root.group(graph._chartGroup, 'chart'); + this._drawColumns(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale); + graph._drawTitle(); + graph._root.text(graph._chartGroup, 0, 0, svgGraphing.region.percentageText, + {text_anchor: 'middle', transform: 'translate(' + (dims[graph.X] - graph.yAxis._titleOffset) + + ',' +(dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}); + graph._drawAxis(graph._getPercentageAxis(), 'yAxis', + dims[graph.X], dims[graph.Y], dims[graph.X], dims[graph.Y] + dims[graph.H]); + this._drawXAxis(graph, numVal, barWidth, barGap, dims, xScale); + graph._drawLegend(); + }, + + /* Plot all of the columns. */ + _drawColumns: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) { + var totals = graph._getTotals(); + var accum = []; + for (var i = 0; i < numVal; i++) { + accum[i] = 0; + } + for (var s = 0; s < numSer; s++) { + var series = graph._series[s]; + var g = graph._root.group(this._chart, 'series' + s, + $.extend({stroke: series._stroke, stroke_width: series._strokeWidth}, + series._settings || {})); + for (var i = 0; i < series._values.length; i++) { + accum[i] += series._values[i]; + graph._root.rect(g, + dims[graph.X] + xScale * (barGap + i * (barWidth + barGap)), + dims[graph.Y] + yScale * (totals[i] - accum[i]) / totals[i], + xScale * barWidth, yScale * series._values[i] / totals[i], + $.extend({fill: series._fill}, graph._showStatus(series._name + ' ' + + roundNumber(series._values[i] / totals[i] * 100, 2) + '%'))); + } + } + }, + + /* Draw the x-axis and its ticks. */ + _drawXAxis: function(graph, numVal, barWidth, barGap, dims, xScale) { + var axis = graph.xAxis; + if (axis._title) { + graph._root.text(graph._chartGroup, dims[graph.X] + dims[graph.W] / 2, + dims[graph.Y] + dims[graph.H] + axis._titleOffset, + axis._title, {text_anchor: 'middle'}); + } + var gl = graph._root.group(graph._chartGroup, 'xAxis', axis._lineFormat); + var gt = graph._root.group(graph._chartGroup, 'xAxisLabels', + $.extend({text_anchor: 'middle'}, axis._labelFormat)); + graph._root.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H], + dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]); + if (axis._ticks.major) { + var offsets = graph._getTickOffsets(axis, true); + for (var i = 1; i < numVal; i++) { + var x = dims[graph.X] + xScale * (barGap / 2 + i * (barWidth + barGap)); + graph._root.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size, + x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size); + } + for (var i = 0; i < numVal; i++) { + var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap)); + graph._root.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size, + (axis._labels ? axis._labels[i] : '' + i)); + } + } + } +}); + +//------------------------------------------------------------------------------ + +/* Draw a standard grouped row bar chart. */ +function SVGRowChart() { +} + +$.extend(SVGRowChart.prototype, { + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Basic row chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare sets of values as horizontal rows with grouped categories.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return barOptions; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + var bg = graph._drawChartBackground(true, true); + var dims = graph._getDims(); + graph._drawGridlines(bg, graph.yAxis, false, dims, graph._gridlines[0]); + var barWidth = graph._chartOptions.barWidth || 10; + var barGap = graph._chartOptions.barGap || 10; + var numSer = graph._series.length; + var numVal = (numSer ? (graph._series[0])._values.length : 0); + var xScale = dims[graph.W] / (graph.yAxis._scale.max - graph.yAxis._scale.min); + var yScale = dims[graph.H] / ((numSer * barWidth + barGap) * numVal + barGap); + this._chart = graph._root.group(graph._chartGroup, 'chart'); + for (var i = 0; i < numSer; i++) { + this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale); + } + graph._drawTitle(); + this._drawAxes(graph, numSer, numVal, barWidth, barGap, dims, yScale); + graph._drawLegend(); + }, + + /* Plot an individual series. */ + _drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) { + var series = graph._series[cur]; + var g = graph._root.group(this._chart, 'series' + cur, + $.extend({stroke: series._stroke, stroke_width: series._strokeWidth}, + series._settings || {})); + for (var i = 0; i < series._values.length; i++) { + graph._root.rect(g, + dims[graph.X] + xScale * (0 - graph.yAxis._scale.min), + dims[graph.Y] + yScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)), + xScale * series._values[i], yScale * barWidth, $.extend({fill: series._fill}, + graph._showStatus(series._name + ' ' + series._values[i]))); + } + }, + + /* Draw the axes for this graph. */ + _drawAxes: function(graph, numSer, numVal, barWidth, barGap, dims, yScale) { + // X-axis + var axis = graph.yAxis; + if (axis) { + if (axis._title) { + graph._root.text(graph._chartGroup, dims[graph.X] + dims[graph.W] / 2, + dims[graph.Y] + dims[graph.H] + axis._titleOffset, axis._title, axis._titleFormat); + } + graph._drawAxis(axis, 'xAxis', dims[graph.X], dims[graph.Y] + dims[graph.H], + dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]); + } + // Y-axis + var axis = graph.xAxis; + if (axis._title) { + graph._root.text(graph._chartGroup, 0, 0, axis._title, {text_anchor: 'middle', + transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' + + (dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}); + } + var gl = graph._root.group(graph._chartGroup, 'yAxis', axis._lineFormat); + var gt = graph._root.group(graph._chartGroup, 'yAxisLabels', + $.extend({text_anchor: 'end'}, axis._labelFormat)); + graph._root.line(gl, dims[graph.X], dims[graph.Y], dims[graph.X], dims[graph.Y] + dims[graph.H]); + if (axis._ticks.major) { + var offsets = graph._getTickOffsets(axis, false); + for (var i = 1; i < numVal; i++) { + var y = dims[graph.Y] + yScale * (barGap / 2 + i * (numSer * barWidth + barGap)); + graph._root.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y, + dims[graph.X] + offsets[1] * axis._ticks.size, y); + } + for (var i = 0; i < numVal; i++) { + var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap)); + graph._root.text(gt, dims[graph.X] - axis._ticks.size, y, + (axis._labels ? axis._labels[i] : '' + i)); + } + } + } +}); + +//------------------------------------------------------------------------------ + +/* Draw a stacked row bar chart. */ +function SVGStackedRowChart() { +} + +$.extend(SVGStackedRowChart.prototype, { + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Stacked row chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare sets of values as horizontal bars showing ' + + 'relative contributions to the whole for each category.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return barOptions; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + var bg = graph._drawChartBackground(true, true); + var dims = graph._getDims(); + if (graph._gridlines[0] && graph.xAxis._ticks.major) { + graph._drawGridlines(bg, graph._getPercentageAxis(), false, dims, graph._gridlines[0]); + } + var barWidth = graph._chartOptions.barWidth || 10; + var barGap = graph._chartOptions.barGap || 10; + var numSer = graph._series.length; + var numVal = (numSer ? (graph._series[0])._values.length : 0); + var xScale = dims[graph.W]; + var yScale = dims[graph.H] / ((barWidth + barGap) * numVal + barGap); + this._chart = graph._root.group(graph._chartGroup, 'chart'); + this._drawRows(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale); + graph._drawTitle(); + graph._root.text(graph._chartGroup, dims[graph.X] + dims[graph.W] / 2, + dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset, + svgGraphing.region.percentageText, {text_anchor: 'middle'}); + graph._drawAxis(graph._getPercentageAxis(), 'xAxis', + dims[graph.X], dims[graph.Y] + dims[graph.H], + dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]); + this._drawYAxis(graph, numVal, barWidth, barGap, dims, yScale); + graph._drawLegend(); + }, + + /* Plot all of the rows. */ + _drawRows: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) { + var totals = graph._getTotals(); + var accum = []; + for (var i = 0; i < numVal; i++) { + accum[i] = 0; + } + for (var s = 0; s < numSer; s++) { + var series = graph._series[s]; + var g = graph._root.group(this._chart, 'series' + s, + $.extend({stroke: series._stroke, stroke_width: series._strokeWidth}, + series._settings || {})); + for (var i = 0; i < series._values.length; i++) { + graph._root.rect(g, + dims[graph.X] + xScale * accum[i] / totals[i], + dims[graph.Y] + yScale * (barGap + i * (barWidth + barGap)), + xScale * series._values[i] / totals[i], yScale * barWidth, + $.extend({fill: series._fill}, graph._showStatus(series._name + ' ' + + roundNumber(series._values[i] / totals[i] * 100, 2) + '%'))); + accum[i] += series._values[i]; + } + } + }, + + /* Draw the y-axis and its ticks. */ + _drawYAxis: function(graph, numVal, barWidth, barGap, dims, yScale) { + var axis = graph.xAxis; + if (axis._title) { + graph._root.text(graph._chartGroup, 0, 0, axis._title, {text_anchor: 'middle', + transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' + + (dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}); + } + var gl = graph._root.group(graph._chartGroup, 'yAxis', axis._lineFormat); + var gt = graph._root.group(graph._chartGroup, 'yAxisLabels', + $.extend({text_anchor: 'end'}, axis._labelFormat)); + graph._root.line(gl, dims[graph.X], dims[graph.Y], dims[graph.X], dims[graph.Y] + dims[graph.H]); + if (axis._ticks.major) { + var offsets = graph._getTickOffsets(axis, false); + for (var i = 1; i < numVal; i++) { + var y = dims[graph.Y] + yScale * (barGap / 2 + i * (barWidth + barGap)); + graph._root.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y, + dims[graph.X] + offsets[1] * axis._ticks.size, y); + } + for (var i = 0; i < numVal; i++) { + var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap)); + graph._root.text(gt, dims[graph.X] - axis._ticks.size, y, + (axis._labels ? axis._labels[i] : '' + i)); + } + } + } +}); + +//------------------------------------------------------------------------------ + +/* Draw a standard line chart. */ +function SVGLineChart() { +} + +$.extend(SVGLineChart.prototype, { + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Basic line chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare sets of values as continuous lines.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return []; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + graph._drawChartBackground(); + var dims = graph._getDims(); + var xScale = dims[graph.W] / (graph.xAxis._scale.max - graph.xAxis._scale.min); + var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min); + this._chart = graph._root.group(graph._chartGroup, 'chart'); + for (var i = 0; i < graph._series.length; i++) { + this._drawSeries(graph, i, dims, xScale, yScale); + } + graph._drawTitle(); + graph._drawAxes(); + graph._drawLegend(); + }, + + /* Plot an individual series. */ + _drawSeries: function(graph, cur, dims, xScale, yScale) { + var series = graph._series[cur]; + var path = graph._root.createPath(); + for (var i = 0; i < series._values.length; i++) { + var x = dims[graph.X] + i * xScale; + var y = dims[graph.Y] + (graph.yAxis._scale.max - series._values[i]) * yScale; + if (i == 0) { + path.moveTo(x, y); + } + else { + path.lineTo(x, y); + } + } + graph._root.path(this._chart, path, + $.extend($.extend({id: 'series' + cur, fill: 'none', stroke: series._stroke, + stroke_width: series._strokeWidth}, graph._showStatus(series._name), + series._settings || {}))); + } +}); + +//------------------------------------------------------------------------------ + +/* Draw a standard pie chart. */ +function SVGPieChart() { +} + +$.extend(SVGPieChart.prototype, { + + _options: ['explode (number[]) - indexes of sections to explode out of the pie', + 'explodeDist (number) - the distance to move an exploded section', + 'pieGap (number) - the distance between pies for multiple values'], + + /* Retrieve the display title for this chart type. + @return the title */ + title: function() { + return 'Pie chart'; + }, + + /* Retrieve a description of this chart type. + @return its description */ + description: function() { + return 'Compare relative sizes of values as contributions to the whole.'; + }, + + /* Retrieve a list of the options that may be set for this chart type. + @return options list */ + options: function() { + return this._options; + }, + + /* Actually draw the graph in this type's style. + @param graph object - the SVGGraph object */ + drawGraph: function(graph) { + graph._drawChartBackground(true, true); + this._chart = graph._root.group(graph._chartGroup, 'chart'); + var dims = graph._getDims(); + this._drawSeries(graph, dims); + graph._drawTitle(); + graph._drawLegend(); + }, + + /* Plot all the series. */ + _drawSeries: function(graph, dims) { + var totals = graph._getTotals(); + var numSer = graph._series.length; + var numVal = (numSer ? (graph._series[0])._values.length : 0); + var path = graph._root.createPath(); + var explode = graph._chartOptions.explode || []; + var explodeDist = graph._chartOptions.explodeDist || 10; + var pieGap = (numVal <= 1 ? 0 : graph._chartOptions.pieGap || 10); + var xBase = (dims[graph.W] - (numVal * pieGap) - pieGap) / numVal / 2; + var yBase = dims[graph.H] / 2; + var radius = Math.min(xBase, yBase) - (explode.length > 0 ? explodeDist : 0); + var gt = graph._root.group(graph._chartGroup, 'xAxisLabels', + $.extend({text_anchor: 'middle'}, graph.xAxis._labelFormat)); + var gl = []; + for (var i = 0; i < numVal; i++) { + var cx = dims[graph.X] + xBase + (i * (2 * Math.min(xBase, yBase) + pieGap)) + pieGap; + var cy = dims[graph.Y] + yBase; + var curTotal = 0; + for (var j = 0; j < numSer; j++) { + var series = graph._series[j]; + if (i == 0) { + gl[j] = graph._root.group(this._chart, 'series' + j, + $.extend({stroke: series._stroke, stroke_width: series._strokeWidth}, + series._settings || {})); + } + if (series._values[i] == 0) { + continue; + } + var start = (curTotal / totals[i]) * 2 * Math.PI; + curTotal += series._values[i]; + var end = (curTotal / totals[i]) * 2 * Math.PI; + var exploding = false; + for (var k = 0; k < explode.length; k++) { + if (explode[k] == j) { + exploding = true; + break; + } + } + var x = cx + (exploding ? explodeDist * Math.cos((start + end) / 2) : 0); + var y = cy + (exploding ? explodeDist * Math.sin((start + end) / 2) : 0); + var status = series._name + ' ' + + roundNumber((end - start) / 2 / Math.PI * 100, 2) + '%'; + graph._root.path(gl[j], path.reset().moveTo(x, y). + lineTo(x + radius * Math.cos(start), y + radius * Math.sin(start)). + arcTo(radius, radius, 0, (end - start < Math.PI ? 0 : 1), 1, + x + radius * Math.cos(end), y + radius * Math.sin(end)).close(), + $.extend({fill: series._fill}, graph._showStatus(status))); + } + if (graph.xAxis) { + graph._root.text(gt, cx, dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset, + graph.xAxis._labels[i]) + } + } + } +}); + +//------------------------------------------------------------------------------ + +/* Determine whether an object is an array. */ +function isArray(a) { + return (a.constructor && a.constructor.toString().match(/\Array\(\)/)); +} + +// Basic chart types +svgGraphing.addChartType('column', new SVGColumnChart()); +svgGraphing.addChartType('stackedColumn', new SVGStackedColumnChart()); +svgGraphing.addChartType('row', new SVGRowChart()); +svgGraphing.addChartType('stackedRow', new SVGStackedRowChart()); +svgGraphing.addChartType('line', new SVGLineChart()); +svgGraphing.addChartType('pie', new SVGPieChart()); + +})(jQuery) diff --git a/wui/src/public/javascripts/jquery-svg/jquery.svggraph.min.js b/wui/src/public/javascripts/jquery-svg/jquery.svggraph.min.js new file mode 100644 index 0000000..b8484c6 --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/jquery.svggraph.min.js @@ -0,0 +1,7 @@ +/* http://keith-wood.name/svg.html + SVG graphing extension for jQuery v1.0.1. + Written by Keith Wood (kbwood@iprimus.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. */ +var svgGraphing=null;(function($){svgManager.addExtension('graph',SVGGraph);svgGraphing=new SVGGraphing();function SVGGraphing(){this.regional=[];this.regional['']={percentageText:'Percentage'};this.region=this.regional['']}$.extend(SVGGraphing.prototype,{_chartTypes:[],addChartType:function(a,b){this._chartTypes[a]=b},chartTypes:function(){return this._chartTypes}});function SVGGraph(a){this._root=a;this._drawNow=true;for(var b in svgGraphing._chartTypes){this._chartType=svgGraphing._chartTypes[b];break}this._chartOptions={};this._title={value:'',offset:25,settings:{text_anchor:'middle'}};this._area=[0.1,0.1,0.8,0.9];this._chartFormat={fill:'none',stroke:'black'};this._gridlines=[];this._series=[];this._onstatus=null;this._chartGroup=this._root.group(null,'graph');this.xAxis=new SVGGraphAxis();this.xAxis.title('',40);this.yAxis=new SVGGraphAxis();this.yAxis.title('',40);this.x2Axis=null;this.y2Axis=null;this.legend=new SVGGraphLegend()}$.extend(SVGGraph.prototype,{X:0,Y:1,W:2,H:3,L:0,T:1,R:2,B:3,_percentageAxis:new SVGGraphAxis(svgGraphing.region.percentageText,0,100,10,0),chartType:function(a,b){if(arguments.length==0){return this._chartType}var c=svgGraphing._chartTypes[a];if(c){this._chartType=c;this._chartOptions=$.extend({},b||{})}this._drawGraph();return this},chartOptions:function(a){if(arguments.length==0){return this._chartOptions}this._chartOptions=$.extend({},a);this._drawGraph();return this},chartFormat:function(a,b,c){if(arguments.length==0){return this._chartFormat}if(typeof b=='object'){c=b;b=null}this._chartFormat=$.extend($.extend({fill:a},(b?{stroke:b}:{})),c||{});this._drawGraph();return this},chartArea:function(a,b,c,d){if(arguments.length==0){return this._area}this._area=(isArray(a)?a:[a,b,c,d]);this._drawGraph();return this},gridlines:function(a,b){if(arguments.length==0){return this._gridlines}this._gridlines=[(typeof a=='string'?{stroke:a}:a),(typeof b=='string'?{stroke:b}:b)];this._drawGraph();return this},title:function(a,b,c){if(arguments.length==0){return this._title}if(typeof b=='object'){c=b;b=null}this._title={value:a,offset:b||this._title.offset,settings:$.extend({text_anchor:'middle'},c||{})};this._drawGraph();return this},addSeries:function(a,b,c,d,e,f){if(typeof e=='object'){f=e;e=null}this._series[this._series.length]=new SVGGraphSeries(a,b,c,d,e,f||{});this._drawGraph();return this},series:function(){return this._series},noDraw:function(){this._drawNow=false;return this},redraw:function(){this._drawNow=true;this._drawGraph();return this},status:function(a){this._onstatus=a;return this},_drawGraph:function(){if(!this._drawNow){return}while(this._chartGroup.firstChild){this._chartGroup.removeChild(this._chartGroup.firstChild)}if(!this._chartGroup.parent){this._root._svg.appendChild(this._chartGroup)}this._chartType.drawGraph(this)},_drawTitle:function(){this._root.text(this._chartGroup,this._root._width()/2,this._title.offset,this._title.value,this._title.settings)},_getDims:function(a){a=a||this._area;var b=(a[this.L]>1?a[this.L]:this._root._width()*a[this.L]);var c=(a[this.T]>1?a[this.T]:this._root._height()*a[this.T]);var d=(a[this.R]>1?a[this.R]:this._root._width()*a[this.R])-b;var e=(a[this.B]>1?a[this.B]:this._root._height()*a[this.B])-c;return[b,c,d,e]},_drawChartBackground:function(a,b){var c=this._root.group(this._chartGroup,'background');var d=this._getDims();this._root.rect(c,d[this.X],d[this.Y],d[this.W],d[this.H],this._chartFormat);if(this._gridlines[0]&&this.yAxis._ticks.major&&!b){this._drawGridlines(c,this.yAxis,true,d,this._gridlines[0])}if(this._gridlines[1]&&this.xAxis._ticks.major&&!a){this._drawGridlines(c,this.xAxis,false,d,this._gridlines[1])}return c},_drawGridlines:function(a,b,c,d,e){var g=this._root.group(a,e);var f=(c?d[this.H]:d[this.W])/(b._scale.max-b._scale.min);var h=Math.floor(b._scale.min/b._ticks.major)*b._ticks.major;h=(h(this._root._width()/2)&&f>(this._root._height()/2));var k=(g?e-c:f-d)/(a._scale.max-a._scale.min);var l=a._ticks.size;var m=Math.floor(a._scale.min/a._ticks.major)*a._ticks.major;m=(ma[this.H];var c=this._series.length;var d=(b?a[this.W]:a[this.H])/c;var e=a[this.X]+5;var f=a[this.Y]+(b?a[this.H]/2:d/2);for(var i=0;i0?h:0);var p=a._root.group(a._chartGroup,'xAxisLabels',$.extend({text_anchor:'middle'},a.xAxis._labelFormat));var q=[];for(var i=0;i35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 1e=1a;(7($){43.3M(\'3E\',36);1e=1k 2Z();7 2Z(){4.2P=[];4.2P[\'\']={28:\'3K\'};4.2m=4.2P[\'\']}$.K(2Z.1r,{23:[],1U:7(a,b){4.23[a]=b},49:7(){F 4.23}});7 36(a){4.E=a;4.2r=17;O(6 b 2J 1e.23){4.2s=1e.23[b];3c}4.1g={};4.Q={35:\'\',2h:25,2B:{19:\'1j\'}};4.1G=[0.1,0.1,0.8,0.9];4.2u={1u:\'3s\',1c:\'2T\'};4.1l=[];4.U=[];4.2k=1a;4.M=4.E.Z(1a,\'3E\');4.V=1k 29();4.V.1q(\'\',40);4.11=1k 29();4.11.1q(\'\',40);4.1L=1a;4.1N=1a;4.1t=1k 2Y()}$.K(36.1r,{X:0,Y:1,W:2,H:3,L:0,T:1,R:2,B:3,31:1k 29(1e.2m.28,0,2g,10,0),4l:7(a,b){G(18.J==0){F 4.2s}6 c=1e.23[a];G(c){4.2s=c;4.1g=$.K({},b||{})}4.1A();F 4},4k:7(a){G(18.J==0){F 4.1g}4.1g=$.K({},a);4.1A();F 4},4j:7(a,b,c){G(18.J==0){F 4.2u}G(1v b==\'1S\'){c=b;b=1a}4.2u=$.K($.K({1u:a},(b?{1c:b}:{})),c||{});4.1A();F 4},4g:7(a,b,c,d){G(18.J==0){F 4.1G}4.1G=(2v(a)?a:[a,b,c,d]);4.1A();F 4},4b:7(a,b){G(18.J==0){F 4.1l}4.1l=[(1v a==\'2W\'?{1c:a}:a),(1v b==\'2W\'?{1c:b}:b)];4.1A();F 4},1q:7(a,b,c){G(18.J==0){F 4.Q}G(1v b==\'1S\'){c=b;b=1a}4.Q={35:a,2h:b||4.Q.2h,2B:$.K({19:\'1j\'},c||{})};4.1A();F 4},48:7(a,b,c,d,e,f){G(1v e==\'1S\'){f=e;e=1a}4.U[4.U.J]=1k 2V(a,b,c,d,e,f||{});4.1A();F 4},1P:7(){F 4.U},3Z:7(){4.2r=1O;F 4},3V:7(){4.2r=17;4.1A();F 4},3T:7(a){4.2k=a;F 4},1A:7(){G(!4.2r){F}2S(4.M.3p){4.M.3R(4.M.3p)}G(!4.M.2Q){4.E.3O.3N(4.M)}4.2s.1R(4)},1M:7(){4.E.14(4.M,4.E.2p()/2,4.Q.2h,4.Q.35,4.Q.2B)},1x:7(a){a=a||4.1G;6 b=(a[4.L]>1?a[4.L]:4.E.2p()*a[4.L]);6 c=(a[4.T]>1?a[4.T]:4.E.2M()*a[4.T]);6 d=(a[4.R]>1?a[4.R]:4.E.2p()*a[4.R])-b;6 e=(a[4.B]>1?a[4.B]:4.E.2M()*a[4.B])-c;F[b,c,d,e]},1K:7(a,b){6 c=4.E.Z(4.M,\'3L\');6 d=4.1x();4.E.1J(c,d[4.X],d[4.Y],d[4.W],d[4.H],4.2u);G(4.1l[0]&&4.11.I.16&&!b){4.1X(c,4.11,17,d,4.1l[0])}G(4.1l[1]&&4.V.I.16&&!a){4.1X(c,4.V,1O,d,4.1l[1])}F c},1X:7(a,b,c,d,e){6 g=4.E.Z(a,e);6 f=(c?d[4.H]:d[4.W])/(b.N.1b-b.N.12);6 h=13.39(b.N.12/b.I.16)*b.I.16;h=(h(4.E.2p()/2)&&f>(4.E.2M()/2));6 k=(g?e-c:f-d)/(a.N.1b-a.N.12);6 l=a.I.1f;6 m=13.39(a.N.12/a.I.16)*a.I.16;m=(ma[4.H];6 c=4.U.J;6 d=(b?a[4.W]:a[4.H])/c;6 e=a[4.X]+5;6 f=a[4.Y]+(b?a[4.H]/2:d/2);O(6 i=0;i0?h:0);6 p=a.E.Z(a.M,\'2L\',$.K({19:\'1j\'},a.V.1C));6 q=[];O(6 i=0;i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wui/src/public/javascripts/jquery-svg/test.js b/wui/src/public/javascripts/jquery-svg/test.js new file mode 100644 index 0000000..27f0dda --- /dev/null +++ b/wui/src/public/javascripts/jquery-svg/test.js @@ -0,0 +1,714 @@ +// Demonstrate an onLoad callback to initialise a canvas +function drawIntro() { + var svg = svgManager.getSVGFor('#cool_graph'); + svg.circle(null, 75, 75, 50, {fill: 'none', stroke: 'red', stroke_width: 3}); + var g = svg.group(null, {stroke: 'black', stroke_width: 2}); + svg.line(g, 15, 75, 135, 75); + svg.line(g, 75, 15, 75, 135); +} + +// ----------------------------------------------------------------------------- + +var svgSpec = 'http://www.w3c.org/TR/SVG11/'; +// SVG examples +var examples = [ + ['Basic shapes', basicShapesDemo, + 'SVG provides for the creation of several basic shapes, including ' + + 'rectangles, rectangles with rounded corners, circles, ellipses, ' + + 'lines segments, polygonal lines, and closed polygons. ' + + 'Each shape may be drawn with its own fill and border colourings. ' + + 'Shapes may be moved and rotated via the transformation abilities of SVG. ' + + 'Grouping elements allows common attributes to be easily applied.
' + + 'See the original documents: rectangle, ' + + 'rounded rectangle, ' + + 'circle, ' + + 'ellipse, ' + + 'line, ' + + 'polyline, ' + + 'and polygon.', + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + ''], + + ['Filters', filterDemo, + 'This example relies on the filter extension for the SVG plugin.
' + + '1. Filter primitive \'feGaussianBlur\' takes input SourceAlpha, ' + + 'which is the alpha channel of the source graphic. The result is ' + + 'stored in a temporary buffer named "blur". Note that "blur" is ' + + 'used as input to both filter primitives 2 and 3.
' + + '2. Filter primitive \'feOffset\' takes buffer "blur", shifts the ' + + 'result in a positive direction in both x and y, and creates a new ' + + 'buffer named "offsetBlur". The effect is that of a drop shadow.
' + + '3. Filter primitive \'feSpecularLighting\', uses buffer "blur" as a ' + + 'model of a surface elevation and generates a lighting effect from a ' + + 'single point source. The result is stored in buffer "specOut".
' + + '4. Filter primitive \'feComposite\' masks out the result of filter ' + + 'primitive 3 by the original source graphics alpha channel so that ' + + 'the intermediate result is no bigger than the original source graphic.
' + + '5. Filter primitive \'feComposite\' composites the result of the ' + + 'specular lighting with the original source graphic.
' + + '6. Filter primitive \'feMerge\' composites two layers together. ' + + 'The lower layer consists of the drop shadow result from filter primitive 2. ' + + 'The upper layer consists of the specular lighting result from filter primitive 5.
' + + 'See the original document.', + 'An example which combines multiple filter primitives\r\n' + + ' to produce a 3D lighting effect on a graphic consisting\r\n' + + ' of the string "SVG" sitting on top of oval filled in red\r\n' + + ' and surrounded by an oval outlined in red.\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' SVG\r\n' + + ' \r\n' + + ' \r\n' + + ''], + + ['Gradients and Patterns', gradientPatternDemo, + 'Shows how to fill a rectangle by referencing a radial gradient paint server.
' + + 'Shows how to fill a rectangle by referencing a pattern paint server. ' + + 'Note how the blue stroke of each triangle has been clipped at the top and the left. ' + + 'This is due to SVG\'s user agent style sheet setting the \'overflow\' property ' + + 'for \'pattern\' elements to hidden, which causes the pattern to be clipped to ' + + 'the bounds of the pattern tile.
' + + 'See the original documents: gradient ' + + 'and pattern.', + 'Example radgrad01 - fill a rectangle by referencing a\r\n' + + ' radial gradient paint server\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ''], + + ['Interactivity', interactiveDemo, + 'This example defines a function circle_click which is called ' + + 'by the onclick event attribute on the \'circle\' element. Each click toggles ' + + 'the circle\'s size between small and large.
' + + 'See the original document.', + 'Example script01 - invoke an ECMAScript function from an onclick event\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' Click on circle to change its size\r\n' + + ''], + + ['Masking', maskingDemo, + 'In SVG, you can specify that any other graphics object or \'g\' element ' + + 'can be used as an alpha mask for compositing the current object into the background.
' + + 'See the original document.', + 'Example mask01 - blue text masked with gradient against red background\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' Masked text\r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ''], + + ['Styles and References', useStyleDemo, + 'Illustrates a \'use\' element with various methods of applying CSS styling.
' + + 'See the original document.', + 'Example Use04 - \'use\' with CSS styling\r\n' + + '\r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ''], + + ['Text', textDemo, + 'Shows how \'tspan\' elements can be included within \'textPath\' ' + + 'elements to adjust styling attributes and adjust the current text ' + + 'position before rendering a particular glyph. The first occurrence ' + + 'of the word "up" is filled with the color red. Attribute dy is used ' + + 'to lift the word "up" from the baseline.
' + + 'See the original document.', + '\r\n' + + ' \r\n' + + '\r\n' + + 'Example toap02 - tspan within textPath\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' We go \r\n' + + ' \r\n' + + ' up\r\n' + + ' \r\n' + + ' \r\n' + + ' ,\r\n' + + ' \r\n' + + ' then we go down, then up again\r\n' + + ' \r\n' + + ''], + + ['Transformations', transformDemo, + 'Defines two coordinate systems which are skewed ' + + 'relative to the origin coordinate system.
' + + 'Transformations can be nested to any level. The effect of nested ' + + 'transformations is to post-multiply (i.e., concatenate) the subsequent ' + + 'transformation matrices onto previously defined transformations.
' + + 'See the original documents: skew ' + + 'and nesting.', + 'Example Skew - Show effects of skewX and skewY\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' ABC (skewX)\r\n' + + ' \r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' ABC (skewY)\r\n' + + ' \r\n' + + ' \r\n' + + '\r\n' + + '\r\n' + + '\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' ....Translate(1)\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' ....Rotate(2)\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + ' ....Translate(3)\r\n' + + ' \r\n' + + ' \r\n' + + ' \r\n' + + '']]; + +// Populate the examples drop-down +function initExamples() { + var html = ''; + for (var i = 0; i < examples.length; i++) { + html += ''; + } + $('#example').html(html).change(pickExample)[0].selectedIndex = 0; +} + +// Display the selected example +function pickExample() { + var ex = $('#example').val(); + if (!ex) { + return; + } + $('#exampledesc').html(examples[ex][2]); + $('#svgsource pre').text(examples[ex][3]); + $('#svgcode pre').text(codeFormat(examples[ex][1].toString())); + var svg = svgManager.getSVGFor('#svgexample'); + svg.clear(); + examples[ex][1](svg); +} + +// Format the JavaScript SVG code for display +function codeFormat(code) { + var lines = code.split('\n'); + var formatted = ''; + for (var i = 0; i < lines.length; i++) { + var line = $.trim(lines[i]); + if (!line || line.length == 1 || line.substring(0, 9) == 'function ') { + continue; + } + while (line.length > 50) { + var pos = line.substr(0, 50).lastIndexOf(' '); + if (pos == -1) { + break; + } + formatted += line.substr(0, pos) + '\r\n ' + ($.browser.msie ? ' ' : ''); + line = line.substr(pos + 1); + } + formatted += line + '\r\n'; + } + return formatted; +} + +// Demonstrate basic SVG shapes and constructs +function basicShapesDemo(svg) { + svg.rect(null, 20, 50, 100, 50, + {fill: 'yellow', stroke: 'navy', stroke_width: 5}); + svg.roundrect(null, 150, 50, 100, 50, 10, 10, {fill: 'green'}); + var g = svg.group(null, {transform: 'translate(270 80) rotate(-30)'}); + svg.roundrect(g, 0, 0, 100, 50, 10, 10, {fill: 'none', stroke: 'purple', stroke_width: 3}); + svg.circle(null, 70, 220, 50, {fill: 'red', stroke: 'blue', stroke_width: 5}); + var g = svg.group(null, {transform: 'translate(175 220)'}); + svg.ellipse(g, '', '', 75, 50, {fill: 'yellow'}); + svg.ellipse(null, '', '', 75, 50, {transform: 'translate(300 220) rotate(-30)', + fill: 'none', stroke: 'blue', stroke_width: 10}); + var g = svg.group(null, {stroke: 'green'}); + svg.line(g, 450, 120, 550, 20, {stroke_width: 5}); + svg.line(g, 550, 120, 650, 20, {stroke_width: 10}); + svg.line(g, 650, 120, 750, 20, {stroke_width: 15}); + svg.line(g, 750, 120, 850, 20, {stroke_width: 20}); + svg.line(g, 850, 120, 950, 20, {stroke_width: 25}); + svg.polyline(null, [[450,250], + [475,250],[475,220],[500,220],[500,250], + [525,250],[525,200],[550,200],[550,250], + [575,250],[575,180],[600,180],[600,250], + [625,250],[625,160],[650,160],[650,250],[675,250]], + {fill: 'none', stroke: 'blue', stroke_width: 5}); + svg.polygon(null, [[800,150],[900,180],[900,240],[800,270],[700,240],[700,180]], + {fill: 'lime', stroke: 'blue', stroke_width: 10}); +} + +// Demonstrate SVG filter effects +function filterDemo(svg) { + svg.describe(null, 'An example which combines multiple filter primitives ' + + 'to produce a 3D lighting effect on a graphic consisting ' + + 'of the string "SVG" sitting on top of oval filled in red ' + + 'and surrounded by an oval outlined in red.'); + var defs = svg.defs(); + var filter = svg.filter(defs, 'MyFilter', 0, 0, 200, 120, + {filterUnits: 'userSpaceOnUse'}); + svg.filters.gaussianBlur(filter, 'blur', 'SourceAlpha', 4); + svg.filters.offset(filter, 'offsetBlur', 'blur', 4, 4); + var spec = svg.filters.specularLighting(filter, 'specOut', 'blur', + 5, 0.75, 20, {lighting_color: '#bbbbbb'}); + svg.filters.pointLight(spec, '', -5000, -10000, 20000); + svg.filters.composite(filter, 'specOut', 'in', 'specOut', 'SourceAlpha'); + svg.filters.composite(filter, 'litPaint', 'arithmetic', 'SourceGraphic', + 'specOut', 0, 1, 1, 0); + var merge = svg.filters.merge(filter, '', ['offsetBlur', 'litPaint']); + var g1 = svg.group(null, {filter: 'url(#MyFilter)'}); + var g2 = svg.group(g1); + var path = svg.createPath(); + svg.path(g2, path.moveTo(50, 90).curveCTo(0, 90, 0, 30, 50, 30). + lineTo(150, 30).curveCTo(200, 30, 200, 90, 150, 90).close(), + {fill: 'none', stroke: '#D90000', stroke_width: 10}); + svg.path(g2, path.reset().moveTo(60, 80).curveCTo(30, 80, 30, 40, 60, 40). + lineTo(140, 40).curveCTo(170, 40, 170, 80, 140, 80).close(), + {fill: '#D90000'}); + var g3 = svg.group(g2, {fill: '#FFFFFF', stroke: 'black', + font_size: 45, font_family: 'Verdana'}); + svg.text(g3, 52, 76, 'SVG'); +} + +// Demonstrate SVG gradient and pattern fills +function gradientPatternDemo(svg) { + svg.describe(null, 'Example radgrad01 - fill a rectangle by ' + + 'referencing a radial gradient paint server'); + var g = svg.group(); + var defs = svg.defs(g); + svg.radialGradient(defs, 'MyGradient', + [['0%', 'red'], ['50%', 'blue'], ['100%', 'red']], + 200, 100, 150, 200, 100, {gradientUnits: 'userSpaceOnUse'}); + svg.rect(g, 50, 50, 300, 100, + {fill: 'url(#MyGradient)', stroke: 'black', stroke_width: 5}); + g = svg.group(); + defs = svg.defs(g); + var ptn = svg.pattern(defs, 'TrianglePattern', 0, 0, 100, 100, + 0, 0, 10, 10, {patternUnits: 'userSpaceOnUse'}); + var path = svg.createPath(); + svg.path(ptn, path.moveTo(0, 0).lineTo([[7, 0], [3.5, 7]]).close(), + {fill: 'red', stroke: 'blue'}); + svg.ellipse(g, 550, 100, 175, 75, + {fill: 'url(#TrianglePattern)', stroke: 'black', stroke_width: 5}); +} + +// Demonstrate SVG interactivity +function interactiveDemo(svg) { + svg.describe(null, 'Example script01 - invoke an ECMAScript function from an onclick event'); + svg.script(null, 'function circle_click(evt) {\n' + + ' var circle = evt.target;\n' + + ' var currentRadius = circle.getAttribute("r");\n' + + ' if (currentRadius == 100)\n' + + ' circle.setAttribute("r", currentRadius*2);\n' + + ' else\n' + + ' circle.setAttribute("r", currentRadius*0.5);\n' + + '}', 'text/ecmascript'); + svg.circle(null, 300, 150, 100, {onclick: 'circle_click(evt)', fill: 'red'}); + svg.text(null, 300, 280, 'Click on circle to change its size', + {font_family: 'Verdana', font_size: 35, text_anchor: 'middle'}); +} + +// Demonstrate SVG masking operations +function maskingDemo(svg) { + svg.describe(null, 'Example mask01 - blue text masked with gradient against red background'); + var defs = svg.defs(); + svg.linearGradient(defs, 'Gradient', [[0, 'white', 0], [1, 'white', 1]], + 0, 0, 800, 0, {gradientUnits: 'userSpaceOnUse'}); + var mask = svg.mask(defs, 'Mask', 0, 0, 800, 300, {maskUnits: 'userSpaceOnUse'}); + svg.rect(mask, 0, 0, 800, 300, {fill: 'url(#Gradient)'}); + svg.text(defs, 400, 200, 'Masked text', {id: 'Text', + font_family: 'Verdana', font_size: 100, text_anchor: 'middle'}); + svg.rect(null, 0, 0, 800, 300, {fill: '#FF8080'}); + svg.use(null, '#Text', {fill: 'blue', mask: 'url(#Mask)'}); + svg.use(null, '#Text', {fill: 'none', stroke: 'black', stroke_width: 2}); +} + +// Demonstrate SVG text rendering +function textDemo(svg) { + var defs = svg.defs(); + var path = svg.createPath(); + svg.path(defs, path.moveTo(100, 200).curveCTo([[200, 100, 300, 0, 400, 100], + [500, 200, 600, 300, 700, 200], [800, 100, 900, 100, 900, 100]]), + {id: 'MyPath'}); + svg.describe(null, 'Example toap02 - tspan within textPath'); + svg.use(null, '#MyPath', {fill: 'none', stroke: 'red'}); + var text = svg.text(null, '', + {font_family: 'Verdana', font_size: '42.5', fill: 'blue'}); + var texts = svg.createText(); + svg.textpath(text, '#MyPath', texts.string('We go ').span('up', {dy: -30, fill: 'red'}). + span(',', {dy: 30}).string(' then we go down, then up again')); +} + +// Demonstrate SVG transformation support +function transformDemo(svg) { + svg.describe(null, 'Example Skew - Show effects of skewX and skewY'); + var g1 = svg.group(null, {transform: 'translate(30,100)'}); + var g2 = svg.group(g1, {transform: 'skewX(30)'}); + var g3 = svg.group(g2, {fill: 'none', stroke: 'red', stroke_width: 3}); + svg.line(g3, 0, 0, 50, 0); + svg.line(g3, 0, 0, 0, 50); + svg.text(g2, 0, 0, 'ABC (skewX)', + {font_size: 20, font_family: 'Verdana', fill: 'blue'}); + g1 = svg.group(null, {transform: 'translate(200,100)'}); + g2 = svg.group(g1, {transform: 'skewY(30)'}); + g3 = svg.group(g2, {fill: 'none', stroke: 'red', stroke_width: 3}); + svg.line(g3, 0, 0, 50, 0); + svg.line(g3, 0, 0, 0, 50); + svg.text(g2, 0, 0, 'ABC (skewY)', + {font_size: 20, font_family: 'Verdana', fill: 'blue'}); + + g1 = svg.group(null, {transform: 'translate(450,150)'}); + g2 = svg.group(g1, {fill: 'none', stroke: 'red', stroke_width: 3}); + svg.line(g2, 0, 0, 50, 0); + svg.line(g2, 0, 0, 0, 50); + svg.text(g1, 0, 0, '....Translate(1)', + {font_size: 16, font_family: 'Verdana'}); + g2 = svg.group(g1, {transform: 'rotate(-45)'}); + g3 = svg.group(g2, {fill: 'none', stroke: 'green', stroke_width: 3}); + svg.line(g3, 0, 0, 50, 0); + svg.line(g3, 0, 0, 0, 50); + svg.text(g2, 0, 0, '....Rotate(2)', + {font_size: 16, font_family: 'Verdana'}); + g3 = svg.group(g2, {transform: 'translate(130,160)'}); + var g4 = svg.group(g3, {fill: 'none', stroke: 'blue', stroke_width: 3}); + svg.line(g4, 0, 0, 50, 0); + svg.line(g4, 0, 0, 0, 50); + svg.text(g3, 0, 0, '....Translate(3)', + {font_size: 16, font_family: 'Verdana'}); +} + +// Demonstrate SVG referencing and CSS styling +function useStyleDemo(svg) { + svg.describe(null, 'Example Use04 - \'use\' with CSS styling'); + var defs = svg.defs(null, '', {style: ' /* rule 9 */ stroke-miterlimit: 10'}); + var path = svg.createPath(); + svg.path(defs, path.moveTo(100, 50).lineTo([[700, 50], [700, 250], [100, 250]]), + {id: 'MyPath', _class: 'MyPathClass', style: ' /* rule 10 */ stroke-dasharray: 300,100'}); + svg.style(null, '/* rule 1 */ #MyUse { fill: blue } ' + + '/* rule 2 */ #MyPath { stroke: red } ' + + '/* rule 3 */ use { fill-opacity: .5 } ' + + '/* rule 4 */ path { stroke-opacity: .5 } ' + + '/* rule 5 */ .MyUseClass { stroke-linecap: round } ' + + '/* rule 6 */ .MyPathClass { stroke-linejoin: bevel } ' + + '/* rule 7 */ use > path { shape-rendering: optimizeQuality } ' + + '/* rule 8 */ g > path { visibility: hidden }'); + var g = svg.group(null, {style: ' /* rule 11 */ stroke-width: 40'}); + svg.use(g, '#MyPath', {id: 'MyUse', _class: 'MyUseClass', + style: '/* rule 12 */ stroke-dashoffset: 50'}); +} + +// ----------------------------------------------------------------------------- + +// Add a node defined inline +function addInline() { + var svg = svgManager.getSVGFor('#svgload'); + svg.add(null, '#svg1'); +} + +// Load an external document +function loadExternal() { + var svg = svgManager.getSVGFor('#svgload'); + svg.load($('#loadURL').val()); +} + +// ----------------------------------------------------------------------------- + +// Synchronise the drawing section with user selection +function setDrawOptions() { + showHide = function(id, show) { + if (show) { + $(id).show(); + } + else { + $(id).hide(); + } + } + var shape = $('#shape').val(); + showHide('#getRect', (shape == 'rect' || shape == 'roundrect')); + showHide('#getRounded', shape == 'roundrect'); + showHide('#getCentre', (shape == 'circle' || shape == 'ellipse')); + showHide('#getRadius', (shape == 'circle')); + showHide('#getRadii', (shape == 'ellipse')); + showHide('#getLine', (shape == 'line')); + showHide('#getFill', (shape != 'line')); +} + +var drawNodes = []; +var sketchpad = null; + +$(document).ready(function() { + sketchpad = $('#svgsketch')[0]; +}); + +// Draw the selected element on the canvas +function draw() { + var svg = svgManager.getSVGFor(sketchpad); + var rotate = $('#rotate').val(); + var settings = $.extend({fill: $('#fill').val(), + stroke: $('#stroke').val(), stroke_width: $('#swidth').val()}, + (rotate ? {transform: 'rotate(' + rotate + ')'} : {})); + var shape = $('#shape').val(); + var node = null; + if (shape == 'rect') { + node = svg.rect(null, $('#left').val(), $('#top').val(), + $('#width').val(), $('#height').val(), settings); + } + else if (shape == 'roundrect') { + node = svg.roundrect(null, $('#left').val(), $('#top').val(), + $('#width').val(), $('#height').val(), + $('#roundX').val(), $('#roundY').val(), settings); + } + else if (shape == 'circle') { + node = svg.circle(null, $('#centreX').val(), $('#centreY').val(), + $('#radius').val(), settings); + } + else if (shape == 'ellipse') { + node = svg.ellipse(null, $('#centreX').val(), $('#centreY').val(), + $('#radiusX').val(), $('#radiusY').val(), settings); + } + else if (shape == 'line') { + node = svg.line(null, $('#startX').val(), $('#startY').val(), + $('#endX').val(), $('#endY').val(), settings); + } + drawNodes[drawNodes.length] = node; +} + +// Remove the last drawn element +function undo() { + if (!drawNodes.length) { + return; + } + var svg = svgManager.getSVGFor(sketchpad); + svg.remove(drawNodes[drawNodes.length - 1]); + drawNodes.splice(drawNodes.length - 1, 1); +} + +// Clear the canvas +function erase(name) { + svgManager.getSVGFor($(name)).clear(); + drawNodes = []; +} + +// ----------------------------------------------------------------------------- + +// Initialise the graphing options +function setGraphOptions() { + var html = ''; + var chartTypes = svgGraphing.chartTypes(); + for (var id in chartTypes) { + html += ''; + } + $('#chartType').html(html)[0].selectedIndex = 0; +} + +var firstGraph = true; +var chartArea = [[0.1, 0.1, 0.95, 0.9], [0.2, 0.1, 0.95, 0.9], + [0.1, 0.1, 0.8, 0.9], [0.1, 0.25, 0.9, 0.9], [0.1, 0.1, 0.9, 0.8]]; +var legendArea = [[0.0, 0.0, 0.0, 0.0], [0.005, 0.1, 0.125, 0.5], + [0.875, 0.1, 0.995, 0.5], [0.2, 0.1, 0.8, 0.2], [0.2, 0.9, 0.8, 0.995]]; +var fills = [['lightblue', 'url(#fadeBlue)'], ['pink', 'url(#fadeRed)'], + ['lightgreen', 'url(#fadeGreen)']]; + +// Draw a new graph with the selected options +function graphIt() { + var svg = svgManager.getSVGFor('#svggraph'); + if (firstGraph) { + var defs = svg.defs(); + svg.linearGradient(defs, 'fadeBlue', [[0, 'lightblue'], [1, 'blue']]); + svg.linearGradient(defs, 'fadeRed', [[0, 'pink'], [1, 'red']]); + svg.linearGradient(defs, 'fadeGreen', [[0, 'lightgreen'], [1, 'green']]); + svg.graph.noDraw().title('Browser Usage'). + addSeries('IE', [95.97, 91.80, 88.16, 86.64], 'lightblue', 'blue', 3). + addSeries('Netscape', [3.39, 2.83, 1.61, 0.00], 'pink', 'red', 3). + addSeries('Firefox', [0.00, 4.06, 8.13, 9.95], 'lightgreen', 'green', 3). + chartFormat('lightyellow', 'gray'). + gridlines({stroke: 'gray', stroke_dasharray: '2,2'}, 'gray'). + status(setStatus); + svg.graph.xAxis.title('Year').scale(0, 3). + ticks(1, 0).labels(['2002', '2004', '2005', '2006']); + svg.graph.yAxis.title('Percentage').scale(-5, 105).ticks(10, 5); + svg.graph.legend.settings({fill: 'lightgoldenrodyellow', stroke: 'gray'}); + firstGraph = false; + } + var chartType = $('#chartType').val(); + var legendPos = $('#legendPos').val() - 0; + var seriesFill = ($('#seriesFill').val() == 'plain' ? 0 : 1); + svg.graph.legend.show(legendPos).area(legendArea[legendPos]); + for (var i = 0; i < 3; i++) { + svg.graph.series()[i].format((fills[i])[seriesFill]); + } + svg.graph.noDraw().chartArea(chartArea[legendPos]). + chartType(chartType, {explode: [2], explodeDist: 10}).redraw(); + chartType = svgGraphing.chartTypes()[chartType]; + $('#graphDesc').text(chartType.description()); + var options = ''; + for (var i = 0; i < chartType.options().length; i++) { + options += '
  • ' + chartType.options()[i] + '
  • '; + } + $('#graphOptions').html(options || '
  • None
  • '); +} + +// Show the status values in a callback from the graph +function setStatus(value) { + window.status = value; +} \ No newline at end of file diff --git a/wui/src/public/javascripts/jquery-treeview/jquery.ovirt.treeview.css b/wui/src/public/javascripts/jquery-treeview/jquery.ovirt.treeview.css index 9bf80f1..7324683 100644 --- a/wui/src/public/javascripts/jquery-treeview/jquery.ovirt.treeview.css +++ b/wui/src/public/javascripts/jquery-treeview/jquery.ovirt.treeview.css @@ -60,6 +60,12 @@ .treeview-ovirt li { background-image: url(images/treeview-famfamfam-line.gif); } .treeview-ovirt .hitarea, .treeview-ovirt li.lastCollapsable, .treeview-ovirt li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); } +.treeview .placeholder { + background: url(images/ajax-loader.gif) 0 0 no-repeat; + height: 16px; + width: 16px; + display: block; +} .filetree li { padding: 3px 0 2px 16px; } .filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } diff --git a/wui/src/public/javascripts/jquery-treeview/jquery.treeview.async.js b/wui/src/public/javascripts/jquery-treeview/jquery.treeview.async.js index fb02324..9168a11 100644 --- a/wui/src/public/javascripts/jquery-treeview/jquery.treeview.async.js +++ b/wui/src/public/javascripts/jquery-treeview/jquery.treeview.async.js @@ -15,11 +15,8 @@ ;(function($) { -function load(settings, root, child, container) { - var params; - root?params={id:root}:settings.current_pool_id!=""?params={current_id:settings.current_pool_id}:null; - //null; - $.getJSON(settings.url, params, function(response) { //{id: root} +function load(settings, params, child, container) { + $.getJSON(settings.url, params, function(response) { //{id: root} function createNode(parent) { if (this.type=="HardwarePool") { settings.link_to=settings.hardware_url @@ -63,7 +60,8 @@ $.fn.treeview = function(settings) { return proxied.apply(this, arguments); } var container = this; - load(settings, settings.id, this, container); //original 2nd param value was "source" + settings.current_pool_id!=""?settings.params={current_id:settings.current_pool_id}:settings.params=null; + load(settings, settings.params, this, container); var userToggle = settings.toggle; return proxied.call(this, $.extend({}, settings, { collapsed: true, @@ -72,7 +70,7 @@ $.fn.treeview = function(settings) { if ($this.hasClass("hasChildren")) { var childList = $this.removeClass("hasChildren").find("ul"); childList.empty(); - load(settings, this.id, childList, container); + load(settings, {id:this.id}, childList, container); } if (userToggle) { userToggle.apply(this, arguments); diff --git a/wui/src/public/stylesheets/components.css b/wui/src/public/stylesheets/components.css index 42ad64b..90d63a9 100644 --- a/wui/src/public/stylesheets/components.css +++ b/wui/src/public/stylesheets/components.css @@ -34,7 +34,7 @@ .header_menu { padding: 5px 0 0 10px; margin: 1px; - background: #CCCCCC url(images/bg_menu_big.jpg) repeat-x top; + /*background: #CCCCCC url(images/bg_menu_big.jpg) repeat-x top;*/ font-size: 110%; line-height:1.5; height: 26px; diff --git a/wui/src/public/stylesheets/layout.css b/wui/src/public/stylesheets/layout.css index ac4910d..59168c6 100644 --- a/wui/src/public/stylesheets/layout.css +++ b/wui/src/public/stylesheets/layout.css @@ -38,7 +38,7 @@ img width: 100%; height: 90px; overflow: hidden; - background:#535353 url(images/bg_header.jpg) repeat-x top; + /*background:#535353 url(images/bg_header.jpg) repeat-x top;*/ } .header_image { @@ -115,7 +115,7 @@ img .toolbar { margin: 1px 0 1px 0; - background: #CCCCCC url(images/bg_menu_big.jpg) repeat-x top; + /*background: #CCCCCC url(images/bg_menu_big.jpg) repeat-x top;*/ height: 31px; border-left: #FFFFFF solid 1px; border-top: #FFFFFF solid 1px; diff --git a/wui/src/test/functional/graph_controller_test.rb b/wui/src/test/functional/graph_controller_test.rb new file mode 100644 index 0000000..6200a4c --- /dev/null +++ b/wui/src/test/functional/graph_controller_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class GraphControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end