All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yaoqiang.graph.canvas.SvgCanvas Maven / Gradle / Ivy

package org.yaoqiang.graph.canvas;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.yaoqiang.bpmn.model.BPMNModelUtils;
import org.yaoqiang.bpmn.model.elements.activities.Activity;
import org.yaoqiang.bpmn.model.elements.activities.AdHocSubProcess;
import org.yaoqiang.bpmn.model.elements.activities.LoopCharacteristics;
import org.yaoqiang.bpmn.model.elements.activities.MultiInstanceLoopCharacteristics;
import org.yaoqiang.bpmn.model.elements.activities.ReceiveTask;
import org.yaoqiang.bpmn.model.elements.activities.StandardLoopCharacteristics;
import org.yaoqiang.bpmn.model.elements.activities.SubProcess;
import org.yaoqiang.bpmn.model.elements.activities.Task;
import org.yaoqiang.bpmn.model.elements.activities.Transaction;
import org.yaoqiang.bpmn.model.elements.choreographyactivities.ChoreographyActivity;
import org.yaoqiang.bpmn.model.elements.collaboration.Participant;
import org.yaoqiang.bpmn.model.elements.core.common.FlowElementsContainer;
import org.yaoqiang.bpmn.model.elements.events.Event;
import org.yaoqiang.bpmn.model.elements.events.EventDefinitions;
import org.yaoqiang.bpmn.model.elements.events.StartEvent;
import org.yaoqiang.bpmn.model.elements.events.ThrowEvent;
import org.yaoqiang.bpmn.model.elements.gateways.EventBasedGateway;
import org.yaoqiang.graph.model.GraphModel;
import org.yaoqiang.util.Constants;

import com.mxgraph.canvas.mxBasicCanvas;
import com.mxgraph.model.mxCell;
import com.mxgraph.util.mxBase64;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;

/**
 * SvgCanvas
 * 
 * @author Shi Yaoqiang([email protected])
 */
public class SvgCanvas extends mxBasicCanvas {

	/**
	 * Holds the HTML document that represents the canvas.
	 */
	protected Document document;

	/**
	 * Used internally for looking up elements. Workaround for getElementById not working.
	 */
	private Map gradients = new Hashtable();

	/**
	 * Used internally for looking up images.
	 */
	private Map images = new Hashtable();

	/**
	 * 
	 */
	protected Element defs = null;

	/**
	 * Specifies if images should be embedded as base64 encoded strings. Default is false.
	 */
	protected boolean embedded = false;

	/**
	 * Constructs a new SVG canvas for the specified dimension and scale.
	 */
	public SvgCanvas() {
		this(null);
	}

	/**
	 * Constructs a new SVG canvas for the specified bounds, scale and background color.
	 */
	public SvgCanvas(Document document) {
		setDocument(document);
	}

	/**
	 * 
	 */
	public void appendSvgElement(Element node) {
		if (document != null) {
			document.getDocumentElement().appendChild(node);
		}
	}

	/**
	 * 
	 */
	protected Element getDefsElement() {
		if (defs == null) {
			defs = document.createElement("defs");

			Element svgNode = document.getDocumentElement();

			if (svgNode.hasChildNodes()) {
				svgNode.insertBefore(defs, svgNode.getFirstChild());
			} else {
				svgNode.appendChild(defs);
			}
		}

		return defs;
	}

	/**
	 * 
	 */
	public Element getGradientElement(String start, String end, String direction) {
		String id = getGradientId(start, end, direction);
		Element gradient = gradients.get(id);

		if (gradient == null) {
			gradient = createGradientElement(start, end, direction);
			gradient.setAttribute("id", "g" + (gradients.size() + 1));
			getDefsElement().appendChild(gradient);
			gradients.put(id, gradient);
		}

		return gradient;
	}

	/**
	 * 
	 */
	public Element getGlassGradientElement() {
		String id = "mx-glass-gradient";

		Element glassGradient = gradients.get(id);

		if (glassGradient == null) {
			glassGradient = document.createElement("linearGradient");
			glassGradient.setAttribute("x1", "0%");
			glassGradient.setAttribute("y1", "0%");
			glassGradient.setAttribute("x2", "0%");
			glassGradient.setAttribute("y2", "100%");

			Element stop1 = document.createElement("stop");
			stop1.setAttribute("offset", "0%");
			stop1.setAttribute("style", "stop-color:#ffffff;stop-opacity:0.9");
			glassGradient.appendChild(stop1);

			Element stop2 = document.createElement("stop");
			stop2.setAttribute("offset", "100%");
			stop2.setAttribute("style", "stop-color:#ffffff;stop-opacity:0.1");
			glassGradient.appendChild(stop2);

			glassGradient.setAttribute("id", "g" + (gradients.size() + 1));
			getDefsElement().appendChild(glassGradient);
			gradients.put(id, glassGradient);
		}

		return glassGradient;
	}

	/**
	 * 
	 */
	protected Element createGradientElement(String start, String end, String direction) {
		Element gradient = document.createElement("linearGradient");
		gradient.setAttribute("x1", "0%");
		gradient.setAttribute("y1", "0%");
		gradient.setAttribute("x2", "0%");
		gradient.setAttribute("y2", "0%");

		if (direction == null || direction.equals(mxConstants.DIRECTION_SOUTH)) {
			gradient.setAttribute("y2", "100%");
		} else if (direction.equals(mxConstants.DIRECTION_EAST)) {
			gradient.setAttribute("x2", "100%");
		} else if (direction.equals(mxConstants.DIRECTION_NORTH)) {
			gradient.setAttribute("y1", "100%");
		} else if (direction.equals(mxConstants.DIRECTION_WEST)) {
			gradient.setAttribute("x1", "100%");
		}

		Element stop = document.createElement("stop");
		stop.setAttribute("offset", "0%");
		stop.setAttribute("style", "stop-color:" + start);
		gradient.appendChild(stop);

		stop = document.createElement("stop");
		stop.setAttribute("offset", "100%");
		stop.setAttribute("style", "stop-color:" + end);
		gradient.appendChild(stop);

		return gradient;
	}

	/**
	 * 
	 */
	public String getGradientId(String start, String end, String direction) {
		// Removes illegal characters from gradient ID
		if (start.startsWith("#")) {
			start = start.substring(1);
		}

		if (end.startsWith("#")) {
			end = end.substring(1);
		}

		// Workaround for gradient IDs not working in Safari 5 / Chrome 6
		// if they contain uppercase characters
		start = start.toLowerCase();
		end = end.toLowerCase();

		String dir = null;

		if (direction == null || direction.equals(mxConstants.DIRECTION_SOUTH)) {
			dir = "south";
		} else if (direction.equals(mxConstants.DIRECTION_EAST)) {
			dir = "east";
		} else {
			String tmp = start;
			start = end;
			end = tmp;

			if (direction.equals(mxConstants.DIRECTION_NORTH)) {
				dir = "south";
			} else if (direction.equals(mxConstants.DIRECTION_WEST)) {
				dir = "east";
			}
		}

		return "mx-gradient-" + start + "-" + end + "-" + dir;
	}

	/**
	 * Returns true if the given string ends with .png, .jpg or .gif.
	 */
	protected boolean isImageResource(String src) {
		return src != null && (src.toLowerCase().endsWith(".png") || src.toLowerCase().endsWith(".jpg") || src.toLowerCase().endsWith(".gif"));
	}

	/**
	 * 
	 */
	protected InputStream getResource(String src) {
		InputStream stream = null;

		try {
			stream = new BufferedInputStream(new URL(src).openStream());
		} catch (Exception e1) {
			stream = getClass().getResourceAsStream(src);
		}

		return stream;
	}

	/**
	 * @throws IOException
	 * 
	 */
	protected String createDataUrl(String src) throws IOException {
		String result = null;
		InputStream inputStream = isImageResource(src) ? getResource(src) : null;

		if (inputStream != null) {
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
			byte[] bytes = new byte[512];

			// Read bytes from the input stream in bytes.length-sized chunks and write
			// them into the output stream
			int readBytes;
			while ((readBytes = inputStream.read(bytes)) > 0) {
				outputStream.write(bytes, 0, readBytes);
			}
			inputStream.close();

			// Convert the contents of the output stream into a Data URL
			String format = "png";
			int dot = src.lastIndexOf('.');

			if (dot > 0 && dot < src.length()) {
				format = src.substring(dot + 1);
			}

			result = "data:image/" + format + ";base64," + mxBase64.encodeToString(outputStream.toByteArray(), false);
		}

		return result;
	}

	/**
	 * 
	 */
	protected Element getEmbeddedImageElement(String src) {
		Element img = images.get(src);

		if (img == null) {
			img = document.createElement("svg");
			Element inner = document.createElement("image");

			// Store before transforming to DataURL
			images.put(src, img);

			if (!src.startsWith("data:image/")) {
				try {
					String tmp = createDataUrl(src);

					if (tmp != null) {
						src = tmp;
					}
				} catch (IOException e) {
					// ignore
				}
			}

			inner.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", src);
			img.appendChild(inner);
			img.setAttribute("id", "i" + (images.size()));
			getDefsElement().appendChild(img);
		}

		return img;
	}

	/**
	 * 
	 */
	protected Element createImageElement(double x, double y, double w, double h, String src, boolean aspect, boolean flipH, boolean flipV, boolean embedded) {
		Element elem = null;

		if (embedded) {
			elem = document.createElement("use");

			Element img = getEmbeddedImageElement(src);
			Element inner = (Element) img.getFirstChild();
			inner.setAttribute("width", String.valueOf(w));
			inner.setAttribute("height", String.valueOf(h));
			img.setAttribute("width", String.valueOf(w));
			img.setAttribute("height", String.valueOf(h));

			elem.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", "#" + img.getAttribute("id"));
		} else {
			elem = document.createElement("image");

			elem.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", src);
		}

		elem.setAttribute("x", String.valueOf(x));
		elem.setAttribute("y", String.valueOf(y));
		elem.setAttribute("width", String.valueOf(w));
		elem.setAttribute("height", String.valueOf(h));

		// FIXME: SVG element must be used for reference to image with
		// aspect but for images with no aspect this does not work.
		if (aspect) {
			elem.setAttribute("preserveAspectRatio", "xMidYMid");
		} else {
			elem.setAttribute("preserveAspectRatio", "none");
		}

		double sx = 1;
		double sy = 1;
		double dx = 0;
		double dy = 0;

		if (flipH) {
			sx *= -1;
			dx = -w - 2 * x;
		}

		if (flipV) {
			sy *= -1;
			dy = -h - 2 * y;
		}

		String transform = "";

		if (sx != 1 || sy != 1) {
			transform += "scale(" + sx + " " + sy + ") ";
		}

		if (dx != 0 || dy != 0) {
			transform += "translate(" + dx + " " + dy + ") ";
		}

		if (transform.length() > 0) {
			elem.setAttribute("transform", transform);
		}

		return elem;
	}

	/**
	 * 
	 */
	public void setDocument(Document document) {
		this.document = document;
	}

	/**
	 * Returns a reference to the document that represents the canvas.
	 * 
	 * @return Returns the document.
	 */
	public Document getDocument() {
		return document;
	}

	/**
	 * 
	 */
	public void setEmbedded(boolean value) {
		embedded = value;
	}

	/**
	 * 
	 */
	public boolean isEmbedded() {
		return embedded;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mxgraph.canvas.mxICanvas#drawCell()
	 */
	public Object drawCell(mxCellState state) {
		Map style = state.getStyle();
		GraphModel model = (GraphModel) state.getView().getGraph().getModel();
		if (model.isConditionalSequenceFlow(state.getCell()) && !model.isGateway(model.getTerminal(state.getCell(), true))) {
			style.put(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, 15);
			style.put(mxConstants.STYLE_STARTARROW, "open_diamond");
		} else if (model.isDefaultSequenceFlow(state.getCell())) {
			style.put(mxConstants.STYLE_STARTARROW, "slash");
		}
		mxCell cell = (mxCell) state.getCell();
		Element elem = null;

		if (state.getAbsolutePointCount() > 1) {
			List pts = state.getAbsolutePoints();

			// Transpose all points by cloning into a new array
			pts = mxUtils.translatePoints(pts, translate.x, translate.y);

			// Draws the line
			elem = drawLine(pts, style);

			// Applies opacity
			float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100);

			if (opacity != 100) {
				String value = String.valueOf(opacity / 100);
				elem.setAttribute("fill-opacity", value);
				elem.setAttribute("stroke-opacity", value);
			}
		} else {
			int x = (int) state.getX() + translate.x;
			int y = (int) state.getY() + translate.y;
			int w = (int) state.getWidth();
			int h = (int) state.getHeight();

			if (!mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals(mxConstants.SHAPE_SWIMLANE)) {
				elem = drawShape(x, y, w, h, state, null);
			} else {
				int start = (int) Math.round(mxUtils.getInt(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale);

				// Removes some styles to draw the content area
				Map cloned = new Hashtable(style);
				cloned.remove(mxConstants.STYLE_FILLCOLOR);
				cloned.remove(mxConstants.STYLE_ROUNDED);

				if (model.isLane(state.getCell())) {
					elem = drawShape(x, y, w, h, state, null);
				} else {
					elem = document.createElement("g");
					if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true)) {
						drawShape(x, y, w, start, state, null);
						drawShape(x, y + start, w, h - start, state, cloned);
					} else {
						drawShape(x, y, start, h, state, null);
						drawShape(x + start, y, w - start, h, state, cloned);
					}
					if (((Participant) model.getValue(state.getCell())).getMultiplicity() > 1) {
						double imgWidth = 16 * scale;
						double imgHeight = 16 * scale;
						Element imageElement = createImageElement(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth, imgHeight, Constants.SHAPE_MARKER
								+ "loop_multiple.png", false, false, false, isEmbedded());
						elem.appendChild(imageElement);
					}
					appendSvgElement(elem);
				}

			}
		}

		if (cell.getId() != null && elem != null) {
			elem.setAttribute("id", cell.getId());
		}
		return elem;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.mxgraph.canvas.mxICanvas#drawLabel()
	 */
	public Object drawLabel(String label, mxCellState state, boolean html) {
		mxRectangle bounds = state.getLabelBounds();

		if (drawLabels && bounds != null) {
			int x = (int) bounds.getX() + translate.x;
			int y = (int) bounds.getY() + translate.y;
			int w = (int) bounds.getWidth();
			int h = (int) bounds.getHeight();
			Map style = state.getStyle();

			return drawText(label, x, y, w, h, style);
		}

		return null;
	}

	/**
	 * Draws the shape specified with the STYLE_SHAPE key in the given style.
	 * 
	 * @param x
	 *            X-coordinate of the shape.
	 * @param y
	 *            Y-coordinate of the shape.
	 * @param w
	 *            Width of the shape.
	 * @param h
	 *            Height of the shape.
	 * @param style
	 *            Style of the the shape.
	 */
	public Element drawShape(int x, int y, int w, int h, mxCellState state, Map style) {
		if (style == null) {
			style = state.getStyle();
		}
		mxCell cell = (mxCell) state.getCell();
		Object value = cell.getValue();
		String styleName = cell.getStyle();
		GraphModel model = (GraphModel) state.getView().getGraph().getModel();

		String fillColor = mxUtils.getString(style, mxConstants.STYLE_FILLCOLOR, "none");
		if (fillColor.startsWith("ff")) {
			fillColor = "#" + fillColor.substring(2);
		}
		String gradientColor = mxUtils.getString(style, mxConstants.STYLE_GRADIENTCOLOR, "none");
		if (gradientColor.startsWith("ff")) {
			gradientColor = "#" + gradientColor.substring(2);
		}
		if (model.isInitiatingChoreographyParticipant(cell)) {
			fillColor = "#E8EEF7";
			gradientColor = "#B8BEC7";
		}
		String strokeColor = mxUtils.getString(style, mxConstants.STYLE_STROKECOLOR, "none");
		if (strokeColor.startsWith("ff")) {
			strokeColor = "#" + strokeColor.substring(2);
		}
		float strokeWidth = (float) (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * scale);
		float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100);
		int inset = (int) ((3 + strokeWidth) * scale);

		// Draws the shape
		String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
		String img = getImageForStyle(style);
		double imgWidth = 16 * scale;
		double imgHeight = 16 * scale;
		int spacing = 5;
		mxRectangle imageBounds = new mxRectangle();
		Element elem = null;
		Element background = null;

		if (shape.equals(Constants.SHAPE_GATEWAY)) {
			elem = document.createElement("g");

			background = document.createElement("path");
			String d = "M " + (x + w / 2) + " " + y + " L " + (x + w) + " " + (y + h / 2) + " L " + (x + w / 2) + " " + (y + h) + " L " + x + " " + (y + h / 2);
			background.setAttribute("d", d + " Z");
			elem.appendChild(background);

			if (model.isInclusiveGateway(cell)) {
				inset = (int) Math.round(11 * scale);

				Element foreground = document.createElement("ellipse");
				foreground.setAttribute("fill", "none");
				foreground.setAttribute("stroke", strokeColor);
				foreground.setAttribute("stroke-width", String.valueOf(strokeWidth * 3));

				foreground.setAttribute("cx", String.valueOf(x + w / 2));
				foreground.setAttribute("cy", String.valueOf(y + h / 2));
				foreground.setAttribute("rx", String.valueOf(w / 2 - inset));
				foreground.setAttribute("ry", String.valueOf(h / 2 - inset));

				elem.appendChild(foreground);
			} else if (model.isExclusiveEventGateway(cell)) {
				inset = (int) Math.round(10 * scale);
				Element foreground = document.createElement("ellipse");
				foreground.setAttribute("fill", "none");
				foreground.setAttribute("stroke", strokeColor);
				foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));

				foreground.setAttribute("cx", String.valueOf(x + w / 2));
				foreground.setAttribute("cy", String.valueOf(y + h / 2));
				foreground.setAttribute("rx", String.valueOf(w / 2 - inset));
				foreground.setAttribute("ry", String.valueOf(h / 2 - inset));

				elem.appendChild(foreground);

				if (!((EventBasedGateway) value).isInstantiate()) {
					inset = (int) Math.round(12 * scale);
					Element outer = document.createElement("ellipse");
					outer.setAttribute("fill", "none");
					outer.setAttribute("stroke", strokeColor);
					outer.setAttribute("stroke-width", String.valueOf(strokeWidth));

					outer.setAttribute("cx", String.valueOf(x + w / 2));
					outer.setAttribute("cy", String.valueOf(y + h / 2));
					outer.setAttribute("rx", String.valueOf(w / 2 - inset));
					outer.setAttribute("ry", String.valueOf(h / 2 - inset));

					elem.appendChild(outer);
				}

				int px = Math.round(x + w / 2);
				int py = (int) Math.round(y + w * 0.34);
				int p2x = (int) Math.round(x + w * 0.65);
				int p2y = (int) Math.round(y + w * 0.45);
				int p3x = (int) Math.round(x + w * 0.6);
				int p3y = (int) Math.round(y + w * 0.63);
				int p4x = (int) Math.round(x + w * 0.4);
				int p5x = (int) Math.round(x + w * 0.35);

				Element inner = document.createElement("path");
				d = " M " + px + " " + py + " L " + p2x + " " + p2y + " L " + p3x + " " + p3y + " L " + p4x + " " + p3y + " L " + p5x + " " + p2y;

				inner.setAttribute("d", d + " Z");
				inner.setAttribute("fill", "none");
				inner.setAttribute("stroke", strokeColor);
				inner.setAttribute("stroke-width", String.valueOf(strokeWidth));

				elem.appendChild(inner);
			} else if (model.isParallelGateway(cell) || model.isParallelEventGateway(cell)) {
				int px = (int) Math.round(x + w * 0.3);
				int py = (int) Math.round(y + w * 0.3);
				int p2x = (int) Math.round(x + w * 0.5);
				int p2y = (int) Math.round(y + w * 0.5);
				int p3x = (int) Math.round(x + w * 0.7);
				int p3y = (int) Math.round(y + w * 0.7);

				Element outer = document.createElement("path");
				d = " M " + px + " " + p2y + " L " + p3x + " " + p2y + " M " + p2x + " " + py + " L " + p2x + " " + p3y;

				outer.setAttribute("d", d);
				outer.setAttribute("fill", "none");
				outer.setAttribute("stroke", strokeColor);
				outer.setAttribute("stroke-width", String.valueOf(strokeWidth * 5));

				elem.appendChild(outer);

				if (model.isParallelEventGateway(cell)) {
					inset = (int) Math.round(9 * scale);
					Element foreground = document.createElement("ellipse");
					foreground.setAttribute("fill", "none");
					foreground.setAttribute("stroke", strokeColor);
					foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));

					foreground.setAttribute("cx", String.valueOf(x + w / 2));
					foreground.setAttribute("cy", String.valueOf(y + h / 2));
					foreground.setAttribute("rx", String.valueOf(w / 2 - inset));
					foreground.setAttribute("ry", String.valueOf(h / 2 - inset));

					elem.appendChild(foreground);

					Element inner = document.createElement("path");
					d = " M " + (px + 1) + " " + p2y + " L " + (p3x - 1) + " " + p2y + " M " + p2x + " " + (py + 1) + " L " + p2x + " " + (p3y - 1);

					inner.setAttribute("d", d);
					inner.setAttribute("fill", "none");
					inner.setAttribute("stroke", fillColor);
					inner.setAttribute("stroke-width", String.valueOf(strokeWidth * 2));

					elem.appendChild(inner);
				}
			} else if (model.isComplexGateway(cell)) {
				int px = (int) Math.round(x + w * 0.7);
				int py = (int) Math.round(y + w * 0.3);
				int p2x = (int) Math.round(x + w * 0.3);
				int p2y = (int) Math.round(y + w * 0.7);
				int p3x = (int) Math.round(x + w * 0.2);
				int p3y = (int) Math.round(y + w * 0.8);
				int p4x = (int) Math.round(x + w * 0.8);
				int p4y = (int) Math.round(y + w * 0.5);
				int p5x = (int) Math.round(x + w * 0.5);
				int p5y = (int) Math.round(y + w * 0.2);

				Element inner = document.createElement("path");
				d = " M " + px + " " + py + " L " + p2x + " " + p2y + " M " + p2x + " " + py + " L " + px + " " + p2y + " M " + p3x + " " + p4y + " L " + p4x
						+ " " + p4y + " M " + p5x + " " + p5y + " L " + p5x + " " + p3y;

				inner.setAttribute("d", d);
				inner.setAttribute("fill", "none");
				inner.setAttribute("stroke", strokeColor);
				inner.setAttribute("stroke-width", String.valueOf(strokeWidth * 4));

				elem.appendChild(inner);
			} else if (model.isExclusiveGatewayWithIndicator(cell)) {
				int px = (int) Math.round(x + w * 0.65);
				int py = (int) Math.round(y + w * 0.25);
				int p2x = (int) Math.round(x + w * 0.35);
				int p2y = (int) Math.round(y + w * 0.75);

				Element foreground = document.createElement("path");
				d = " M " + px + " " + py + " L " + p2x + " " + p2y + " M " + p2x + " " + py + " L " + px + " " + p2y;

				foreground.setAttribute("d", d);
				foreground.setAttribute("fill", "none");
				foreground.setAttribute("stroke", strokeColor);
				foreground.setAttribute("stroke-width", String.valueOf(strokeWidth * 3));

				elem.appendChild(foreground);
			}
		} else if (shape.equals(Constants.SHAPE_EVENT)) {
			elem = document.createElement("g");

			Event event = (Event) value;
			EventDefinitions eventDefinitions = event.getEventDefinitions();
			if (model.isNonInterruptingEvent(event)) {
				style.put(mxConstants.STYLE_DASHED, "1");
			}
			background = document.createElement("ellipse");
			background.setAttribute("cx", String.valueOf(x + w / 2));
			background.setAttribute("cy", String.valueOf(y + h / 2));
			background.setAttribute("rx", String.valueOf(w / 2));
			background.setAttribute("ry", String.valueOf(h / 2));
			elem.appendChild(background);

			if (styleName.startsWith("intermediate")) {
				Element foreground = document.createElement("ellipse");
				foreground.setAttribute("fill", "none");
				foreground.setAttribute("stroke", strokeColor);
				foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));

				foreground.setAttribute("cx", String.valueOf(x + w / 2));
				foreground.setAttribute("cy", String.valueOf(y + h / 2));
				foreground.setAttribute("rx", String.valueOf(w / 2 - inset));
				foreground.setAttribute("ry", String.valueOf(h / 2 - inset));
				elem.appendChild(foreground);
			}

			String image = null;
			if (styleName.equals("startMultipleEvent") || styleName.equals("intermediateMultipleEvent")) {
				image = Constants.SHAPE_MARKER + "event_multiple.png";
			} else if (styleName.equals("intermediateMultipleThrowEvent") || styleName.equals("endMultipleEvent")) {
				image = Constants.SHAPE_MARKER + "event_multiple_throwing.png";
			} else if (styleName.equals("startParallelMultipleEvent") || styleName.equals("intermediateParallelMultipleEvent")) {
				image = Constants.SHAPE_MARKER + "event_parallel_multiple.png";
			} else {
				if (eventDefinitions.size() == 1) {
					String suffix = "";
					if (event instanceof ThrowEvent) {
						suffix = "_throwing";
					}
					String eventDef = event.getEventDefinition().toName();
					image = Constants.SHAPE_MARKER + "event_" + eventDef.substring(0, eventDef.indexOf("EventDefinition")) + suffix + ".png";
				}
			}
			if (image != null) {
				imgWidth = 22 * scale;
				imgHeight = 22 * scale;
				imageBounds.setRect(x + (w - imgWidth) / 2, y + (h - imgHeight) / 2, imgWidth, imgHeight);
				Element imageElement = createImageElement(imageBounds.getX(), imageBounds.getY(), imageBounds.getWidth(), imageBounds.getHeight(), image,
						false, false, false, isEmbedded());

				elem.appendChild(imageElement);
			}
		} else if (shape.equals(Constants.SHAPE_CONVERSATION_NODE)) {
			elem = document.createElement("g");
			background = document.createElement("path");
			String d = "M " + (x + 0.25 * w) + " " + y + " L " + (x + 0.75 * w) + " " + y + " L " + (x + w) + " " + (y + 0.5 * h) + " L " + (x + 0.75 * w)
					+ " " + (y + h) + " L " + (x + 0.25 * w) + " " + (y + h) + " L " + x + " " + (y + 0.5 * h);

			background.setAttribute("d", d + " Z");
			elem.appendChild(background);

			if (img != null) {
				imageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth, imgHeight);
				Element imageElement = createImageElement(imageBounds.getX(), imageBounds.getY(), imageBounds.getWidth(), imageBounds.getHeight(), img, false,
						false, false, isEmbedded());

				elem.appendChild(imageElement);
			}
		} else if (shape.equals(Constants.SHAPE_DATAOBJECT)) {
			elem = document.createElement("g");
			background = document.createElement("path");

			double dy = Math.min(40, Math.floor(w / 3));
			String d = " M " + x + " " + y + " L " + x + " " + (y + h) + " L " + (x + w) + " " + (y + h) + " L " + (x + w) + " " + (y + dy) + " L "
					+ (x + w - dy) + " " + y;
			background.setAttribute("d", d + " Z");
			elem.appendChild(background);

			Element foreground = document.createElement("path");
			d = " M " + (x + w - dy) + " " + y + " L " + (x + w - dy) + " " + (y + dy) + " L " + (x + w) + " " + (y + dy);

			foreground.setAttribute("d", d);
			foreground.setAttribute("fill", "none");
			foreground.setAttribute("stroke", strokeColor);
			foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));

			elem.appendChild(foreground);

			if (img != null) {
				imageBounds.setRect(x + (w - imgWidth) / 4, y, imgWidth, imgHeight);
				Element imageElement = createImageElement(imageBounds.getX(), imageBounds.getY(), imageBounds.getWidth(), imageBounds.getHeight(), img, false,
						false, false, isEmbedded());

				elem.appendChild(imageElement);
			}

		} else if (shape.equals(Constants.SHAPE_DATASTORE)) {
			elem = document.createElement("g");
			background = document.createElement("path");

			double dy = Math.min(40, Math.floor(h / 5));
			String d = " M " + x + " " + (y + dy) + " C " + x + " " + (y - dy / 3) + " " + (x + w) + " " + (y - dy / 3) + " " + (x + w) + " " + (y + dy)
					+ " L " + (x + w) + " " + (y + h - dy) + " C " + (x + w) + " " + (y + h + dy / 3) + " " + x + " " + (y + h + dy / 3) + " " + x + " "
					+ (y + h - dy);
			background.setAttribute("d", d + " Z");
			elem.appendChild(background);

			Element foreground = document.createElement("path");
			d = "M " + x + " " + (y + dy) + " C " + x + " " + (y + 2 * dy) + " " + (x + w) + " " + (y + 2 * dy) + " " + (x + w) + " " + (y + dy) + " " + "M "
					+ x + " " + (y + 1.5 * dy) + " C " + x + " " + (y + 2.5 * dy) + " " + (x + w) + " " + (y + 2.5 * dy) + " " + (x + w) + " " + (y + 1.5 * dy)
					+ " " + "M " + x + " " + (y + 2 * dy) + " C " + x + " " + (y + 3 * dy) + " " + (x + w) + " " + (y + 3 * dy) + " " + (x + w) + " "
					+ (y + 2 * dy);

			foreground.setAttribute("d", d);
			foreground.setAttribute("fill", "none");
			foreground.setAttribute("stroke", strokeColor);
			foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));

			elem.appendChild(foreground);

		} else if (shape.equals(Constants.SHAPE_ANNOTATION)) {
			double a = 20 * scale;
			elem = document.createElement("g");
			Element foreground = document.createElement("path");
			String d = "M " + (x + a) + " " + y + " L " + x + " " + y + " L " + x + " " + (y + h) + " L " + (x + a) + " " + (y + h);
			foreground.setAttribute("fill", "none");
			foreground.setAttribute("stroke", strokeColor);
			foreground.setAttribute("stroke-width", String.valueOf(strokeWidth));
			foreground.setAttribute("d", d);
			elem.appendChild(foreground);
		} else if (shape.equals(mxConstants.SHAPE_IMAGE)) {
			if (img != null) {
				// Vertical and horizontal image flipping
				boolean flipH = mxUtils.isTrue(style, mxConstants.STYLE_IMAGE_FLIPH, false);
				boolean flipV = mxUtils.isTrue(style, mxConstants.STYLE_IMAGE_FLIPV, false);

				elem = createImageElement(x, y, w, h, img, PRESERVE_IMAGE_ASPECT, flipH, flipV, isEmbedded());
			}
		} else {
			background = document.createElement("rect");
			elem = background;

			elem.setAttribute("x", String.valueOf(x));
			elem.setAttribute("y", String.valueOf(y));
			elem.setAttribute("width", String.valueOf(w));
			elem.setAttribute("height", String.valueOf(h));

			if (mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false) || mxUtils.isTrue(style, Constants.STYLE_BORDER, false)) {
				String r = "8.25";
				elem.setAttribute("rx", r);
				elem.setAttribute("ry", r);
			}

			if (shape.equals(Constants.SHAPE_PARTICIPANT_BAND) && mxUtils.isTrue(style, Constants.STYLE_BORDER, false)) {
				elem.setAttribute("height", String.valueOf(h + h / 2));
				String position = mxUtils.getString(style, Constants.STYLE_POSITION, "top");
				if (!position.equals("top")) {
					elem.setAttribute("y", String.valueOf(y - h / 2));
				}
				if (((Participant) value).getMultiplicity() > 1) {
					elem = document.createElement("g");
					elem.appendChild(background);

					Element imageElement = createImageElement(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth, imgHeight, Constants.SHAPE_MARKER
							+ "loop_multiple.png", false, false, false, isEmbedded());
					elem.appendChild(imageElement);
				}
			} else if (shape.equals(Constants.SHAPE_ACTIVITY) || shape.equals(Constants.SHAPE_SUBPROCESS)) {
				elem = document.createElement("g");
				elem.appendChild(background);

				mxRectangle loopImageBounds = new mxRectangle();
				mxRectangle compensationImageBounds = new mxRectangle();
				mxRectangle adHocImageBounds = new mxRectangle();
				String loopMarker = null;
				String compensationMarker = null;
				if (value instanceof Activity) {
					Activity activity = (Activity) value;
					LoopCharacteristics loopType = activity.getLoopCharacteristics();
					if (loopType instanceof StandardLoopCharacteristics) {
						loopMarker = Constants.SHAPE_MARKER + "loop_standard.png";
					} else if (loopType instanceof MultiInstanceLoopCharacteristics) {
						if (((MultiInstanceLoopCharacteristics) loopType).isSequential()) {
							loopMarker = Constants.SHAPE_MARKER + "loop_multiple_sequential.png";
						} else {
							loopMarker = Constants.SHAPE_MARKER + "loop_multiple.png";
						}
					}
					compensationMarker = activity.isForCompensation() ? Constants.SHAPE_MARKER + "compensation.png" : null;
				} else if (value instanceof ChoreographyActivity) {
					ChoreographyActivity activity = (ChoreographyActivity) value;
					if (activity.getLoopType().equals("Standard")) {
						loopMarker = Constants.SHAPE_MARKER + "loop_standard.png";
					} else if (activity.getLoopType().equals("MultiInstanceSequential")) {
						loopMarker = Constants.SHAPE_MARKER + "loop_multiple_sequential.png";
					} else if (activity.getLoopType().equals("MultiInstanceParallel")) {
						loopMarker = Constants.SHAPE_MARKER + "loop_multiple.png";
					}
				}

				if (shape.equals(Constants.SHAPE_ACTIVITY)) {
					if (value instanceof Task) {
						if (!((Task) value).toName().equals("task")) {
							img = Constants.SHAPE_MARKER + "task_" + ((Task) value).toName().substring(0, ((Task) value).toName().indexOf("Task")) + ".png";
							if (((Task) value).toName().equals("receiveTask") && ((ReceiveTask) value).isInstantiate()) {
								Element instantiate = document.createElement("ellipse");
								instantiate.setAttribute("fill", "none");
								instantiate.setAttribute("stroke", strokeColor);
								instantiate.setAttribute("stroke-width", String.valueOf(strokeWidth));

								instantiate.setAttribute("cx", String.valueOf(x + 13));
								instantiate.setAttribute("cy", String.valueOf(y + 11));
								instantiate.setAttribute("rx", String.valueOf(10));
								instantiate.setAttribute("ry", String.valueOf(10));
								elem.appendChild(instantiate);
							}
						}
					}

					if (img != null) {
						imageBounds.setRect(x + spacing, y + spacing - 2, imgWidth, imgHeight);
					}
					if (loopMarker != null || compensationMarker != null) {
						if (loopMarker != null && compensationMarker != null) {
							loopImageBounds.setRect(x + (w / 2 - imgWidth), y + h - imgHeight, imgWidth, imgHeight);
							compensationImageBounds.setRect(x + w / 2, y + h - imgHeight, imgWidth, imgHeight);
						} else {
							loopImageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth, imgHeight);
							compensationImageBounds = loopImageBounds;
						}
					}
				} else if (shape.equals(Constants.SHAPE_SUBPROCESS)) {
					imgWidth = 32 * scale;

					if (value instanceof Transaction) {
						Element innerRect = document.createElement("rect");
						innerRect.setAttribute("x", String.valueOf(x + inset));
						innerRect.setAttribute("y", String.valueOf(y + inset));
						innerRect.setAttribute("width", String.valueOf(w - 2 * inset));
						innerRect.setAttribute("height", String.valueOf(h - 2 * inset));
						innerRect.setAttribute("rx", "8");
						innerRect.setAttribute("ry", "8");
						innerRect.setAttribute("fill", "none");
						innerRect.setAttribute("stroke", strokeColor);
						innerRect.setAttribute("stroke-width", String.valueOf(strokeWidth));
						elem.appendChild(innerRect);
					}

					if (!mxUtils.isTrue(style, Constants.STYLE_CALL, false)
							&& ((w > Constants.FOLDED_SUBPROCESS_WIDTH * scale + 1) || (h > Constants.FOLDED_SUBPROCESS_HEIGHT * scale + 1))) { // expanded
						if (value instanceof AdHocSubProcess) {
							if (loopMarker != null || compensationMarker != null) {
								if (loopMarker != null && compensationMarker != null) {
									loopImageBounds.setRect(x + (2 * w - 3 * imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds.setRect(loopImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									adHocImageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								} else {
									loopImageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds = loopImageBounds;
									adHocImageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								}
							} else {
								adHocImageBounds.setRect(x + (2 * w - imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
							}
						} else {
							if (loopMarker != null || compensationMarker != null) {
								if (loopMarker != null && compensationMarker != null) {
									loopImageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds.setRect(loopImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								} else {
									loopImageBounds.setRect(x + (2 * w - imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds = loopImageBounds;
								}
							}
						}
					} else { // Call Activity (Process) or Collapsed Sub-Process
						img = Constants.SHAPE_MARKER + "subprocess.png";
						if (value instanceof AdHocSubProcess) {
							if (loopMarker != null || compensationMarker != null) {
								if (loopMarker != null && compensationMarker != null) {
									loopImageBounds.setRect(x + (w / 2 - imgWidth), y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds.setRect(loopImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									imageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									adHocImageBounds.setRect(imageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								} else {
									loopImageBounds.setRect(x + (2 * w - 3 * imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds = loopImageBounds;
									imageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									adHocImageBounds.setRect(imageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								}
							} else {
								imageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								adHocImageBounds.setRect(imageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
							}
						} else {
							if (loopMarker != null || compensationMarker != null) {
								if (loopMarker != null && compensationMarker != null) {
									loopImageBounds.setRect(x + (2 * w - 3 * imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds.setRect(loopImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									imageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								} else {
									loopImageBounds.setRect(x + (w - imgWidth) / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
									compensationImageBounds = loopImageBounds;
									imageBounds.setRect(compensationImageBounds.getX() + imgWidth / 2, y + h - imgHeight, imgWidth / 2, imgHeight);
								}
							} else {
								imageBounds.setRect(x + (2 * w - imgWidth) / 4, y + h - imgHeight, imgWidth / 2, imgHeight);
							}
						}
					}
					if (value instanceof AdHocSubProcess) {
						Element adHocImageElement = createImageElement(adHocImageBounds.getX(), adHocImageBounds.getY(), adHocImageBounds.getWidth(),
								adHocImageBounds.getHeight(), Constants.SHAPE_MARKER + "adhoc_subprocess.png", false, false, false, isEmbedded());
						elem.appendChild(adHocImageElement);
					}
					if (value instanceof SubProcess && ((SubProcess) value).isTriggeredByEvent()) {
						StartEvent startEvent = BPMNModelUtils.getStartEvent((FlowElementsContainer) value);
						if (startEvent != null) {
							styleName = model.getStyle(model.getCell(startEvent.getId()));
							String image = null;
							if (styleName.equals("startMultipleEvent")) {
								image = Constants.SHAPE_MARKER + "event_multiple.png";
							} else if (styleName.equals("startParallelMultipleEvent")) {
								image = Constants.SHAPE_MARKER + "event_parallel_multiple.png";
							} else {
								EventDefinitions eventDefinitions = startEvent.getEventDefinitions();
								if (eventDefinitions.size() == 1) {
									String eventDef = startEvent.getEventDefinition().toName();
									image = Constants.SHAPE_MARKER + "event_" + eventDef.substring(0, eventDef.indexOf("EventDefinition")) + ".png";
								}
							}
							if (image != null) {
								Element trigger = document.createElement("ellipse");
								trigger.setAttribute("fill", "none");
								trigger.setAttribute("stroke", strokeColor);
								trigger.setAttribute("stroke-width", String.valueOf(strokeWidth));
								trigger.setAttribute("cx", String.valueOf(x + 13));
								trigger.setAttribute("cy", String.valueOf(y + 11));
								trigger.setAttribute("rx", String.valueOf(10));
								trigger.setAttribute("ry", String.valueOf(10));
								if (startEvent.isInterrupting()) {
									trigger.setAttribute("stroke-dasharray", "none");
								}
								elem.appendChild(trigger);
								Element imageElement = createImageElement(x + 3.85, y + 2, 18, 18, image.toLowerCase(), false, false, false, isEmbedded());
								elem.appendChild(imageElement);
							}
						}
					}
				}

				if (img != null) {
					Element imageElement = createImageElement(imageBounds.getX(), imageBounds.getY(), imageBounds.getWidth(), imageBounds.getHeight(),
							img.toLowerCase(), false, false, false, isEmbedded());
					elem.appendChild(imageElement);
				}
				if (loopMarker != null) {
					Element loopImageElement = createImageElement(loopImageBounds.getX(), loopImageBounds.getY(), loopImageBounds.getWidth(),
							loopImageBounds.getHeight(), loopMarker, false, false, false, isEmbedded());
					elem.appendChild(loopImageElement);
				}
				if (compensationMarker != null) {
					Element compensationImageElement = createImageElement(compensationImageBounds.getX(), compensationImageBounds.getY(),
							compensationImageBounds.getWidth(), compensationImageBounds.getHeight(), compensationMarker, false, false, false, isEmbedded());
					elem.appendChild(compensationImageElement);
				}

			} else if (shape.equals(mxConstants.SHAPE_LABEL)) {
				// Paints the glass effect
				if (mxUtils.isTrue(style, mxConstants.STYLE_GLASS, false)) {
					double size = 0.4;

					// TODO: Mask with rectangle or rounded rectangle of label
					// Creates glass overlay
					Element glassOverlay = document.createElement("path");

					// LATER: Not sure what the behaviour is for mutiple SVG elements in page.
					// Probably its possible that this points to an element in another SVG
					// node which when removed will result in an undefined background.
					glassOverlay.setAttribute("fill", "url(#" + getGlassGradientElement().getAttribute("id") + ")");

					String d = "m " + (x - strokeWidth) + "," + (y - strokeWidth) + " L " + (x - strokeWidth) + "," + (y + h * size) + " Q " + (x + w * 0.5)
							+ "," + (y + h * 0.7) + " " + (x + w + strokeWidth) + "," + (y + h * size) + " L " + (x + w + strokeWidth) + ","
							+ (y - strokeWidth) + " z";
					glassOverlay.setAttribute("stroke-width", String.valueOf(strokeWidth / 2));
					glassOverlay.setAttribute("d", d);
					elem.appendChild(glassOverlay);
				}
			}
		}

		double rotation = mxUtils.getDouble(style, mxConstants.STYLE_ROTATION);
		int cx = x + w / 2;
		int cy = y + h / 2;

		Element bg = background;

		if (bg == null) {
			bg = elem;
		}

		if (!bg.getNodeName().equalsIgnoreCase("use") && !bg.getNodeName().equalsIgnoreCase("image")) {
			if (!fillColor.equalsIgnoreCase("none") && !gradientColor.equalsIgnoreCase("none")) {
				String direction = mxUtils.getString(style, mxConstants.STYLE_GRADIENT_DIRECTION);
				Element gradient = getGradientElement(fillColor, gradientColor, direction);

				if (gradient != null) {
					bg.setAttribute("fill", "url(#" + gradient.getAttribute("id") + ")");
				}
			} else {
				bg.setAttribute("fill", fillColor);
			}

			bg.setAttribute("stroke", strokeColor);
			bg.setAttribute("stroke-width", String.valueOf(strokeWidth));

			// Adds the shadow element
			Element shadowElement = null;

			if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false) && !shape.equals(Constants.SHAPE_ANNOTATION)
					&& !shape.equals(Constants.SHAPE_PARTICIPANT_BAND) && !fillColor.equals("none")) {
				shadowElement = (Element) bg.cloneNode(true);

				shadowElement.setAttribute("transform", mxConstants.SVG_SHADOWTRANSFORM);
				shadowElement.setAttribute("fill", mxConstants.W3C_SHADOWCOLOR);
				shadowElement.setAttribute("stroke", mxConstants.W3C_SHADOWCOLOR);
				shadowElement.setAttribute("stroke-width", String.valueOf(strokeWidth));

				if (rotation != 0) {
					shadowElement.setAttribute("transform", "rotate(" + rotation + "," + cx + "," + cy + ") " + mxConstants.SVG_SHADOWTRANSFORM);
				}

				if (opacity != 100) {
					String val = String.valueOf(opacity / 100);
					shadowElement.setAttribute("fill-opacity", val);
					shadowElement.setAttribute("stroke-opacity", val);
				}

				appendSvgElement(shadowElement);
			}
		}

		if (rotation != 0) {
			elem.setAttribute("transform", elem.getAttribute("transform") + " rotate(" + rotation + "," + cx + "," + cy + ")");

		}

		if (opacity != 100) {
			String val = String.valueOf(opacity / 100);
			elem.setAttribute("fill-opacity", val);
			elem.setAttribute("stroke-opacity", val);
		}

		if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED) || value instanceof SubProcess && ((SubProcess) value).isTriggeredByEvent()) {
			String pattern = mxUtils.getString(style, mxConstants.STYLE_DASH_PATTERN, "3, 3");
			elem.setAttribute("stroke-dasharray", pattern);
		}

		appendSvgElement(elem);

		return elem;
	}

	/**
	 * Draws the given lines as segments between all points of the given list of mxPoints.
	 * 
	 * @param pts
	 *            List of points that define the line.
	 * @param style
	 *            Style to be used for painting the line.
	 */
	public Element drawLine(List pts, Map style) {
		Element group = document.createElement("g");
		Element path = document.createElement("path");

		boolean rounded = mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false);
		String strokeColor = mxUtils.getString(style, mxConstants.STYLE_STROKECOLOR);
		if (strokeColor.startsWith("ff")) {
			strokeColor = "#" + strokeColor.substring(2);
		}
		float tmpStroke = (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1));
		float strokeWidth = (float) (tmpStroke * scale);

		if (strokeColor != null && strokeWidth > 0) {
			// Draws the start marker
			Object marker = style.get(mxConstants.STYLE_STARTARROW);

			mxPoint pt = pts.get(1);
			mxPoint p0 = pts.get(0);
			mxPoint offset = null;

			if (marker != null) {
				float size = (mxUtils.getFloat(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE));
				offset = drawMarker(group, marker, pt, p0, size, tmpStroke, strokeColor);
			} else {
				double dx = pt.getX() - p0.getX();
				double dy = pt.getY() - p0.getY();

				double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
				double nx = dx * strokeWidth / dist;
				double ny = dy * strokeWidth / dist;

				offset = new mxPoint(nx / 2, ny / 2);
			}

			// Applies offset to the point
			if (offset != null) {
				p0 = (mxPoint) p0.clone();
				p0.setX(p0.getX() + offset.getX());
				p0.setY(p0.getY() + offset.getY());

				offset = null;
			}

			// Draws the end marker
			marker = style.get(mxConstants.STYLE_ENDARROW);

			pt = pts.get(pts.size() - 2);
			mxPoint pe = pts.get(pts.size() - 1);

			if (marker != null) {
				float size = (mxUtils.getFloat(style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE));
				offset = drawMarker(group, marker, pt, pe, size, tmpStroke, strokeColor);
			} else {
				double dx = pt.getX() - p0.getX();
				double dy = pt.getY() - p0.getY();

				double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
				double nx = dx * strokeWidth / dist;
				double ny = dy * strokeWidth / dist;

				offset = new mxPoint(nx / 2, ny / 2);
			}

			// Applies offset to the point
			if (offset != null) {
				pe = (mxPoint) pe.clone();
				pe.setX(pe.getX() + offset.getX());
				pe.setY(pe.getY() + offset.getY());

				offset = null;
			}

			// Draws the line segments
			double arcSize = mxConstants.LINE_ARCSIZE * scale;
			pt = p0;
			String d = "M " + pt.getX() + " " + pt.getY();

			for (int i = 1; i < pts.size() - 1; i++) {
				mxPoint tmp = pts.get(i);
				double dx = pt.getX() - tmp.getX();
				double dy = pt.getY() - tmp.getY();

				if ((rounded && i < pts.size() - 1) && (dx != 0 || dy != 0)) {
					// Draws a line from the last point to the current
					// point with a spacing of size off the current point
					// into direction of the last point
					double dist = Math.sqrt(dx * dx + dy * dy);
					double nx1 = dx * Math.min(arcSize, dist / 2) / dist;
					double ny1 = dy * Math.min(arcSize, dist / 2) / dist;

					double x1 = tmp.getX() + nx1;
					double y1 = tmp.getY() + ny1;
					d += " L " + x1 + " " + y1;

					// Draws a curve from the last point to the current
					// point with a spacing of size off the current point
					// into direction of the next point
					mxPoint next = pts.get(i + 1);
					dx = next.getX() - tmp.getX();
					dy = next.getY() - tmp.getY();

					dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
					double nx2 = dx * Math.min(arcSize, dist / 2) / dist;
					double ny2 = dy * Math.min(arcSize, dist / 2) / dist;

					double x2 = tmp.getX() + nx2;
					double y2 = tmp.getY() + ny2;

					d += " Q " + tmp.getX() + " " + tmp.getY() + " " + x2 + " " + y2;
					tmp = new mxPoint(x2, y2);
				} else {
					d += " L " + tmp.getX() + " " + tmp.getY();
				}

				pt = tmp;
			}

			d += " L " + pe.getX() + " " + pe.getY();

			path.setAttribute("d", d);
			path.setAttribute("stroke", strokeColor);
			path.setAttribute("fill", "none");
			path.setAttribute("stroke-width", String.valueOf(strokeWidth));

			if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED)) {
				String pattern = mxUtils.getString(style, mxConstants.STYLE_DASH_PATTERN, "3, 3");
				path.setAttribute("stroke-dasharray", pattern);
			}

			group.appendChild(path);
			appendSvgElement(group);
		}

		return group;
	}

	/**
	 * Draws the specified marker as a child path in the given parent.
	 */
	public mxPoint drawMarker(Element parent, Object type, mxPoint p0, mxPoint pe, float size, float strokeWidth, String color) {
		mxPoint offset = null;

		// Computes the norm and the inverse norm
		double dx = pe.getX() - p0.getX();
		double dy = pe.getY() - p0.getY();

		double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
		double absSize = size * scale;
		double nx = dx * absSize / dist;
		double ny = dy * absSize / dist;

		pe = (mxPoint) pe.clone();
		pe.setX(pe.getX() - nx * strokeWidth / (2 * size));
		pe.setY(pe.getY() - ny * strokeWidth / (2 * size));

		nx *= 0.5 + strokeWidth / 2;
		ny *= 0.5 + strokeWidth / 2;

		Element path = document.createElement("path");
		path.setAttribute("stroke-width", String.valueOf(strokeWidth * scale));
		path.setAttribute("stroke", color);
		path.setAttribute("fill", "none");

		String d = null;

		if (type.equals(mxConstants.ARROW_CLASSIC)) {
			d = "M " + pe.getX() + " " + pe.getY() + " L " + (pe.getX() - nx - ny / 2) + " " + (pe.getY() - ny + nx / 2) + " L " + (pe.getX() - nx * 3 / 4)
					+ " " + (pe.getY() - ny * 3 / 4) + " L " + (pe.getX() + ny / 2 - nx) + " " + (pe.getY() - ny - nx / 2) + " z";
			path.setAttribute("fill", color);
		} else if (type.equals(Constants.ARROW_STYLE_OPEN_BLOCK)) {
			d = "M " + pe.getX() + " " + pe.getY() + " L " + (pe.getX() - nx - ny / 2) + " " + (pe.getY() - ny + nx / 2) + " L " + (pe.getX() + ny / 2 - nx)
					+ " " + (pe.getY() - ny - nx / 2) + " z";
			offset = new mxPoint(-nx, -ny);
		} else if (type.equals(mxConstants.ARROW_OPEN)) {
			nx *= 1.2;
			ny *= 1.2;

			d = "M " + (pe.getX() - nx - ny / 2) + " " + (pe.getY() - ny + nx / 2) + " L " + (pe.getX() - nx / 6) + " " + (pe.getY() - ny / 6) + " L "
					+ (pe.getX() + ny / 2 - nx) + " " + (pe.getY() - ny - nx / 2) + " M " + pe.getX() + " " + pe.getY();
		} else if (type.equals(Constants.ARROW_STYLE_OPEN_OVAL)) {
			nx *= 1.2;
			ny *= 1.2;
			absSize *= 1.2;

			d = "M " + (pe.getX() - ny / 2) + " " + (pe.getY() - ny / 2) + " a " + (absSize / 2) + " " + (absSize / 2) + " 0  1,1 " + (nx / 8) + " " + (ny / 8)
					+ " z";
			offset = new mxPoint(-nx, -ny);
		} else if (type.equals(Constants.ARROW_STYLE_OPEN_DIAMOND)) {
			nx *= 1.8;
			ny *= 1.8;
			int a = (int) (-8 * scale);
			if (nx > 0 || ny > 0) {
				a = (int) (8 * scale);
			}
			if (ny == 0) {
				d = "M " + (pe.getX() + nx / 2 - a) + " " + pe.getY() + " L " + (pe.getX() - a) + " " + (pe.getY() + nx / 3) + " L " + (pe.getX() - nx / 2 - a)
						+ " " + pe.getY() + " L " + (pe.getX() + ny / 2 - a) + " " + (pe.getY() - nx / 3) + " z";
			} else {
				d = "M " + (pe.getX() + ny / 3) + " " + (pe.getY() - a) + " L " + pe.getX() + " " + (pe.getY() + ny / 2 - a) + " L " + (pe.getX() - ny / 3)
						+ " " + (pe.getY() - a) + " L " + pe.getX() + " " + (pe.getY() - ny / 2 - a) + " z";
			}
			offset = new mxPoint(-nx, -ny);
		} else if (type.equals(Constants.ARROW_STYLE_SLASH)) {
			nx *= 1.8;
			ny *= 1.8;
			int a = (int) (-8 * scale);
			if (nx > 0 || ny > 0) {
				a = (int) (8 * scale);
			}
			if (ny == 0) {
				d = "M " + (pe.getX() - a) + " " + (pe.getY() + nx / 3) + " L " + (pe.getX() - 2 * a) + " " + (pe.getY() - nx / 3) + " L " + (pe.getX() - a)
						+ " " + (pe.getY() + nx / 3) + " L " + (pe.getX() - 2 * a) + " " + (pe.getY() - nx / 3) + " z";
			} else {
				d = "M " + (pe.getX() + ny / 3) + " " + (pe.getY() - a) + " L " + (pe.getX() - ny / 3) + " " + (pe.getY() - 2 * a) + " L "
						+ (pe.getX() + ny / 3) + " " + (pe.getY() - a) + " L " + (pe.getX() - ny / 3) + " " + (pe.getY() - 2 * a) + " z";
			}
		}

		if (d != null) {
			path.setAttribute("d", d);
			parent.appendChild(path);
		}

		return offset;
	}

	/**
	 * Draws the specified text either using drawHtmlString or using drawString.
	 * 
	 * @param text
	 *            Text to be painted.
	 * @param x
	 *            X-coordinate of the text.
	 * @param y
	 *            Y-coordinate of the text.
	 * @param w
	 *            Width of the text.
	 * @param h
	 *            Height of the text.
	 * @param style
	 *            Style to be used for painting the text.
	 */
	public Object drawText(String text, int x, int y, int w, int h, Map style) {
		Element elem = null;
		String fontColor = mxUtils.getString(style, mxConstants.STYLE_FONTCOLOR, "black");
		if (fontColor.startsWith("ff")) {
			fontColor = "#" + fontColor.substring(2);
		}
		String fontFamily = mxUtils.getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES);
		int fontSize = (int) (mxUtils.getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale);

		if (text != null && text.length() > 0) {
			float strokeWidth = (float) (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * scale);

			// Applies the opacity
			float opacity = mxUtils.getFloat(style, mxConstants.STYLE_TEXT_OPACITY, 100);

			// Draws the label background and border
			String bg = mxUtils.getString(style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
			String border = mxUtils.getString(style, mxConstants.STYLE_LABEL_BORDERCOLOR);

			String transform = null;

			if (!mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true)) {
				double cx = x + w / 2;
				double cy = y + h / 2;
				transform = "rotate(270 " + cx + " " + cy + ")";
			}

			if (bg != null || border != null) {
				Element background = document.createElement("rect");

				background.setAttribute("x", String.valueOf(x));
				background.setAttribute("y", String.valueOf(y));
				background.setAttribute("width", String.valueOf(w));
				background.setAttribute("height", String.valueOf(h));

				if (bg != null) {
					if (bg.startsWith("ff")) {
						bg = "#" + bg.substring(2);
					}
					background.setAttribute("fill", bg);
				} else {
					background.setAttribute("fill", "none");
				}

				if (border != null) {
					if (border.startsWith("ff")) {
						border = "#" + border.substring(2);
					}
					background.setAttribute("stroke", border);
				} else {
					background.setAttribute("stroke", "none");
				}

				background.setAttribute("stroke-width", String.valueOf(strokeWidth));

				if (opacity != 100) {
					String value = String.valueOf(opacity / 100);
					background.setAttribute("fill-opacity", value);
					background.setAttribute("stroke-opacity", value);
				}

				if (transform != null) {
					background.setAttribute("transform", transform);
				}

				appendSvgElement(background);
			}
			// ==============start==============
			String align = mxUtils.getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
			String anchor = "start";

			if (align.equals(mxConstants.ALIGN_RIGHT)) {
				anchor = "end";
				x += w - mxConstants.LABEL_INSET * scale;
			} else if (align.equals(mxConstants.ALIGN_CENTER)) {
				anchor = "middle";
				x += w / 2;
			} else {
				x += mxConstants.LABEL_INSET * scale;
			}

			int fontStyle = mxUtils.getInt(style, mxConstants.STYLE_FONTSTYLE);
			String weight = ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? "bold" : "normal";

			String uline = ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) ? "underline" : "none";

			String[] lines = text.split("\n");
			y -= mxConstants.LABEL_INSET * scale / 2;
			y += fontSize + (h - lines.length * (fontSize + mxConstants.LINESPACING)) / 2 - 2;

			for (int i = 0; i < lines.length; i++) {
				elem = document.createElement("text");

				elem.setAttribute("x", String.valueOf(x));
				elem.setAttribute("y", String.valueOf(y));

				elem.setAttribute("font-weight", weight);
				elem.setAttribute("font-decoration", uline);

				if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
					elem.setAttribute("font-style", "italic");
				}

				elem.setAttribute("font-size", String.valueOf(fontSize));
				elem.setAttribute("font-family", fontFamily);
				elem.setAttribute("fill", fontColor);

				if (opacity != 100) {
					String value = String.valueOf(opacity / 100);
					elem.setAttribute("fill-opacity", value);
					elem.setAttribute("stroke-opacity", value);
				}

				elem.setAttribute("text-anchor", anchor);

				if (transform != null) {
					elem.setAttribute("transform", transform);
				}

				elem.appendChild(document.createTextNode(lines[i]));

				appendSvgElement(elem);

				y += fontSize + mxConstants.LINESPACING;
			}
			// ==============end================
		}

		return elem;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy