123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- /*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.5
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the SVG renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- /**
- * SVG support for jsPlumb.
- *
- * things to investigate:
- *
- * gradients: https://developer.mozilla.org/en/svg_in_html_introduction
- * css:http://tutorials.jenkov.com/svg/svg-and-css.html
- * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
- * pointer events: https://developer.mozilla.org/en/css/pointer-events
- *
- * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
- *
- */
- ;(function() {
-
- var svgAttributeMap = {
- "joinstyle":"stroke-linejoin",
- "stroke-linejoin":"stroke-linejoin",
- "stroke-dashoffset":"stroke-dashoffset",
- "stroke-linecap":"stroke-linecap"
- },
- STROKE_DASHARRAY = "stroke-dasharray",
- DASHSTYLE = "dashstyle",
- LINEAR_GRADIENT = "linearGradient",
- RADIAL_GRADIENT = "radialGradient",
- FILL = "fill",
- STOP = "stop",
- STROKE = "stroke",
- STROKE_WIDTH = "stroke-width",
- STYLE = "style",
- NONE = "none",
- JSPLUMB_GRADIENT = "jsplumb_gradient_",
- LINE_WIDTH = "lineWidth",
- ns = {
- svg:"http://www.w3.org/2000/svg",
- xhtml:"http://www.w3.org/1999/xhtml"
- },
- _attr = function(node, attributes) {
- for (var i in attributes)
- node.setAttribute(i, "" + attributes[i]);
- },
- _node = function(name, attributes) {
- var n = document.createElementNS(ns.svg, name);
- attributes = attributes || {};
- attributes["version"] = "1.1";
- attributes["xmnls"] = ns.xhtml;
- _attr(n, attributes);
- return n;
- },
- _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },
- _clearGradient = function(parent) {
- for (var i = 0; i < parent.childNodes.length; i++) {
- if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
- parent.removeChild(parent.childNodes[i]);
- }
- },
- _updateGradient = function(parent, node, style, dimensions, uiComponent) {
- var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp();
- // first clear out any existing gradient
- _clearGradient(parent);
- // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
- // we want a linear gradient. if it's there, we create a radial gradient.
- // it is possible that a more explicit means of defining the gradient type would be
- // better. relying on 'offset' means that we can never have a radial gradient that uses
- // some default offset, for instance.
- if (!style.gradient.offset) {
- var g = _node(LINEAR_GRADIENT, {id:id});
- parent.appendChild(g);
- }
- else {
- var g = _node(RADIAL_GRADIENT, {
- id:id
- });
- parent.appendChild(g);
- }
-
- // the svg radial gradient seems to treat stops in the reverse
- // order to how canvas does it. so we want to keep all the maths the same, but
- // iterate the actual style declarations in reverse order, if the x indexes are not in order.
- for (var i = 0; i < style.gradient.stops.length; i++) {
- // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of
- // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments.
- // so, not too concerned about leaving this in for now.
- var styleToUse = i;
- if (dimensions.length == 8)
- styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i;
- else
- styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i;
- var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true);
- var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
- g.appendChild(s);
- }
- var applyGradientTo = style.strokeStyle ? STROKE : FILL;
- node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
- },
- _applyStyles = function(parent, node, style, dimensions, uiComponent) {
-
- if (style.gradient) {
- _updateGradient(parent, node, style, dimensions, uiComponent);
- }
- else {
- // make sure we clear any existing gradient
- _clearGradient(parent);
- node.setAttribute(STYLE, "");
- }
-
- node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE);
- node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE);
- if (style.lineWidth) {
- node.setAttribute(STROKE_WIDTH, style.lineWidth);
- }
-
- // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
- // the syntax in VML but is actually kind of nasty: values are given in the pixel
- // coordinate space, whereas in VML they are multiples of the width of the stroked
- // line, which makes a lot more sense. for that reason, jsPlumb is supporting both
- // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
- // VML, which will be the preferred method. the code below this converts a dashstyle
- // attribute given in terms of stroke width into a pixel representation, by using the
- // stroke's lineWidth.
- if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
- var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
- parts = style[DASHSTYLE].split(sep),
- styleToUse = "";
- parts.forEach(function(p) {
- styleToUse += (Math.floor(p * style.lineWidth) + sep);
- });
- node.setAttribute(STROKE_DASHARRAY, styleToUse);
- }
- else if(style[STROKE_DASHARRAY]) {
- node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
- }
-
- // extra attributes such as join type, dash offset.
- for (var i in svgAttributeMap) {
- if (style[i]) {
- node.setAttribute(svgAttributeMap[i], style[i]);
- }
- }
- },
- _decodeFont = function(f) {
- var r = /([0-9].)(p[xt])\s(.*)/;
- var bits = f.match(r);
- return {size:bits[1] + bits[2], font:bits[3]};
- },
- _classManip = function(el, add, clazz) {
- var classesToAddOrRemove = clazz.split(" "),
- className = el.className,
- curClasses = className.baseVal.split(" ");
-
- for (var i = 0; i < classesToAddOrRemove.length; i++) {
- if (add) {
- if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
- curClasses.push(classesToAddOrRemove[i]);
- }
- else {
- var idx = curClasses.indexOf(classesToAddOrRemove[i]);
- if (idx != -1)
- curClasses.splice(idx, 1);
- }
- }
-
- el.className.baseVal = curClasses.join(" ");
- },
- _addClass = function(el, clazz) {
- _classManip(el, true, clazz);
- },
- _removeClass = function(el, clazz) {
- _classManip(el, false, clazz);
- };
-
- /**
- utility methods for other objects to use.
- */
- jsPlumb.util.svg = {
- addClass:_addClass,
- removeClass:_removeClass
- };
-
- /*
- * Base class for SVG components.
- */
- //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) {
- var SvgComponent = function(params) {
- var self = this,
- pointerEventsSpec = params.pointerEventsSpec || "all";
- jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
- self.canvas = null, self.path = null, self.svg = null;
-
- var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),
- svgParams = {
- "style":"",
- "width":0,
- "height":0,
- "pointer-events":pointerEventsSpec,
- "position":"absolute"
- };
- if (self.tooltip) svgParams["title"] = self.tooltip;
- self.svg = _node("svg", svgParams);
- if (params.useDivWrapper) {
- self.canvas = document.createElement("div");
- self.canvas.style["position"] = "absolute";
- jsPlumb.sizeCanvas(self.canvas,0,0,1,1);
- self.canvas.className = clazz;
- if (self.tooltip) self.canvas.setAttribute("title", self.tooltip);
- }
- else {
- _attr(self.svg, { "class":clazz });
- self.canvas = self.svg;
- }
-
- params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]);
- if (params.useDivWrapper) self.canvas.appendChild(self.svg);
-
- // TODO this displayElement stuff is common between all components, across all
- // renderers. would be best moved to jsPlumbUIComponent.
- var displayElements = [ self.canvas ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el) {
- displayElements.push(el);
- };
-
- this.paint = function(d, style, anchor) {
- if (style != null) {
- var x = d[0], y = d[1];
- if (params.useDivWrapper) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- x = 0, y = 0;
- }
- _attr(self.svg, {
- "style":_pos([x, y, d[2], d[3]]),
- "width": d[2],
- "height": d[3]
- });
- self._paint.apply(this, arguments);
- }
- };
- };
-
- /*
- * Base class for SVG connectors.
- */
- var SvgConnector = jsPlumb.SvgConnector = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].connectorClass,
- originalArgs:arguments,
- pointerEventsSpec:"none",
- tooltip:params.tooltip,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var p = self.getPath(d), a = { "d":p }, outlineStyle = null;
- a["pointer-events"] = "all";
-
- // outline style. actually means drawing an svg object underneath the main one.
- if (style.outlineColor) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
- outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor);
- outlineStyle.lineWidth = outlineStrokeWidth;
-
- if (self.bgPath == null) {
- self.bgPath = _node("path", a);
- self.svg.appendChild(self.bgPath);
- self.attachListeners(self.bgPath, self);
- }
- else {
- _attr(self.bgPath, a);
- }
-
- _applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
- }
-
-
- // test - see below
- // a["clip-path"]= "url(#testClip)";
-
- if (self.path == null) {
- self.path = _node("path", a);
- self.svg.appendChild(self.path);
- self.attachListeners(self.path, self);
-
- /*
- this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection.
- you could do this by walking along the line, stepping along a little at a time, and setting the clip
- path to extend as far as that point.
-
- self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"});
- self.svg.appendChild(self.clip);
- self.clip.appendChild(_node("rect", {
- x:"0",y:"0",width:"0.5",height:"1"
- }));
- */
- }
- else {
- _attr(self.path, a);
- }
-
- _applyStyles(self.svg, self.path, style, d, self);
- };
-
- this.reattachListeners = function() {
- if (self.bgPath) self.reattachListenersForElement(self.bgPath, self);
- if (self.path) self.reattachListenersForElement(self.path, self);
- };
-
- };
- /*
- * SVG Bezier Connector
- */
- jsPlumb.Connectors.svg.Bezier = function(params) {
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; };
- };
-
- /*
- * SVG straight line Connector
- */
- jsPlumb.Connectors.svg.Straight = function(params) {
- jsPlumb.Connectors.Straight.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; };
- };
-
- jsPlumb.Connectors.svg.Flowchart = function() {
- var self = this;
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(dimensions) {
- var p = "M " + dimensions[4] + "," + dimensions[5];
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)];
- }
- // finally draw a line to the end
- p = p + " " + dimensions[6] + "," + dimensions[7];
- return p;
- };
- };
-
- /*
- * Base class for SVG endpoints.
- */
- var SvgEndpoint = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].endpointClass,
- originalArgs:arguments,
- pointerEventsSpec:"all",
- useDivWrapper:true,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var s = jsPlumb.extend({}, style);
- if (s.outlineColor) {
- s.strokeWidth = s.outlineWidth;
- s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true);
- }
-
- if (self.node == null) {
- self.node = self.makeNode(d, s);
- self.svg.appendChild(self.node);
- self.attachListeners(self.node, self);
- }
- _applyStyles(self.svg, self.node, s, d, self);
- _pos(self.node, d);
- };
-
- this.reattachListeners = function() {
- if (self.node) self.reattachListenersForElement(self.node, self);
- };
- };
-
- /*
- * SVG Dot Endpoint
- */
- jsPlumb.Endpoints.svg.Dot = function() {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("circle", {
- "cx" : d[2] / 2,
- "cy" : d[3] / 2,
- "r" : d[2] / 2
- });
- };
- };
-
- /*
- * SVG Rectangle Endpoint
- */
- jsPlumb.Endpoints.svg.Rectangle = function() {
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("rect", {
- "width":d[2],
- "height":d[3]
- });
- };
- };
-
- /*
- * SVG Image Endpoint is the default image endpoint.
- */
- jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
- /*
- * Blank endpoint in svg renderer is the default Blank endpoint.
- */
- jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;
- /*
- * Label endpoint in svg renderer is the default Label endpoint.
- */
- jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
-
- var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
- this.isAppendedAtTopLevel = false;
- var self = this, path =null;
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
- }
- var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
-
- _attr(path, {
- "d" : makePath(d),
- "class" : clazz,
- stroke : strokeStyle ? strokeStyle : null,
- fill : fillStyle ? fillStyle : null
- });
- };
- var makePath = function(d) {
- return "M" + d.hxy.x + "," + d.hxy.y +
- " L" + d.tail[0].x + "," + d.tail[0].y +
- " L" + d.cxy.x + "," + d.cxy.y +
- " L" + d.tail[1].x + "," + d.tail[1].y +
- " L" + d.hxy.x + "," + d.hxy.y;
- };
- this.reattachListeners = function() {
- if (path) self.reattachListenersForElement(path, self);
- };
- };
-
- jsPlumb.Overlays.svg.Arrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.PlainArrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.Diamond = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
- // a test
- jsPlumb.Overlays.svg.GuideLines = function() {
- var path = null, self = this, path2 = null, p1_1, p1_2;
- jsPlumb.Overlays.GuideLines.apply(this, arguments);
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
- p1_1 = _node("path");
- connector.svg.appendChild(p1_1);
- self.attachListeners(p1_1, connector);
- self.attachListeners(p1_1, self);
- p1_2 = _node("path");
- connector.svg.appendChild(p1_2);
- self.attachListeners(p1_2, connector);
- self.attachListeners(p1_2, self);
- }
- _attr(path, {
- "d" : makePath(d[0], d[1]),
- stroke : "red",
- fill : null
- });
- _attr(p1_1, {
- "d" : makePath(d[2][0], d[2][1]),
- stroke : "blue",
- fill : null
- });
- _attr(p1_2, {
- "d" : makePath(d[3][0], d[3][1]),
- stroke : "green",
- fill : null
- });
- };
- var makePath = function(d1, d2) {
- return "M " + d1.x + "," + d1.y +
- " L" + d2.x + "," + d2.y;
- };
- };
- })();
|