Home > actionscript, flex > Flex Chart Zoom Window

Flex Chart Zoom Window

This code will show you how to create zoom functionality on your flex charts, as shown here: (click and drag on the chart to create the zoom window)
(Either JavaScript is not active or you are using an old version of Adobe Flash Player. Please install the newest Flash Player.)

Before I get to the code, there’s a couple of notes:

You will notice that the data tips don’t show up, even if you enable showDataTips. This is a known bug, caused by disabling filterData on the chart series. filterData forces the chart to render all data points, even if they’re not shown on the current chart view. This is important, because if those points aren’t rendered, the chart doesn’t draw the lines that extend to those points.

If you’re worried that rendering unnecessary data points will cause performance issues, you can extend your series class and override the updateFilter method. You’ll need to rewrite it so that it excludes/includes the proper data points.

If you’re not worried about the effects of filterData, and you’d like a quick work-around for showing data tips, you’ll still need to extend your series class, but its a bit more simple. You just need to override findDataPoints with some minor adjustments, and add the private function formatDataTip. Here’s a link with the edited class: FilterLineSeries.as I copied the code directly from the LineSeries source, and made some minor adjustments.

Now on to the important stuff…

main.mxml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="com.*" 
	initialize="init()" width="600" height="520">
	<mx:Panel title="Line Chart">
		<mx:LineChart id="chart1" mouseDown="startDraw(event)" mouseUp="finishDraw(event)" 
			mouseMove="showDraw(event)" width="510">
 
			<!-- zoom window is drawn here -->
			<mx:annotationElements>
				<mx:CartesianDataCanvas id="chartCanvas"/>
			</mx:annotationElements>
 
			<mx:horizontalAxis>
				<mx:LinearAxis id="haxis"/>
			</mx:horizontalAxis>
 
			<mx:verticalAxis>
				<mx:LinearAxis id="vaxis"/>
			</mx:verticalAxis>
 
			<mx:series>
				<mx:LineSeries filterData="false" id="series1" xField="month" yField="profit" 
					displayName="Profit" dataProvider="{profits}"/>
			</mx:series>
 
		</mx:LineChart>
	</mx:Panel>
 
	<mx:Button label="Reset Zoom" click="resetZoom()" />
</mx:Application>

actionscript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
[Bindable]
private var profits:Array;
private var dragStart:Point;
private var dragEnd:Point;
private var zooming:Boolean;
 
// initializes the data provider with random data
private function init():void{
	profits = new Array({month: 0, profit: 15});
	for(var i:int=1; i<40; i++)
		profits.push({month: i, profit: Math.round(Math.random()*25-10)+profits[i-1].profit});
}
 
// sets the start point of the zoom window
private function startDraw(e:MouseEvent):void{
	zooming = true;
	dragStart = new Point(series1.mouseX, series1.mouseY);
}
 
// draws the zoom window as your mouse moves
private function showDraw(e:MouseEvent):void{
	if(zooming){
		dragEnd = new Point(series1.mouseX, series1.mouseY);
 
		// finds the top-left and bottom-right ponits of the zoom window
		var TL:Point = new Point();  // top-left point
		var BR:Point = new Point();  // bottom-right point
		if(dragStart.x < dragEnd.x){
			TL.x = dragStart.x;
			BR.x = dragEnd.x;
		}
		else{
			TL.x = dragEnd.x;
			BR.x = dragStart.x;
		}
		if(dragStart.y < dragEnd.y){
			TL.y = dragStart.y;
			BR.y = dragEnd.y;
		}
		else{
			TL.y = dragEnd.y;
			BR.y = dragStart.y;
		}
 
		// prevents the zoom window from going off the canvas
		if(TL.x < 0) TL.x = 0;
		if(BR.x > chartCanvas.width-1) BR.x = chartCanvas.width-1;
		if(TL.y < 0) TL.y = 0;
		if(BR.y > chartCanvas.height-1) BR.y = chartCanvas.height-1;
 
		// draw the actual zoom window
		chartCanvas.graphics.clear();
		chartCanvas.graphics.lineStyle(1, 0x000000, 0.25);
		chartCanvas.graphics.beginFill(0xd4e3f0,0.5);
		chartCanvas.graphics.drawRect(TL.x, TL.y, BR.x-TL.x, BR.y-TL.y);
		chartCanvas.graphics.endFill();
	}
}
 
// clears the drawing canvas and sets the new max/mins
private function finishDraw(e:MouseEvent):void{
	zooming = false;
	chartCanvas.clear();
 
	// converts the drag coordinates into axis data points
	var chartValStart:Array = chartCanvas.localToData(dragStart);
	var chartValEnd:Array = chartCanvas.localToData(dragEnd);
 
	// sets the new maximum and minimum for both axes
	haxis.minimum = (chartValStart[0] < chartValEnd[0]) ? chartValStart[0] : chartValEnd[0];
	haxis.maximum = (chartValStart[0] < chartValEnd[0]) ? chartValEnd[0] : chartValStart[0];
	vaxis.minimum = (chartValStart[1] < chartValEnd[1]) ? chartValStart[1] : chartValEnd[1];
	vaxis.maximum = (chartValStart[1] < chartValEnd[1]) ? chartValEnd[1] : chartValStart[1];
}
 
// resets the axis max/mins
private function resetZoom():void{
	haxis.minimum = NaN; 
	haxis.maximum = NaN; 
	vaxis.minimum = NaN; 
	vaxis.maximum = NaN;
}

I know there seems to be a lot of unnecessary code in the showDraw function, but it serves a good purpose. Your user isn’t always going to start their zoom at the top-left corner and drag down to the bottom-right. The code distinguishes where the corners are, and draws the box accordingly.

EDIT (11/2/2010): Here’s how it would be done if you’re using a DateTimeAxis for your horizontal axis:
main.mxml:

13
14
15
<mx:horizontalAxis>
	<mx:DateTimeAxis id="haxis"/>
</mx:horizontalAxis>

actionscript:

70
71
72
73
74
75
var ds:Date = new Date(chartValStart[0]);
var de:Date = new Date(chartValEnd[0]);
haxis.minimum = (ds < de ? ds:de);
haxis.maximum = (ds < de ? de:ds);
vaxis.minimum = (chartValStart[1] < chartValEnd[1]) ? chartValStart[1] : chartValEnd[1];
vaxis.maximum = (chartValStart[1] < chartValEnd[1]) ? chartValEnd[1] : chartValStart[1];
77
78
79
80
81
82
private function resetZoom():void{
	haxis.minimum = null; 
	haxis.maximum = null; 
	vaxis.minimum = NaN; 
	vaxis.maximum = NaN;
}
  1. March 31st, 2010 at 16:15 | #1

    Thank you for posting this. I found this blog particularly useful.

  2. April 26th, 2010 at 01:55 | #2

    I have made something similar:http://www.riafan.com/flex/zoomchart/

  3. pratik
    July 3rd, 2010 at 00:57 | #3

    THNX A LOT TO NINJACAPTAIN.COM

  4. Tcash
    October 21st, 2010 at 17:28 | #4

    What if we’ve got 3 axes?

  5. October 21st, 2010 at 18:21 | #5

    @Tcash
    I haven’t tried it, but maybe the code will work for multiple axes. Have you tried it?

  6. November 2nd, 2010 at 13:16 | #6

    Thanks for this handy code. Do you know how to make it work for a DateTimeAxis? The problem is that canvas.localToData(dragStart) returns an Array of Numbers, but this line:
    haxis.minimum = (chartValStart[0] < chartValEnd[0]) ? chartValStart[0] : chartValEnd[0];
    throws an error because haxis.minimum requires a Date object (haxis being the DateTimeAxis). I noticed Flying who commented above got it going in his example but unfortunately he hasn't revealed his code. I did quite some searching in the API and tried a number of things including haxis.invertTransform() but it just didn't work for me.

  7. November 2nd, 2010 at 13:42 | #7

    @Fletch
    I’ve added your solution at the end of my original post. Let me know if that works for you.

  8. November 3rd, 2010 at 07:07 | #8

    Yes that solution works, thanks. I had been trying chartValEnd[0] as Date.

    I now seem to have come upon a bug in Flex 4 which is interesting. Sometimes when I draw right to left I get a null pointer exception. After some experimentation I have discovered it is triggered by these two lines:
    var chartValStart:Array = canvas.localToData(dragStart);
    var chartValEnd:Array = canvas.localToData(dragEnd);
    Though they do not directly create the error, they actually run fine and the error occurs briefly after they have run. The NPE comes from LineSeries line 1460:
    var n:int = _renderData.filteredCache.length;

    It usually works when you zoom right to left at the base zoom level in a part of the chart with no data in it.

    I suppose flex thinks that filteredCache has been created, yet this is not always the case. Or something.

    By the way speaking of bugs there’s a bug in your blog… as I am a returning commenter it remembers my details and hides the form including the CAPTCHA. On submit I get an error that I didn’t enter the captcha and (the highly frustrating part) my comment is deleted and I have to rewrite it.

  9. a.samy
    January 21st, 2011 at 07:41 | #9

    Check this example too,
    http://www.flex4ex.com/?p=69

  10. Jaime GarcĂ­a
    February 24th, 2011 at 03:41 | #10

    Good morning ninja captain.

    How about this example but with a dynamic linechart?

    I have a dynamic graph object that get series from a xml file. User can change graphs options (color, axis options, forms…) I don’t know how to apply this code to my object.

    Any idea?

  11. Harry
    June 3rd, 2011 at 15:45 | #11

    Fletch I think I have solved that bug. I was getting that bug too but I was able to reproduce it consistently by just click on one spot and releasing. The problem is that dragEnd is not being updated if you do not drag a box. Put this in finishDraw() after chartCanvas.clear()

    dragEnd = new Point(macroCurve.mouseX, macroCurve.mouseY);
    if (dragStart.x == dragEnd.x && dragStart.y == dragEnd.y) {
    return;
    }

  1. No trackbacks yet.
*