Collapsible/Expandable UML Class Node Representation
Applies to: yFiles for Java 2.7, yFiles for Java 2.6, yFiles for Java 2.5, yFiles for Java 2.4, yFiles for Java 2.3, yFiles for Java 2.2, yFiles for Java 2.1, yFiles for Java 2.0 print article email article

Type: Tips & Tricks

Sample NodeRealizer implementation to represent UML class nodes.

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);
  }
}

Keywords: UML - class - NodeRealizer - node - representation - rendering - graphical - collapse - expand - collapsing - expanding

Provide feedback:
How useful was this article?    less 1 2 3 4 5 more
Email address (optional):
COPYRIGHT © 2008 yWorks · ALL RIGHTS RESERVED imprint | top | home