Editable charts with Adobe Flex

One of the cool things about Flex is its ability to snapshot just about anything in the application. It's usually as simple as:

var snapshot:String =
  ImageSnapshot.encodeImageAsBase64(
    ImageSnapshot.captureImage("your view component here", 0,
    new PNGEncoder));

This is particularly handy when you've got a dynamically generated resource, such as a chart, that you'd like to have customized by the user and then exported as an image. One thing that is handy to customize in a chart is its axis titles. That's exactly what we are going to do.

Check out the demo here.

This demo application has view source enabled, so just right click to see how it was put together.

Chart axis titles are typically just strings of text that you specify when you create a chart. However you can use your own TitleRenderer to produce just about anything you want instead of the Flex default. We are going to use the excellent IPE controls by Ely Greenfield available here: http://demo.quietlyscheming.com/IPE/index.html

Much of the code in editablecharts.mxml is standard chart boiler plate, with the exception of horizontal and vertical AxisRenderer properties called titleRenderer. These are set to our custom implementation that hooks up an editable IPE control instead of the default flex TextField.

Following is the code for EditableTitleRenderer:

package editablecharts {
  import mx.charts.AxisRenderer;
  import mx.core.IDataRenderer;
  import mx.core.UIComponent;
 
  import qs.ipeControls.IPETextInput;
 
  public class EditableTitleRenderer extends UIComponent
    implements IDataRenderer {
    public function EditableTitleRenderer() {
      super();
    }
 
    private var editableTitle:IPETextInput;
 
    public function get data():Object {
      return editableTitle;
    }
 
    public function set data(value:Object):void {
      editableTitle.text = String(value);
 
      invalidateSize();
      invalidateDisplayList();
    }
 
    override protected function createChildren():void {
      super.createChildren();
 
      editableTitle = new IPETextInput;
      editableTitle.editOnClick = true;
      editableTitle.commitOnEnter = true;
      editableTitle.commitOnBlur = true;
      editableTitle.styleName = "editableAxisTitle";
 
      addChild(editableTitle);
    }
 
    override protected function measure():void {
      var oldRotation:Number = rotation;
 
      if (parent && parent.rotation == 90) {
        rotation = -90;
      }
      editableTitle.validateNow();
 
      measuredWidth = editableTitle.measuredWidth;
      measuredHeight = editableTitle.measuredHeight;
 
      rotation = oldRotation;
    } 
 
    override protected function updateDisplayList(unscaledWidth:Number,
      unscaledHeight:Number):void {
      if (parent && parent is AxisRenderer
        && parent.rotation == 90) {
        var p:AxisRenderer = AxisRenderer(parent);
        if(p.getStyle('verticalAxisTitleAlignment') == 'vertical') {
          editableTitle.rotation = 180;
          editableTitle.y = editableTitle.y + editableTitle.height;
          editableTitle.x = editableTitle.x + editableTitle.width;
        }
      }
      editableTitle.validateNow();
      editableTitle.setActualSize(unscaledWidth, unscaledHeight);
    }
  }
}

One thing to keep in mind is that you need embedded fonts to pull off rotation. If you sent rotation to anything other than 0 on a normal TextField control it'll just disappear. To work around that use an embedded font.

That's pretty much all there's to editable chart axis titles in Flex.

Aspect-Oriented Programming in ActionScript3

Summary

AOP techniques are possible in ActionScript3 (AS3) but it sure ain't pretty. In fact, it's virtually unusable.

Motivation

Before Flex came along online ads, movies and fairly small animation-driven websites were the primary consumers of the Flash platform. Now that you've got reusable components and an extensible event framework people are writing some pretty big and complex Object-Oriented (OO) applications in Flex/AS3 for the Flash platform.

Sooner or later most complex OO systems begin to suffer from some degree of code tangling and scattering. Good design can help delay the symptoms but it cannot solve the fundamental problem. No amount of re-factoring or juggling design patterns can overcome the fact that primary OO modularity techniques (e.g. Abstract Data Types + Inheritance) just cannot modularize certain class of problems in a clean way. Things like transactions, caching, logging, security and so on cut across the inheritance boundaries and hence cannot be captured with classes and methods alone. Aspect-Oriented Programming (AOP) offers some very handy and practical solutions to the problem.

It's been battle proven in a host of other languages so why not AS3? The fact of the matter is that eventually the same problems encountered in Java/C/C++/C# applications will re-appear or already have re-appeared in Flex/AS3 apps. The only logical conclusion that doesn't involve re-inventing the wheel is that you'll need a similar set of tools to solve these problems.

Solution

There's a couple of different ways AOP can be implemented in a language like AS3. The most accessible way in AS3 at this point of time is using proxies.

You can create proxies in AS3 easily enough, e.g.:

 
package test {
       import flash.utils.*;
 
       use namespace flash_proxy;
 
       public dynamic class TestProxy extends Proxy {
               protected var target:*;
 
               public function TestProxy(target:*) {
                       super();
                       this.target = target;
               }
 
               flash_proxy override function callProperty(name:*, ...args):* {
                       trace("call property: " + name + args);
                       return target[name].apply(target, args);
               }
 
               flash_proxy override function getProperty(name:*):* {
                       trace("get property: " + name);
                       return target[name];
               }
 
               flash_proxy override function setProperty(name:*, value:*):void {
                       trace("set property: " + name + ":" + value);
                       target[name] = value;
               }
 
               flash_proxy override function hasProperty(name:*):Boolean {
                       trace("has property: " + name);
                       return target.hasOwnProperty(name);
               }
 
               flash_proxy override function isAttribute(name:*):Boolean {
                       trace("is attribute: " + name);
                       return !(target[name] is Function);
               }
       }
}
 

This'll wrap property, method invocations on a target object which you
can use along these lines:

 
// original object used directly
var foo:Foo = new Foo;
trace(foo.bar());
 
// now proxy the object
var testProxy:TestProxy = new TestProxy(foo);
// this will print the proxy trace statement first and then invoke bar
// on the target object
trace(testProxy.bar());
 

So far so good, this can totally be a foundation for a fully functional AOP framework in AS3. Couple this will custom annotations a-la [Bindable] and you can start churning out code for [Loggable], [Transactionable], etc. All you need to do is parse the annotations off classes at runtime and substitute those classes with proxies.

But there's a big fat problem with the way you create and use proxies in AS3. You have to extend flash.utils.Proxy class which subclasses Object class directly (the root of AS3 inheritance hierarchy). As far as the compiler and AVM are concerned your new proxy is a subclass of flash.utils.Proxy full stop. There's no way to dynamically extend classes or implement interfaces in AS3 at runtime (that I am aware of). So unless you are planning on creating custom proxies for each class you intend to proxy or abandon the type system entirely and change all method signatures to deal with Objects these proxies are pretty much useless.

Here's what you should be able to say in code but you can't given the way proxies work in AS3 right now:

 
var testProxy:Foo = new TestProxy(foo) as Foo;
// testProxy is now null because we can't convert TestProxy to type Foo
 

The point is that you can't use AS3 proxies instead of the original objects if the exact type of the object is expected. For example, you can't wrap around a Button and add it to the view root because addChild method expects a DisplayObject. I doubt anybody will want anything to do with AOP in AS3 if it means abandoning the type system, which is one of its strongest points.

This is quite unfortunate because there are some attempts in the wild to bring more AOP into AS3 world (e.g. the Advanced Flex project. They've even implemented some of the AOP semantic concepts such as Advices and Pointcuts. However the entire thing is based on proxies and you can't really use them for AOP right now.

To put things in perspective there are Java-land frameworks that quite successfully implement AOP using dynamic Java proxies. That's because you can make Java proxies implement arbitrary interfaces on-demand at runtime.

What are the other options?

I am aware of two other possibilities for AOP implementation:

  • Dynamic Subclassing (a-la CGLIB in Java). This is a no go area in AS3 because you can't dynamically subclass classes at runtime (not that I am aware of).
  • Byte-code weaving. This is the "real" man's (read non-trivial) solution and would end up doing what AspectJ does for Java. You'd need to parse SWF files/ABC bytecode and weave it straight after AS3 compilation phase. This also implies writing some kind of a domain specific language to do that (pretty much porting AspectJ syntax to AS3). Quite a lot of work any way you look at it.

As it stands proxies is the easiest way to go, let's hope Adobe cooks up some way of dynamically extending classes/interfaces at run-time using proxies. If that happens we are back in the AOP game, until then hold your horses and keep scattering your code :)

Back Blogging

I decided to pick up the virtual pen again and start throwing some ideas/experiments I've accumulated recently out there. Hopefully somebody else will find this stuff useful.

This is going to be primarily a software development blog focused on web/RIA apps.