azkaban-memoizeit
Changes
build.xml 76(+54 -22)
src/java/azkaban/utils/Emailer.java 2(+1 -1)
src/less/azkaban-svg.less 1(+1 -0)
src/less/flow.less 125(+80 -45)
src/web/css/azkaban-graph.css 7(+7 -0)
src/web/js/azkaban.flow.job.view.js 243(+159 -84)
src/web/js/azkaban.flow.view.js 81(+6 -75)
src/web/js/azkaban.svg.flow.loader.js 21(+12 -9)
src/web/js/azkaban.svg.graph.view.js 79(+30 -49)
src/web/js/jquery.svg.js 1394(+1394 -0)
src/web/js/jquery.svg.min.js 7(+7 -0)
src/web/js/jquery.svganim.min.js 7(+7 -0)
src/web/js/jquery.svgdom.js 406(+406 -0)
src/web/js/jquery.svgdom.min.js 7(+7 -0)
src/web/js/jquery.svgfilter.min.js 7(+7 -0)
Details
build.xml 76(+54 -22)
diff --git a/build.xml b/build.xml
index c695082..9ccb34e 100644
--- a/build.xml
+++ b/build.xml
@@ -6,6 +6,7 @@
<property name="dist.jar.dir" value="${basedir}/dist/jars" />
<property name="dist.dust.dir" value="${basedir}/dist/dust" />
<property name="dist.less.dir" value="${basedir}/dist/less" />
+ <property name="dist.web.dir" value="${basedir}/dist/web" />
<property name="dist.classes.dir" value="${basedir}/dist/classes" />
<property name="dist.packages.dir" value="${basedir}/dist/packages" />
<property name="dist.web.package.dir" value="${dist.packages.dir}/azkaban-web-server" />
@@ -49,27 +50,7 @@
<delete dir="${dist.classes.dir}" />
</target>
- <target name="build" description="Compile main source tree java files">
- <delete dir="${dist.classes.dir}" />
- <mkdir dir="${dist.classes.dir}" />
- <delete dir="${dist.dust.dir}" />
- <mkdir dir="${dist.dust.dir}" />
- <delete dir="${dist.less.dir}" />
- <mkdir dir="${dist.less.dir}" />
-
- <!-- copy non-java files to classes dir to load from classpath -->
- <copy todir="${dist.classes.dir}">
- <fileset dir="${java.src.dir}">
- <exclude name="**/*.java" />
- </fileset>
- </copy>
-
- <javac fork="true" destdir="${dist.classes.dir}"
- target="1.6" debug="true" deprecation="false" failonerror="true">
- <src path="${java.src.dir}" />
- <classpath refid="main.classpath" />
- </javac>
-
+ <target name="dust" description="Compile Less css files.">
<!-- Compile dustjs templates -->
<!-- Note: Because apply does not support multiple srcfile and targetfile
elements, and for and foreach requires ant-contrib, we use targetfile
@@ -85,7 +66,9 @@
<outputmapper id="out" type="glob" from="*.tl" to="${dist.dust.dir}/*.js" />
</redirector>
</apply>
-
+ </target>
+
+ <target name="less" description="Compile Less css files.">
<!-- Compile LESS to CSS -->
<echo message="Compiling LESS style sheets." />
<apply dir="${less.src.dir}" executable="lessc" relative="true">
@@ -98,6 +81,55 @@
</apply>
</target>
+ <target name="build" description="Compile main source tree java files">
+ <delete dir="${dist.classes.dir}" />
+ <mkdir dir="${dist.classes.dir}" />
+ <delete dir="${dist.dust.dir}" />
+ <mkdir dir="${dist.dust.dir}" />
+ <delete dir="${dist.less.dir}" />
+ <mkdir dir="${dist.less.dir}" />
+
+ <!-- copy non-java files to classes dir to load from classpath -->
+ <copy todir="${dist.classes.dir}">
+ <fileset dir="${java.src.dir}">
+ <exclude name="**/*.java" />
+ </fileset>
+ </copy>
+
+ <javac fork="true" destdir="${dist.classes.dir}"
+ target="1.6" debug="true" deprecation="false" failonerror="true">
+ <src path="${java.src.dir}" />
+ <classpath refid="main.classpath" />
+ </javac>
+
+ <antcall target="dust"></antcall>
+ <antcall target="less"></antcall>
+ </target>
+
+ <target name="webmin" description="Copies only the non compiled web resources to dist dir">
+ <copy todir="${dist.web.dir}" overwrite="true">
+ <fileset dir="${web.src.dir}" />
+ </copy>
+ </target>
+
+ <target name="web" description="Creates web resourses in a dir. Useful for development">
+ <mkdir dir="${dist.web.dir}" />
+
+ <antcall target="webmin"></antcall>
+ <antcall target="dust"></antcall>
+ <antcall target="less"></antcall>
+
+ <!-- Copy compiled dust templates -->
+ <copy todir="${dist.web.dir}/js">
+ <fileset dir="${dist.dust.dir}" />
+ </copy>
+
+ <!-- Copy compiled less CSS -->
+ <copy todir="${dist.web.dir}/css">
+ <fileset dir="${dist.less.dir}" />
+ </copy>
+ </target>
+
<target name="jars" depends="build" description="Create azkaban jar">
<mkdir dir="${dist.jar.dir}" />
<jar destfile="${azkaban.jar}">
diff --git a/src/java/azkaban/execapp/FlowRunnerManager.java b/src/java/azkaban/execapp/FlowRunnerManager.java
index 97f10fe..ecc0847 100644
--- a/src/java/azkaban/execapp/FlowRunnerManager.java
+++ b/src/java/azkaban/execapp/FlowRunnerManager.java
@@ -637,7 +637,7 @@ public class FlowRunnerManager implements EventListener {
}
public String getRunningFlowIds() {
- List<Integer> ids = new ArrayList<Integer>(runningFlows.keySet());
+ ArrayList<Integer> ids = new ArrayList<Integer>(runningFlows.keySet());
Collections.sort(ids);
return ids.toString();
}
diff --git a/src/java/azkaban/project/JdbcProjectLoader.java b/src/java/azkaban/project/JdbcProjectLoader.java
index e61c2d7..9553d48 100644
--- a/src/java/azkaban/project/JdbcProjectLoader.java
+++ b/src/java/azkaban/project/JdbcProjectLoader.java
@@ -271,6 +271,7 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
}
}
+ @SuppressWarnings("resource")
private void uploadProjectFile(Connection connection, Project project, int version, String filetype, String filename, File localFile, String uploader) throws ProjectManagerException {
QueryRunner runner = new QueryRunner();
long updateTime = System.currentTimeMillis();
@@ -360,6 +361,7 @@ public class JdbcProjectLoader extends AbstractJdbcLoader implements ProjectLoad
return handler;
}
+ @SuppressWarnings("resource")
private ProjectFileHandler getUploadedFile(Connection connection, int projectId, int version) throws ProjectManagerException {
QueryRunner runner = new QueryRunner();
ProjectVersionResultHandler pfHandler = new ProjectVersionResultHandler();
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index a5afa44..39e42e9 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -58,7 +58,6 @@ public class ScheduleManager implements TriggerAgent {
*
* @param loader
*/
-<<<<<<< HEAD
public ScheduleManager (ScheduleLoader loader)
{
this.loader = loader;
diff --git a/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java b/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java
index ab1586a..caa07bf 100644
--- a/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java
+++ b/src/java/azkaban/trigger/builtin/ExecuteFlowAction.java
@@ -216,7 +216,7 @@ public class ExecuteFlowAction implements TriggerAction {
throw new RuntimeException("Error finding the flow to execute " + flowName);
}
- ExecutableFlow exflow = new ExecutableFlow(flow);
+ ExecutableFlow exflow = new ExecutableFlow(project, flow);
exflow.setSubmitUser(submitUser);
exflow.addAllProxyUsers(project.getProxyUsers());
diff --git a/src/java/azkaban/trigger/builtin/KillExecutionAction.java b/src/java/azkaban/trigger/builtin/KillExecutionAction.java
index dd53efe..3114fd2 100644
--- a/src/java/azkaban/trigger/builtin/KillExecutionAction.java
+++ b/src/java/azkaban/trigger/builtin/KillExecutionAction.java
@@ -23,6 +23,7 @@ import org.apache.log4j.Logger;
import azkaban.executor.ExecutableFlow;
import azkaban.executor.ExecutorManagerAdapter;
+import azkaban.executor.Status;
import azkaban.trigger.TriggerAction;
public class KillExecutionAction implements TriggerAction{
@@ -89,7 +90,7 @@ public class KillExecutionAction implements TriggerAction{
public void doAction() throws Exception {
ExecutableFlow exFlow = executorManager.getExecutableFlow(execId);
logger.info("ready to kill execution " + execId);
- if(!ExecutableFlow.isFinished(exFlow)) {
+ if(!Status.isStatusFinished(exFlow.getStatus())) {
logger.info("Killing execution " + execId);
executorManager.cancelFlow(exFlow, "azkaban_sla");
}
src/java/azkaban/utils/Emailer.java 2(+1 -1)
diff --git a/src/java/azkaban/utils/Emailer.java b/src/java/azkaban/utils/Emailer.java
index dcc749c..a4e2ef8 100644
--- a/src/java/azkaban/utils/Emailer.java
+++ b/src/java/azkaban/utils/Emailer.java
@@ -163,7 +163,7 @@ public class Emailer extends AbstractMailer implements Alerter {
ArrayList<String> failedJobs = new ArrayList<String>();
for (ExecutableNode node : flow.getExecutableNodes()) {
if (node.getStatus() == Status.FAILED) {
- failedJobs.add(node.getJobId());
+ failedJobs.add(node.getId());
}
}
return failedJobs;
diff --git a/src/java/azkaban/utils/StringUtils.java b/src/java/azkaban/utils/StringUtils.java
index 0ea393c..77c5941 100644
--- a/src/java/azkaban/utils/StringUtils.java
+++ b/src/java/azkaban/utils/StringUtils.java
@@ -17,6 +17,7 @@
package azkaban.utils;
import java.util.Collection;
+import java.util.List;
public class StringUtils {
public static final char SINGLE_QUOTE = '\'';
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index fdae097..00ed276 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
@@ -860,7 +859,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
return;
}
- ExecutableFlow exflow = new ExecutableFlow(flow);
+ ExecutableFlow exflow = new ExecutableFlow(project, flow);
exflow.setSubmitUser(user.getUserId());
exflow.addAllProxyUsers(project.getProxyUsers());
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 8a271f5..9f907cc 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -31,7 +31,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletConfig;
@@ -596,41 +595,6 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
ret.put("nodes", nodeList);
}
- private void fillFlowInfo(Project project, String flowId, HashMap<String, Object> ret) {
- Flow flow = project.getFlow(flowId);
-
- //Collections.sort(flowNodes, NODE_LEVEL_COMPARATOR);
- ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
- for (Node node: flow.getNodes()) {
- HashMap<String, Object> nodeObj = new HashMap<String,Object>();
- nodeObj.put("id", node.getId());
- nodeObj.put("level", node.getLevel());
- nodeObj.put("type", node.getType());
- if (node.getEmbeddedFlowId() != null) {
- nodeObj.put("flowId", node.getEmbeddedFlowId());
- }
-
- nodeList.add(nodeObj);
- }
-
- ArrayList<Map<String, Object>> edgeList = new ArrayList<Map<String, Object>>();
- for (Edge edge: flow.getEdges()) {
- HashMap<String, Object> edgeObj = new HashMap<String,Object>();
- edgeObj.put("from", edge.getSourceId());
- edgeObj.put("target", edge.getTargetId());
-
- if (edge.hasError()) {
- edgeObj.put("error", edge.getError());
- }
-
- edgeList.add(edgeObj);
- }
-
- ret.put("flowId", flowId);
- ret.put("nodes", nodeList);
- ret.put("edges", edgeList);
- }
-
private void ajaxFetchFlowNodeData(Project project, HashMap<String, Object> ret, HttpServletRequest req) throws ServletException {
String flowId = getParam(req, "flow");
Flow flow = project.getFlow(flowId);
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index 281bdf7..01f328c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -14,13 +14,6 @@
* the License.
*#
- <script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
- <script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
-
<div class="modal modal-wide" id="execute-flow-panel">
<div class="modal-dialog">
<div class="modal-content">
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
index 60cdb96..44d6654 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
@@ -24,8 +24,13 @@
<div class="panel-heading">
<input id="filter" type="text" placeholder="Job Filter" class="form-control">
</div>
- <div id="list" class="list-group"></div>
+ <div id="joblist" class="list-group"></div>
<div class="panel-footer">
+ <div id="autoPanZoom" class="checkbox">
+ <label>
+ <input type="checkbox" id="autoPanZoomCheckbox" class="autoPanZoom" value="autoPanZoom" />Auto Pan Zoom
+ </label>
+ </div>
<button type="button" class="btn btn-sm btn-default" id="resetPanZoomBtn">Reset Pan Zoom</button>
</div>
</div><!-- /.panel -->
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index a3672b3..eb355ef 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -24,16 +24,23 @@
<script type="text/javascript" src="${context}/js/moment.min.js"></script>
<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
- <script type="text/javascript" src="${context}/js/dust-core-2.2.2.min.js"></script>
- <script type="text/javascript" src="${context}/js/flowsummary.js"></script>
-
+ <script type="text/javascript" src="${context}/js/dust-core-2.2.2.min.js"></script>
+ <script type="text/javascript" src="${context}/js/flowsummary.js"></script>
+
+ <script type="text/javascript" src="${context}/js/jquery.svg.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery.svganim.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery.svgfilter.min.js"></script>
+
+ <script type="text/javascript" src="${context}/js/svgutils.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.job.status.utils.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.flow.graph.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.svg.flow.loader.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
@@ -47,6 +54,7 @@
var flowId = "${flowid}";
var execId = null;
</script>
+ <link rel="stylesheet" type="text/css" href="${context}/css/flow.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
</head>
src/less/azkaban-svg.less 1(+1 -0)
diff --git a/src/less/azkaban-svg.less b/src/less/azkaban-svg.less
index 31811f9..211d1b5 100644
--- a/src/less/azkaban-svg.less
+++ b/src/less/azkaban-svg.less
@@ -1,3 +1,4 @@
+
svg {
.edge {
stroke: #777;
src/less/flow.less 125(+80 -45)
diff --git a/src/less/flow.less b/src/less/flow.less
index 931f47c..042f1cd 100644
--- a/src/less/flow.less
+++ b/src/less/flow.less
@@ -120,51 +120,86 @@ td {
}
// TODO: Rename this as #job-list
-#list {
+#joblist {
height: 100%;
-
- a {
- &.nodedisabled,
- &.DISABLED {
- opacity: 0.3;
- }
-
- &.DISABLED .icon {
- background-position: 16px 0px;
- }
-
- &.READY .icon {
- background-position: 16px 0px;
- }
-
- &.QUEUED .icon {
- opacity: 0.5;
- background-position: 32px 0px;
- }
-
- &.RUNNING .icon {
- background-position: 32px 0px;
- }
-
- &.SUCCEEDED .icon {
- background-position: 48px 0px;
- }
-
- &.FAILED .icon {
- background-position: 0px 0px;
- }
-
- &.KILLED .icon {
- background-position: 0px 0px;
- }
-
- .icon {
- float: left;
- width: 16px;
- height: 16px;
- margin: 2px 4px 0px -5px;
- background-image: url("./images/dot-icon.png");
- background-position: 16px 0px;
- }
+ ul {
+ list-style-type: none;
+ padding-left: 0px;
+
+ li {
+ &.active > a {
+ background-color: #D9EDFF;
+ }
+
+ ul {
+ padding-left: 20px;
+ }
+
+ &.subFilter > a > .expandarrow {
+ color : #3398cc;
+ }
+
+ a {
+ clear:both;
+ border-bottom-width: 0;
+
+ &.nodedisabled,
+ &.DISABLED {
+ opacity: 0.3;
+ }
+
+ &.DISABLED .icon {
+ background-position: 16px 0px;
+ }
+
+ &.READY .icon {
+ background-position: 16px 0px;
+ }
+
+ &.QUEUED .icon {
+ opacity: 0.5;
+ background-position: 32px 0px;
+ }
+
+ &.RUNNING .icon {
+ background-position: 32px 0px;
+ }
+
+ &.SUCCEEDED .icon {
+ background-position: 48px 0px;
+ }
+
+ &.FAILED .icon {
+ background-position: 0px 0px;
+ }
+
+ &.KILLED .icon {
+ background-position: 0px 0px;
+ }
+
+ &.FAILED_FINISHING .icon {
+ background-position: 0px 0px;
+ }
+
+ .icon {
+ float: left;
+ width: 16px;
+ height: 16px;
+ margin: 2px 4px 0px -5px;
+ background-image: url("./images/dot-icon.png");
+ background-position: 16px 0px;
+ }
+
+ .expandarrow {
+ float: right;
+ width: 16px;
+ height: 16px;
+ }
+
+ .filterHighlight {
+ background-color: #FFFF00;
+ }
+ }
+ }
}
}
src/web/css/azkaban-graph.css 7(+7 -0)
diff --git a/src/web/css/azkaban-graph.css b/src/web/css/azkaban-graph.css
index c78a508..8b1184a 100644
--- a/src/web/css/azkaban-graph.css
+++ b/src/web/css/azkaban-graph.css
@@ -11,6 +11,13 @@
cursor: pointer;
}
+.node.selected > .nodebox .border {
+ stroke-width: 3;
+}
+
+.node.selected > .nodebox .flowborder {
+ stroke-width: 3;
+}
.nodebox > .border:hover {
fill-opacity: 0.7;
}
src/web/js/azkaban.flow.job.view.js 243(+159 -84)
diff --git a/src/web/js/azkaban.flow.job.view.js b/src/web/js/azkaban.flow.job.view.js
index cb28674..4cf6c13 100644
--- a/src/web/js/azkaban.flow.job.view.js
+++ b/src/web/js/azkaban.flow.job.view.js
@@ -18,11 +18,11 @@ azkaban.JobListView = Backbone.View.extend({
events: {
"keyup input": "filterJobs",
"click .job": "handleJobClick",
- "click .resetPanZoomBtn": "handleResetPanZoom",
- "contextmenu li": "handleContextMenuClick"
+ "click #resetPanZoomBtn": "handleResetPanZoom",
+ "contextmenu li.listElement": "handleContextMenuClick",
"change .autoPanZoom" : "handleAutoPanZoom",
+ "click .expandarrow" : "handleToggleMenuExpand"
},
-
initialize: function(settings) {
this.model.bind('change:selected', this.handleSelectionChange, this);
this.model.bind('change:disabled', this.handleDisabledChange, this);
@@ -30,61 +30,69 @@ azkaban.JobListView = Backbone.View.extend({
this.model.bind('change:update', this.handleStatusUpdate, this);
this.filterInput = $(this.el).find("#filter");
- this.list = $(this.el).find("#list");
+ this.list = $(this.el).find("#joblist");
this.contextMenu = settings.contextMenuCallback;
this.listNodes = {};
},
-
filterJobs: function(self) {
var filter = this.filterInput.val();
- if (filter && filter.trim() != "") {
- filter = filter.trim();
- if (filter == "") {
- if (this.filter) {
- this.jobs.children().each(function(){
- var a = $(this).find("a");
- $(a).html(this.jobid);
- $(this).show();
- });
- }
- this.filter = null;
- return;
- }
- }
- else {
- if (this.filter) {
- this.jobs.children().each(function(){
- var a = $(this).find("a");
- $(a).html(this.jobid);
- $(this).show();
- });
- }
-
- this.filter = null;
+ // Clear all filters first
+ if (!filter || filter.trim() == "") {
+ this.unfilterAll(self);
return;
}
- this.jobs.children().each(function() {
- var jobid = this.jobid;
- var index = jobid.indexOf(filter);
+ this.hideAll(self);
+ var showList = {};
+
+ // find the jobs that need to be exposed.
+ for (var key in this.listNodes) {
+ var li = this.listNodes[key];
+ var node = li.node;
+ var nodeName = node.id;
+ node.listElement = li;
+
+ var index = nodeName.indexOf(filter);
if (index != -1) {
- var a = $(this).find("a");
+ var spanlabel = $(li).find("> a > span");
+
var endIndex = index + filter.length;
- var newHTML = jobid.substring(0, index) + "<span>" +
- jobid.substring(index, endIndex) + "</span>" +
- jobid.substring(endIndex, jobid.length);
+ var newHTML = nodeName.substring(0, index) + "<span class=\"filterHighlight\">" +
+ nodeName.substring(index, endIndex) + "</span>" +
+ nodeName.substring(endIndex, nodeName.length);
+ $(spanlabel).html(newHTML);
- $(a).html(newHTML);
- $(this).show();
- }
- else {
- $(this).hide();
+ // Apply classes to all the included embedded flows.
+ var pIndex = key.length;
+ while((pIndex = key.lastIndexOf(":", pIndex - 1)) > 0) {
+ var parentId = key.substr(0, pIndex);
+ var parentLi = this.listNodes[parentId];
+ $(parentLi).show();
+ $(parentLi).addClass("subFilter");
+ }
+
+ $(li).show();
}
- });
-
- this.filter = filter;
+ }
+ },
+ hideAll: function(self) {
+ for (var key in this.listNodes) {
+ var li = this.listNodes[key];
+ var label = $(li).find("> a > span");
+ $(label).text(li.node.id);
+ $(li).removeClass("subFilter");
+ $(li).hide();
+ }
+ },
+ unfilterAll: function(self) {
+ for (var key in this.listNodes) {
+ var li = this.listNodes[key];
+ var label = $(li).find("> a > span");
+ $(label).text(li.node.id);
+ $(li).removeClass("subFilter");
+ $(li).show();
+ }
},
-
handleStatusUpdate: function(evt) {
var updateData = this.model.get("update");
if (updateData.nodes) {
@@ -97,28 +105,38 @@ azkaban.JobListView = Backbone.View.extend({
}
}
},
-
- assignInitialStatus: function(evt) {
- var data = this.model.get("data");
+ changeStatuses: function(data) {
for (var i = 0; i < data.nodes.length; ++i) {
- var updateNode = data.nodes[i];
- var job = this.listNodes[updateNode.id];
- if (!$(job).hasClass("list-group-item")) {
- $(job).addClass("list-group-item");
- }
- $(job).addClass(updateNode.status);
+ var node = data.nodes[i];
+ if (node.status) {
+ var liElement = node.listElement;
+ $(liElement).removeClass(statusList.join(' '));
+ $(liElement).addClass(node.status);
+ }
+
+ if (node.flowData) {
+ this.changeStatuses(node.flowData);
+ }
}
},
-
render: function(self) {
var data = this.model.get("data");
var nodes = data.nodes;
- this.listNodes = {};
+ this.renderTree(this.list, data);
+//
+// this.assignInitialStatus(self);
+// this.handleDisabledChange(self);
+ },
+ renderTree : function(el, data, prefix) {
+ var nodes = data.nodes;
if (nodes.length == 0) {
console.log("No results");
return;
};
+ if (!prefix) {
+ prefix = "";
+ }
var nodeArray = nodes.slice(0);
nodeArray.sort(function(a, b) {
@@ -131,53 +149,104 @@ azkaban.JobListView = Backbone.View.extend({
}
});
- var list = this.list;
- this.jobs = $(list);
- for (var i = 0; i < nodeArray.length; ++i) {
+ var ul = document.createElement('ul');
+ for(var i=0; i < nodeArray.length; ++i) {
+ var li = document.createElement("li");
+ $(li).addClass("listElement");
+
+ // This is used for the filter step.
+ var listNodeName = prefix + nodeArray[i].id;
+ this.listNodes[listNodeName]=li;
+ li.node = nodeArray[i];
+ li.node.listElement = li;
+
var a = document.createElement("a");
+ var iconDiv = document.createElement('div');
+ $(iconDiv).addClass('icon');
+
+ $(a).append(iconDiv);
$(a).addClass('list-group-item').addClass('job');
- $(a).attr('href', '#');
-
- var iconDiv = document.createElement('div');
- $(iconDiv).addClass('icon');
- $(a).append(iconDiv);
- $(a).append(nodeArray[i].id);
- $(list).append(a);
- a.jobid = nodeArray[i].id;
- this.listNodes[nodeArray[i].id] = a;
+
+ var span = document.createElement("span");
+ $(span).text(nodeArray[i].id);
+ $(span).addClass("jobname");
+ $(a).append(span);
+ $(li).append(a);
+ $(ul).append(li);
+
+ if (nodeArray[i].flowData) {
+ // Add the up down
+ var expandDiv = document.createElement("div");
+ $(expandDiv).addClass("expandarrow glyphicon glyphicon-chevron-down");
+ $(a).append(expandDiv);
+
+ // Create subtree
+ var subul = this.renderTree(li, nodeArray[i].flowData, listNodeName + ":");
+ $(subul).hide();
+ }
}
- this.assignInitialStatus(self);
- this.handleDisabledChange(self);
+ $(el).append(ul);
+ return ul;
+ },
+ handleMenuExpand: function(li) {
+ var expandArrow = $(li).find("> a > .expandarrow");
+ var submenu = $(li).find("> ul");
+
+ $(expandArrow).removeClass("glyphicon-chevron-down");
+ $(expandArrow).addClass("glyphicon-chevron-up");
+ $(submenu).slideDown();
+ },
+ handleMenuCollapse: function(li) {
+ var expandArrow = $(li).find("> a > .expandarrow");
+ var submenu = $(li).find("> ul");
+
+ $(expandArrow).removeClass("glyphicon-chevron-up");
+ $(expandArrow).addClass("glyphicon-chevron-down");
+ $(submenu).slideUp();
+ },
+ handleToggleMenuExpand: function(evt) {
+ var expandarrow = evt.currentTarget;
+ var li = $(evt.currentTarget).closest("li.listElement");
+ var submenu = $(li).find("> ul");
+
+ if ($(submenu).is(":visible")) {
+ this.handleMenuCollapse(li);
+ }
+ else {
+ this.handleMenuExpand(li);
+ }
+
+ evt.stopImmediatePropagation();
},
-
handleContextMenuClick: function(evt) {
if (this.contextMenu) {
- this.contextMenu(evt);
+ this.contextMenu(evt, this.model, evt.currentTarget.node);
return false;
}
},
-
handleJobClick: function(evt) {
- var jobid = evt.currentTarget.jobid;
- if (!evt.currentTarget.jobid) {
+ console.log("Job clicked");
+ var li = $(evt.currentTarget).closest("li.listElement");
+ var node = li[0].node;
+ if (!node) {
return;
}
if (this.model.has("selected")) {
var selected = this.model.get("selected");
- if (selected == jobid) {
+ if (selected == node) {
this.model.unset("selected");
}
else {
- this.model.set({"selected": jobid});
+ this.model.set({"selected": node});
}
}
else {
- this.model.set({"selected": jobid});
+ this.model.set({"selected": node});
}
+
},
-
handleDisabledChange: function(evt) {
var disabledMap = this.model.get("disabled");
var nodes = this.model.get("nodes");
@@ -191,7 +260,6 @@ azkaban.JobListView = Backbone.View.extend({
}
}
},
-
handleSelectionChange: function(evt) {
if (!this.model.hasChanged("selected")) {
return;
@@ -199,16 +267,23 @@ azkaban.JobListView = Backbone.View.extend({
var previous = this.model.previous("selected");
var current = this.model.get("selected");
-
+
if (previous) {
- $(this.listNodes[previous]).removeClass("active");
+ $(previous.listElement).removeClass("active");
}
if (current) {
- $(this.listNodes[current]).addClass("active");
+ $(current.listElement).addClass("active");
+ this.propagateExpansion(current.listElement);
+ }
+ },
+ propagateExpansion: function(li) {
+ var li = $(li).parent().closest("li.listElement")[0];
+ if (li) {
+ this.propagateExpansion(li);
+ this.handleMenuExpand(li);
}
},
-
handleResetPanZoom: function(evt) {
this.model.trigger("resetPanZoom");
},
src/web/js/azkaban.flow.view.js 81(+6 -75)
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 2e33ddb..a6fd36e 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -362,52 +362,6 @@ azkaban.SummaryView = Backbone.View.extend({
},
});
-var exNodeClickCallback = function(event) {
- console.log("Node clicked callback");
- var jobId = event.currentTarget.jobid;
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
- flowId + "&job=" + jobId;
-
- var menu = [
- {title: "Open Job...", callback: function() {window.location.href=requestURL;}},
- {title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
- ];
-
- contextMenuView.show(event, menu);
-}
-
-var exJobClickCallback = function(event) {
- console.log("Node clicked callback");
- var jobId = event.currentTarget.jobid;
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
- flowId + "&job=" + jobId;
-
- var menu = [
- {title: "Open Job...", callback: function() {window.location.href=requestURL;}},
- {title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
- ];
-
- contextMenuView.show(event, menu);
-}
-
-var exEdgeClickCallback = function(event) {
- console.log("Edge clicked callback");
-}
-
-var exGraphClickCallback = function(event) {
- console.log("Graph clicked callback");
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
-
- var menu = [
- {title: "Open Flow...", callback: function() {window.location.href=requestURL;}},
- {title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
- {break: 1},
- {title: "Center Graph", callback: function() {graphModel.trigger("resetPanZoom");}}
- ];
-
- contextMenuView.show(event, menu);
-}
-
var graphModel;
azkaban.GraphModel = Backbone.Model.extend({});
@@ -444,16 +398,16 @@ $(function() {
el: $('#svgDiv'),
model: graphModel,
rightClick: {
- "node": exNodeClickCallback,
- "edge": exEdgeClickCallback,
- "graph": exGraphClickCallback
+ "node": nodeClickCallback,
+ "edge": edgeClickCallback,
+ "graph": graphClickCallback
}
});
jobsListView = new azkaban.JobListView({
el: $('#jobList'),
model: graphModel,
- contextMenuCallback: exJobClickCallback
+ contextMenuCallback: jobClickCallback
});
var requestURL = contextURL + "/manager";
@@ -477,32 +431,9 @@ $(function() {
"flow": flowId
};
var successHandler = function(data) {
- // Create the nodes
- var nodes = {};
- for (var i = 0; i < data.nodes.length; ++i) {
- var node = data.nodes[i];
- nodes[node.id] = node;
- }
- for (var i = 0; i < data.edges.length; ++i) {
- var edge = data.edges[i];
- var fromNode = nodes[edge.from];
- var toNode = nodes[edge.target];
-
- if (!fromNode.outNodes) {
- fromNode.outNodes = {};
- }
- fromNode.outNodes[toNode.id] = toNode;
-
- if (!toNode.inNodes) {
- toNode.inNodes = {};
- }
- toNode.inNodes[fromNode.id] = fromNode;
- }
-
console.log("data fetched");
- graphModel.set({data: data});
- graphModel.set({nodes: nodes});
- graphModel.set({disabled: {}});
+ processFlowData(data);
+ graphModel.set({data:data});
graphModel.trigger("change:graph");
// Handle the hash changes here so the graph finishes rendering first.
diff --git a/src/web/js/azkaban.job.status.utils.js b/src/web/js/azkaban.job.status.utils.js
index ee03ae6..6813d10 100644
--- a/src/web/js/azkaban.job.status.utils.js
+++ b/src/web/js/azkaban.job.status.utils.js
@@ -14,10 +14,10 @@
* the License.
*/
-var statusList = ["FAILED", "FAILED_FINISHING", "SUCCEEDED", "RUNNING", "WAITING", "KILLED", "DISABLED", "READY", "UNKNOWN", "PAUSED", "SKIPPED"];
+var statusList = ["QUEUED", "FAILED", "FAILED_FINISHING", "SUCCEEDED", "RUNNING", "WAITING", "KILLED", "DISABLED", "READY", "UNKNOWN", "PAUSED", "SKIPPED"];
var statusStringMap = {
+ "QUEUED":"Queued",
"SKIPPED": "Skipped",
- "PREPARING": "Preparing",
"FAILED": "Failed",
"SUCCEEDED": "Success",
"FAILED_FINISHING": "Running w/Failure",
src/web/js/azkaban.svg.flow.loader.js 21(+12 -9)
diff --git a/src/web/js/azkaban.svg.flow.loader.js b/src/web/js/azkaban.svg.flow.loader.js
index 88e46cf..a3cbc71 100644
--- a/src/web/js/azkaban.svg.flow.loader.js
+++ b/src/web/js/azkaban.svg.flow.loader.js
@@ -104,14 +104,14 @@ var nodeClickCallback = function(event, model, node) {
var target = event.currentTarget;
var type = node.type;
- var flowId = node.flowId;
+ var flowId = node.parent.flow;
var jobId = node.id;
var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
var menu = [];
if (type == "flow") {
- var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + event.currentTarget.flowId;
+ var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + node.flowId;
if (node.expanded) {
menu = [{title: "Collapse Flow...", callback: function() {model.trigger("collapseFlow", node);}}];
}
@@ -144,17 +144,20 @@ var nodeClickCallback = function(event, model, node) {
contextMenuView.show(event, menu);
}
-var jobClickCallback = function(event, model) {
+var jobClickCallback = function(event, model, node) {
console.log("Node clicked callback");
- var jobId = event.currentTarget.jobid;
- var node = target.data;
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+ var target = event.currentTarget;
+ var type = node.type;
+ var flowId = node.parent.flow;
+ var jobId = node.id;
+
+ var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + node.id;
var menu;
- if (event.currentTarget.jobtype == "flow") {
- var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + event.currentTarget.flowId;
+ if (type == "flow") {
+ var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + node.flowId;
menu = [
- {title: "View Flow...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
+ {title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
{break: 1},
{title: "Open Flow...", callback: function() {window.location.href=flowRequestURL;}},
{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}},
src/web/js/azkaban.svg.graph.view.js 79(+30 -49)
diff --git a/src/web/js/azkaban.svg.graph.view.js b/src/web/js/azkaban.svg.graph.view.js
index 7f90247..a3cc2e0 100644
--- a/src/web/js/azkaban.svg.graph.view.js
+++ b/src/web/js/azkaban.svg.graph.view.js
@@ -17,12 +17,8 @@ $.namespace('azkaban');
azkaban.SvgGraphView = Backbone.View.extend({
events: {
-
- },
- test: function() {
- console.log("test");
+
},
-
initialize: function(settings) {
this.model.bind('change:selected', this.changeSelected, this);
this.model.bind('centerNode', this.centerNode, this);
@@ -97,8 +93,21 @@ azkaban.SvgGraphView = Backbone.View.extend({
nodes[i].label = nodes[i].id;
}
+ var self = this;
for (var i = 0; i < nodes.length; ++i) {
this.drawNode(this, nodes[i], g);
+ $(nodes[i].gNode).click(function(evt) {
+ var selected = self.model.get("selected");
+ if (selected == evt.currentTarget.data) {
+ self.model.unset("selected");
+ }
+ else {
+ self.model.set({"selected":evt.currentTarget.data});
+ }
+
+ evt.stopPropagation();
+ evt.cancelBubble = true;
+ });
}
// layout
@@ -126,25 +135,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
this.handleDisabledChange(self);
}
-/*
- if (this.rightClick) {
- var callbacks = this.rightClick;
- var currentTarget = self.currentTarget;
- if (callbacks.node && currentTarget.jobid) {
- callbacks.node(self, this.model, currentTarget.nodeobj);
- }
- else if (callbacks.edge && (currentTarget.nodeName == "polyline" || currentTarget.nodeName == "line")) {
- callbacks.edge(self, this.model);
- }
- else if (callbacks.graph) {
- callbacks.graph(self, this.model);
- }
- return false;
- }
-
-*/
-
- var self = this;
if (self.rightClick) {
if (self.rightClick.node) {
// Proper children selectors don't work properly on svg
@@ -197,36 +187,35 @@ azkaban.SvgGraphView = Backbone.View.extend({
$(g).attr("title", initialStatus);
}
},
-
- changeSelected: function(self) {
+ changeSelected: function(self) {
console.log("change selected");
var selected = this.model.get("selected");
var previous = this.model.previous("selected");
if (previous) {
// Unset previous
- var g = this.gNodes[previous];
- removeClass(g, "selected");
+ removeClass(previous.gNode, "selected");
}
if (selected) {
- var g = this.gNodes[selected];
- var node = this.nodes[selected];
-
+ this.propagateExpansion(selected);
+ var g = selected.gNode;
addClass(g, "selected");
console.log(this.model.get("autoPanZoom"));
if (this.model.get("autoPanZoom")) {
- var offset = 150;
- var widthHeight = offset*2;
- var x = node.x - offset;
- var y = node.y - offset;
-
- $(this.svgGraph).svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
+ this.centerNode(selected);
+ }
+ }
+ },
+ propagateExpansion: function(node) {
+ if (node.parent) {
+ if (node.parent.node) {
+ this.propagateExpansion(node.parent.node);
+ this.expandFlow(node.parent.node);
}
}
},
-
handleStatusUpdate: function(evt) {
var updateData = this.model.get("update");
if (updateData.nodes) {
@@ -251,8 +240,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
clickGraph: function(self) {
console.log("click");
- if (self.currentTarget.jobid) {
- this.model.set({"selected": self.currentTarget.jobid});
+ if (self.currentTarget.data) {
+ this.model.set({"selected": self.currentTarget.data});
}
},
@@ -310,12 +299,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
else {
this.drawBoxNode(self, node, g);
}
-//
-// var boundingBox = node.gNode.getBBox();
-// node.width = boundingBox.width;
-// node.height = boundingBox.height;
-// node.centerX = node.width/2;
-// node.centerY = node.height/2;
},
moveNodes: function(nodes) {
var svg = this.svg;
@@ -326,7 +309,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
svg.change(gNode, {"transform": translateStr(node.x, node.y)});
}
},
-
expandFlow: function(node) {
var svg = this.svg;
var gnode = node.gNode;
@@ -592,8 +574,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
$(borderRect).animate({svgWidth: node.width, svgHeight: node.height}, time);
$(borderRect).animate({svgFill: 'white'}, time);
},
-
- resetPanZoom: function(duration) {
+ resetPanZoom: function(duration) {
var bounds = this.graphBounds;
var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
src/web/js/jquery.svg.js 1394(+1394 -0)
diff --git a/src/web/js/jquery.svg.js b/src/web/js/jquery.svg.js
new file mode 100644
index 0000000..dcbf95f
--- /dev/null
+++ b/src/web/js/jquery.svg.js
@@ -0,0 +1,1394 @@
+/* http://keith-wood.name/svg.html
+ SVG for jQuery v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+
+(function($) { // Hide scope, no $ conflict
+
+/* SVG manager.
+ Use the singleton instance of this class, $.svg,
+ to interact with the SVG functionality. */
+function SVGManager() {
+ this._settings = []; // Settings to be remembered per SVG object
+ this._extensions = []; // List of SVG extensions added to SVGWrapper
+ // for each entry [0] is extension name, [1] is extension class (function)
+ // the function takes one parameter - the SVGWrapper instance
+ this.regional = []; // Localisations, indexed by language, '' for default (English)
+ this.regional[''] = {errorLoadingText: 'Error loading',
+ notSupportedText: 'This browser does not support SVG'};
+ this.local = this.regional['']; // Current localisation
+ this._uuid = new Date().getTime();
+ this._renesis = detectActiveX('RenesisX.RenesisCtrl');
+}
+
+/* Determine whether a given ActiveX control is available.
+ @param classId (string) the ID for the ActiveX control
+ @return (boolean) true if found, false if not */
+function detectActiveX(classId) {
+ try {
+ return !!(window.ActiveXObject && new ActiveXObject(classId));
+ }
+ catch (e) {
+ return false;
+ }
+}
+
+var PROP_NAME = 'svgwrapper';
+
+$.extend(SVGManager.prototype, {
+ /* Class name added to elements to indicate already configured with SVG. */
+ markerClassName: 'hasSVG',
+
+ /* SVG namespace. */
+ svgNS: 'http://www.w3.org/2000/svg',
+ /* XLink namespace. */
+ xlinkNS: 'http://www.w3.org/1999/xlink',
+
+ /* SVG wrapper class. */
+ _wrapperClass: SVGWrapper,
+
+ /* Camel-case versions of attribute names containing dashes or are reserved words. */
+ _attrNames: {class_: 'class', in_: 'in',
+ alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift',
+ clipPath: 'clip-path', clipRule: 'clip-rule',
+ colorInterpolation: 'color-interpolation',
+ colorInterpolationFilters: 'color-interpolation-filters',
+ colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline',
+ enableBackground: 'enable-background', fillOpacity: 'fill-opacity',
+ fillRule: 'fill-rule', floodColor: 'flood-color',
+ floodOpacity: 'flood-opacity', fontFamily: 'font-family',
+ fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust',
+ fontStretch: 'font-stretch', fontStyle: 'font-style',
+ fontVariant: 'font-variant', fontWeight: 'font-weight',
+ glyphOrientationHorizontal: 'glyph-orientation-horizontal',
+ glyphOrientationVertical: 'glyph-orientation-vertical',
+ horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x',
+ imageRendering: 'image-rendering', letterSpacing: 'letter-spacing',
+ lightingColor: 'lighting-color', markerEnd: 'marker-end',
+ markerMid: 'marker-mid', markerStart: 'marker-start',
+ stopColor: 'stop-color', stopOpacity: 'stop-opacity',
+ strikethroughPosition: 'strikethrough-position',
+ strikethroughThickness: 'strikethrough-thickness',
+ strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset',
+ strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin',
+ strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity',
+ strokeWidth: 'stroke-width', textAnchor: 'text-anchor',
+ textDecoration: 'text-decoration', textRendering: 'text-rendering',
+ underlinePosition: 'underline-position', underlineThickness: 'underline-thickness',
+ vertAdvY: 'vert-adv-y', vertOriginY: 'vert-origin-y',
+ wordSpacing: 'word-spacing', writingMode: 'writing-mode'},
+
+ /* Add the SVG object to its container. */
+ _attachSVG: function(container, settings) {
+ var svg = (container.namespaceURI == this.svgNS ? container : null);
+ var container = (svg ? null : container);
+ if ($(container || svg).hasClass(this.markerClassName)) {
+ return;
+ }
+ if (typeof settings == 'string') {
+ settings = {loadURL: settings};
+ }
+ else if (typeof settings == 'function') {
+ settings = {onLoad: settings};
+ }
+ $(container || svg).addClass(this.markerClassName);
+ try {
+ if (!svg) {
+ svg = document.createElementNS(this.svgNS, 'svg');
+ svg.setAttribute('version', '1.1');
+ if (container.clientWidth > 0) {
+ svg.setAttribute('width', container.clientWidth);
+ }
+ if (container.clientHeight > 0) {
+ svg.setAttribute('height', container.clientHeight);
+ }
+ container.appendChild(svg);
+ }
+ this._afterLoad(container, svg, settings || {});
+ }
+ catch (e) {
+ if ($.browser.msie) {
+ if (!container.id) {
+ container.id = 'svg' + (this._uuid++);
+ }
+ this._settings[container.id] = settings;
+ container.innerHTML = '<embed type="image/svg+xml" width="100%" ' +
+ 'height="100%" src="' + (settings.initPath || '') + 'blank.svg" ' +
+ 'pluginspage="http://www.adobe.com/svg/viewer/install/main.html"/>';
+ }
+ else {
+ container.innerHTML = '<p class="svg_error">' +
+ this.local.notSupportedText + '</p>';
+ }
+ }
+ },
+
+ /* SVG callback after loading - register SVG root. */
+ _registerSVG: function() {
+ for (var i = 0; i < document.embeds.length; i++) { // Check all
+ var container = document.embeds[i].parentNode;
+ if (!$(container).hasClass($.svg.markerClassName) || // Not SVG
+ $.data(container, PROP_NAME)) { // Already done
+ continue;
+ }
+ var svg = null;
+ try {
+ svg = document.embeds[i].getSVGDocument();
+ }
+ catch(e) {
+ setTimeout($.svg._registerSVG, 250); // Renesis takes longer to load
+ return;
+ }
+ svg = (svg ? svg.documentElement : null);
+ if (svg) {
+ $.svg._afterLoad(container, svg);
+ }
+ }
+ },
+
+ /* Post-processing once loaded. */
+ _afterLoad: function(container, svg, settings) {
+ var settings = settings || this._settings[container.id];
+ this._settings[container ? container.id : ''] = null;
+ var wrapper = new this._wrapperClass(svg, container);
+ $.data(container || svg, PROP_NAME, wrapper);
+ try {
+ if (settings.loadURL) { // Load URL
+ wrapper.load(settings.loadURL, settings);
+ }
+ if (settings.settings) { // Additional settings
+ wrapper.configure(settings.settings);
+ }
+ if (settings.onLoad && !settings.loadURL) { // Onload callback
+ settings.onLoad.apply(container || svg, [wrapper]);
+ }
+ }
+ catch (e) {
+ alert(e);
+ }
+ },
+
+ /* Return the SVG wrapper created for a given container.
+ @param container (string) selector for the container or
+ (element) the container for the SVG object or
+ jQuery collection - first entry is the container
+ @return (SVGWrapper) the corresponding SVG wrapper element, or null if not attached */
+ _getSVG: function(container) {
+ container = (typeof container == 'string' ? $(container)[0] :
+ (container.jquery ? container[0] : container));
+ return $.data(container, PROP_NAME);
+ },
+
+ /* Remove the SVG functionality from a div.
+ @param container (element) the container for the SVG object */
+ _destroySVG: function(container) {
+ var $container = $(container);
+ if (!$container.hasClass(this.markerClassName)) {
+ return;
+ }
+ $container.removeClass(this.markerClassName);
+ if (container.namespaceURI != this.svgNS) {
+ $container.empty();
+ }
+ $.removeData(container, PROP_NAME);
+ },
+
+ /* Extend the SVGWrapper object with an embedded class.
+ The constructor function must take a single parameter that is
+ a reference to the owning SVG root object. This allows the
+ extension to access the basic SVG functionality.
+ @param name (string) the name of the SVGWrapper attribute to access the new class
+ @param extClass (function) the extension class constructor */
+ addExtension: function(name, extClass) {
+ this._extensions.push([name, extClass]);
+ },
+
+ /* Does this node belong to SVG?
+ @param node (element) the node to be tested
+ @return (boolean) true if an SVG node, false if not */
+ isSVGElem: function(node) {
+ return (node.nodeType == 1 && node.namespaceURI == $.svg.svgNS);
+ }
+});
+
+/* The main SVG interface, which encapsulates the SVG element.
+ Obtain a reference from $().svg('get') */
+function SVGWrapper(svg, container) {
+ this._svg = svg; // The SVG root node
+ this._container = container; // The containing div
+ for (var i = 0; i < $.svg._extensions.length; i++) {
+ var extension = $.svg._extensions[i];
+ this[extension[0]] = new extension[1](this);
+ }
+}
+
+$.extend(SVGWrapper.prototype, {
+
+ /* Retrieve the width of the SVG object. */
+ _width: function() {
+ return (this._container ? this._container.clientWidth : this._svg.width);
+ },
+
+ /* Retrieve the height of the SVG object. */
+ _height: function() {
+ return (this._container ? this._container.clientHeight : this._svg.height);
+ },
+
+ /* Retrieve the root SVG element.
+ @return the top-level SVG element */
+ root: function() {
+ return this._svg;
+ },
+
+ /* Configure a SVG node.
+ @param node (element, optional) the node to configure
+ @param settings (object) additional settings for the root
+ @param clear (boolean) true to remove existing attributes first,
+ false to add to what is already there (optional)
+ @return (SVGWrapper) this root */
+ configure: function(node, settings, clear) {
+ if (!node.nodeName) {
+ clear = settings;
+ settings = node;
+ node = this._svg;
+ }
+ if (clear) {
+ for (var i = node.attributes.length - 1; i >= 0; i--) {
+ var attr = node.attributes.item(i);
+ if (!(attr.nodeName == 'onload' || attr.nodeName == 'version' ||
+ attr.nodeName.substring(0, 5) == 'xmlns')) {
+ node.attributes.removeNamedItem(attr.nodeName);
+ }
+ }
+ }
+ for (var attrName in settings) {
+ node.setAttribute($.svg._attrNames[attrName] || attrName, settings[attrName]);
+ }
+ return this;
+ },
+
+ /* Locate a specific element in the SVG document.
+ @param id (string) the element's identifier
+ @return (element) the element reference, or null if not found */
+ getElementById: function(id) {
+ return this._svg.ownerDocument.getElementById(id);
+ },
+
+ /* Change the attributes for a SVG node.
+ @param element (SVG element) the node to change
+ @param settings (object) the new settings
+ @return (SVGWrapper) this root */
+ change: function(element, settings) {
+ if (element) {
+ for (var name in settings) {
+ if (settings[name] == null) {
+ element.removeAttribute($.svg._attrNames[name] || name);
+ }
+ else {
+ element.setAttribute($.svg._attrNames[name] || name, settings[name]);
+ }
+ }
+ }
+ return this;
+ },
+
+ /* Check for parent being absent and adjust arguments accordingly. */
+ _args: function(values, names, optSettings) {
+ names.splice(0, 0, 'parent');
+ names.splice(names.length, 0, 'settings');
+ var args = {};
+ var offset = 0;
+ if (values[0] != null && values[0].jquery) {
+ values[0] = values[0][0];
+ }
+ if (values[0] != null && !(typeof values[0] == 'object' && values[0].nodeName)) {
+ args['parent'] = null;
+ offset = 1;
+ }
+ for (var i = 0; i < values.length; i++) {
+ args[names[i + offset]] = values[i];
+ }
+ if (optSettings) {
+ $.each(optSettings, function(i, value) {
+ if (typeof args[value] == 'object') {
+ args.settings = args[value];
+ args[value] = null;
+ }
+ });
+ }
+ return args;
+ },
+
+ /* Add a title.
+ @param parent (element or jQuery) the parent node for the new title (optional)
+ @param text (string) the text of the title
+ @param settings (object) additional settings for the title (optional)
+ @return (element) the new title node */
+ title: function(parent, text, settings) {
+ var args = this._args(arguments, ['text']);
+ var node = this._makeNode(args.parent, 'title', args.settings || {});
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
+ return node;
+ },
+
+ /* Add a description.
+ @param parent (element or jQuery) the parent node for the new description (optional)
+ @param text (string) the text of the description
+ @param settings (object) additional settings for the description (optional)
+ @return (element) the new description node */
+ describe: function(parent, text, settings) {
+ var args = this._args(arguments, ['text']);
+ var node = this._makeNode(args.parent, 'desc', args.settings || {});
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
+ return node;
+ },
+
+ /* Add a definitions node.
+ @param parent (element or jQuery) the parent node for the new definitions (optional)
+ @param id (string) the ID of this definitions (optional)
+ @param settings (object) additional settings for the definitions (optional)
+ @return (element) the new definitions node */
+ defs: function(parent, id, settings) {
+ var args = this._args(arguments, ['id'], ['id']);
+ return this._makeNode(args.parent, 'defs', $.extend(
+ (args.id ? {id: args.id} : {}), args.settings || {}));
+ },
+
+ /* Add a symbol definition.
+ @param parent (element or jQuery) the parent node for the new symbol (optional)
+ @param id (string) the ID of this symbol
+ @param x1 (number) the left coordinate for this symbol
+ @param y1 (number) the top coordinate for this symbol
+ @param width (number) the width of this symbol
+ @param height (number) the height of this symbol
+ @param settings (object) additional settings for the symbol (optional)
+ @return (element) the new symbol node */
+ symbol: function(parent, id, x1, y1, width, height, settings) {
+ var args = this._args(arguments, ['id', 'x1', 'y1', 'width', 'height']);
+ return this._makeNode(args.parent, 'symbol', $.extend({id: args.id,
+ viewBox: args.x1 + ' ' + args.y1 + ' ' + args.width + ' ' + args.height},
+ args.settings || {}));
+ },
+
+ /* Add a marker definition.
+ @param parent (element or jQuery) the parent node for the new marker (optional)
+ @param id (string) the ID of this marker
+ @param refX (number) the x-coordinate for the reference point
+ @param refY (number) the y-coordinate for the reference point
+ @param mWidth (number) the marker viewport width
+ @param mHeight (number) the marker viewport height
+ @param orient (string or int) 'auto' or angle (degrees) (optional)
+ @param settings (object) additional settings for the marker (optional)
+ @return (element) the new marker node */
+ marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) {
+ var args = this._args(arguments, ['id', 'refX', 'refY',
+ 'mWidth', 'mHeight', 'orient'], ['orient']);
+ return this._makeNode(args.parent, 'marker', $.extend(
+ {id: args.id, refX: args.refX, refY: args.refY, markerWidth: args.mWidth,
+ markerHeight: args.mHeight, orient: args.orient || 'auto'}, args.settings || {}));
+ },
+
+ /* Add a style node.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param styles (string) the CSS styles
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new style node */
+ style: function(parent, styles, settings) {
+ var args = this._args(arguments, ['styles']);
+ var node = this._makeNode(args.parent, 'style', $.extend(
+ {type: 'text/css'}, args.settings || {}));
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.styles));
+ if ($.browser.opera) {
+ $('head').append('<style type="text/css">' + args.styles + '</style>');
+ }
+ return node;
+ },
+
+ /* Add a script node.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param script (string) the JavaScript code
+ @param type (string) the MIME type for the code (optional, default 'text/javascript')
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new script node */
+ script: function(parent, script, type, settings) {
+ var args = this._args(arguments, ['script', 'type'], ['type']);
+ var node = this._makeNode(args.parent, 'script', $.extend(
+ {type: args.type || 'text/javascript'}, args.settings || {}));
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.script));
+ if (!$.browser.mozilla) {
+ $.globalEval(args.script);
+ }
+ return node;
+ },
+
+ /* Add a linear gradient definition.
+ Specify all of x1, y1, x2, y2 or none of them.
+ @param parent (element or jQuery) the parent node for the new gradient (optional)
+ @param id (string) the ID for this gradient
+ @param stops (string[][]) the gradient stops, each entry is
+ [0] is offset (0.0-1.0 or 0%-100%), [1] is colour,
+ [2] is opacity (optional)
+ @param x1 (number) the x-coordinate of the gradient start (optional)
+ @param y1 (number) the y-coordinate of the gradient start (optional)
+ @param x2 (number) the x-coordinate of the gradient end (optional)
+ @param y2 (number) the y-coordinate of the gradient end (optional)
+ @param settings (object) additional settings for the gradient (optional)
+ @return (element) the new gradient node */
+ linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) {
+ var args = this._args(arguments,
+ ['id', 'stops', 'x1', 'y1', 'x2', 'y2'], ['x1']);
+ var sets = $.extend({id: args.id},
+ (args.x1 != null ? {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2} : {}));
+ return this._gradient(args.parent, 'linearGradient',
+ $.extend(sets, args.settings || {}), args.stops);
+ },
+
+ /* Add a radial gradient definition.
+ Specify all of cx, cy, r, fx, fy or none of them.
+ @param parent (element or jQuery) the parent node for the new gradient (optional)
+ @param id (string) the ID for this gradient
+ @param stops (string[][]) the gradient stops, each entry
+ [0] is offset, [1] is colour, [2] is opacity (optional)
+ @param cx (number) the x-coordinate of the largest circle centre (optional)
+ @param cy (number) the y-coordinate of the largest circle centre (optional)
+ @param r (number) the radius of the largest circle (optional)
+ @param fx (number) the x-coordinate of the gradient focus (optional)
+ @param fy (number) the y-coordinate of the gradient focus (optional)
+ @param settings (object) additional settings for the gradient (optional)
+ @return (element) the new gradient node */
+ radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) {
+ var args = this._args(arguments,
+ ['id', 'stops', 'cx', 'cy', 'r', 'fx', 'fy'], ['cx']);
+ var sets = $.extend({id: args.id}, (args.cx != null ?
+ {cx: args.cx, cy: args.cy, r: args.r, fx: args.fx, fy: args.fy} : {}));
+ return this._gradient(args.parent, 'radialGradient',
+ $.extend(sets, args.settings || {}), args.stops);
+ },
+
+ /* Add a gradient node. */
+ _gradient: function(parent, name, settings, stops) {
+ var node = this._makeNode(parent, name, settings);
+ for (var i = 0; i < stops.length; i++) {
+ var stop = stops[i];
+ this._makeNode(node, 'stop', $.extend(
+ {offset: stop[0], stopColor: stop[1]},
+ (stop[2] != null ? {stopOpacity: stop[2]} : {})));
+ }
+ return node;
+ },
+
+ /* Add a pattern definition.
+ Specify all of vx, vy, xwidth, vheight or none of them.
+ @param parent (element or jQuery) the parent node for the new pattern (optional)
+ @param id (string) the ID for this pattern
+ @param x (number) the x-coordinate for the left edge of the pattern
+ @param y (number) the y-coordinate for the top edge of the pattern
+ @param width (number) the width of the pattern
+ @param height (number) the height of the pattern
+ @param vx (number) the minimum x-coordinate for view box (optional)
+ @param vy (number) the minimum y-coordinate for the view box (optional)
+ @param vwidth (number) the width of the view box (optional)
+ @param vheight (number) the height of the view box (optional)
+ @param settings (object) additional settings for the pattern (optional)
+ @return (element) the new pattern node */
+ pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) {
+ var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height',
+ 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
+ var sets = $.extend({id: args.id, x: args.x, y: args.y,
+ width: args.width, height: args.height}, (args.vx != null ?
+ {viewBox: args.vx + ' ' + args.vy + ' ' + args.vwidth + ' ' + args.vheight} : {}));
+ return this._makeNode(args.parent, 'pattern', $.extend(sets, args.settings || {}));
+ },
+
+ /* Add a clip path definition.
+ @param parent (element) the parent node for the new element (optional)
+ @param id (string) the ID for this path
+ @param units (string) either 'userSpaceOnUse' (default) or 'objectBoundingBox' (optional)
+ @return (element) the new clipPath node */
+ clipPath: function(parent, id, units, settings) {
+ var args = this._args(arguments, ['id', 'units']);
+ args.units = args.units || 'userSpaceOnUse';
+ return this._makeNode(args.parent, 'clipPath', $.extend(
+ {id: args.id, clipPathUnits: args.units}, args.settings || {}));
+ },
+
+ /* Add a mask definition.
+ @param parent (element or jQuery) the parent node for the new mask (optional)
+ @param id (string) the ID for this mask
+ @param x (number) the x-coordinate for the left edge of the mask
+ @param y (number) the y-coordinate for the top edge of the mask
+ @param width (number) the width of the mask
+ @param height (number) the height of the mask
+ @param settings (object) additional settings for the mask (optional)
+ @return (element) the new mask node */
+ mask: function(parent, id, x, y, width, height, settings) {
+ var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height']);
+ return this._makeNode(args.parent, 'mask', $.extend(
+ {id: args.id, x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ },
+
+ /* Create a new path object.
+ @return (SVGPath) a new path object */
+ createPath: function() {
+ return new SVGPath();
+ },
+
+ /* Create a new text object.
+ @return (SVGText) a new text object */
+ createText: function() {
+ return new SVGText();
+ },
+
+ /* Add an embedded SVG element.
+ Specify all of vx, vy, vwidth, vheight or none of them.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param x (number) the x-coordinate for the left edge of the node
+ @param y (number) the y-coordinate for the top edge of the node
+ @param width (number) the width of the node
+ @param height (number) the height of the node
+ @param vx (number) the minimum x-coordinate for view box (optional)
+ @param vy (number) the minimum y-coordinate for the view box (optional)
+ @param vwidth (number) the width of the view box (optional)
+ @param vheight (number) the height of the view box (optional)
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new node */
+ svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height',
+ 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
+ var sets = $.extend({x: args.x, y: args.y, width: args.width, height: args.height},
+ (args.vx != null ? {viewBox: args.vx + ' ' + args.vy + ' ' +
+ args.vwidth + ' ' + args.vheight} : {}));
+ return this._makeNode(args.parent, 'svg', $.extend(sets, args.settings || {}));
+ },
+
+ /* Create a group.
+ @param parent (element or jQuery) the parent node for the new group (optional)
+ @param id (string) the ID of this group (optional)
+ @param settings (object) additional settings for the group (optional)
+ @return (element) the new group node */
+ group: function(parent, id, settings) {
+ var args = this._args(arguments, ['id'], ['id']);
+ return this._makeNode(args.parent, 'g', $.extend({id: args.id}, args.settings || {}));
+ },
+
+ /* Add a usage reference.
+ Specify all of x, y, width, height or none of them.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param x (number) the x-coordinate for the left edge of the node (optional)
+ @param y (number) the y-coordinate for the top edge of the node (optional)
+ @param width (number) the width of the node (optional)
+ @param height (number) the height of the node (optional)
+ @param ref (string) the ID of the definition node
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new node */
+ use: function(parent, x, y, width, height, ref, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
+ if (typeof args.x == 'string') {
+ args.ref = args.x;
+ args.settings = args.y;
+ args.x = args.y = args.width = args.height = null;
+ }
+ var node = this._makeNode(args.parent, 'use', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Add a link, which applies to all child elements.
+ @param parent (element or jQuery) the parent node for the new link (optional)
+ @param ref (string) the target URL
+ @param settings (object) additional settings for the link (optional)
+ @return (element) the new link node */
+ link: function(parent, ref, settings) {
+ var args = this._args(arguments, ['ref']);
+ var node = this._makeNode(args.parent, 'a', args.settings);
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Add an image.
+ @param parent (element or jQuery) the parent node for the new image (optional)
+ @param x (number) the x-coordinate for the left edge of the image
+ @param y (number) the y-coordinate for the top edge of the image
+ @param width (number) the width of the image
+ @param height (number) the height of the image
+ @param ref (string) the path to the image
+ @param settings (object) additional settings for the image (optional)
+ @return (element) the new image node */
+ image: function(parent, x, y, width, height, ref, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
+ var node = this._makeNode(args.parent, 'image', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Draw a path.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param path (string or SVGPath) the path to draw
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ path: function(parent, path, settings) {
+ var args = this._args(arguments, ['path']);
+ return this._makeNode(args.parent, 'path', $.extend(
+ {d: (args.path.path ? args.path.path() : args.path)}, args.settings || {}));
+ },
+
+ /* Draw a rectangle.
+ Specify both of rx and ry or neither.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param x (number) the x-coordinate for the left edge of the rectangle
+ @param y (number) the y-coordinate for the top edge of the rectangle
+ @param width (number) the width of the rectangle
+ @param height (number) the height of the rectangle
+ @param rx (number) the x-radius of the ellipse for the rounded corners (optional)
+ @param ry (number) the y-radius of the ellipse for the rounded corners (optional)
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ rect: function(parent, x, y, width, height, rx, ry, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'rx', 'ry'], ['rx']);
+ return this._makeNode(args.parent, 'rect', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ (args.rx ? {rx: args.rx, ry: args.ry} : {}), args.settings || {}));
+ },
+
+ /* Draw a circle.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param cx (number) the x-coordinate for the centre of the circle
+ @param cy (number) the y-coordinate for the centre of the circle
+ @param r (number) the radius of the circle
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ circle: function(parent, cx, cy, r, settings) {
+ var args = this._args(arguments, ['cx', 'cy', 'r']);
+ return this._makeNode(args.parent, 'circle', $.extend(
+ {cx: args.cx, cy: args.cy, r: args.r}, args.settings || {}));
+ },
+
+ /* Draw an ellipse.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param cx (number) the x-coordinate for the centre of the ellipse
+ @param cy (number) the y-coordinate for the centre of the ellipse
+ @param rx (number) the x-radius of the ellipse
+ @param ry (number) the y-radius of the ellipse
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ ellipse: function(parent, cx, cy, rx, ry, settings) {
+ var args = this._args(arguments, ['cx', 'cy', 'rx', 'ry']);
+ return this._makeNode(args.parent, 'ellipse', $.extend(
+ {cx: args.cx, cy: args.cy, rx: args.rx, ry: args.ry}, args.settings || {}));
+ },
+
+ /* Draw a line.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param x1 (number) the x-coordinate for the start of the line
+ @param y1 (number) the y-coordinate for the start of the line
+ @param x2 (number) the x-coordinate for the end of the line
+ @param y2 (number) the y-coordinate for the end of the line
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ line: function(parent, x1, y1, x2, y2, settings) {
+ var args = this._args(arguments, ['x1', 'y1', 'x2', 'y2']);
+ return this._makeNode(args.parent, 'line', $.extend(
+ {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2}, args.settings || {}));
+ },
+
+ /* Draw a polygonal line.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param points (number[][]) the x-/y-coordinates for the points on the line
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ polyline: function(parent, points, settings) {
+ var args = this._args(arguments, ['points']);
+ return this._poly(args.parent, 'polyline', args.points, args.settings);
+ },
+
+ /* Draw a polygonal shape.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param points (number[][]) the x-/y-coordinates for the points on the shape
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ polygon: function(parent, points, settings) {
+ var args = this._args(arguments, ['points']);
+ return this._poly(args.parent, 'polygon', args.points, args.settings);
+ },
+
+ /* Draw a polygonal line or shape. */
+ _poly: function(parent, name, points, settings) {
+ var ps = '';
+ for (var i = 0; i < points.length; i++) {
+ ps += points[i].join() + ' ';
+ }
+ return this._makeNode(parent, name, $.extend(
+ {points: $.trim(ps)}, settings || {}));
+ },
+
+ /* Draw text.
+ Specify both of x and y or neither of them.
+ @param parent (element or jQuery) the parent node for the text (optional)
+ @param x (number or number[]) the x-coordinate(s) for the text (optional)
+ @param y (number or number[]) the y-coordinate(s) for the text (optional)
+ @param value (string) the text content or
+ (SVGText) text with spans and references
+ @param settings (object) additional settings for the text (optional)
+ @return (element) the new text node */
+ text: function(parent, x, y, value, settings) {
+ var args = this._args(arguments, ['x', 'y', 'value']);
+ if (typeof args.x == 'string' && arguments.length < 4) {
+ args.value = args.x;
+ args.settings = args.y;
+ args.x = args.y = null;
+ }
+ return this._text(args.parent, 'text', args.value, $.extend(
+ {x: (args.x && isArray(args.x) ? args.x.join(' ') : args.x),
+ y: (args.y && isArray(args.y) ? args.y.join(' ') : args.y)},
+ args.settings || {}));
+ },
+
+ /* Draw text along a path.
+ @param parent (element or jQuery) the parent node for the text (optional)
+ @param path (string) the ID of the path
+ @param value (string) the text content or
+ (SVGText) text with spans and references
+ @param settings (object) additional settings for the text (optional)
+ @return (element) the new text node */
+ textpath: function(parent, path, value, settings) {
+ var args = this._args(arguments, ['path', 'value']);
+ var node = this._text(args.parent, 'textPath', args.value, args.settings || {});
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.path);
+ return node;
+ },
+
+ /* Draw text. */
+ _text: function(parent, name, value, settings) {
+ var node = this._makeNode(parent, name, settings);
+ if (typeof value == 'string') {
+ node.appendChild(node.ownerDocument.createTextNode(value));
+ }
+ else {
+ for (var i = 0; i < value._parts.length; i++) {
+ var part = value._parts[i];
+ if (part[0] == 'tspan') {
+ var child = this._makeNode(node, part[0], part[2]);
+ child.appendChild(node.ownerDocument.createTextNode(part[1]));
+ node.appendChild(child);
+ }
+ else if (part[0] == 'tref') {
+ var child = this._makeNode(node, part[0], part[2]);
+ child.setAttributeNS($.svg.xlinkNS, 'href', part[1]);
+ node.appendChild(child);
+ }
+ else if (part[0] == 'textpath') {
+ var set = $.extend({}, part[2]);
+ set.href = null;
+ var child = this._makeNode(node, part[0], set);
+ child.setAttributeNS($.svg.xlinkNS, 'href', part[2].href);
+ child.appendChild(node.ownerDocument.createTextNode(part[1]));
+ node.appendChild(child);
+ }
+ else { // straight text
+ node.appendChild(node.ownerDocument.createTextNode(part[1]));
+ }
+ }
+ }
+ return node;
+ },
+
+ /* Add a custom SVG element.
+ @param parent (element or jQuery) the parent node for the new element (optional)
+ @param name (string) the name of the element
+ @param settings (object) additional settings for the element (optional)
+ @return (element) the new custom node */
+ other: function(parent, name, settings) {
+ var args = this._args(arguments, ['name']);
+ return this._makeNode(args.parent, args.name, args.settings || {});
+ },
+
+ /* Create a shape node with the given settings. */
+ _makeNode: function(parent, name, settings) {
+ parent = parent || this._svg;
+ var node = this._svg.ownerDocument.createElementNS($.svg.svgNS, name);
+ for (var name in settings) {
+ var value = settings[name];
+ if (value != null && value != null &&
+ (typeof value != 'string' || value != '')) {
+ node.setAttribute($.svg._attrNames[name] || name, value);
+ }
+ }
+ parent.appendChild(node);
+ return node;
+ },
+
+ /* Add an existing SVG node to the diagram.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param node (element) the new node to add or
+ (string) the jQuery selector for the node or
+ (jQuery collection) set of nodes to add
+ @return (SVGWrapper) this wrapper */
+ add: function(parent, node) {
+ var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
+ var svg = this;
+ args.parent = args.parent || this._svg;
+ args.node = (args.node.jquery ? args.node : $(args.node));
+ try {
+ if ($.svg._renesis) {
+ throw 'Force traversal';
+ }
+ args.parent.appendChild(args.node.cloneNode(true));
+ }
+ catch (e) {
+ args.node.each(function() {
+ var child = svg._cloneAsSVG(this);
+ if (child) {
+ args.parent.appendChild(child);
+ }
+ });
+ }
+ return this;
+ },
+
+ /* Clone an existing SVG node and add it to the diagram.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param node (element) the new node to add or
+ (string) the jQuery selector for the node or
+ (jQuery collection) set of nodes to add
+ @return (element[]) collection of new nodes */
+ clone: function(parent, node) {
+ var svg = this;
+ var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
+ args.parent = args.parent || this._svg;
+ args.node = (args.node.jquery ? args.node : $(args.node));
+ var newNodes = [];
+ args.node.each(function() {
+ var child = svg._cloneAsSVG(this);
+ if (child) {
+ child.id = '';
+ args.parent.appendChild(child);
+ newNodes.push(child);
+ }
+ });
+ return newNodes;
+ },
+
+ /* SVG nodes must belong to the SVG namespace, so clone and ensure this is so.
+ @param node (element) the SVG node to clone
+ @return (element) the cloned node */
+ _cloneAsSVG: function(node) {
+ var newNode = null;
+ if (node.nodeType == 1) { // element
+ newNode = this._svg.ownerDocument.createElementNS(
+ $.svg.svgNS, this._checkName(node.nodeName));
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes.item(i);
+ if (attr.nodeName != 'xmlns' && attr.nodeValue) {
+ if (attr.prefix == 'xlink') {
+ newNode.setAttributeNS($.svg.xlinkNS,
+ attr.localName || attr.baseName, attr.nodeValue);
+ }
+ else {
+ newNode.setAttribute(this._checkName(attr.nodeName), attr.nodeValue);
+ }
+ }
+ }
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = this._cloneAsSVG(node.childNodes[i]);
+ if (child) {
+ newNode.appendChild(child);
+ }
+ }
+ }
+ else if (node.nodeType == 3) { // text
+ if ($.trim(node.nodeValue)) {
+ newNode = this._svg.ownerDocument.createTextNode(node.nodeValue);
+ }
+ }
+ else if (node.nodeType == 4) { // CDATA
+ if ($.trim(node.nodeValue)) {
+ try {
+ newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue);
+ }
+ catch (e) {
+ newNode = this._svg.ownerDocument.createTextNode(
+ node.nodeValue.replace(/&/g, '&').
+ replace(/</g, '<').replace(/>/g, '>'));
+ }
+ }
+ }
+ return newNode;
+ },
+
+ /* Node names must be lower case and without SVG namespace prefix. */
+ _checkName: function(name) {
+ name = (name.substring(0, 1) >= 'A' && name.substring(0, 1) <= 'Z' ?
+ name.toLowerCase() : name);
+ return (name.substring(0, 4) == 'svg:' ? name.substring(4) : name);
+ },
+
+ /* Load an external SVG document.
+ @param url (string) the location of the SVG document or
+ the actual SVG content
+ @param settings (boolean) see addTo below or
+ (function) see onLoad below or
+ (object) additional settings for the load with attributes below:
+ addTo (boolean) true to add to what's already there,
+ or false to clear the canvas first
+ changeSize (boolean) true to allow the canvas size to change,
+ or false to retain the original
+ onLoad (function) callback after the document has loaded,
+ 'this' is the container, receives SVG object and
+ optional error message as a parameter
+ parent (string or element or jQuery) the parent to load
+ into, defaults to top-level svg element
+ @return (SVGWrapper) this root */
+ load: function(url, settings) {
+ settings = (typeof settings == 'boolean' ? {addTo: settings} :
+ (typeof settings == 'function' ? {onLoad: settings} :
+ (typeof settings == 'string' ? {parent: settings} :
+ (typeof settings == 'object' && settings.nodeName ? {parent: settings} :
+ (typeof settings == 'object' && settings.jquery ? {parent: settings} :
+ settings || {})))));
+ if (!settings.parent && !settings.addTo) {
+ this.clear(false);
+ }
+ var size = [this._svg.getAttribute('width'), this._svg.getAttribute('height')];
+ var wrapper = this;
+ // Report a problem with the load
+ var reportError = function(message) {
+ message = $.svg.local.errorLoadingText + ': ' + message;
+ if (settings.onLoad) {
+ settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper, message]);
+ }
+ else {
+ wrapper.text(null, 10, 20, message);
+ }
+ };
+ // Create a DOM from SVG content
+ var loadXML4IE = function(data) {
+ var xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.validateOnParse = false;
+ xml.resolveExternals = false;
+ xml.async = false;
+ xml.loadXML(data);
+ if (xml.parseError.errorCode != 0) {
+ reportError(xml.parseError.reason);
+ return null;
+ }
+ return xml;
+ };
+ // Load the SVG DOM
+ var loadSVG = function(data) {
+ if (!data) {
+ return;
+ }
+ if (data.documentElement.nodeName != 'svg') {
+ var errors = data.getElementsByTagName('parsererror');
+ var messages = (errors.length ? errors[0].getElementsByTagName('div') : []); // Safari
+ reportError(!errors.length ? '???' :
+ (messages.length ? messages[0] : errors[0]).firstChild.nodeValue);
+ return;
+ }
+ var parent = (settings.parent ? $(settings.parent)[0] : wrapper._svg);
+ var attrs = {};
+ for (var i = 0; i < data.documentElement.attributes.length; i++) {
+ var attr = data.documentElement.attributes.item(i);
+ if (!(attr.nodeName == 'version' || attr.nodeName.substring(0, 5) == 'xmlns')) {
+ attrs[attr.nodeName] = attr.nodeValue;
+ }
+ }
+ wrapper.configure(parent, attrs, !settings.parent);
+ var nodes = data.documentElement.childNodes;
+ for (var i = 0; i < nodes.length; i++) {
+ try {
+ if ($.svg._renesis) {
+ throw 'Force traversal';
+ }
+ parent.appendChild(wrapper._svg.ownerDocument.importNode(nodes[i], true));
+ if (nodes[i].nodeName == 'script') {
+ $.globalEval(nodes[i].textContent);
+ }
+ }
+ catch (e) {
+ wrapper.add(parent, nodes[i]);
+ }
+ }
+ if (!settings.changeSize) {
+ wrapper.configure(parent, {width: size[0], height: size[1]});
+ }
+ if (settings.onLoad) {
+ settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper]);
+ }
+ };
+ if (url.match('<svg')) { // Inline SVG
+ loadSVG($.browser.msie ? loadXML4IE(url) :
+ new DOMParser().parseFromString(url, 'text/xml'));
+ }
+ else { // Remote SVG
+ $.ajax({url: url, dataType: ($.browser.msie ? 'text' : 'xml'),
+ success: function(xml) {
+ loadSVG($.browser.msie ? loadXML4IE(xml) : xml);
+ }, error: function(http, message, exc) {
+ reportError(message + (exc ? ' ' + exc.message : ''));
+ }});
+ }
+ return this;
+ },
+
+ /* Delete a specified node.
+ @param node (element or jQuery) the drawing node to remove
+ @return (SVGWrapper) this root */
+ remove: function(node) {
+ node = (node.jquery ? node[0] : node);
+ node.parentNode.removeChild(node);
+ return this;
+ },
+
+ /* Delete everything in the current document.
+ @param attrsToo (boolean) true to clear any root attributes as well,
+ false to leave them (optional)
+ @return (SVGWrapper) this root */
+ clear: function(attrsToo) {
+ if (attrsToo) {
+ this.configure({}, true);
+ }
+ while (this._svg.firstChild) {
+ this._svg.removeChild(this._svg.firstChild);
+ }
+ return this;
+ },
+
+ /* Serialise the current diagram into an SVG text document.
+ @param node (SVG element) the starting node (optional)
+ @return (string) the SVG as text */
+ toSVG: function(node) {
+ node = node || this._svg;
+ return (typeof XMLSerializer == 'undefined' ? this._toSVG(node) :
+ new XMLSerializer().serializeToString(node));
+ },
+
+ /* 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 = '<![CDATA[' + node.nodeValue + ']]>';
+ }
+ else { // Element
+ svgDoc = '<' + node.nodeName;
+ if (node.attributes) {
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes.item(i);
+ if (!($.trim(attr.nodeValue) == '' || attr.nodeValue.match(/^\[object/) ||
+ attr.nodeValue.match(/^function/))) {
+ svgDoc += ' ' + (attr.namespaceURI == $.svg.xlinkNS ? 'xlink:' : '') +
+ attr.nodeName + '="' + attr.nodeValue + '"';
+ }
+ }
+ }
+ if (node.firstChild) {
+ svgDoc += '>';
+ var child = node.firstChild;
+ while (child) {
+ svgDoc += this._toSVG(child);
+ child = child.nextSibling;
+ }
+ svgDoc += '</' + node.nodeName + '>';
+ }
+ else {
+ svgDoc += '/>';
+ }
+ }
+ return svgDoc;
+ }
+});
+
+/* Helper to generate an SVG path.
+ Obtain an instance from the SVGWrapper object.
+ String calls together to generate the path and use its value:
+ var path = root.createPath();
+ root.path(null, path.move(100, 100).line(300, 100).line(200, 300).close(), {fill: 'red'});
+ or
+ root.path(null, path.move(100, 100).line([[300, 100], [200, 300]]).close(), {fill: 'red'}); */
+function SVGPath() {
+ this._path = '';
+}
+
+$.extend(SVGPath.prototype, {
+ /* Prepare to create a new path.
+ @return (SVGPath) this path */
+ reset: function() {
+ this._path = '';
+ return this;
+ },
+
+ /* Move the pointer to a position.
+ @param x (number) x-coordinate to move to or
+ (number[][]) x-/y-coordinates to move to
+ @param y (number) y-coordinate to move to (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ move: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 'm' : 'M'), x, y);
+ },
+
+ /* Draw a line to a position.
+ @param x (number) x-coordinate to move to or
+ (number[][]) x-/y-coordinates to move to
+ @param y (number) y-coordinate to move to (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ line: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 'l' : 'L'), x, y);
+ },
+
+ /* Draw a horizontal line to a position.
+ @param x (number) x-coordinate to draw to or
+ (number[]) x-coordinates to draw to
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ horiz: function(x, relative) {
+ this._path += (relative ? 'h' : 'H') + (isArray(x) ? x.join(' ') : x);
+ return this;
+ },
+
+ /* Draw a vertical line to a position.
+ @param y (number) y-coordinate to draw to or
+ (number[]) y-coordinates to draw to
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ vert: function(y, relative) {
+ this._path += (relative ? 'v' : 'V') + (isArray(y) ? y.join(' ') : y);
+ return this;
+ },
+
+ /* Draw a cubic Bézier curve.
+ @param x1 (number) x-coordinate of beginning control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y1 (number) y-coordinate of beginning control point (omitted if x1 is array)
+ @param x2 (number) x-coordinate of ending control point (omitted if x1 is array)
+ @param y2 (number) y-coordinate of ending control point (omitted if x1 is array)
+ @param x (number) x-coordinate of curve end (omitted if x1 is array)
+ @param y (number) y-coordinate of curve end (omitted if x1 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ curveC: function(x1, y1, x2, y2, x, y, relative) {
+ relative = (isArray(x1) ? y1 : relative);
+ return this._coords((relative ? 'c' : 'C'), x1, y1, x2, y2, x, y);
+ },
+
+ /* Continue a cubic Bézier curve.
+ Starting control point is the reflection of the previous end control point.
+ @param x2 (number) x-coordinate of ending control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y2 (number) y-coordinate of ending control point (omitted if x2 is array)
+ @param x (number) x-coordinate of curve end (omitted if x2 is array)
+ @param y (number) y-coordinate of curve end (omitted if x2 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ smoothC: function(x2, y2, x, y, relative) {
+ relative = (isArray(x2) ? y2 : relative);
+ return this._coords((relative ? 's' : 'S'), x2, y2, x, y);
+ },
+
+ /* Draw a quadratic Bézier curve.
+ @param x1 (number) x-coordinate of control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y1 (number) y-coordinate of control point (omitted if x1 is array)
+ @param x (number) x-coordinate of curve end (omitted if x1 is array)
+ @param y (number) y-coordinate of curve end (omitted if x1 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ curveQ: function(x1, y1, x, y, relative) {
+ relative = (isArray(x1) ? y1 : relative);
+ return this._coords((relative ? 'q' : 'Q'), x1, y1, x, y);
+ },
+
+ /* Continue a quadratic Bézier curve.
+ Control point is the reflection of the previous control point.
+ @param x (number) x-coordinate of curve end or
+ (number[][]) x-/y-coordinates of points to draw to
+ @param y (number) y-coordinate of curve end (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ smoothQ: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 't' : 'T'), x, y);
+ },
+
+ /* Generate a path command with (a list of) coordinates. */
+ _coords: function(cmd, x1, y1, x2, y2, x3, y3) {
+ if (isArray(x1)) {
+ for (var i = 0; i < x1.length; i++) {
+ var cs = x1[i];
+ this._path += (i == 0 ? cmd : ' ') + cs[0] + ',' + cs[1] +
+ (cs.length < 4 ? '' : ' ' + cs[2] + ',' + cs[3] +
+ (cs.length < 6 ? '': ' ' + cs[4] + ',' + cs[5]));
+ }
+ }
+ else {
+ this._path += cmd + x1 + ',' + y1 +
+ (x2 == null ? '' : ' ' + x2 + ',' + y2 +
+ (x3 == null ? '' : ' ' + x3 + ',' + y3));
+ }
+ return this;
+ },
+
+ /* Draw an arc to a position.
+ @param rx (number) x-radius of arc or
+ (number/boolean[][]) x-/y-coordinates and flags for points to draw to
+ @param ry (number) y-radius of arc (omitted if rx is array)
+ @param xRotate (number) x-axis rotation (degrees, clockwise) (omitted if rx is array)
+ @param large (boolean) true to draw the large part of the arc,
+ false to draw the small part (omitted if rx is array)
+ @param clockwise (boolean) true to draw the clockwise arc,
+ false to draw the anti-clockwise arc (omitted if rx is array)
+ @param x (number) x-coordinate of arc end (omitted if rx is array)
+ @param y (number) y-coordinate of arc end (omitted if rx is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ arc: function(rx, ry, xRotate, large, clockwise, x, y, relative) {
+ relative = (isArray(rx) ? ry : relative);
+ this._path += (relative ? 'a' : 'A');
+ if (isArray(rx)) {
+ for (var i = 0; i < rx.length; i++) {
+ var cs = rx[i];
+ this._path += (i == 0 ? '' : ' ') + cs[0] + ',' + cs[1] + ' ' +
+ cs[2] + ' ' + (cs[3] ? '1' : '0') + ',' +
+ (cs[4] ? '1' : '0') + ' ' + cs[5] + ',' + cs[6];
+ }
+ }
+ else {
+ this._path += rx + ',' + ry + ' ' + xRotate + ' ' +
+ (large ? '1' : '0') + ',' + (clockwise ? '1' : '0') + ' ' + x + ',' + y;
+ }
+ return this;
+ },
+
+ /* Close the current path.
+ @return (SVGPath) this path */
+ close: function() {
+ this._path += 'z';
+ return this;
+ },
+
+ /* Return the string rendering of the specified path.
+ @return (string) stringified path */
+ path: function() {
+ return this._path;
+ }
+});
+
+SVGPath.prototype.moveTo = SVGPath.prototype.move;
+SVGPath.prototype.lineTo = SVGPath.prototype.line;
+SVGPath.prototype.horizTo = SVGPath.prototype.horiz;
+SVGPath.prototype.vertTo = SVGPath.prototype.vert;
+SVGPath.prototype.curveCTo = SVGPath.prototype.curveC;
+SVGPath.prototype.smoothCTo = SVGPath.prototype.smoothC;
+SVGPath.prototype.curveQTo = SVGPath.prototype.curveQ;
+SVGPath.prototype.smoothQTo = SVGPath.prototype.smoothQ;
+SVGPath.prototype.arcTo = SVGPath.prototype.arc;
+
+/* Helper to generate an SVG text object.
+ Obtain an instance from the SVGWrapper object.
+ String calls together to generate the text and use its value:
+ var text = root.createText();
+ root.text(null, x, y, text.string('This is ').
+ span('red', {fill: 'red'}).string('!'), {fill: 'blue'}); */
+function SVGText() {
+ this._parts = []; // The components of the text object
+}
+
+$.extend(SVGText.prototype, {
+ /* Prepare to create a new text object.
+ @return (SVGText) this text */
+ reset: function() {
+ this._parts = [];
+ return this;
+ },
+
+ /* Add a straight string value.
+ @param value (string) the actual text
+ @return (SVGText) this text object */
+ string: function(value) {
+ this._parts[this._parts.length] = ['text', value];
+ return this;
+ },
+
+ /* Add a separate text span that has its own settings.
+ @param value (string) the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ span: function(value, settings) {
+ this._parts[this._parts.length] = ['tspan', value, settings];
+ return this;
+ },
+
+ /* Add a reference to a previously defined text string.
+ @param id (string) the ID of the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ ref: function(id, settings) {
+ this._parts[this._parts.length] = ['tref', id, settings];
+ return this;
+ },
+
+ /* Add text drawn along a path.
+ @param id (string) the ID of the path
+ @param value (string) the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ path: function(id, value, settings) {
+ this._parts[this._parts.length] = ['textpath', value,
+ $.extend({href: id}, settings || {})];
+ return this;
+ }
+});
+
+/* Attach the SVG functionality to a jQuery selection.
+ @param command (string) the command to run (optional, default 'attach')
+ @param options (object) the new settings to use for these SVG instances
+ @return jQuery (object) for chaining further calls */
+$.fn.svg = function(options) {
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
+ if (typeof options == 'string' && options == 'get') {
+ return $.svg['_' + options + 'SVG'].apply($.svg, [this[0]].concat(otherArgs));
+ }
+ return this.each(function() {
+ if (typeof options == 'string') {
+ $.svg['_' + options + 'SVG'].apply($.svg, [this].concat(otherArgs));
+ }
+ else {
+ $.svg._attachSVG(this, options || {});
+ }
+ });
+};
+
+/* Determine whether an object is an array. */
+function isArray(a) {
+ return (a && a.constructor == Array);
+}
+
+// Singleton primary SVG interface
+$.svg = new SVGManager();
+
+})(jQuery);
src/web/js/jquery.svg.min.js 7(+7 -0)
diff --git a/src/web/js/jquery.svg.min.js b/src/web/js/jquery.svg.min.js
new file mode 100644
index 0000000..5b922fb
--- /dev/null
+++ b/src/web/js/jquery.svg.min.js
@@ -0,0 +1,7 @@
+/* http://keith-wood.name/svg.html
+ SVG for jQuery v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+(function($){function SVGManager(){this._settings=[];this._extensions=[];this.regional=[];this.regional['']={errorLoadingText:'Error loading',notSupportedText:'This browser does not support SVG'};this.local=this.regional[''];this._uuid=new Date().getTime();this._renesis=detectActiveX('RenesisX.RenesisCtrl')}function detectActiveX(a){try{return!!(window.ActiveXObject&&new ActiveXObject(a))}catch(e){return false}}var q='svgwrapper';$.extend(SVGManager.prototype,{markerClassName:'hasSVG',svgNS:'http://www.w3.org/2000/svg',xlinkNS:'http://www.w3.org/1999/xlink',_wrapperClass:SVGWrapper,_attrNames:{class_:'class',in_:'in',alignmentBaseline:'alignment-baseline',baselineShift:'baseline-shift',clipPath:'clip-path',clipRule:'clip-rule',colorInterpolation:'color-interpolation',colorInterpolationFilters:'color-interpolation-filters',colorRendering:'color-rendering',dominantBaseline:'dominant-baseline',enableBackground:'enable-background',fillOpacity:'fill-opacity',fillRule:'fill-rule',floodColor:'flood-color',floodOpacity:'flood-opacity',fontFamily:'font-family',fontSize:'font-size',fontSizeAdjust:'font-size-adjust',fontStretch:'font-stretch',fontStyle:'font-style',fontVariant:'font-variant',fontWeight:'font-weight',glyphOrientationHorizontal:'glyph-orientation-horizontal',glyphOrientationVertical:'glyph-orientation-vertical',horizAdvX:'horiz-adv-x',horizOriginX:'horiz-origin-x',imageRendering:'image-rendering',letterSpacing:'letter-spacing',lightingColor:'lighting-color',markerEnd:'marker-end',markerMid:'marker-mid',markerStart:'marker-start',stopColor:'stop-color',stopOpacity:'stop-opacity',strikethroughPosition:'strikethrough-position',strikethroughThickness:'strikethrough-thickness',strokeDashArray:'stroke-dasharray',strokeDashOffset:'stroke-dashoffset',strokeLineCap:'stroke-linecap',strokeLineJoin:'stroke-linejoin',strokeMiterLimit:'stroke-miterlimit',strokeOpacity:'stroke-opacity',strokeWidth:'stroke-width',textAnchor:'text-anchor',textDecoration:'text-decoration',textRendering:'text-rendering',underlinePosition:'underline-position',underlineThickness:'underline-thickness',vertAdvY:'vert-adv-y',vertOriginY:'vert-origin-y',wordSpacing:'word-spacing',writingMode:'writing-mode'},_attachSVG:function(a,b){var c=(a.namespaceURI==this.svgNS?a:null);var a=(c?null:a);if($(a||c).hasClass(this.markerClassName)){return}if(typeof b=='string'){b={loadURL:b}}else if(typeof b=='function'){b={onLoad:b}}$(a||c).addClass(this.markerClassName);try{if(!c){c=document.createElementNS(this.svgNS,'svg');c.setAttribute('version','1.1');if(a.clientWidth>0){c.setAttribute('width',a.clientWidth)}if(a.clientHeight>0){c.setAttribute('height',a.clientHeight)}a.appendChild(c)}this._afterLoad(a,c,b||{})}catch(e){if($.browser.msie){if(!a.id){a.id='svg'+(this._uuid++)}this._settings[a.id]=b;a.innerHTML='<embed type="image/svg+xml" width="100%" '+'height="100%" src="'+(b.initPath||'')+'blank.svg" '+'pluginspage="http://www.adobe.com/svg/viewer/install/main.html"/>'}else{a.innerHTML='<p class="svg_error">'+this.local.notSupportedText+'</p>'}}},_registerSVG:function(){for(var i=0;i<document.embeds.length;i++){var a=document.embeds[i].parentNode;if(!$(a).hasClass($.svg.markerClassName)||$.data(a,q)){continue}var b=null;try{b=document.embeds[i].getSVGDocument()}catch(e){setTimeout($.svg._registerSVG,250);return}b=(b?b.documentElement:null);if(b){$.svg._afterLoad(a,b)}}},_afterLoad:function(a,b,c){var c=c||this._settings[a.id];this._settings[a?a.id:'']=null;var d=new this._wrapperClass(b,a);$.data(a||b,q,d);try{if(c.loadURL){d.load(c.loadURL,c)}if(c.settings){d.configure(c.settings)}if(c.onLoad&&!c.loadURL){c.onLoad.apply(a||b,[d])}}catch(e){alert(e)}},_getSVG:function(a){a=(typeof a=='string'?$(a)[0]:(a.jquery?a[0]:a));return $.data(a,q)},_destroySVG:function(a){var b=$(a);if(!b.hasClass(this.markerClassName)){return}b.removeClass(this.markerClassName);if(a.namespaceURI!=this.svgNS){b.empty()}$.removeData(a,q)},addExtension:function(a,b){this._extensions.push([a,b])},isSVGElem:function(a){return(a.nodeType==1&&a.namespaceURI==$.svg.svgNS)}});function SVGWrapper(a,b){this._svg=a;this._container=b;for(var i=0;i<$.svg._extensions.length;i++){var c=$.svg._extensions[i];this[c[0]]=new c[1](this)}}$.extend(SVGWrapper.prototype,{_width:function(){return(this._container?this._container.clientWidth:this._svg.width)},_height:function(){return(this._container?this._container.clientHeight:this._svg.height)},root:function(){return this._svg},configure:function(a,b,c){if(!a.nodeName){c=b;b=a;a=this._svg}if(c){for(var i=a.attributes.length-1;i>=0;i--){var d=a.attributes.item(i);if(!(d.nodeName=='onload'||d.nodeName=='version'||d.nodeName.substring(0,5)=='xmlns')){a.attributes.removeNamedItem(d.nodeName)}}}for(var e in b){a.setAttribute($.svg._attrNames[e]||e,b[e])}return this},getElementById:function(a){return this._svg.ownerDocument.getElementById(a)},change:function(a,b){if(a){for(var c in b){if(b[c]==null){a.removeAttribute($.svg._attrNames[c]||c)}else{a.setAttribute($.svg._attrNames[c]||c,b[c])}}}return this},_args:function(b,c,d){c.splice(0,0,'parent');c.splice(c.length,0,'settings');var e={};var f=0;if(b[0]!=null&&b[0].jquery){b[0]=b[0][0]}if(b[0]!=null&&!(typeof b[0]=='object'&&b[0].nodeName)){e['parent']=null;f=1}for(var i=0;i<b.length;i++){e[c[i+f]]=b[i]}if(d){$.each(d,function(i,a){if(typeof e[a]=='object'){e.settings=e[a];e[a]=null}})}return e},title:function(a,b,c){var d=this._args(arguments,['text']);var e=this._makeNode(d.parent,'title',d.settings||{});e.appendChild(this._svg.ownerDocument.createTextNode(d.text));return e},describe:function(a,b,c){var d=this._args(arguments,['text']);var e=this._makeNode(d.parent,'desc',d.settings||{});e.appendChild(this._svg.ownerDocument.createTextNode(d.text));return e},defs:function(a,b,c){var d=this._args(arguments,['id'],['id']);return this._makeNode(d.parent,'defs',$.extend((d.id?{id:d.id}:{}),d.settings||{}))},symbol:function(a,b,c,d,e,f,g){var h=this._args(arguments,['id','x1','y1','width','height']);return this._makeNode(h.parent,'symbol',$.extend({id:h.id,viewBox:h.x1+' '+h.y1+' '+h.width+' '+h.height},h.settings||{}))},marker:function(a,b,c,d,e,f,g,h){var i=this._args(arguments,['id','refX','refY','mWidth','mHeight','orient'],['orient']);return this._makeNode(i.parent,'marker',$.extend({id:i.id,refX:i.refX,refY:i.refY,markerWidth:i.mWidth,markerHeight:i.mHeight,orient:i.orient||'auto'},i.settings||{}))},style:function(a,b,c){var d=this._args(arguments,['styles']);var e=this._makeNode(d.parent,'style',$.extend({type:'text/css'},d.settings||{}));e.appendChild(this._svg.ownerDocument.createTextNode(d.styles));if($.browser.opera){$('head').append('<style type="text/css">'+d.styles+'</style>')}return e},script:function(a,b,c,d){var e=this._args(arguments,['script','type'],['type']);var f=this._makeNode(e.parent,'script',$.extend({type:e.type||'text/javascript'},e.settings||{}));f.appendChild(this._svg.ownerDocument.createTextNode(e.script));if(!$.browser.mozilla){$.globalEval(e.script)}return f},linearGradient:function(a,b,c,d,e,f,g,h){var i=this._args(arguments,['id','stops','x1','y1','x2','y2'],['x1']);var j=$.extend({id:i.id},(i.x1!=null?{x1:i.x1,y1:i.y1,x2:i.x2,y2:i.y2}:{}));return this._gradient(i.parent,'linearGradient',$.extend(j,i.settings||{}),i.stops)},radialGradient:function(a,b,c,d,e,r,f,g,h){var i=this._args(arguments,['id','stops','cx','cy','r','fx','fy'],['cx']);var j=$.extend({id:i.id},(i.cx!=null?{cx:i.cx,cy:i.cy,r:i.r,fx:i.fx,fy:i.fy}:{}));return this._gradient(i.parent,'radialGradient',$.extend(j,i.settings||{}),i.stops)},_gradient:function(a,b,c,d){var e=this._makeNode(a,b,c);for(var i=0;i<d.length;i++){var f=d[i];this._makeNode(e,'stop',$.extend({offset:f[0],stopColor:f[1]},(f[2]!=null?{stopOpacity:f[2]}:{})))}return e},pattern:function(a,b,x,y,c,d,e,f,g,h,i){var j=this._args(arguments,['id','x','y','width','height','vx','vy','vwidth','vheight'],['vx']);var k=$.extend({id:j.id,x:j.x,y:j.y,width:j.width,height:j.height},(j.vx!=null?{viewBox:j.vx+' '+j.vy+' '+j.vwidth+' '+j.vheight}:{}));return this._makeNode(j.parent,'pattern',$.extend(k,j.settings||{}))},clipPath:function(a,b,c,d){var e=this._args(arguments,['id','units']);e.units=e.units||'userSpaceOnUse';return this._makeNode(e.parent,'clipPath',$.extend({id:e.id,clipPathUnits:e.units},e.settings||{}))},mask:function(a,b,x,y,c,d,e){var f=this._args(arguments,['id','x','y','width','height']);return this._makeNode(f.parent,'mask',$.extend({id:f.id,x:f.x,y:f.y,width:f.width,height:f.height},f.settings||{}))},createPath:function(){return new SVGPath()},createText:function(){return new SVGText()},svg:function(a,x,y,b,c,d,e,f,g,h){var i=this._args(arguments,['x','y','width','height','vx','vy','vwidth','vheight'],['vx']);var j=$.extend({x:i.x,y:i.y,width:i.width,height:i.height},(i.vx!=null?{viewBox:i.vx+' '+i.vy+' '+i.vwidth+' '+i.vheight}:{}));return this._makeNode(i.parent,'svg',$.extend(j,i.settings||{}))},group:function(a,b,c){var d=this._args(arguments,['id'],['id']);return this._makeNode(d.parent,'g',$.extend({id:d.id},d.settings||{}))},use:function(a,x,y,b,c,d,e){var f=this._args(arguments,['x','y','width','height','ref']);if(typeof f.x=='string'){f.ref=f.x;f.settings=f.y;f.x=f.y=f.width=f.height=null}var g=this._makeNode(f.parent,'use',$.extend({x:f.x,y:f.y,width:f.width,height:f.height},f.settings||{}));g.setAttributeNS($.svg.xlinkNS,'href',f.ref);return g},link:function(a,b,c){var d=this._args(arguments,['ref']);var e=this._makeNode(d.parent,'a',d.settings);e.setAttributeNS($.svg.xlinkNS,'href',d.ref);return e},image:function(a,x,y,b,c,d,e){var f=this._args(arguments,['x','y','width','height','ref']);var g=this._makeNode(f.parent,'image',$.extend({x:f.x,y:f.y,width:f.width,height:f.height},f.settings||{}));g.setAttributeNS($.svg.xlinkNS,'href',f.ref);return g},path:function(a,b,c){var d=this._args(arguments,['path']);return this._makeNode(d.parent,'path',$.extend({d:(d.path.path?d.path.path():d.path)},d.settings||{}))},rect:function(a,x,y,b,c,d,e,f){var g=this._args(arguments,['x','y','width','height','rx','ry'],['rx']);return this._makeNode(g.parent,'rect',$.extend({x:g.x,y:g.y,width:g.width,height:g.height},(g.rx?{rx:g.rx,ry:g.ry}:{}),g.settings||{}))},circle:function(a,b,c,r,d){var e=this._args(arguments,['cx','cy','r']);return this._makeNode(e.parent,'circle',$.extend({cx:e.cx,cy:e.cy,r:e.r},e.settings||{}))},ellipse:function(a,b,c,d,e,f){var g=this._args(arguments,['cx','cy','rx','ry']);return this._makeNode(g.parent,'ellipse',$.extend({cx:g.cx,cy:g.cy,rx:g.rx,ry:g.ry},g.settings||{}))},line:function(a,b,c,d,e,f){var g=this._args(arguments,['x1','y1','x2','y2']);return this._makeNode(g.parent,'line',$.extend({x1:g.x1,y1:g.y1,x2:g.x2,y2:g.y2},g.settings||{}))},polyline:function(a,b,c){var d=this._args(arguments,['points']);return this._poly(d.parent,'polyline',d.points,d.settings)},polygon:function(a,b,c){var d=this._args(arguments,['points']);return this._poly(d.parent,'polygon',d.points,d.settings)},_poly:function(a,b,c,d){var e='';for(var i=0;i<c.length;i++){e+=c[i].join()+' '}return this._makeNode(a,b,$.extend({points:$.trim(e)},d||{}))},text:function(a,x,y,b,c){var d=this._args(arguments,['x','y','value']);if(typeof d.x=='string'&&arguments.length<4){d.value=d.x;d.settings=d.y;d.x=d.y=null}return this._text(d.parent,'text',d.value,$.extend({x:(d.x&&isArray(d.x)?d.x.join(' '):d.x),y:(d.y&&isArray(d.y)?d.y.join(' '):d.y)},d.settings||{}))},textpath:function(a,b,c,d){var e=this._args(arguments,['path','value']);var f=this._text(e.parent,'textPath',e.value,e.settings||{});f.setAttributeNS($.svg.xlinkNS,'href',e.path);return f},_text:function(a,b,c,d){var e=this._makeNode(a,b,d);if(typeof c=='string'){e.appendChild(e.ownerDocument.createTextNode(c))}else{for(var i=0;i<c._parts.length;i++){var f=c._parts[i];if(f[0]=='tspan'){var g=this._makeNode(e,f[0],f[2]);g.appendChild(e.ownerDocument.createTextNode(f[1]));e.appendChild(g)}else if(f[0]=='tref'){var g=this._makeNode(e,f[0],f[2]);g.setAttributeNS($.svg.xlinkNS,'href',f[1]);e.appendChild(g)}else if(f[0]=='textpath'){var h=$.extend({},f[2]);h.href=null;var g=this._makeNode(e,f[0],h);g.setAttributeNS($.svg.xlinkNS,'href',f[2].href);g.appendChild(e.ownerDocument.createTextNode(f[1]));e.appendChild(g)}else{e.appendChild(e.ownerDocument.createTextNode(f[1]))}}}return e},other:function(a,b,c){var d=this._args(arguments,['name']);return this._makeNode(d.parent,d.name,d.settings||{})},_makeNode:function(a,b,c){a=a||this._svg;var d=this._svg.ownerDocument.createElementNS($.svg.svgNS,b);for(var b in c){var e=c[b];if(e!=null&&e!=null&&(typeof e!='string'||e!='')){d.setAttribute($.svg._attrNames[b]||b,e)}}a.appendChild(d);return d},add:function(b,c){var d=this._args((arguments.length==1?[null,b]:arguments),['node']);var f=this;d.parent=d.parent||this._svg;d.node=(d.node.jquery?d.node:$(d.node));try{if($.svg._renesis){throw'Force traversal';}d.parent.appendChild(d.node.cloneNode(true))}catch(e){d.node.each(function(){var a=f._cloneAsSVG(this);if(a){d.parent.appendChild(a)}})}return this},clone:function(b,c){var d=this;var e=this._args((arguments.length==1?[null,b]:arguments),['node']);e.parent=e.parent||this._svg;e.node=(e.node.jquery?e.node:$(e.node));var f=[];e.node.each(function(){var a=d._cloneAsSVG(this);if(a){a.id='';e.parent.appendChild(a);f.push(a)}});return f},_cloneAsSVG:function(a){var b=null;if(a.nodeType==1){b=this._svg.ownerDocument.createElementNS($.svg.svgNS,this._checkName(a.nodeName));for(var i=0;i<a.attributes.length;i++){var c=a.attributes.item(i);if(c.nodeName!='xmlns'&&c.nodeValue){if(c.prefix=='xlink'){b.setAttributeNS($.svg.xlinkNS,c.localName||c.baseName,c.nodeValue)}else{b.setAttribute(this._checkName(c.nodeName),c.nodeValue)}}}for(var i=0;i<a.childNodes.length;i++){var d=this._cloneAsSVG(a.childNodes[i]);if(d){b.appendChild(d)}}}else if(a.nodeType==3){if($.trim(a.nodeValue)){b=this._svg.ownerDocument.createTextNode(a.nodeValue)}}else if(a.nodeType==4){if($.trim(a.nodeValue)){try{b=this._svg.ownerDocument.createCDATASection(a.nodeValue)}catch(e){b=this._svg.ownerDocument.createTextNode(a.nodeValue.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'))}}}return b},_checkName:function(a){a=(a.substring(0,1)>='A'&&a.substring(0,1)<='Z'?a.toLowerCase():a);return(a.substring(0,4)=='svg:'?a.substring(4):a)},load:function(j,k){k=(typeof k=='boolean'?{addTo:k}:(typeof k=='function'?{onLoad:k}:(typeof k=='string'?{parent:k}:(typeof k=='object'&&k.nodeName?{parent:k}:(typeof k=='object'&&k.jquery?{parent:k}:k||{})))));if(!k.parent&&!k.addTo){this.clear(false)}var l=[this._svg.getAttribute('width'),this._svg.getAttribute('height')];var m=this;var n=function(a){a=$.svg.local.errorLoadingText+': '+a;if(k.onLoad){k.onLoad.apply(m._container||m._svg,[m,a])}else{m.text(null,10,20,a)}};var o=function(a){var b=new ActiveXObject('Microsoft.XMLDOM');b.validateOnParse=false;b.resolveExternals=false;b.async=false;b.loadXML(a);if(b.parseError.errorCode!=0){n(b.parseError.reason);return null}return b};var p=function(a){if(!a){return}if(a.documentElement.nodeName!='svg'){var b=a.getElementsByTagName('parsererror');var c=(b.length?b[0].getElementsByTagName('div'):[]);n(!b.length?'???':(c.length?c[0]:b[0]).firstChild.nodeValue);return}var d=(k.parent?$(k.parent)[0]:m._svg);var f={};for(var i=0;i<a.documentElement.attributes.length;i++){var g=a.documentElement.attributes.item(i);if(!(g.nodeName=='version'||g.nodeName.substring(0,5)=='xmlns')){f[g.nodeName]=g.nodeValue}}m.configure(d,f,!k.parent);var h=a.documentElement.childNodes;for(var i=0;i<h.length;i++){try{if($.svg._renesis){throw'Force traversal';}d.appendChild(m._svg.ownerDocument.importNode(h[i],true));if(h[i].nodeName=='script'){$.globalEval(h[i].textContent)}}catch(e){m.add(d,h[i])}}if(!k.changeSize){m.configure(d,{width:l[0],height:l[1]})}if(k.onLoad){k.onLoad.apply(m._container||m._svg,[m])}};if(j.match('<svg')){p($.browser.msie?o(j):new DOMParser().parseFromString(j,'text/xml'))}else{$.ajax({url:j,dataType:($.browser.msie?'text':'xml'),success:function(a){p($.browser.msie?o(a):a)},error:function(a,b,c){n(b+(c?' '+c.message:''))}})}return this},remove:function(a){a=(a.jquery?a[0]:a);a.parentNode.removeChild(a);return this},clear:function(a){if(a){this.configure({},true)}while(this._svg.firstChild){this._svg.removeChild(this._svg.firstChild)}return this},toSVG:function(a){a=a||this._svg;return(typeof XMLSerializer=='undefined'?this._toSVG(a):new XMLSerializer().serializeToString(a))},_toSVG:function(a){var b='';if(!a){return b}if(a.nodeType==3){b=a.nodeValue}else if(a.nodeType==4){b='<![CDATA['+a.nodeValue+']]>'}else{b='<'+a.nodeName;if(a.attributes){for(var i=0;i<a.attributes.length;i++){var c=a.attributes.item(i);if(!($.trim(c.nodeValue)==''||c.nodeValue.match(/^\[object/)||c.nodeValue.match(/^function/))){b+=' '+(c.namespaceURI==$.svg.xlinkNS?'xlink:':'')+c.nodeName+'="'+c.nodeValue+'"'}}}if(a.firstChild){b+='>';var d=a.firstChild;while(d){b+=this._toSVG(d);d=d.nextSibling}b+='</'+a.nodeName+'>'}else{b+='/>'}}return b}});function SVGPath(){this._path=''}$.extend(SVGPath.prototype,{reset:function(){this._path='';return this},move:function(x,y,a){a=(isArray(x)?y:a);return this._coords((a?'m':'M'),x,y)},line:function(x,y,a){a=(isArray(x)?y:a);return this._coords((a?'l':'L'),x,y)},horiz:function(x,a){this._path+=(a?'h':'H')+(isArray(x)?x.join(' '):x);return this},vert:function(y,a){this._path+=(a?'v':'V')+(isArray(y)?y.join(' '):y);return this},curveC: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)},smoothC:function(a,b,x,y,c){c=(isArray(a)?b:c);return this._coords((c?'s':'S'),a,b,x,y)},curveQ:function(a,b,x,y,c){c=(isArray(a)?b:c);return this._coords((c?'q':'Q'),a,b,x,y)},smoothQ: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;i<b.length;i++){var h=b[i];this._path+=(i==0?a:' ')+h[0]+','+h[1]+(h.length<4?'':' '+h[2]+','+h[3]+(h.length<6?'':' '+h[4]+','+h[5]))}}else{this._path+=a+b+','+c+(d==null?'':' '+d+','+e+(f==null?'':' '+f+','+g))}return this},arc:function(a,b,c,d,e,x,y,f){f=(isArray(a)?b:f);this._path+=(f?'a':'A');if(isArray(a)){for(var i=0;i<a.length;i++){var g=a[i];this._path+=(i==0?'':' ')+g[0]+','+g[1]+' '+g[2]+' '+(g[3]?'1':'0')+','+(g[4]?'1':'0')+' '+g[5]+','+g[6]}}else{this._path+=a+','+b+' '+c+' '+(d?'1':'0')+','+(e?'1':'0')+' '+x+','+y}return this},close:function(){this._path+='z';return this},path:function(){return this._path}});SVGPath.prototype.moveTo=SVGPath.prototype.move;SVGPath.prototype.lineTo=SVGPath.prototype.line;SVGPath.prototype.horizTo=SVGPath.prototype.horiz;SVGPath.prototype.vertTo=SVGPath.prototype.vert;SVGPath.prototype.curveCTo=SVGPath.prototype.curveC;SVGPath.prototype.smoothCTo=SVGPath.prototype.smoothC;SVGPath.prototype.curveQTo=SVGPath.prototype.curveQ;SVGPath.prototype.smoothQTo=SVGPath.prototype.smoothQ;SVGPath.prototype.arcTo=SVGPath.prototype.arc;function SVGText(){this._parts=[]}$.extend(SVGText.prototype,{reset:function(){this._parts=[];return this},string:function(a){this._parts[this._parts.length]=['text',a];return this},span:function(a,b){this._parts[this._parts.length]=['tspan',a,b];return this},ref:function(a,b){this._parts[this._parts.length]=['tref',a,b];return this},path:function(a,b,c){this._parts[this._parts.length]=['textpath',b,$.extend({href:a},c||{})];return this}});$.fn.svg=function(a){var b=Array.prototype.slice.call(arguments,1);if(typeof a=='string'&&a=='get'){return $.svg['_'+a+'SVG'].apply($.svg,[this[0]].concat(b))}return this.each(function(){if(typeof a=='string'){$.svg['_'+a+'SVG'].apply($.svg,[this].concat(b))}else{$.svg._attachSVG(this,a||{})}})};function isArray(a){return(a&&a.constructor==Array)}$.svg=new SVGManager()})(jQuery);
\ No newline at end of file
src/web/js/jquery.svganim.min.js 7(+7 -0)
diff --git a/src/web/js/jquery.svganim.min.js b/src/web/js/jquery.svganim.min.js
new file mode 100644
index 0000000..3cc4020
--- /dev/null
+++ b/src/web/js/jquery.svganim.min.js
@@ -0,0 +1,7 @@
+/* http://keith-wood.name/svg.html
+ SVG attribute animations for jQuery v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) June 2008.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+(function($){$.each(['x','y','width','height','rx','ry','cx','cy','r','x1','y1','x2','y2','stroke-width','strokeWidth','opacity','fill-opacity','fillOpacity','stroke-opacity','strokeOpacity','stroke-dashoffset','strokeDashOffset','font-size','fontSize','font-weight','fontWeight','letter-spacing','letterSpacing','word-spacing','wordSpacing'],function(i,f){var g=f.charAt(0).toUpperCase()+f.substr(1);if($.cssProps){$.cssProps['svg'+g]=$.cssProps['svg-'+f]=f}$.fx.step['svg'+g]=$.fx.step['svg-'+f]=function(a){var b=$.svg._attrNames[f]||f;var c=a.elem.attributes.getNamedItem(b);if(!a.set){a.start=(c?parseFloat(c.nodeValue):0);var d=($.fn.jquery>='1.6'?'':a.options.curAnim['svg'+g]||a.options.curAnim['svg-'+f]);if(/^[+-]=/.exec(d)){a.end=a.start+parseFloat(d.replace(/=/,''))}$(a.elem).css(b,'');a.set=true}var e=(a.pos*(a.end-a.start)+a.start)+(a.unit=='%'?'%':'');(c?c.nodeValue=e:a.elem.setAttribute(b,e))}});$.fx.step['svgStrokeDashArray']=$.fx.step['svg-strokeDashArray']=$.fx.step['svgStroke-dasharray']=$.fx.step['svg-stroke-dasharray']=function(a){var b=a.elem.attributes.getNamedItem('stroke-dasharray');if(!a.set){a.start=parseDashArray(b?b.nodeValue:'');var c=($.fn.jquery>='1.6'?a.end:a.options.curAnim['svgStrokeDashArray']||a.options.curAnim['svg-strokeDashArray']||a.options.curAnim['svgStroke-dasharray']||a.options.curAnim['svg-stroke-dasharray']);a.end=parseDashArray(c);if(/^[+-]=/.exec(c)){c=c.split(/[, ]+/);if(c.length%2==1){var d=c.length;for(var i=0;i<d;i++){c.push(c[i])}}for(var i=0;i<c.length;i++){if(/^[+-]=/.exec(c[i])){a.end[i]=a.start[i]+parseFloat(c[i].replace(/=/,''))}}}a.set=true}var e=$.map(a.start,function(n,i){return(a.pos*(a.end[i]-n)+n)}).join(',');(b?b.nodeValue=e:a.elem.setAttribute('stroke-dasharray',e))};function parseDashArray(a){var b=a.split(/[, ]+/);for(var i=0;i<b.length;i++){b[i]=parseFloat(b[i]);if(isNaN(b[i])){b[i]=0}}if(b.length%2==1){var c=b.length;for(var i=0;i<c;i++){b.push(b[i])}}return b}$.fx.step['svgViewBox']=$.fx.step['svg-viewBox']=function(a){var b=a.elem.attributes.getNamedItem('viewBox');if(!a.set){a.start=parseViewBox(b?b.nodeValue:'');var c=($.fn.jquery>='1.6'?a.end:a.options.curAnim['svgViewBox']||a.options.curAnim['svg-viewBox']);a.end=parseViewBox(c);if(/^[+-]=/.exec(c)){c=c.split(/[, ]+/);while(c.length<4){c.push('0')}for(var i=0;i<4;i++){if(/^[+-]=/.exec(c[i])){a.end[i]=a.start[i]+parseFloat(c[i].replace(/=/,''))}}}a.set=true}var d=$.map(a.start,function(n,i){return(a.pos*(a.end[i]-n)+n)}).join(' ');(b?b.nodeValue=d:a.elem.setAttribute('viewBox',d))};function parseViewBox(a){var b=a.split(/[, ]+/);for(var i=0;i<b.length;i++){b[i]=parseFloat(b[i]);if(isNaN(b[i])){b[i]=0}}while(b.length<4){b.push(0)}return b}$.fx.step['svgTransform']=$.fx.step['svg-transform']=function(a){var b=a.elem.attributes.getNamedItem('transform');if(!a.set){a.start=parseTransform(b?b.nodeValue:'');a.end=parseTransform(a.end,a.start);a.set=true}var c='';for(var i=0;i<a.end.order.length;i++){switch(a.end.order.charAt(i)){case't':c+=' translate('+(a.pos*(a.end.translateX-a.start.translateX)+a.start.translateX)+','+(a.pos*(a.end.translateY-a.start.translateY)+a.start.translateY)+')';break;case's':c+=' scale('+(a.pos*(a.end.scaleX-a.start.scaleX)+a.start.scaleX)+','+(a.pos*(a.end.scaleY-a.start.scaleY)+a.start.scaleY)+')';break;case'r':c+=' rotate('+(a.pos*(a.end.rotateA-a.start.rotateA)+a.start.rotateA)+','+(a.pos*(a.end.rotateX-a.start.rotateX)+a.start.rotateX)+','+(a.pos*(a.end.rotateY-a.start.rotateY)+a.start.rotateY)+')';break;case'x':c+=' skewX('+(a.pos*(a.end.skewX-a.start.skewX)+a.start.skewX)+')';case'y':c+=' skewY('+(a.pos*(a.end.skewY-a.start.skewY)+a.start.skewY)+')';break;case'm':var d='';for(var j=0;j<6;j++){d+=','+(a.pos*(a.end.matrix[j]-a.start.matrix[j])+a.start.matrix[j])}c+=' matrix('+d.substr(1)+')';break}}(b?b.nodeValue=c:a.elem.setAttribute('transform',c))};function parseTransform(a,b){a=a||'';if(typeof a=='object'){a=a.nodeValue}var c=$.extend({translateX:0,translateY:0,scaleX:0,scaleY:0,rotateA:0,rotateX:0,rotateY:0,skewX:0,skewY:0,matrix:[0,0,0,0,0,0]},b||{});c.order='';var d=/([a-zA-Z]+)\(\s*([+-]?[\d\.]+)\s*(?:[\s,]\s*([+-]?[\d\.]+)\s*(?:[\s,]\s*([+-]?[\d\.]+)\s*(?:[\s,]\s*([+-]?[\d\.]+)\s*[\s,]\s*([+-]?[\d\.]+)\s*[\s,]\s*([+-]?[\d\.]+)\s*)?)?)?\)/g;var e=d.exec(a);while(e){switch(e[1]){case'translate':c.order+='t';c.translateX=parseFloat(e[2]);c.translateY=(e[3]?parseFloat(e[3]):0);break;case'scale':c.order+='s';c.scaleX=parseFloat(e[2]);c.scaleY=(e[3]?parseFloat(e[3]):c.scaleX);break;case'rotate':c.order+='r';c.rotateA=parseFloat(e[2]);c.rotateX=(e[3]?parseFloat(e[3]):0);c.rotateY=(e[4]?parseFloat(e[4]):0);break;case'skewX':c.order+='x';c.skewX=parseFloat(e[2]);break;case'skewY':c.order+='y';c.skewY=parseFloat(e[2]);break;case'matrix':c.order+='m';c.matrix=[parseFloat(e[2]),parseFloat(e[3]),parseFloat(e[4]),parseFloat(e[5]),parseFloat(e[6]),parseFloat(e[7])];break}e=d.exec(a)}if(c.order=='m'&&Math.abs(c.matrix[0])==Math.abs(c.matrix[3])&&c.matrix[1]!=0&&Math.abs(c.matrix[1])==Math.abs(c.matrix[2])){var f=Math.acos(c.matrix[0])*180/Math.PI;f=(c.matrix[1]<0?360-f:f);c.order='rt';c.rotateA=f;c.rotateX=c.rotateY=0;c.translateX=c.matrix[4];c.translateY=c.matrix[5]}return c}$.each(['fill','stroke'],function(i,e){var f=e.charAt(0).toUpperCase()+e.substr(1);$.fx.step['svg'+f]=$.fx.step['svg-'+e]=function(a){if(!a.set){a.start=$.svg._getColour(a.elem,e);var b=(a.end=='none');a.end=(b?$.svg._getColour(a.elem.parentNode,e):$.svg._getRGB(a.end));a.end[3]=b;$(a.elem).css(e,'');a.set=true}var c=a.elem.attributes.getNamedItem(e);var d='rgb('+[Math.min(Math.max(parseInt((a.pos*(a.end[0]-a.start[0]))+a.start[0],10),0),255),Math.min(Math.max(parseInt((a.pos*(a.end[1]-a.start[1]))+a.start[1],10),0),255),Math.min(Math.max(parseInt((a.pos*(a.end[2]-a.start[2]))+a.start[2],10),0),255)].join(',')+')';d=(a.end[3]&&a.state==1?'none':d);(c?c.nodeValue=d:a.elem.setAttribute(e,d))}});$.svg._getColour=function(a,b){a=$(a);var c;do{c=a.attr(b)||a.css(b);if((c!=''&&c!='none')||a.hasClass($.svg.markerClassName)){break}}while(a=a.parent());return $.svg._getRGB(c)};$.svg._getRGB=function(a){var b;if(a&&a.constructor==Array){return(a.length==3||a.length==4?a:h['none'])}if(b=/^rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)$/.exec(a)){return[parseInt(b[1],10),parseInt(b[2],10),parseInt(b[3],10)]}if(b=/^rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)$/.exec(a)){return[parseFloat(b[1])*2.55,parseFloat(b[2])*2.55,parseFloat(b[3])*2.55]}if(b=/^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/.exec(a)){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}if(b=/^#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/.exec(a)){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}return h[$.trim(a).toLowerCase()]||h['none']};var h={'':[255,255,255,1],none:[255,255,255,1],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}})(jQuery);
\ No newline at end of file
src/web/js/jquery.svgdom.js 406(+406 -0)
diff --git a/src/web/js/jquery.svgdom.js b/src/web/js/jquery.svgdom.js
new file mode 100644
index 0000000..b5ad8af
--- /dev/null
+++ b/src/web/js/jquery.svgdom.js
@@ -0,0 +1,406 @@
+/* http://keith-wood.name/svg.html
+ jQuery DOM compatibility for jQuery SVG v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) April 2009.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+
+(function($) { // Hide scope, no $ conflict
+
+/* Support adding class names to SVG nodes. */
+$.fn.addClass = function(origAddClass) {
+ return function(classNames) {
+ classNames = classNames || '';
+ return this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ var node = this;
+ $.each(classNames.split(/\s+/), function(i, className) {
+ var classes = (node.className ? node.className.baseVal : node.getAttribute('class'));
+ if ($.inArray(className, classes.split(/\s+/)) == -1) {
+ classes += (classes ? ' ' : '') + className;
+ (node.className ? node.className.baseVal = classes :
+ node.setAttribute('class', classes));
+ }
+ });
+ }
+ else {
+ origAddClass.apply($(this), [classNames]);
+ }
+ });
+ };
+}($.fn.addClass);
+
+/* Support removing class names from SVG nodes. */
+$.fn.removeClass = function(origRemoveClass) {
+ return function(classNames) {
+ classNames = classNames || '';
+ return this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ var node = this;
+ $.each(classNames.split(/\s+/), function(i, className) {
+ var classes = (node.className ? node.className.baseVal : node.getAttribute('class'));
+ classes = $.grep(classes.split(/\s+/), function(n, i) { return n != className; }).
+ join(' ');
+ (node.className ? node.className.baseVal = classes :
+ node.setAttribute('class', classes));
+ });
+ }
+ else {
+ origRemoveClass.apply($(this), [classNames]);
+ }
+ });
+ };
+}($.fn.removeClass);
+
+/* Support toggling class names on SVG nodes. */
+$.fn.toggleClass = function(origToggleClass) {
+ return function(className, state) {
+ return this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ if (typeof state !== 'boolean') {
+ state = !$(this).hasClass(className);
+ }
+ $(this)[(state ? 'add' : 'remove') + 'Class'](className);
+ }
+ else {
+ origToggleClass.apply($(this), [className, state]);
+ }
+ });
+ };
+}($.fn.toggleClass);
+
+/* Support checking class names on SVG nodes. */
+$.fn.hasClass = function(origHasClass) {
+ return function(className) {
+ className = className || '';
+ var found = false;
+ this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ var classes = (this.className ? this.className.baseVal :
+ this.getAttribute('class')).split(/\s+/);
+ found = ($.inArray(className, classes) > -1);
+ }
+ else {
+ found = (origHasClass.apply($(this), [className]));
+ }
+ return !found;
+ });
+ return found;
+ };
+}($.fn.hasClass);
+
+/* Support attributes on SVG nodes. */
+$.fn.attr = function(origAttr) {
+ return function(name, value, type) {
+ if (typeof name === 'string' && value === undefined) {
+ var val = origAttr.apply(this, [name]);
+ if (val && val.baseVal && val.baseVal.numberOfItems != null) { // Multiple values
+ value = '';
+ val = val.baseVal;
+ if (name == 'transform') {
+ for (var i = 0; i < val.numberOfItems; i++) {
+ var item = val.getItem(i);
+ switch (item.type) {
+ case 1: value += ' matrix(' + item.matrix.a + ',' + item.matrix.b + ',' +
+ item.matrix.c + ',' + item.matrix.d + ',' +
+ item.matrix.e + ',' + item.matrix.f + ')';
+ break;
+ case 2: value += ' translate(' + item.matrix.e + ',' + item.matrix.f + ')'; break;
+ case 3: value += ' scale(' + item.matrix.a + ',' + item.matrix.d + ')'; break;
+ case 4: value += ' rotate(' + item.angle + ')'; break; // Doesn't handle new origin
+ case 5: value += ' skewX(' + item.angle + ')'; break;
+ case 6: value += ' skewY(' + item.angle + ')'; break;
+ }
+ }
+ val = value.substring(1);
+ }
+ else {
+ val = val.getItem(0).valueAsString;
+ }
+ }
+ return (val && val.baseVal ? val.baseVal.valueAsString : val);
+ }
+
+ var options = name;
+ if (typeof name === 'string') {
+ options = {};
+ options[name] = value;
+ }
+ return this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ for (var n in options) {
+ var val = ($.isFunction(options[n]) ? options[n]() : options[n]);
+ (type ? this.style[n] = val : this.setAttribute(n, val));
+ }
+ }
+ else {
+ origAttr.apply($(this), [name, value, type]);
+ }
+ });
+ };
+}($.fn.attr);
+
+/* Support removing attributes on SVG nodes. */
+$.fn.removeAttr = function(origRemoveAttr) {
+ return function(name) {
+ return this.each(function() {
+ if ($.svg.isSVGElem(this)) {
+ (this[name] && this[name].baseVal ? this[name].baseVal.value = '' :
+ this.setAttribute(name, ''));
+ }
+ else {
+ origRemoveAttr.apply($(this), [name]);
+ }
+ });
+ };
+}($.fn.removeAttr);
+
+/* Add numeric only properties. */
+$.extend($.cssNumber, {
+ 'stopOpacity': true,
+ 'strokeMitrelimit': true,
+ 'strokeOpacity': true
+});
+
+/* Support retrieving CSS/attribute values on SVG nodes. */
+if ($.cssProps) {
+ $.css = function(origCSS) {
+ return function(elem, name, extra) {
+ var value = (name.match(/^svg.*/) ? $(elem).attr($.cssProps[name] || name) : '');
+ return value || origCSS(elem, name, extra);
+ };
+ }($.css);
+}
+
+/* Determine if any nodes are SVG nodes. */
+function anySVG(checkSet) {
+ for (var i = 0; i < checkSet.length; i++) {
+ if (checkSet[i].nodeType == 1 && checkSet[i].namespaceURI == $.svg.svgNS) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Update Sizzle selectors. */
+
+$.expr.relative['+'] = function(origRelativeNext) {
+ return function(checkSet, part, isXML) {
+ origRelativeNext(checkSet, part, isXML || anySVG(checkSet));
+ };
+}($.expr.relative['+']);
+
+$.expr.relative['>'] = function(origRelativeChild) {
+ return function(checkSet, part, isXML) {
+ origRelativeChild(checkSet, part, isXML || anySVG(checkSet));
+ };
+}($.expr.relative['>']);
+
+$.expr.relative[''] = function(origRelativeDescendant) {
+ return function(checkSet, part, isXML) {
+ origRelativeDescendant(checkSet, part, isXML || anySVG(checkSet));
+ };
+}($.expr.relative['']);
+
+$.expr.relative['~'] = function(origRelativeSiblings) {
+ return function(checkSet, part, isXML) {
+ origRelativeSiblings(checkSet, part, isXML || anySVG(checkSet));
+ };
+}($.expr.relative['~']);
+
+$.expr.find.ID = function(origFindId) {
+ return function(match, context, isXML) {
+ return ($.svg.isSVGElem(context) ?
+ [context.ownerDocument.getElementById(match[1])] :
+ origFindId(match, context, isXML));
+ };
+}($.expr.find.ID);
+
+var div = document.createElement('div');
+div.appendChild(document.createComment(''));
+if (div.getElementsByTagName('*').length > 0) { // Make sure no comments are found
+ $.expr.find.TAG = function(match, context) {
+ var results = context.getElementsByTagName(match[1]);
+ if (match[1] === '*') { // Filter out possible comments
+ var tmp = [];
+ for (var i = 0; results[i] || results.item(i); i++) {
+ if ((results[i] || results.item(i)).nodeType === 1) {
+ tmp.push(results[i] || results.item(i));
+ }
+ }
+ results = tmp;
+ }
+ return results;
+ };
+}
+
+$.expr.preFilter.CLASS = function(match, curLoop, inplace, result, not, isXML) {
+ match = ' ' + match[1].replace(/\\/g, '') + ' ';
+ if (isXML) {
+ return match;
+ }
+ for (var i = 0, elem = {}; elem != null; i++) {
+ elem = curLoop[i];
+ if (!elem) {
+ try {
+ elem = curLoop.item(i);
+ }
+ catch (e) {
+ // Ignore
+ }
+ }
+ if (elem) {
+ var className = (!$.svg.isSVGElem(elem) ? elem.className :
+ (elem.className ? elem.className.baseVal : '') || elem.getAttribute('class'));
+ if (not ^ (className && (' ' + className + ' ').indexOf(match) > -1)) {
+ if (!inplace)
+ result.push(elem);
+ }
+ else if (inplace) {
+ curLoop[i] = false;
+ }
+ }
+ }
+ return false;
+};
+
+$.expr.filter.CLASS = function(elem, match) {
+ var className = (!$.svg.isSVGElem(elem) ? elem.className :
+ (elem.className ? elem.className.baseVal : elem.getAttribute('class')));
+ return (' ' + className + ' ').indexOf(match) > -1;
+};
+
+$.expr.filter.ATTR = function(origFilterAttr) {
+ return function(elem, match) {
+ var handler = null;
+ if ($.svg.isSVGElem(elem)) {
+ handler = match[1];
+ $.expr.attrHandle[handler] = function(elem){
+ var attr = elem.getAttribute(handler);
+ return attr && attr.baseVal || attr;
+ };
+ }
+ var filter = origFilterAttr(elem, match);
+ if (handler) {
+ $.expr.attrHandle[handler] = null;
+ }
+ return filter;
+ };
+}($.expr.filter.ATTR);
+
+/*
+ In the removeData function (line 1881, v1.7.2):
+
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else {
+ try { // SVG
+ elem.removeAttribute( internalKey );
+ } catch (e) {
+ elem[ internalKey ] = null;
+ }
+ }
+
+ In the event.add function (line 2985, v1.7.2):
+
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ try { // SVG
+ elem.addEventListener( type, eventHandle, false );
+ } catch(e) {
+ if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ In the event.remove function (line 3074, v1.7.2):
+
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ try { // SVG
+ elem.removeEventListener(type, elemData.handle, false);
+ }
+ catch (e) {
+ if (elem.detachEvent) {
+ elem.detachEvent("on" + type, elemData.handle);
+ }
+ }
+ }
+
+ In the event.fix function (line 3394, v1.7.2):
+
+ if (event.target.namespaceURI == 'http://www.w3.org/2000/svg') { // SVG
+ event.button = [1, 4, 2][event.button];
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ In the Sizzle function (line 4083, v1.7.2):
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] || set.item(i) ); // SVG
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] || set.item(i) ); // SVG
+ }
+ }
+ }
+ } else {...
+
+ In the fallback for the Sizzle makeArray function (line 4877, v1.7.2):
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] || array.item(i) ); // SVG
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ In the jQuery.cleandata function (line 6538, v1.7.2):
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else {
+ try { // SVG
+ elem.removeAttribute( jQuery.expando );
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ In the fallback getComputedStyle function (line 6727, v1.7.2):
+
+ defaultView = (elem.ownerDocument ? elem.ownerDocument.defaultView : elem.defaultView); // SVG
+ if ( defaultView &&
+ (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+
+ ret = computedStyle.getPropertyValue( name );
+ ...
+
+*/
+
+})(jQuery);
src/web/js/jquery.svgdom.min.js 7(+7 -0)
diff --git a/src/web/js/jquery.svgdom.min.js b/src/web/js/jquery.svgdom.min.js
new file mode 100644
index 0000000..3c280a5
--- /dev/null
+++ b/src/web/js/jquery.svgdom.min.js
@@ -0,0 +1,7 @@
+/* http://keith-wood.name/svg.html
+ jQuery DOM compatibility for jQuery SVG v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) April 2009.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+(function($){$.fn.addClass=function(e){return function(d){d=d||'';return this.each(function(){if($.svg.isSVGElem(this)){var c=this;$.each(d.split(/\s+/),function(i,a){var b=(c.className?c.className.baseVal:c.getAttribute('class'));if($.inArray(a,b.split(/\s+/))==-1){b+=(b?' ':'')+a;(c.className?c.className.baseVal=b:c.setAttribute('class',b))}})}else{e.apply($(this),[d])}})}}($.fn.addClass);$.fn.removeClass=function(e){return function(d){d=d||'';return this.each(function(){if($.svg.isSVGElem(this)){var c=this;$.each(d.split(/\s+/),function(i,a){var b=(c.className?c.className.baseVal:c.getAttribute('class'));b=$.grep(b.split(/\s+/),function(n,i){return n!=a}).join(' ');(c.className?c.className.baseVal=b:c.setAttribute('class',b))})}else{e.apply($(this),[d])}})}}($.fn.removeClass);$.fn.toggleClass=function(c){return function(a,b){return this.each(function(){if($.svg.isSVGElem(this)){if(typeof b!=='boolean'){b=!$(this).hasClass(a)}$(this)[(b?'add':'remove')+'Class'](a)}else{c.apply($(this),[a,b])}})}}($.fn.toggleClass);$.fn.hasClass=function(d){return function(b){b=b||'';var c=false;this.each(function(){if($.svg.isSVGElem(this)){var a=(this.className?this.className.baseVal:this.getAttribute('class')).split(/\s+/);c=($.inArray(b,a)>-1)}else{c=(d.apply($(this),[b]))}return!c});return c}}($.fn.hasClass);$.fn.attr=function(h){return function(b,c,d){if(typeof b==='string'&&c===undefined){var e=h.apply(this,[b]);if(e&&e.baseVal&&e.baseVal.numberOfItems!=null){c='';e=e.baseVal;if(b=='transform'){for(var i=0;i<e.numberOfItems;i++){var f=e.getItem(i);switch(f.type){case 1:c+=' matrix('+f.matrix.a+','+f.matrix.b+','+f.matrix.c+','+f.matrix.d+','+f.matrix.e+','+f.matrix.f+')';break;case 2:c+=' translate('+f.matrix.e+','+f.matrix.f+')';break;case 3:c+=' scale('+f.matrix.a+','+f.matrix.d+')';break;case 4:c+=' rotate('+f.angle+')';break;case 5:c+=' skewX('+f.angle+')';break;case 6:c+=' skewY('+f.angle+')';break}}e=c.substring(1)}else{e=e.getItem(0).valueAsString}}return(e&&e.baseVal?e.baseVal.valueAsString:e)}var g=b;if(typeof b==='string'){g={};g[b]=c}return this.each(function(){if($.svg.isSVGElem(this)){for(var n in g){var a=($.isFunction(g[n])?g[n]():g[n]);(d?this.style[n]=a:this.setAttribute(n,a))}}else{h.apply($(this),[b,c,d])}})}}($.fn.attr);$.fn.removeAttr=function(b){return function(a){return this.each(function(){if($.svg.isSVGElem(this)){(this[a]&&this[a].baseVal?this[a].baseVal.value='':this.setAttribute(a,''))}else{b.apply($(this),[a])}})}}($.fn.removeAttr);$.extend($.cssNumber,{'stopOpacity':true,'strokeMitrelimit':true,'strokeOpacity':true});if($.cssProps){$.css=function(e){return function(a,b,c){var d=(b.match(/^svg.*/)?$(a).attr($.cssProps[b]||b):'');return d||e(a,b,c)}}($.css)}function anySVG(a){for(var i=0;i<a.length;i++){if(a[i].nodeType==1&&a[i].namespaceURI==$.svg.svgNS){return true}}return false}$.expr.relative['+']=function(d){return function(a,b,c){d(a,b,c||anySVG(a))}}($.expr.relative['+']);$.expr.relative['>']=function(d){return function(a,b,c){d(a,b,c||anySVG(a))}}($.expr.relative['>']);$.expr.relative['']=function(d){return function(a,b,c){d(a,b,c||anySVG(a))}}($.expr.relative['']);$.expr.relative['~']=function(d){return function(a,b,c){d(a,b,c||anySVG(a))}}($.expr.relative['~']);$.expr.find.ID=function(d){return function(a,b,c){return($.svg.isSVGElem(b)?[b.ownerDocument.getElementById(a[1])]:d(a,b,c))}}($.expr.find.ID);var j=document.createElement('div');j.appendChild(document.createComment(''));if(j.getElementsByTagName('*').length>0){$.expr.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==='*'){var d=[];for(var i=0;c[i]||c.item(i);i++){if((c[i]||c.item(i)).nodeType===1){d.push(c[i]||c.item(i))}}c=d}return c}}$.expr.preFilter.CLASS=function(a,b,c,d,f,g){a=' '+a[1].replace(/\\/g,'')+' ';if(g){return a}for(var i=0,elem={};elem!=null;i++){elem=b[i];if(!elem){try{elem=b.item(i)}catch(e){}}if(elem){var h=(!$.svg.isSVGElem(elem)?elem.className:(elem.className?elem.className.baseVal:'')||elem.getAttribute('class'));if(f^(h&&(' '+h+' ').indexOf(a)>-1)){if(!c)d.push(elem)}else if(c){b[i]=false}}}return false};$.expr.filter.CLASS=function(a,b){var c=(!$.svg.isSVGElem(a)?a.className:(a.className?a.className.baseVal:a.getAttribute('class')));return(' '+c+' ').indexOf(b)>-1};$.expr.filter.ATTR=function(g){return function(c,d){var e=null;if($.svg.isSVGElem(c)){e=d[1];$.expr.attrHandle[e]=function(a){var b=a.getAttribute(e);return b&&b.baseVal||b}}var f=g(c,d);if(e){$.expr.attrHandle[e]=null}return f}}($.expr.filter.ATTR)})(jQuery);
\ No newline at end of file
src/web/js/jquery.svgfilter.min.js 7(+7 -0)
diff --git a/src/web/js/jquery.svgfilter.min.js b/src/web/js/jquery.svgfilter.min.js
new file mode 100644
index 0000000..551bdc9
--- /dev/null
+++ b/src/web/js/jquery.svgfilter.min.js
@@ -0,0 +1,7 @@
+/* http://keith-wood.name/svg.html
+ SVG filters for jQuery v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+(function($){$.svg.addExtension('filters',SVGFilter);$.extend($.svg._wrapperClass.prototype,{filter:function(a,b,x,y,c,d,e){var f=this._args(arguments,['id','x','y','width','height']);return this._makeNode(f.parent,'filter',$.extend({id:f.id,x:f.x,y:f.y,width:f.width,height:f.height},f.settings||{}))}});function SVGFilter(a){this._wrapper=a}$.extend(SVGFilter.prototype,{distantLight:function(a,b,c,d,e){var f=this._wrapper._args(arguments,['result','azimuth','elevation']);return this._wrapper._makeNode(f.parent,'feDistantLight',$.extend({result:f.result,azimuth:f.azimuth,elevation:f.elevation},f.settings||{}))},pointLight:function(a,b,x,y,z,c){var d=this._wrapper._args(arguments,['result','x','y','z']);return this._wrapper._makeNode(d.parent,'fePointLight',$.extend({result:d.result,x:d.x,y:d.y,z:d.z},d.settings||{}))},spotLight:function(a,b,x,y,z,c,d,e,f){var g=this._wrapper._args(arguments,['result','x','y','z','toX','toY','toZ'],['toX']);var h=$.extend({result:g.result,x:g.x,y:g.y,z:g.z},(g.toX!=null?{pointsAtX:g.toX,pointsAtY:g.toY,pointsAtZ:g.toZ}:{}));return this._wrapper._makeNode(g.parent,'feSpotLight',$.extend(h,g.settings||{}))},blend:function(a,b,c,d,e,f){var g=this._wrapper._args(arguments,['result','mode','in1','in2']);return this._wrapper._makeNode(g.parent,'feBlend',$.extend({result:g.result,mode:g.mode,in_:g.in1,in2:g.in2},g.settings||{}))},colorMatrix:function(a,b,c,d,e,f){var g=this._wrapper._args(arguments,['result','in1','type','values']);if(isArray(g.values)){var h='';for(var i=0;i<g.values.length;i++){h+=(i==0?'':' ')+g.values[i].join(' ')}g.values=h}else if(typeof g.values=='object'){g.settings=g.values;g.values=null}var j=$.extend({result:g.result,in_:g.in1,type:g.type},(g.values!=null?{values:g.values}:{}));return this._wrapper._makeNode(g.parent,'feColorMatrix',$.extend(j,g.settings||{}))},componentTransfer:function(a,b,c,d){var e=this._wrapper._args(arguments,['result','functions']);var f=this._wrapper._makeNode(e.parent,'feComponentTransfer',$.extend({result:e.result},e.settings||{}));var g=['R','G','B','A'];for(var i=0;i<Math.min(4,e.functions.length);i++){var h=e.functions[i];var j=$.extend({type:h[0]},(h[0]=='table'||h[0]=='discrete'?{tableValues:h[1].join(' ')}:(h[0]=='linear'?{slope:h[1],intercept:h[2]}:(h[0]=='gamma'?{amplitude:h[1],exponent:h[2],offset:h[3]}:{}))));this._wrapper._makeNode(f,'feFunc'+g[i],j)}return f},composite:function(a,b,c,d,e,f,g,h,i,j){var k=this._wrapper._args(arguments,['result','operator','in1','in2','k1','k2','k3','k4'],['k1']);var l=$.extend({result:k.result,operator:k.operator,'in':k.in1,in2:k.in2},(k.k1!=null?{k1:k.k1,k2:k.k2,k3:k.k3,k4:k.k4}:{}));return this._wrapper._makeNode(k.parent,'feComposite',$.extend(l,k.settings||{}))},convolveMatrix:function(a,b,c,d,e){var f=this._wrapper._args(arguments,['result','order','matrix']);var g='';for(var i=0;i<f.matrix.length;i++){g+=(i==0?'':' ')+f.matrix[i].join(' ')}f.matrix=g;return this._wrapper._makeNode(f.parent,'feConvolveMatrix',$.extend({result:f.result,order:f.order,kernelMatrix:f.matrix},f.settings||{}))},diffuseLighting:function(a,b,c,d){var e=this._wrapper._args(arguments,['result','colour'],['colour']);return this._wrapper._makeNode(e.parent,'feDiffuseLighting',$.extend($.extend({result:e.result},(e.colour?{lightingColor:e.colour}:{})),e.settings||{}))},displacementMap:function(a,b,c,d,e){var f=this._wrapper._args(arguments,['result','in1','in2']);return this._wrapper._makeNode(f.parent,'feDisplacementMap',$.extend({result:f.result,in_:f.in1,in2:f.in2},f.settings||{}))},flood:function(a,b,x,y,c,d,e,f,g){var h=this._wrapper._args(arguments,['result','x','y','width','height','colour','opacity']);if(arguments.length<6){h.colour=h.x;h.opacity=h.y;h.settings=h.width;h.x=null}var i=$.extend({result:h.result,floodColor:h.colour,floodOpacity:h.opacity},(h.x!=null?{x:h.x,y:h.y,width:h.width,height:h.height}:{}));return this._wrapper._makeNode(h.parent,'feFlood',$.extend(i,h.settings||{}))},gaussianBlur:function(a,b,c,d,e,f){var g=this._wrapper._args(arguments,['result','in1','stdDevX','stdDevY'],['stdDevY']);return this._wrapper._makeNode(g.parent,'feGaussianBlur',$.extend({result:g.result,in_:g.in1,stdDeviation:g.stdDevX+(g.stdDevY?' '+g.stdDevY:'')},g.settings||{}))},image:function(a,b,c,d){var e=this._wrapper._args(arguments,['result','href']);var f=this._wrapper._makeNode(e.parent,'feImage',$.extend({result:e.result},e.settings||{}));f.setAttributeNS($.svg.xlinkNS,'href',e.href);return f},merge:function(a,b,c,d){var e=this._wrapper._args(arguments,['result','refs']);var f=this._wrapper._makeNode(e.parent,'feMerge',$.extend({result:e.result},e.settings||{}));for(var i=0;i<e.refs.length;i++){this._wrapper._makeNode(f,'feMergeNode',{in_:e.refs[i]})}return f},morphology:function(a,b,c,d,e,f,g){var h=this._wrapper._args(arguments,['result','in1','operator','radiusX','radiusY'],['radiusY']);return this._wrapper._makeNode(h.parent,'feMorphology',$.extend({result:h.result,in_:h.in1,operator:h.operator,radius:h.radiusX+(h.radiusY?' '+h.radiusY:'')},h.settings||{}))},offset:function(a,b,c,d,e,f){var g=this._wrapper._args(arguments,['result','in1','dx','dy']);return this._wrapper._makeNode(g.parent,'feOffset',$.extend({result:g.result,in_:g.in1,dx:g.dx,dy:g.dy},g.settings||{}))},specularLighting:function(a,b,c,d,e,f,g){var h=this._wrapper._args(arguments,['result','in1','surfaceScale','specularConstant','specularExponent'],['surfaceScale','specularConstant','specularExponent']);return this._wrapper._makeNode(h.parent,'feSpecularLighting',$.extend({result:h.result,in_:h.in1,surfaceScale:h.surfaceScale,specularConstant:h.specularConstant,specularExponent:h.specularExponent},h.settings||{}))},tile:function(a,b,c,x,y,d,e,f){var g=this._wrapper._args(arguments,['result','in1','x','y','width','height']);return this._wrapper._makeNode(g.parent,'feTile',$.extend({result:g.result,in_:g.in1,x:g.x,y:g.y,width:g.width,height:g.height},g.settings||{}))},turbulence:function(a,b,c,d,e,f){var g=this._wrapper._args(arguments,['result','type','baseFreq','octaves'],['octaves']);return this._wrapper._makeNode(g.parent,'feTurbulence',$.extend({result:g.result,type:g.type,baseFrequency:g.baseFreq,numOctaves:g.octaves},g.settings||{}))}});function isArray(a){return(a&&a.constructor==Array)}})(jQuery)
\ No newline at end of file