Search code examples
apache-flexflex4flex-sparkflex-chartingflex-charts

Custom AxisRenderer


I am working with a LineChart with a CategoryAxis for the horizontal axis and I need each category to have a very custom formatted display. Using CategoryAxis.LabelFunction is not powerful enough for my purposes, as it only allows custom formatting of the text field of a Label object. Specifically for this project I need each category to be a grid or HGroup of two different multi-line labels. The left label needs to be left aligned and the right to be right aligned.

I tried creating a custom class that extends Group and implements IDataRenderer. However, the public function set data(value:Object), defined in IDataRenderer, always seems to be called with value as an AxisLabel object, which does not pass an Object within it, just a string (AxisLabel.text)


As a hack-ey alternative, I tried using the LabelFunction to pass an encoded string but even that causes problems. Somehow this code causes nothing to be displayed on the CategoryAxis:

public class HeatmapAxisLabelRenderer 
extends HGroup
implements IDataRenderer
{
public function HeatmapAxisLabelRenderer()
{
    super();

    this.includeInLayout = true;
    this.visible = true;

    this.width = 100;
    this.height = 40;
//      this.percentWidth = 100;
//      this.percentHeight = 100;
}


// Internal variable for the property value.
private var _data:Object;

// Make the data property bindable.
[Bindable("dataChange")]

// Define the getter method.
public function get data():Object {
    return _data;
}

// Define the setter method, and dispatch an event when the property
// changes to support data binding.
public function set data(value:Object):void {
    _data = value;

    if (value is AxisLabel && AxisLabel(value).text!=null) {
        var parts:Array = AxisLabel(value).text.split("|~|");
        if (parts.length != 2) return;

        var name:Label = new Label();
        name.text = parts[0];
        name.setStyle("textAlign","left");
        name.width = 100;

        var limit:Label = new Label();
        limit.text = parts[1];
        limit.setStyle("textAlign","right");
        limit.width = 100;

        this.addElement(name);
        this.addElement(limit);

        dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    } else {
        trace("renderer bad value");
    }
}

Solution

  • The problem seems to be a weird interaction between Group and AxisRenderer, and the fact that your renderer (an HGroup) has explicit width/height values (set in its constructor):

    After setting the data on your custom renderer, AxisRenderer` tells the custom renderer to invalidate it's size.

    While doing so, the Group correctly decides not to do anything because it has an explicit width/height.

    I made some changes to get this to work:

    • only set the width in the constructor
    • don't set width on child labels (you will likely want to play w/this)
    • made the label objects member variables, and create them only once in create children (the setter for data may get called frequently)

    Code:

    package
    {
        import mx.charts.AxisLabel;
        import mx.core.IDataRenderer;
        import mx.events.FlexEvent;
    
        import spark.components.HGroup;
        import spark.components.Label;
    
        public class HeatmapAxisLabelRenderer extends HGroup implements IDataRenderer
        {
            public function HeatmapAxisLabelRenderer()
            {
                super();
                this.width=100;
            }
    
            private var _data:Object;
    
            [Bindable("dataChange")]
            public function get data():Object {
                    return _data;
            }
    
            private var nameLabel:Label;
            private var limit:Label;
    
            override protected function createChildren():void
            {
                super.createChildren();
                if (!nameLabel)
                {
                    nameLabel=new Label();
                    nameLabel.setStyle("textAlign","left");
                    limit = new Label();
                    limit.setStyle("textAlign", "right");
                    addElement(nameLabel);
                    addElement(limit);
                }
            }
    
            public function set data(value:Object):void
            {
                if (_data === value)
                    return;
    
                _data = value;
                if (value is AxisLabel && AxisLabel(value).text!=null)
                {
                    var parts:Array = AxisLabel(value).text.split("|~|");
                    if (parts.length == 2)
                    {
                        nameLabel.text = parts[0];
                        limit.text = parts[1];
                    }
                }
                else
                {
                    trace("renderer bad value");
                }
                dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
            }
        }
    }