Collapsible/Expandable UML Class Node Representation
Tips & TricksSummary
Sample NodeRealizer implementation to represent UML class nodes.
Description
Representations for UML class nodes can become quite huge when a class has many attributes and/or methods. To counteract, a UML class node should be collapsible and expandable to reduce its size when needed.
The following sample code presents a NodeRealizer implementation that provides such a collapse/expand feature. It includes an inner class that defines a customized ViewMode implementation to toggle the state of a UML class node.
package demo.view;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.swing.JFrame;
import y.base.NodeCursor;
import y.geom.YPoint;
import y.util.D;
import y.util.YVersion;
import y.view.EditMode;
import y.view.Graph2D;
import y.view.Graph2DView;
import y.view.NodeLabel;
import y.view.NodeRealizer;
import y.view.ShapeNodeRealizer;
import y.view.ViewMode;
import y.view.YLabel;
import y.view.hierarchy.GroupNodeRealizer;
/**
* NodeRealizer implementation that represents a UML class node.
* This node realizer displays the following properties of a class
* in UML notation:
* <ul>
* <li>class name
* <li>stereotype property
* <li>constraint property
* <li>attribute list
* <li>method list
* <ul>
*
* Executing this class will display a sample instance of this realizer.
*/
public class UMLClassNodeRealizer extends ShapeNodeRealizer
{
private NodeLabel aLabel; // attributeLabel
private NodeLabel mLabel; // methodLabel
private NodeLabel sLabel; // stateLabel
private boolean clipContent;
private boolean omitDetails;
private String constraint = "";
private String stereotype = "";
private static NodeLabel constraintLabel = new NodeLabel();
private static NodeLabel stereotypeLabel = new NodeLabel();
/**
* Instantiates a new UMLClassNodeRealizer.
*/
public UMLClassNodeRealizer()
{
init();
}
void init()
{
setShapeType(RECT_3D);
getLabel().setModel(NodeLabel.INTERNAL);
getLabel().setPosition(NodeLabel.TOP);
getLabel().setFontSize(13);
getLabel().setFontStyle(Font.BOLD);
aLabel = new NodeLabel();
aLabel.bindRealizer(this);
aLabel.setAlignment(YLabel.ALIGN_LEFT);
aLabel.setModel(NodeLabel.FREE);
mLabel = new NodeLabel();
mLabel.bindRealizer(this);
mLabel.setAlignment(YLabel.ALIGN_LEFT);
mLabel.setModel(NodeLabel.FREE);
clipContent = true;
omitDetails = false;
sLabel = new NodeLabel();
sLabel.bindRealizer(this);
sLabel.setPosition(NodeLabel.TOP_RIGHT);
sLabel.setIcon(GroupNodeRealizer.defaultOpenGroupIcon);
}
/**
* Instantiates a new UMLClassNodeRealzier as a copy of a given
* realizer.
*/
public UMLClassNodeRealizer(NodeRealizer r)
{
super(r);
if (r instanceof UMLClassNodeRealizer)
{
UMLClassNodeRealizer cnr = (UMLClassNodeRealizer)r;
aLabel = (NodeLabel)cnr.aLabel.clone();
aLabel.bindRealizer(this);
mLabel = (NodeLabel)cnr.mLabel.clone();
mLabel.bindRealizer(this);
constraint = cnr.constraint;
stereotype = cnr.stereotype;
clipContent = cnr.clipContent;
omitDetails = cnr.omitDetails;
sLabel = (NodeLabel)cnr.sLabel.clone();
sLabel.bindRealizer(this);
}
else
init();
}
/**
* Returns a UMLClassNodeRealizer that is a copy of the given
* realizer.
*/
public NodeRealizer createCopy(NodeRealizer r)
{
return new UMLClassNodeRealizer(r);
}
//////////////////////////////////////////////////////////////////////////////
// SETTER & GETTER ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/**
* Set the class name to be displayed by this realizer.
*/
public void setClassName(String name)
{
setLabelText(name);
}
/**
* Returns the class name to be displayed by this realizer.
*/
public String getClassName()
{
return getLabelText();
}
/**
* Sets the constraint property of this realizer.
*/
public void setConstraint(String constraint)
{
this.constraint = constraint;
}
/**
* Sets the stereotype property of this realizer.
*/
public void setStereotype(String stereotype)
{
this.stereotype = stereotype;
}
/**
* Returns the constraint property of this realizer.
*/
public String getConstraint()
{
return constraint;
}
/**
* Returns the stereotype property of this realizer.
*/
public String getStereotype()
{
return stereotype;
}
/**
* Returns the node label that represents all added
* method strings.
*/
public NodeLabel getMethodLabel()
{
return mLabel;
}
/**
* Returns the node label that represents all added
* attribute strings.
*/
public NodeLabel getAttributeLabel()
{
return aLabel;
}
/**
* Returns the node label that represents the omitDetails
* state.
*/
public NodeLabel getStateLabel()
{
return sLabel;
}
/**
* Returns whether or not the display of the labels should be
* clipped with the bounding box of the realizer.
*/
public boolean getClipContent()
{
return clipContent;
}
/**
* Sets whether or not the display of the labels should be
* clipped with the bounding box of the realizer.
*/
public void setClipContent(boolean clipping)
{
clipContent = clipping;
}
/**
* Set whether or not this realizer should omit details when being displayed.
*/
public void setOmitDetails(boolean b)
{
omitDetails = b;
if (omitDetails)
sLabel.setIcon(GroupNodeRealizer.defaultClosedGroupIcon);
else
sLabel.setIcon(GroupNodeRealizer.defaultOpenGroupIcon);
}
/**
* Returns whether or not this realizer should omit details when being displayed.
*/
public boolean getOmitDetails()
{
return omitDetails;
}
private void addToLabel(NodeLabel l, String s)
{
if (l.getText().length() > 0)
l.setText(l.getText() + "\n" + s);
else
l.setText(s);
}
/**
* Adds a class method label to this realizer.
*/
public void addMethod(String method)
{
addToLabel(mLabel, method);
}
/**
* Adds a class attribute label to this realizer.
*/
public void addAttribute(String attr)
{
addToLabel(aLabel, attr);
}
/**
* Set the size of this realizer automatically. This method will adapt the size
* of this realizer so that the labels defined for it will fit within its
* bounding box.
*/
public void fitContent()
{
double height = 3.0;
double width = getLabel().getWidth() + 10;
if (stereotype.length() > 0)
{
NodeLabel l = new NodeLabel();
l.setText("<<" + getStereotype() + ">>");
l.setModel(NodeLabel.FREE);
l.bindRealizer(this);
height += l.getHeight() + 5;
width = Math.max(l.getWidth() + 10, width);
}
height += getLabel().getHeight() + 3;
if (constraint.length() > 0)
{
NodeLabel l = new NodeLabel();
l.setText("{" + getConstraint() + "}");
l.setModel(NodeLabel.FREE);
height += l.getHeight() + 5;
width = Math.max(l.getWidth() + 10, width);
}
if (!omitDetails && !(aLabel.getText().equals("") && mLabel.getText().equals("")))
{
height += 3;
height += aLabel.getHeight() + 3;
width = Math.max(aLabel.getWidth() + 10, width);
height += 3;
height += mLabel.getHeight() + 3;
width = Math.max(mLabel.getWidth() + 10, width);
}
setSize(width, height);
}
//////////////////////////////////////////////////////////////////////////////
// GRAPHICS /////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/**
* Paint the labels associated with this realizer.
*/
public void paintText(Graphics2D gfx)
{
Rectangle oldClip = null;
if (clipContent)
{
oldClip = gfx.getClipBounds();
gfx.clipRect((int)x, (int)y, (int)width, (int)height);
}
sLabel.paint(gfx);
double yoff = 3.0;
if (stereotype.length() > 0)
{
NodeLabel l = new NodeLabel();
l.setText("<<" + getStereotype() + ">>");
l.setModel(NodeLabel.FREE);
l.setOffset((getWidth() - l.getWidth()) / 2.0, yoff);
l.bindRealizer(this);
l.paint(gfx);
yoff += l.getHeight() + 5;
}
NodeLabel label = getLabel();
label.setOffset((getWidth() - label.getWidth()) / 2.0, yoff);
label.paint(gfx);
yoff += label.getHeight() + 3;
if (constraint.length() > 0)
{
NodeLabel l = new NodeLabel();
l.setText("{" + getConstraint() + "}");
l.setModel(NodeLabel.FREE);
l.setOffset(getWidth() - l.getWidth() - 5.0, yoff);
l.bindRealizer(this);
l.paint(gfx);
yoff += l.getHeight() + 5;
}
if (!omitDetails && !(aLabel.getText().equals("") && mLabel.getText().equals("")))
{
gfx.setColor(getLineColor());
gfx.drawLine((int)x + 1, (int)(y + yoff), (int)(x + width - 1), (int)(y + yoff));
yoff += 3;
aLabel.setOffset(3, yoff);
aLabel.paint(gfx);
yoff += aLabel.getHeight() + 3;
gfx.drawLine((int)x + 1, (int)(y + yoff), (int)(x + width - 1), (int)(y + yoff));
yoff += 3;
mLabel.setOffset(3, yoff);
mLabel.paint(gfx);
}
if (clipContent)
{
gfx.setClip(oldClip);
}
}
//////////////////////////////////////////////////////////////////////////////
// SERIALIZATION /////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/**
* Serialization routine that allows this realizer to be written out
* in YGF graph format.
*/
public void write(ObjectOutputStream out) throws IOException
{
out.writeByte(YVersion.VERSION_1);
super.write(out);
aLabel.write(out);
mLabel.write(out);
out.writeBoolean(clipContent);
out.writeBoolean(omitDetails);
out.writeObject(getStereotype());
out.writeObject(getConstraint());
}
/**
* Deserialization routine that allows this realizer to be read in
* from YGF graph format.
*/
public void read(ObjectInputStream in) throws IOException, ClassNotFoundException
{
switch (in.readByte())
{
case YVersion.VERSION_1:
super.read(in);
init();
aLabel.read(in);
mLabel.read(in);
clipContent = in.readBoolean();
omitDetails = in.readBoolean();
stereotype = (String)in.readObject();
constraint = (String)in.readObject();
break;
default:
D.fatal("Unsupported Format");
}
}
static class ToggleDetailsMode extends ViewMode
{
public void mouseClicked(MouseEvent ev)
{
double x = translateX(ev.getX());
double y = translateY(ev.getY());
Graph2D graph = getGraph2D();
for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next())
{
if (graph.getRealizer(nc.node()) instanceof UMLClassNodeRealizer)
{
UMLClassNodeRealizer cnr = (UMLClassNodeRealizer)graph.getRealizer(nc.node());
NodeLabel l = cnr.getStateLabel();
if (l.getBox().contains(x, y))
{
YPoint p = graph.getLocation(nc.node());
cnr.setOmitDetails(!cnr.getOmitDetails());
cnr.fitContent();
graph.setLocation(nc.node(), p);
graph.updateViews();
break;
}
}
}
}
}
/**
* Launcher method. Execute this method to see a sample instantiation of
* this node realizer in action.
*/
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Graph2DView view = new Graph2DView();
view.addViewMode(new ToggleDetailsMode());
frame.setContentPane(view);
UMLClassNodeRealizer r = new UMLClassNodeRealizer();
r.setClassName("com.mycompany.MyClass");
r.setConstraint("abstract");
r.setStereotype("factory");
r.addAttribute("-graph");
r.addAttribute("-id");
r.addMethod("+setGraph(Graph)");
r.addMethod("+getGraph():Graph");
r.addMethod("+setID(int)");
r.addMethod("+getID():int");
r.fitContent();
view.getGraph2D().setDefaultNodeRealizer(r);
view.getGraph2D().createNode();
view.addViewMode(new EditMode());
view.fitContent();
frame.pack();
frame.setVisible(true);
}
}
Resources
Categories this article belongs to:
yFiles for Java > yFiles Viewer > Displaying and Editing Graphs > Bringing Graph Elements to Life: The Realizer Concept
Applies to:
yFiles for Java 2: 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.10, 2.11, 2.12, 2.13, 2.14, 2.15, 2.16, 2.17, 2.18
Keywords:
UML - class - NodeRealizer - node - representation - rendering - graphical - collapse - expand - collapsing - expanding