com.mxgraph.shape.mxStencil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jgraphx Show documentation
Show all versions of jgraphx Show documentation
JGraphX Swing Component - Java Graph Visualization Library
This is a binary & source redistribution of the original, unmodified JGraphX library originating from:
"https://github.com/jgraph/jgraphx/archive/v3.4.1.3.zip".
The purpose of this redistribution is to make the library available to other Maven projects.
/**
* $Id: mxStencil.java,v 1.8 2012/04/24 13:56:56 gaudenz Exp $
* Copyright (c) 2010-2012, JGraph Ltd
*/
package com.mxgraph.shape;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.canvas.mxGraphicsCanvas2D;
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;
/**
* Implements a stencil for the given XML definition. This class implements the mxGraph
* stencil schema.
*/
public class mxStencil implements mxIShape
{
/**
* Holds the top-level node of the stencil definition.
*/
protected Element desc;
/**
* Holds the aspect of the shape. Default is "auto".
*/
protected String aspect = null;
/**
* Holds the width of the shape. Default is 100.
*/
protected double w0 = 100;
/**
* Holds the height of the shape. Default is 100.
*/
protected double h0 = 100;
/**
* Holds the XML node with the stencil description.
*/
protected Element bgNode = null;
/**
* Holds the XML node with the stencil description.
*/
protected Element fgNode = null;
/**
* Holds the strokewidth direction from the description.
*/
protected String strokewidth = null;
/**
* Holds the last x-position of the cursor.
*/
protected double lastMoveX = 0;
/**
* Holds the last y-position of the cursor.
*/
protected double lastMoveY = 0;
/**
* Constructs a new stencil for the given mxGraph shape description.
*/
public mxStencil(Element description)
{
setDescription(description);
}
/**
* Returns the description.
*/
public Element getDescription()
{
return desc;
}
/**
* Sets the description.
*/
public void setDescription(Element value)
{
desc = value;
parseDescription();
}
/**
* Creates the canvas for rendering the stencil.
*/
protected mxGraphicsCanvas2D createCanvas(mxGraphics2DCanvas gc)
{
return new mxGraphicsCanvas2D(gc.getGraphics());
}
/**
* Paints the stencil for the given state.
*/
public void paintShape(mxGraphics2DCanvas gc, mxCellState state)
{
Map style = state.getStyle();
mxGraphicsCanvas2D canvas = createCanvas(gc);
double rotation = mxUtils.getDouble(style, mxConstants.STYLE_ROTATION,
0);
String direction = mxUtils.getString(style,
mxConstants.STYLE_DIRECTION, null);
// Default direction is east (ignored if rotation exists)
if (direction != null)
{
if (direction.equals("north"))
{
rotation += 270;
}
else if (direction.equals("west"))
{
rotation += 180;
}
else if (direction.equals("south"))
{
rotation += 90;
}
}
// New styles for shape flipping the stencil
boolean flipH = mxUtils.isTrue(style, mxConstants.STYLE_STENCIL_FLIPH,
false);
boolean flipV = mxUtils.isTrue(style, mxConstants.STYLE_STENCIL_FLIPV,
false);
if (flipH && flipV)
{
rotation += 180;
flipH = false;
flipV = false;
}
// Saves the global state for each cell
canvas.save();
// Adds rotation and horizontal/vertical flipping
rotation = rotation % 360;
if (rotation != 0 || flipH || flipV)
{
canvas.rotate(rotation, flipH, flipV, state.getCenterX(),
state.getCenterY());
}
// Note: Overwritten in mxStencil.paintShape (can depend on aspect)
double scale = state.getView().getScale();
double sw = mxUtils.getDouble(style, mxConstants.STYLE_STROKEWIDTH, 1)
* scale;
canvas.setStrokeWidth(sw);
double alpha = mxUtils.getDouble(style, mxConstants.STYLE_OPACITY, 100) / 100;
String gradientColor = mxUtils.getString(style,
mxConstants.STYLE_GRADIENTCOLOR, null);
// Converts colors with special keyword none to null
if (gradientColor != null && gradientColor.equals(mxConstants.NONE))
{
gradientColor = null;
}
String fillColor = mxUtils.getString(style,
mxConstants.STYLE_FILLCOLOR, null);
if (fillColor != null && fillColor.equals(mxConstants.NONE))
{
fillColor = null;
}
String strokeColor = mxUtils.getString(style,
mxConstants.STYLE_STROKECOLOR, null);
if (strokeColor != null && strokeColor.equals(mxConstants.NONE))
{
strokeColor = null;
}
// Draws the shadow if the fillColor is not transparent
if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false))
{
drawShadow(canvas, state, rotation, flipH, flipV, state, alpha, fillColor != null);
}
canvas.setAlpha(alpha);
// Sets the dashed state
if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED, false))
{
canvas.setDashed(true);
}
// Draws background and foreground
if (strokeColor != null || fillColor != null)
{
if (strokeColor != null)
{
canvas.setStrokeColor(strokeColor);
}
if (fillColor != null)
{
if (gradientColor != null
&& !gradientColor.equals("transparent"))
{
canvas.setGradient(fillColor, gradientColor, state.getX(),
state.getY(), state.getWidth(), state.getHeight(),
direction);
}
else
{
canvas.setFillColor(fillColor);
}
}
// Draws background and foreground of shape
drawShape(canvas, state, state, true);
drawShape(canvas, state, state, false);
}
}
/**
* Draws the shadow.
*/
protected void drawShadow(mxGraphicsCanvas2D canvas, mxCellState state, double rotation, boolean flipH,
boolean flipV, mxRectangle bounds, double alpha, boolean filled)
{
// Requires background in generic shape for shadow, looks like only one
// fillAndStroke is allowed per current path, try working around that
// Computes rotated shadow offset
double rad = rotation * Math.PI / 180;
double cos = Math.cos(-rad);
double sin = Math.sin(-rad);
mxPoint offset = mxUtils.getRotatedPoint(new mxPoint(mxConstants.SHADOW_OFFSETX, mxConstants.SHADOW_OFFSETY), cos, sin);
if (flipH)
{
offset.setX(offset.getX() * -1);
}
if (flipV)
{
offset.setY(offset.getY() * -1);
}
// TODO: Use save/restore instead of negative offset to restore (requires fix for HTML canvas)
canvas.translate(offset.getX(), offset.getY());
// Returns true if a shadow has been painted (path has been created)
if (drawShape(canvas, state, bounds, true))
{
canvas.setAlpha(mxConstants.STENCIL_SHADOW_OPACITY * alpha);
canvas.shadow(mxConstants.STENCIL_SHADOWCOLOR, filled);
}
canvas.translate(-offset.getX(), -offset.getY());
}
/**
* Draws this stencil inside the given bounds.
*/
public boolean drawShape(mxGraphicsCanvas2D canvas, mxCellState state,
mxRectangle bounds, boolean background)
{
Element elt = (background) ? bgNode : fgNode;
if (elt != null)
{
String direction = mxUtils.getString(state.getStyle(),
mxConstants.STYLE_DIRECTION, null);
mxRectangle aspect = computeAspect(state, bounds, direction);
double minScale = Math.min(aspect.getWidth(), aspect.getHeight());
double sw = strokewidth.equals("inherit") ? mxUtils.getDouble(
state.getStyle(), mxConstants.STYLE_STROKEWIDTH, 1)
* state.getView().getScale() : Double
.parseDouble(strokewidth) * minScale;
lastMoveX = 0;
lastMoveY = 0;
canvas.setStrokeWidth(sw);
Node tmp = elt.getFirstChild();
while (tmp != null)
{
if (tmp.getNodeType() == Node.ELEMENT_NODE)
{
drawElement(canvas, state, (Element) tmp, aspect);
}
tmp = tmp.getNextSibling();
}
return true;
}
return false;
}
/**
* Returns a rectangle that contains the offset in x and y and the horizontal
* and vertical scale in width and height used to draw this shape inside the
* given rectangle.
*/
protected mxRectangle computeAspect(mxCellState state, mxRectangle bounds,
String direction)
{
double x0 = bounds.getX();
double y0 = bounds.getY();
double sx = bounds.getWidth() / w0;
double sy = bounds.getHeight() / h0;
boolean inverse = (direction != null && (direction.equals("north") || direction
.equals("south")));
if (inverse)
{
sy = bounds.getWidth() / h0;
sx = bounds.getHeight() / w0;
double delta = (bounds.getWidth() - bounds.getHeight()) / 2;
x0 += delta;
y0 -= delta;
}
if (aspect.equals("fixed"))
{
sy = Math.min(sx, sy);
sx = sy;
// Centers the shape inside the available space
if (inverse)
{
x0 += (bounds.getHeight() - this.w0 * sx) / 2;
y0 += (bounds.getWidth() - this.h0 * sy) / 2;
}
else
{
x0 += (bounds.getWidth() - this.w0 * sx) / 2;
y0 += (bounds.getHeight() - this.h0 * sy) / 2;
}
}
return new mxRectangle(x0, y0, sx, sy);
}
/**
* Drawsthe given element.
*/
protected void drawElement(mxGraphicsCanvas2D canvas, mxCellState state,
Element node, mxRectangle aspect)
{
String name = node.getNodeName();
double x0 = aspect.getX();
double y0 = aspect.getY();
double sx = aspect.getWidth();
double sy = aspect.getHeight();
double minScale = Math.min(sx, sy);
// LATER: Move to lookup table
if (name.equals("save"))
{
canvas.save();
}
else if (name.equals("restore"))
{
canvas.restore();
}
else if (name.equals("path"))
{
canvas.begin();
// Renders the elements inside the given path
Node childNode = node.getFirstChild();
while (childNode != null)
{
if (childNode.getNodeType() == Node.ELEMENT_NODE)
{
drawElement(canvas, state, (Element) childNode, aspect);
}
childNode = childNode.getNextSibling();
}
}
else if (name.equals("close"))
{
canvas.close();
}
else if (name.equals("move"))
{
lastMoveX = x0 + getDouble(node, "x") * sx;
lastMoveY = y0 + getDouble(node, "y") * sy;
canvas.moveTo(lastMoveX, lastMoveY);
}
else if (name.equals("line"))
{
lastMoveX = x0 + getDouble(node, "x") * sx;
lastMoveY = y0 + getDouble(node, "y") * sy;
canvas.lineTo(lastMoveX, lastMoveY);
}
else if (name.equals("quad"))
{
lastMoveX = x0 + getDouble(node, "x2") * sx;
lastMoveY = y0 + getDouble(node, "y2") * sy;
canvas.quadTo(x0 + getDouble(node, "x1") * sx,
y0 + getDouble(node, "y1") * sy, lastMoveX, lastMoveY);
}
else if (name.equals("curve"))
{
lastMoveX = x0 + getDouble(node, "x3") * sx;
lastMoveY = y0 + getDouble(node, "y3") * sy;
canvas.curveTo(x0 + getDouble(node, "x1") * sx,
y0 + getDouble(node, "y1") * sy, x0 + getDouble(node, "x2")
* sx, y0 + getDouble(node, "y2") * sy, lastMoveX,
lastMoveY);
}
else if (name.equals("arc"))
{
// Arc from stencil is turned into curves in image output
double r1 = getDouble(node, "rx") * sx;
double r2 = getDouble(node, "ry") * sy;
double angle = getDouble(node, "x-axis-rotation");
double largeArcFlag = getDouble(node, "large-arc-flag");
double sweepFlag = getDouble(node, "sweep-flag");
double x = x0 + getDouble(node, "x") * sx;
double y = y0 + getDouble(node, "y") * sy;
double[] curves = mxUtils.arcToCurves(this.lastMoveX,
this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x,
y);
for (int i = 0; i < curves.length; i += 6)
{
canvas.curveTo(curves[i], curves[i + 1], curves[i + 2],
curves[i + 3], curves[i + 4], curves[i + 5]);
lastMoveX = curves[i + 4];
lastMoveY = curves[i + 5];
}
}
else if (name.equals("rect"))
{
canvas.rect(x0 + getDouble(node, "x") * sx,
y0 + getDouble(node, "y") * sy, getDouble(node, "w") * sx,
getDouble(node, "h") * sy);
}
else if (name.equals("roundrect"))
{
double arcsize = getDouble(node, "arcsize");
if (arcsize == 0)
{
arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
}
double w = getDouble(node, "w") * sx;
double h = getDouble(node, "h") * sy;
double factor = arcsize / 100;
double r = Math.min(w * factor, h * factor);
canvas.roundrect(x0 + getDouble(node, "x") * sx,
y0 + getDouble(node, "y") * sy, getDouble(node, "w") * sx,
getDouble(node, "h") * sy, r, r);
}
else if (name.equals("ellipse"))
{
canvas.ellipse(x0 + getDouble(node, "x") * sx,
y0 + getDouble(node, "y") * sy, getDouble(node, "w") * sx,
getDouble(node, "h") * sy);
}
else if (name.equals("image"))
{
String src = evaluateAttribute(node, "src", state);
canvas.image(x0 + getDouble(node, "x") * sx,
y0 + getDouble(node, "y") * sy, getDouble(node, "w") * sx,
getDouble(node, "h") * sy, src, false,
getString(node, "flipH", "0").equals("1"),
getString(node, "flipV", "0").equals("1"));
}
else if (name.equals("text"))
{
String str = evaluateAttribute(node, "str", state);
canvas.text(x0 + getDouble(node, "x") * sx,
y0 + getDouble(node, "y") * sy, 0, 0, str,
node.getAttribute("align"), node.getAttribute("valign"),
getString(node, "vertical", "0").equals("1"), false, "");
}
else if (name.equals("include-shape"))
{
mxStencil stencil = mxStencilRegistry.getStencil(node
.getAttribute("name"));
if (stencil != null)
{
double x = x0 + getDouble(node, "x") * sx;
double y = y0 + getDouble(node, "y") * sy;
double w = getDouble(node, "w") * sx;
double h = getDouble(node, "h") * sy;
mxRectangle tmp = new mxRectangle(x, y, w, h);
stencil.drawShape(canvas, state, tmp, true);
stencil.drawShape(canvas, state, tmp, false);
}
}
else if (name.equals("fillstroke"))
{
canvas.fillAndStroke();
}
else if (name.equals("fill"))
{
canvas.fill();
}
else if (name.equals("stroke"))
{
canvas.stroke();
}
else if (name.equals("strokewidth"))
{
canvas.setStrokeWidth(getDouble(node, "width") * minScale);
}
else if (name.equals("dashed"))
{
canvas.setDashed(node.getAttribute("dashed") == "1");
}
else if (name.equals("dashpattern"))
{
String value = node.getAttribute("pattern");
if (value != null)
{
String[] tmp = value.split(" ");
StringBuffer pat = new StringBuffer();
for (int i = 0; i < tmp.length; i++)
{
if (tmp[i].length() > 0)
{
pat.append(Double.parseDouble(tmp[i]) * minScale);
pat.append(" ");
}
}
value = pat.toString();
}
canvas.setDashPattern(value);
}
else if (name.equals("strokecolor"))
{
canvas.setStrokeColor(node.getAttribute("color"));
}
else if (name.equals("linecap"))
{
canvas.setLineCap(node.getAttribute("cap"));
}
else if (name.equals("linejoin"))
{
canvas.setLineJoin(node.getAttribute("join"));
}
else if (name.equals("miterlimit"))
{
canvas.setMiterLimit(getDouble(node, "limit"));
}
else if (name.equals("fillcolor"))
{
canvas.setFillColor(node.getAttribute("color"));
}
else if (name.equals("fontcolor"))
{
canvas.setFontColor(node.getAttribute("color"));
}
else if (name.equals("fontstyle"))
{
canvas.setFontStyle(getInt(node, "style", 0));
}
else if (name.equals("fontfamily"))
{
canvas.setFontFamily(node.getAttribute("family"));
}
else if (name.equals("fontsize"))
{
canvas.setFontSize(getDouble(node, "size") * minScale);
}
}
/**
* Returns the given attribute or the default value.
*/
protected int getInt(Element elt, String attribute, int defaultValue)
{
String value = elt.getAttribute(attribute);
if (value != null && value.length() > 0)
{
try
{
defaultValue = (int) Math.floor(Float.parseFloat(value));
}
catch (NumberFormatException e)
{
// ignore
}
}
return defaultValue;
}
/**
* Returns the given attribute or 0.
*/
protected double getDouble(Element elt, String attribute)
{
return getDouble(elt, attribute, 0);
}
/**
* Returns the given attribute or the default value.
*/
protected double getDouble(Element elt, String attribute,
double defaultValue)
{
String value = elt.getAttribute(attribute);
if (value != null && value.length() > 0)
{
try
{
defaultValue = Double.parseDouble(value);
}
catch (NumberFormatException e)
{
// ignore
}
}
return defaultValue;
}
/**
* Returns the given attribute or the default value.
*/
protected String getString(Element elt, String attribute,
String defaultValue)
{
String value = elt.getAttribute(attribute);
if (value != null && value.length() > 0)
{
defaultValue = value;
}
return defaultValue;
}
/**
* Parses the description of this shape.
*/
protected void parseDescription()
{
// LATER: Preprocess nodes for faster painting
fgNode = (Element) desc.getElementsByTagName("foreground").item(0);
bgNode = (Element) desc.getElementsByTagName("background").item(0);
w0 = getDouble(desc, "w", w0);
h0 = getDouble(desc, "h", h0);
// Possible values for aspect are: variable and fixed where
// variable means fill the available space and fixed means
// use w0 and h0 to compute the aspect.
aspect = getString(desc, "aspect", "variable");
// Possible values for strokewidth are all numbers and "inherit"
// where the inherit means take the value from the style (ie. the
// user-defined stroke-width). Note that the strokewidth is scaled
// by the minimum scaling that is used to draw the shape (sx, sy).
strokewidth = getString(desc, "strokewidth", "1");
}
/**
* Gets the attribute for the given name from the given node. If the attribute
* does not exist then the text content of the node is evaluated and if it is
* a function it is invoked with as the only argument and the return
* value is used as the attribute value to be returned.
*/
public String evaluateAttribute(Element elt, String attribute,
mxCellState state)
{
String result = elt.getAttribute(attribute);
if (result == null)
{
// JS functions as text content are currently not supported in Java
}
return result;
}
}