Skip to content
deanjones edited this page Jul 20, 2014 · 1 revision
The Serialization framework changed significantly in version 1.1. See Extending Lienzo 1.0 for Lienzo version 1.0.

To add a new Shape class:

  1. extend the class from Shape
  2. add new attribute names (if necessary)
  3. add new attribute types (if necessary)
  4. write Javascript wrapper class for new attribute type (if necessary)
  5. add getters/setters for its attributes
  6. implement draw method
  7. add support for copying and Serialization
  8. add an IFactory
  9. register IFactory with FactoryRegistry
  10. add getFactory method to Shape class
See Node Attributes

Table of Contents

Example: Pear Shape

To illustrate the process we'll add a fictitious Pear shape. The Pear has two properties: "pearWidth", which is a double and "pearity", which is represented by the Java object "Pearity". The Javascript object that represents a "Pearity" has the following attributes: "flavor" (string), "leafCount" (number) and "leafColor" (color String).

Somehow these attributes describe the Pear's pearness and influence how the Pear will be drawn :-)

Extend class from Shape

public class Pear extends Shape<Pear>
{
     public static final ShapeType PEAR_SHAPE = new ShapeType("Pear");
}

"Pear" is used as the shape type identifier when serializing the shape.

Look at ShapeType Javadoc and NodeType Javadoc for the type names used by Lienzo, to avoid conflicts.

Add Attribute name

The Attribute class is a type-safe, extensible enumeration of all possible Node attributes. When adding a new Shape class, it may be necessary to define new Attributes.

To add a new Attribute for PEAR_WIDTH and PEARITY we need to create two static variables (anywhere will do, we'll put them inside the Pear class):

public final static Attribute PEAR_WIDTH = new Attribute("pearWidth", "pearWidthLabel",
     "pearWidthDescription", AttributeType.NUMBER_TYPE);
public final static Attribute PEARITY = new Attribute("pearity", "pearityLabel",
     "pearityDescription", Pear.PEARITY_TYPE);

The first argument will be used internally as the attribute key. It is also used when deserializing the node to JSON.

The 2nd and 3rd value (label and description) values are placeholders for attribute sheets in bean editors. These values should be internationalized if you want to share your new class with international users.

The last argument describes what AttributeType the values of the new attribute have.

Add AttributeType

When adding a new Attribute, it may be necessary to add a new AttributeType (but try to use the existing AttributeTypes if possible.)

AttributeType is a type-safe, extensible enumeration of attribute types for all possible Node attributes.

Here are some of the built-in AttributeTypes:

AttributeType Java Type Javascript Type Notes
STRING_TYPE String String
NUMBER_TYPE int, Integer, double, Double etc. Number
BOOLEAN_TYPE boolean Boolean
COLOR_TYPE String String Any valid CSS Color String. See Color.
POINT2D_TYPE Point2D Object with attributes: x (Number), y (Number)
POINT2D_ARRAY Point2DArray Array with Point2D objects
SHADOW_TYPE Shadow Object with attributes: color (String), blur (Number), offset (Number)
DASH_ARRAY_TYPE DashArray Array of Numbers
LINEAR_GRADIENT_TYPE LinearGradient Object with attributes: start, end, colorStops and type="LinearGradient" start and end are Point2D objects.
colorStops is an Array of Objects with stop (Number) and color (String) attributes.
PATTERN_GRADIENT_TYPE PatternGradient Object with attributes: image (TODO), repeat (see enum FillRepeat) and type="PatternGradient"
RADIAL_GRADIENT_TYPE RadialGradient Object with attributes: start, end, colorStops and type="RadialGradient" start and end are Objects with x (Number), y (Number) and radius (Number) attributes.
colorStops is an Array of Objects with stop (Number) and color (String) attributes.
IMAGE_TYPE
DRAG_BOUNDS_TYPE DragBounds Object with attributes: x1, y1, x2, y2 (all Numbers)
FILL_TYPE Color, LinearGradient, PatternGradient or RadialGradient see above
STROKE_TYPE String String (same as COLOR_TYPE)

Each enumeration has its own type (the internal value is the string value obtained with EnumWithValue.getValue):

AttributeType Enum Javadoc
LINE_CAP_TYPE LineCap Javadoc
LINE_JOIN_TYPE LineJoin Javadoc
DRAG_CONSTRAINT_TYPE DragConstraint Javadoc
TEXT_ALIGN_TYPE TextAlign Javadoc
TEXT_BASELINE_TYPE TextBaseLine Javadoc
COMPOSITE_OPERATION_TYPE CompositeOperation Javadoc
ARROW_TYPE ArrowType Javadoc

Here is an example for adding the new AttributeType for the "pearity". Pearity will need a custom validator. This is used during deserialization.

public static AttributeType PEARITY_TYPE = new AttributeType(new PearityValidator());

Write Javascript wrapper class

To represent the "pearity" type, we would probably write a Java class called "Pearity". It's a wrapper around a Javascript object. We normally create a separate class that extends JavaScriptObject (PearityJSO in this case.) See the Lienzo source code for examples on how to implement these classes (e.g. DragBounds).

public static class Pearity
{
  private final PearityJSO m_jso;

  public Pearity(PearityJSO jso)
  { ... }

  public final PearityJSO getJSO()
  {
     return m_jso;
  }

  public static final class PearityJSO extends JavaScriptObject
  { ... }
}

Add getters and setters for new attributes

  public Pear setPearity(Pearity pearity)
  {
      if (null == pearity)
          getAttributes().delete(PEARITY.getProperty());
      else
          getAttributes.put(PEARITY.getProperty(), pearity.getJSO())

      return cast();
  }

  public Pearity getPearity()
  {
      JavaScriptObject jso = getAttributes().getObject(PEARITY.getProperty());
      if (null == jso)
          return null;
      else
          return new Pearity(jso);
  }

  public Pear setPearWidth(double pearWidth)
  {
      getAttributes().put(PEAR_WIDTH.getProperty(), pearWidth);

      return this;
  }

  public double getPearWidth()
  {
      return getAttributes().getDouble(PEAR_WIDTH.getProperty());
  }

Implement draw method

The draw method in the Shape class should assume that the Transform related attributes and fill/stroke related attributes have already been applied to the Context2D.

Here is the draw method for the Circle class. Note that it draws the center at (0,0) because the Transform related attributes (such as X and Y) have already been applied to the Context2D.

  public void draw(Context2D context)
  {
      context.beginPath();
      context.arc(0, 0, getRadius(), 0, Math.PI * 2, true);
      context.closePath();
  }

If the new Node class is a type of ContainerNode, it should also draw its children here. See the source code for Group.draw() for an example. (Usually you don't need to derive from ContainerNode. Just use a Group with child nodes instead.)

Add support for copying and Serialization

By adding support for Serialization to JSON we get a copy method for free. copy simply serializes to JSON and then deserializes the JSON into a new object.

Add type Validator

All we need to do is add validators for the new AttributeTypes, in this case PearityValidator which was used by PEARITY_TYPE above.

public class PearityValidator extends ObjectValidator
{
    public PearityValidator ()
    {
        super("Pearity");

        // the 3rd parameter indicates whether the attribute is required
        addAttribute("flavor", StringValidator.INSTANCE, true);
        addAttribute("leafCount", NumberValidator.INSTANCE, true);
        addAttribute("leafColor", ColorValidator.INSTANCE, true);
    }
}

Add an IFactory

  public static class PearFactory extends ShapeFactory<Pear>
  {
      public PearFactory()
      {
          super(Pear.PEAR_SHAPE);
          addAttribute(PEAR_WIDTH, true); // true indicates that it's a required attribute
          addAttribute(PEARITY, true);
      }

      @Override
      public Pear create(JSONObject node, ValidationContext ctx)
      {
          return new Pear(node);
      }
  }

If you're implementing a subclass of ContainerNode, extend the ContainerNodeFactory instead. This requires you to also implement the isValidForContainer method to restrict the which child nodes can be added.

Register IFactory with FactoryRegistry

And register the factory with FactoryRegistry:

FactoryRegistry registry = FactoryRegistry.getInstance();
registry.registerFactory(new Pear.PearFactory());

Add getFactory method

In the Pear class, add a method that returns the PearFactory:

@Override
public IFactory<Pear> getFactory()
{
    return new PearFactory();
}

Also add the Pear constructor that takes a JSONObject.