Adobe Flex 4 TagCloud Layout with Multi-Line Tags from XML Data Provider

22. July 2010 09:10

Looking back at an older example of using Adobe Flex as a data bound Tag Cloud, I noticed that for any given height/width of the application if the child tag was too long for the container, the text would simply be truncated.  So I began to think about how to create a tag cloud whose child tags can resize to multi-line when necessary, and would display as single line when multi-line was not necessary.

There were several issues that needed to be addressed.  First, a tag cloud, by its very nature is used to "weight" certain links based upon the total amount of times a particular tag is used.  Implementation of these weights can be seen as font size modifications.  The more times a tag is used, the larger its font size.  Other implementations involve using, in the true CSS sense of the word, font weight to add emphasis to tags as their usage increases.  Sometimes, its a combination of both.

Next we have to think about the weight issue in relation to how Flex 4 renders text and more specifically Multi-Line text.  For a given line of text, and knowing that its weight/size could be any value and is completely independent of any other tag within the cloud, the main question is how do we layout these tags in relation to each other and in relation to a resizable container especially when the data is not known until runtime??





For this usage, i created a new layout called DynamicTagCloudLayout.as and applied it to my application directly since the entire application is going to be a widget used in a larger application.  My first draft was a little sloppy and I found an article that was doing almost the same thing and used it as a reference.  That article, written by Evtim Georgiev,is called Spark Layouts with Flex 4 and served as a nice resource. I would also like to give credit to Shongrunden for helping to answer the question of using mx_internal in order to grab the $height value from the ILayoutElement. You can read that post at http://forums.adobe.com/message/2982272#2982272

Here is a snippet of the UpdateDisplayList override from my code to give you an idea of how this issue is resolved:


   
    import flash.geom.Matrix;
    import flash.geom.Matrix3D;
   
    import mx.core.ILayoutElement;
    import mx.core.mx_internal;
   
    import spark.components.supportClasses.GroupBase;
    import spark.layouts.supportClasses.LayoutBase;
   
    import com.shawnyale.components.TagCloudLink;
   
    /**
     *    TagCloudLayout
     *    <p>The DynamicTagCloudLayout is used to place children side-by-side, top-to-bottom in a similar 2D fashion to the TileLayout class, however, the difference
     *     between the TileLayout and the DynamicTagCloudLayout is where a TileLayout places its children in equally sized columns and rows, the DynamicTagCloudLayout does not.
     *  Instead, the DynamicTagCloudLayout will place child elements according to their size inline and in relationship to both each other and the container without trying to emulate a column/row structure.</p>
     * <p>The typical use of a "Tag Cloud" is to display written link tags based upon a sizing mechanism.  In order to prevent truncation of text,
     *  we check each child element and resize it to an appropriate size if multi-line functionality is necessary for each link.</p>
     *    @created: Jun 17, 2010
     *    @author: Shawn Yale
     *    
     */   
    public class DynamicTagCloudLayout extends LayoutBase
    {
        use namespace mx_internal;
        private var _verticalGap:Number = 5;
        private var _horizontalGap:Number = 5;
       
        [Bindable("propertyChange")]
        [Inspectable(category="General")]
        public function get HorizontalGap():Number
        {
            return _horizontalGap;
        }
        public function set HorizontalGap(val:Number):void
        {
            _horizontalGap = val;
            var layoutTarget:GroupBase = target;
            if(layoutTarget)
            {
                layoutTarget.invalidateSize();               
                layoutTarget.invalidateDisplayList();   
            }
   
        }
        [Bindable("propertyChange")]
        [Inspectable(category="General")]
        public function get VerticalGap():Number
        {
            return _horizontalGap;
        }
        public function set VerticalGap(val:Number):void
        {
            _horizontalGap = val;
            var layoutTarget:GroupBase = target;
            if(layoutTarget)
            {
                layoutTarget.invalidateSize();               
                layoutTarget.invalidateDisplayList();
            }
        }
       
        override public function updateDisplayList(width:Number, height:Number):void
        {
            super.updateDisplayList(width, height);
            if(!target)
                return;
            var count:uint = target.numElements;
           
            var maxYForRow:Number = 0;
            var xPos:Number = 0;
            var yPos:Number = 0;
            var maxWidth:Number = 0;
            var maxHeight:Number = 0;
            var layoutTarget:GroupBase = target;
            for(var i:int = 0; i < count; i++)
            {
                var element:ILayoutElement = useVirtualLayout ? target.getVirtualElementAt(i) : target.getElementAt(i);

                element.setLayoutBoundsSize(NaN, NaN);
                var eleWidth:Number = element.getLayoutBoundsWidth();
                var eleHeight:Number = element.getLayoutBoundsHeight();

                // If the sum of the size of the current element and the previous elements
                // is less than the amount of the container width,
                // place the element on this row
                if(xPos + eleWidth < width)
                {
                    // Check the value of the lineHeight against the height of the current element
                    // If the element is taller than the current lineHeight, make lineHeight equal to the tallest element
                    if(eleHeight > maxYForRow)
                    {
                        maxYForRow = eleHeight;
                    }
                }
                else // Otherwise set the element on a new row and update the elementY position
                {
                    xPos = 0;
                    if(eleWidth > width)
                    {
                        element.setLayoutBoundsSize(target.width, NaN);
                        eleHeight= element.getLayoutBoundsHeight();
                    }
                    yPos += maxYForRow+VerticalGap;
                    if(element.mx_internal::$height > maxYForRow)
                    {
                        maxYForRow = 0;
                        // Here we need to check if the reduced width has caused the element to grow in height.  If that
                        // height is greater than the current max height for the "row" apply the value to the max Y variable.
                        maxYForRow = element.mx_internal::$height;
                    }
                   
                   
                }
               
                element.setLayoutBoundsPosition(xPos, yPos);
                maxWidth = Math.max(maxWidth, xPos + eleWidth);
                maxHeight = Math.max(maxHeight, yPos + eleHeight);

                xPos += eleWidth + HorizontalGap;       
               
            }
            layoutTarget.setContentSize(maxWidth, maxHeight);
        }
    } 

 





You can view a working example of this approach by visiting my Labs page at Labs.shawnyale.com and looking in the Play Projects / Components section. Hope this helps provide a little direction regarding multi-line and tag clouds in Adobe Flex 4.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Log in