Flex Chart DataTip Renderer

Here is a quick post with some code to create custom DataTip renderers for Flex charts using Spark components and containers. Below is a screen shot of the custom DataTip and the source code to create it follows.

Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
					   xmlns:s="library://ns.adobe.com/flex/spark" 
					   xmlns:mx="library://ns.adobe.com/flex/mx" 
					   xmlns:charts="com.thanksmister.charts.*" 
					   width="480" height="340">
	
	<fx:Style source="assets/css/style.css"/>
	
	<fx:Script>
		<![CDATA[
			import mx.charts.CategoryAxis;
			
			private function categoryAxis_labelFunc(item:Object, prevValue:Object, axis:CategoryAxis, categoryItem:Object):String 
			{
				var datNum:Number = Date.parse(item);
				var tempDate:Date = new Date(datNum);
				
				return dateFormatter.format(tempDate).toUpperCase();
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<mx:DateFormatter id="dateFormatter" formatString="MMMM-DD-YYYY" />
	
		<s:XMLListCollection id="dp">
			<s:source>
				<fx:XMLList>
					<quote date="8/1/2007" open="40.29" close="39.58" />
					<quote date="8/2/2007" open="39.4" close="39.52" />
					<quote date="8/3/2007" open="39.47" close="38.75" />
					<quote date="8/6/2007" open="38.71" close="39.38" />
					<quote date="8/7/2007" open="39.08" close="39.42" />
					<quote date="8/8/2007" open="39.61" close="40.23" />
					<quote date="8/9/2007" open="39.9" close="40.75" />
					<quote date="8/10/2007" open="41.3" close="41.06" />
					<quote date="8/13/2007" open="41" close="40.83" />
					<quote date="8/14/2007" open="41.01" close="40.41" />
					<quote date="8/15/2007" open="40.22" close="40.18" />
					<quote date="8/16/2007" open="39.83" close="39.96" />
					<quote date="8/17/2007" open="40.18" close="40.32" />
					<quote date="8/20/2007" open="40.55" close="40.74" />
					<quote date="8/21/2007" open="40.41" close="40.13" />
					<quote date="8/22/2007" open="40.4" close="40.77" />
					<quote date="8/23/2007" open="40.82" close="40.6" />
					<quote date="8/24/2007" open="40.5" close="40.41" />
					<quote date="8/27/2007" open="40.38" close="40.81" />
				</fx:XMLList>
			</s:source>
		</s:XMLListCollection>

		<s:SolidColorStroke id="lineStroke" color="#CCCCCCC" alpha=".2" weight="1"/>
		
	</fx:Declarations>
	
	<s:VGroup width="100%" height="100%" paddingBottom="10" paddingTop="10" paddingLeft="10" paddingRight="10">
		
		<mx:LineChart id="lineChart"
					  showDataTips="true"
					  dataProvider="{dp}"
					  width="100%" gutterRight="10"
					  height="100%" 
					  dataTipRenderer="com.thanksmister.charts.DataTipSkin">
			
			<!-- vertical axis -->
			<mx:verticalAxis>
				<mx:LinearAxis baseAtZero="false" title="Price" />
			</mx:verticalAxis>
			
			<!-- horizontal axis -->
			<mx:horizontalAxis>
				<mx:CategoryAxis id="ca" categoryField="@date" title="Date" labelFunction="categoryAxis_labelFunc" />
			</mx:horizontalAxis>
			
			<!-- horizontal axis renderer -->
			<mx:horizontalAxisRenderers>
				<mx:AxisRenderer axis="{ca}" canDropLabels="true" />
			</mx:horizontalAxisRenderers>
			
			<!-- series -->
			<mx:series>
				<mx:LineSeries yField="@open" form="segment" displayName="Open" />
			</mx:series>
			
			<!-- series filters -->
			<mx:seriesFilters>
				<fx:Array/>
			</mx:seriesFilters>
			
			<!-- assign stroke to grid lines -->
			<mx:backgroundElements>
				<mx:GridLines gridDirection="both" horizontalChangeCount="2" verticalChangeCount="6">
					<mx:horizontalStroke>{lineStroke}</mx:horizontalStroke>
					<mx:verticalStroke>{lineStroke}</mx:verticalStroke>
				</mx:GridLines>
			</mx:backgroundElements>
			
			<mx:annotationElements>
				<fx:Array>
					<charts:RangeSelector id="selectedRange" />
				</fx:Array>
			</mx:annotationElements>
			
		</mx:LineChart>
		
	</s:VGroup>
	
</s:WindowedApplication>

DataTipSkin.mxml

'
<?xml version="1.0" encoding="utf-8"?>
<s:Group  xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark"  
		 implements="mx.core.IFlexDisplayObject, mx.core.IDataRenderer"
		 xmlns:mx="library://ns.adobe.com/flex/mx" width="120">
	
	<fx:Script>
		<![CDATA[
			import flashx.textLayout.conversion.TextConverter;
			import flashx.textLayout.elements.TextFlow;
			
			import mx.charts.HitData;
			import mx.charts.series.items.LineSeriesItem;
			
			private var _data:HitData;
			
			[Bindable]
			private var _xValue:String;
			
			[Bindable]
			private var _yValue:String;
			
			[Bindable]
			private var _displayText:TextFlow;
			
			public function get data():Object
			{
				// TODO Auto Generated method stub
				return null;
			}
			
			public function set data(value:Object):void
			{
				// HitData data from chart
				_data = value as HitData;
				
				// The display text used in datatip which comes in HTML format
				_displayText = TextConverter.importToFlow(_data.displayText, TextConverter.TEXT_FIELD_HTML_FORMAT);
				
				// HitData contains a reference to the ChartItem
				var item:LineSeriesItem = _data.chartItem as LineSeriesItem;
				
				// ChartItem xValue and yValue 
				_xValue = String(item.xValue);
				_yValue = String(item.yValue);
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		
	</fx:Declarations>
	
	<s:Rect right="0" left="0" bottom="0" top="0">
		<s:filters>
			<s:DropShadowFilter blurX="20" blurY="20" alpha="0.22" distance="5" angle="90" knockout="false" />
		</s:filters>
		<s:fill>
			<s:SolidColor color="0x393939"/>
		</s:fill>    
		<s:stroke>
			<s:SolidColorStroke color="0x1a1a19"  weight="1" alpha=".2" />
		</s:stroke>
	</s:Rect>
	
	<s:VGroup width="100%" height="100%" paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10">
		
		<s:RichEditableText textFlow="{_displayText}" width="100%" textAlign="center" selectable="false" editable="false"/>
		
	</s:VGroup>

</s:Group>

-Mister

SuperImage Redux, caching images in Flex List

I have been meaning for so long to post this code. Some time ago I had the pleasure of working with John Yanarella from Universal Mind. John was helping my employer at the time to put together an application that allows users to upload, manage, and share media assets. We needed an efficient way to cache and display images to optimize the performance of loading and viewing a large number of thumbnails.

SuperImage

Last year Ely Greenfield posted on his blog QuietlyScheming a way to end images from flickering when you display them in a Flex List control. Ely created a new component called SuperImage that replaces the Flex Image control. SuperImage fixes a few issues with the current Image control layout in addition to adding the ability to cache loaded images to stop them from constantly reloading (causing the flicker when scrolling a list).

SuperImage Update

In our project we wanted use SuperImage, but what we wanted was for SuperImage to behave more like the Flex Image control. Specifically we wanted the control to broadcast the same standard events as the Image control; ioError, securityError, imageComplete, progress, completem, completeEffect. The new SuperImage also implements IDataRenderer, IDropInListItemRenderer, and IListItemRenderer interfaces. John Yanarella did a great job cleaning up the SuperImage control and add the missing functionality. We ended up not using the SuperImage and instead used a simpler implementation for caching the Image control.

Example

Below is an example similar to Ely’s SuperImage that demonstrates the problem (on the left) with the Image control and the fix (on right) using the updated SuperImage. This example also shows a text dump of events broadcast from the new SuperImage control.

Limitations

One thing the updated SuperImage still lacks is the ability to display loaded SWF files. Since our company never used the code in any project and it was based on Ely’s code, I thought it only fair to repost the update and the code for everyone to use as needed. If anyone has an further updates to the code or suggestions to fix the SWF loading issue, please post them here and I will continue to update the component.

Code

You can download the code for the above project here. You will need your own Flickr API key to make the application work. Be sure to also read Ely’s original post to see the other benefits of SuperImage. Many thanks to John Yanarella who actually did the coding on the project, I finally posted it as I promised him many months ago :).

-Mister

Rotate using Flex BitmapData and Matrix

Recently, I found myself in need of an image rotation component in Flex so that the rotation takes place on the client and saves the image back to the server.  The image source I use has a thumbnail 100×100 and the original image.    I wanted to load just the thumbnail to perform the rotation and then once I am done with rotating the image, apply the rotation to the original image size.

Rotating the thumbnail turns out to be easy when you redraw the BitmapData using a Matrix with the rotate property set to the desired rotation:

 // Pass in a reference to the Image Control
 private function rotateImageRight(img:Image):void
 {
 img.source = new Bitmap( rotateRight(img));
 }

private function rotateLeft(img:Image):BitmapData
 {
 var matrix:Matrix = new Matrix();
 matrix.rotate(-1*Math.PI/2);
 matrix.ty = img.content.width;
 var bd:BitmapData = getBitmapDataMatrix(img, matrix);
 return bd;
 }

private function rotateRight(img:Image):BitmapData
 {
 var matrix:Matrix = new Matrix();
 matrix.rotate(Math.PI/2);
 matrix.tx = img.content.height;
 var bd:BitmapData = getBitmapDataMatrix(img, matrix);
 return bd;
 }
 

The issue I ran into was that the Image Control couldn’t have any dimensions or the loaded data would progressively become smaller and smaller with each rotation.   So my Image control just loads the thumbnail source, but does not size the image.   You have to trust that the image thumb is really small or it will blow up the example application.  You could probably also use a Loader Class and attach the image to the stage after you size it, but I didn’t test that method.

Rotating the original image is not as easy.  I didn’t want to load the image until I was ready to save the rotation. When I want to save the rotation, I load the original image in the background, to an invisible Image control with fixed dimensions (it can be as small as you want), then I manipulate the BitmapData of the content because you need the original images width and height.   At first I tried to get this with contentWidth and contentHeight properties of the Image control, but I found it worked with the actual content.width and content.height:

 // Pass in reference to of the Image control with
 // the original image and the matrix

private function getBitmapDataMatrix(img:Image, matrix:Matrix) : BitmapData
 {
 var bd:BitmapData = new BitmapData(img.content.height, img.content.width);
 bd.draw(img.content, matrix);
 return bd;
 }
 

The returned BitmapData can be posted back to a server to write out to the users disk or you can display the data in another Image control.   In the example application I show the mothod for both using a trick that Doug McCune posted on his blog. Unfortunately, it only works for Firefox and fails for some larger images.

The rest of the work just invovles keeping track of rotated angle of the thumbnail so you can apply this to the original image that you load.  In my example the original image is loaded at the start so you can see it.  But it would be easy to make this inivisible and only load the original image when the users wants to save or output the rotated image.  You will need to have the as3corelib to run the example locally.

Example File (right-click to view source)

-Mr