Show error when value of numeric stepper out of range - actionscript-3

When I set maximum and minimum for a numeric stepper, I still can enter a number out of that range. But it is automatically receive the maximum value if I enter a number greater than that. How can I show error for this case, but still keep set maximum and minimum value?

When the user hit ENTER key or focus out from the text input of the NumericStepper you can put your logic to check if the entered number is within the range as below:
<?xml version="1.0"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script><![CDATA[
import mx.controls.Alert;
private var isProcessRequiredOnFocusOut:Boolean = true;
private function addEventListeners():void {
numericStepper.addEventListener(KeyboardEvent.KEY_DOWN, checkRange, true);
numericStepper.addEventListener(FocusEvent.FOCUS_OUT, checkRange, true);
}
private function checkRange(event:Event):void {
if (event is KeyboardEvent && (event as KeyboardEvent).keyCode == Keyboard.ENTER) {
isProcessRequiredOnFocusOut = false;
processRangeCheck();
}
else if (event is FocusEvent && (event as FocusEvent).type == FocusEvent.FOCUS_OUT
&& isProcessRequiredOnFocusOut) {
processRangeCheck();
}
}
private function processRangeCheck():void {
var typedNumber:Number = Number(numericStepper.textDisplay.text);
if (typedNumber > numericStepper.maximum || typedNumber < numericStepper.minimum) {
Alert.show("The typed number " + typedNumber + " is out of range.", "Out of Range Warning");
}
isProcessRequiredOnFocusOut = true;
}
]]></fx:Script>
<s:Panel title="Show Error message when Typed Number is out of range in Numeric Stepper."
verticalCenter="0" horizontalCenter="0">
<s:VGroup paddingTop="10" paddingLeft="10"
paddingRight="10" paddingBottom="10"
horizontalAlign="center"
width="100%">
<s:Label text="The valid range of Numeric Stepper is 10-20."/>
<s:NumericStepper id="numericStepper"
minimum="10"
maximum="20"
creationComplete="addEventListeners()"/>
<s:Button label="Click here to focus out from the Text Input of Numeric Stepper."/>
</s:VGroup>
</s:Panel>
</s:Application>

Well, an intuitive way to check the entered value against the maximum and the minimum values of an NumericStepper component –while it's still getting entered, would be to listen to the keyboard event of KEY_UP on the textField property of the NumericStepper instance:
import fl.controls.NumericStepper;
import flash.events.KeyboardEvent;
var numericStepper:NumericStepper = new NumericStepper();
numericStepper.maximum = 250;
numericStepper.minimum = 3;
numericStepper.textField.addEventListener(KeyboardEvent.KEY_UP, function(e:Event):void {
var n:Number = Number(numericStepper.textField.text);
if (n > numericStepper.maximum || n < numericStepper.minimum) {
trace("Error: Out of Range!");
}
});
addChild(numericStepper);

Use mx_internal property to refer to the inputField of the NumericStepper
numericStepper.maximum = 250;
numericStepper.minimum = 3;
numericStepper.mx_internal::inputField.addEventListener(KeyboardEvent.KEY_UP, function(e:Event):void {
var n:Number = Number(numericStepper.mx_internal::inputField.text);
if (n > numericStepper.maximum || n < numericStepper.minimum) {
Alert.show("Error: Out of Range!");
}
});

Related

Can't get animation in Custom layout to work

I'd like to create a layout that I can animate my items in. So when a dataprovider gets set on my List, the List's layout will then animate those items onto the screen.
So I've created a CustomLayout and added an update() function.
In the updateDisplayList the items are traced out just fine.
But in the update() function where I want to do the animation, the items are tracing out as null even though if I trace layoutTarget's numElement's I get four! If I use a setTimeout after setting the _dataProvider in my Main app of a second or two, then call update() in the ListLayout, then the traces work fine. So my question is, how can I ensure that I call update() to animate the items in when they are actually available??
Main app:
<?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:custom="*"
creationComplete="handleCreationComplete()">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var _dataProvider:ArrayCollection;
public function set dataProvider(value:ArrayCollection):void
{
_dataProvider = value;
listLayout.update();
}
private function handleCreationComplete():void
{
var arr:Array = new Array( { label:"1" }, { label:"2" }, { label:"3" }, { label:"4" } );
dataProvider = new ArrayCollection(arr);
}
]]>
</fx:Script>
<s:List id="list"
dataProvider="{ _dataProvider }"
labelField="label">
<s:layout>
<custom:ListLayout id="listLayout" />
</s:layout>
</s:List>
</s:WindowedApplication>
ListLayout:
package
{
import mx.core.ILayoutElement;
import spark.components.supportClasses.GroupBase;
import spark.layouts.supportClasses.LayoutBase;
/**
* ...
* #author
*/
public class ListLayout extends LayoutBase
{
public function update():void
{
var layoutTarget:GroupBase = target;
//layoutTarget.autoLayout = false;
var count:int = layoutTarget.numElements;
trace(count); //traces 4
for (var i:int = 0; i < count; i++)
{
var item:ILayoutElement = useVirtualLayout ? layoutTarget.getVirtualElementAt(i) : layoutTarget.getElementAt(i);
trace(item); //traces null
}
}
override public function updateDisplayList(width:Number, height:Number):void
{
super.updateDisplayList(width, height);
var layoutTarget:GroupBase = target;
var count:int = layoutTarget.numElements;
for (var i:int = 0; i < count; i++)
{
var item:ILayoutElement = useVirtualLayout ? layoutTarget.getVirtualElementAt(i) : layoutTarget.getElementAt(i);
trace(item); //traces out the item
}
}
}
}
Before you call update on your listLayout, you need to validate your "list" to make sure all the elements are available. So add one line list.validateNow() in set dataProvider function as below:
public function set dataProvider(value:ArrayCollection):void
{
_dataProvider = value;
list.validateNow();
listLayout.update();
}

ColorPicker text input field truncates the value on paste

When pasting into a ColorPicker text field the color value is truncated sometimes.
For example, if I paste, #0000FF then the text field displays, #0000F.
If I close the color picker and then open it open again and paste again it shows #0000FF.
I started trying to fix it and then I found this code in the textInput change handler in the SwatchPanel class:
private function textInput_changeHandler(event:Event):void
{
// Handle events from hex TextField.
var color:String = ITextInput(event.target).text;
if (color.charAt(0) == "#")
{
textInput.maxChars = 7;
color = "0x"+color.substring(1);
}
else if (color.substring(0,2) == "0x")
{
textInput.maxChars = 8;
}
else
{
textInput.maxChars = 6;
color = "0x"+color;
}
highlight.visible = false;
isOverGrid = false;
selectedColor = Number(color);
dispatchEvent(new Event("change"));
}
It looks like the RichEditableText is truncating the value before the change event has a chance to update the maxChars value. From RichEditableText:
if (maxChars != 0)
{
var length1:int = text.length - delLen;
var length2:int = textToInsert.length;
// it is truncated from "#0000FF" to "#0000F" here
if (length1 + length2 > maxChars)
textToInsert = textToInsert.substr(0, maxChars - length1);
}
So it looks like in SwatchPanel the change event, textInput_changeHandler is too late to change the maxChars property to not truncate pasted values. Is there any recommendations on how to fix this?
Full Example Code:
<?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">
<mx:ColorPicker id="colorPicker"
horizontalCenter="0"
verticalCenter="0"
paste="trace('pasted')"
valueCommit="colorPicker_valueCommitHandler(event)"
creationComplete="colorpicker1_creationCompleteHandler(event)"/>
<fx:Script>
<![CDATA[
protected function colorpicker1_creationCompleteHandler(event:FlexEvent):void {
var textinput:ITextInput;
if (colorPicker.dropdown==null) {
var swatch:SwatchPanel = colorPicker.getDropdown();
textinput = swatch.textInput;
}
else {
textinput = colorPicker.getDropdown().textInput;
}
IEventDispatcher(textinput).addEventListener("paste", pasteincolorpicker);
IEventDispatcher(textinput).addEventListener("change", pasteincolorpicker);
IEventDispatcher(textinput).addEventListener("changing", pasteincolorpicker);
IEventDispatcher(textinput).addEventListener("valueCommit", pasteincolorpicker);
}
private function pasteincolorpicker(event:Event):void
{
trace("event.type: " + event.type);
if (event.type=="changing") {
event.currentTarget.maxChars = 7;
}
var text:String = "";
if (event is TextOperationEvent) {
text = TextOperationEvent(event).operation.textFlow.getText();
}
trace("pasting: " + text);
}
protected function colorPicker_valueCommitHandler(event:FlexEvent):void
{
trace("value commit: ");
}
]]>
</fx:Script>
</s:WindowedApplication>
It seems to occur when using FTE in mx components in your Flex project. Below is a extended ColorPicker that fixes the issue. This doesn't seem to work when FTE in mx components is not checked (tested on one project).
use namespace mx_internal;
public class ColorPicker extends mx.controls.ColorPicker
{
public function ColorPicker()
{
super();
}
override protected function createChildren():void
{
super.createChildren();
var swatch:SwatchPanel = getDropdown();
if (!swatch.textInput.hasEventListener(FlexEvent.CHANGING)) {
swatch.textInput.addEventListener(FlexEvent.CHANGING, changingEventHandler);
}
}
protected function changingEventHandler(event:Event):void
{
// set it to max characters of 8
// allow room for "123456", "#234567", "0x345678" before paste truncates it
// change event handler in SwatchPanel will set it back to 8, 7 or 6 max chars
if (event is TextOperationEvent) {
dropdown.textInput.maxChars = 8;
//text = TextOperationEvent(event).operation.textFlow.getText();
//trace("changing to: " + text);
}
}
}

AS3 Extended Jump when Holding Button

So my mario project must include a staple of Mario's movement, and that of course is the option to jump a short height or a fairly large one. As we all know, holding down the jump button makes him jump higher, that's what my goal is here. In my case, that button is X and I am unsure of how to do that.
This is currently my unsuccessful attempt, and gravity is set to 0.87 by default in my variables.
This is in my keyDownHandler (when the key is pressed)
if (event.keyCode == Keyboard.X && onGround == true)
{
vy += jumpForce;
holdJump = true;
onGround = false;
if(holdJump == true && onGround == false)
{
_mario.y += 1;
}
else
{
vy = vy + (grav * 0.20);
holdJump = false;
}
This is in my keyUpHandler (when the key is not pressed/let go)
if (event.keyCode == Keyboard.X)
{
if (holdJump == false)
{
accy = 0;
gravity = 0.80;
incSpeedY = 0;
}
}
Ok, I've extended my comment.
You can use standard vy=vyLast-g*(t-tLast), and just set vyLast to min(0,vyLast) when jump key is released, and set it to jump starting speed when jump key is pressed on the ground.
Here is the sample Adobe Air application with jumping red circle. It implements the logics that I've described into the comment:
<?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"
>
<fx:Script>
<![CDATA[
import flash.utils.getTimer;
import mx.graphics.SolidColor;
public var marioY:Number = 0; //jump height above ground (meters)
public var g:Number = -9.81; //meter/(second^2)
public var lastTime:Number = NaN;
public var lastVerticalSpeed:Number = 0;//speed of a flight -meters/second
public var jumpSpeed:Number = 10;//initial jump speed - meters/second
public var timeRatio:Number = 1000;//milliseconds in a second
public var heightRatio:Number = 50; //pixels/meter
protected function get landed():Boolean{
return marioY <= 0 && lastVerticalSpeed <= 0;
}
protected function onKeyDown(event:KeyboardEvent):void{
if(event.keyCode==Keyboard.UP && landed){
lastVerticalSpeed = jumpSpeed;
trace('fly!');
}
}
protected function onKeyUp(event:KeyboardEvent):void{
if(event.keyCode==Keyboard.UP){
lastVerticalSpeed = Math.min(0,lastVerticalSpeed);
trace('fall!');
}
}
protected function onEnterFrame(event:Event):void{
if(!isNaN(lastTime)){
var deltaTime:Number = (getTimer() - lastTime)/timeRatio;
marioY+=lastVerticalSpeed*deltaTime;
if(landed){
lastVerticalSpeed=0;
marioY=0;
}else{
lastVerticalSpeed+=g*deltaTime;
}
}
mario.y=area.height-marioY*heightRatio-20;
lastTime = getTimer();
}
]]>
</fx:Script>
<s:Group width="100%" height="100%" keyDown="onKeyDown(event)" keyUp="onKeyUp(event)"
enterFrame="onEnterFrame(event)" id="area"
creationComplete="area.setFocus()"
>
<s:Rect width="100%" height="100%" fill="{new SolidColor(0x0000FF)}"/>
<s:Ellipse id="mario" width="10" height="10" fill="{new SolidColor(0xFF0000)}"
y="100" x="100"
/>
</s:Group>
</s:WindowedApplication>

Is there a way to generate true random numbers in Flash Builder?

I have a project that generates strings of random numbers that are used to select items from a test bank. I noticed that some of the items were being selected at disproportionately high rates so I decided to check the 'randomness' of Math.Random.
The following code produces randomly ordered lists of the numbers 0 through n-1. It then counts the number of times the first item is a 0, 1, 2,... ,n-1
You can change the number of samples that are generated with the horizontal slider.
What I have produced appears to indicate that the random numbers are not at all random For example, if I select 100 samples of a six digit string, I get the following distribution suggesting that 0, and 5 are greatly under represented: 11,23,15,18,24,9
This pattern holds as I re-run the simulation.
I've checked my code but would greatly appreciate the insight of others concerning the accuracy of this.
I've heard that AS3 does not produce true random numbers, but can they really be this bad?
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" >
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
private var startingArray:Array = [];
private var questionsArray:Array;
private var countArray:Array;
private var randomNumbers:int = 3;
private function calculate():void{
countArray = [0,0,0,0,0,0,0,0,0,0];
for( var i:int = 0; i < slider.value; i++){
questionsArray = [];//Reset the list of questions
createRandomListOfQuestions(randomNumbers);
}
result.text = String(countArray);
}
public function createRandomListOfQuestions(_numQuestions:int):void{
//Create an array containing the sequence of test questions
var numQuestions:int=_numQuestions;
//Reset the array
startingArray=[];//Contains a randomized question order
for (var i:int=0;i<numQuestions; i++){//Create an array of question numbers starting at 1
var count:int = 0
startingArray.push(i);
}
//splice() removes one or more elements from an array and returns the deleted elements,
//here only one (as specified in the second argument)
while (startingArray.length > 0) {//Create a randomized list (questionsArray) from startingArray
var val:int =startingArray.splice(Math.round(Math.random() * (startingArray.length - 1)), 1)
questionsArray.push(val);
if(count == 0){
countArray[val] += 1
count++
}
}
questionsArrayText.text += String(questionsArray) + "\r";
}
private function changeEvt(event:Event):void {
randomNumbers = event.currentTarget.selectedItem.data
}
]]>
</mx:Script>
<mx:VBox horizontalCenter="0" verticalCenter="0">
<mx:Text x="487" y="261" text="{}" width="500" id="result"/>
<mx:ComboBox change="changeEvt(event)" >
<mx:ArrayCollection>
<mx:Object label="Three" data="3"/>
<mx:Object label="Four" data="4"/>
<mx:Object label="Five" data="5"/>
<mx:Object label="Six" data="6"/>
<mx:Object label="Seven" data="7"/>
<mx:Object label="Eight" data="8"/>
<mx:Object label="Nine" data="9"/>
<mx:Object label="Ten" data="10"/>
</mx:ArrayCollection>
</mx:ComboBox>
<mx:Button label="New list" click="calculate()"/>
<mx:HSlider id="slider" value="5" minimum="5" maximum="100" snapInterval="1" />
<mx:Label text="Random Numbers: {Math.round(slider.value) }"/>
</mx:VBox>
<mx:Text id="questionsArrayText" horizontalCenter="0" verticalCenter="0"/>
</mx:Application>
There are numerous implementations of custom random number generation available; however, ActionScript's Math.random() produces an expected distribution given a fair number of samples.
Below are distributions via Math.random() generating a number between 0 and 100. Vertical axis shows the number of times that value was generated.
1,000,000 Samples
100,000 Samples
10,000 Samples
1,000 Samples
100 Samples
Code to generate and visualize samples:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.events.FlexEvent;
[Bindable]
protected var data:ArrayList = new ArrayList();
protected function creationCompleteHandler(event:FlexEvent):void
{
var i:uint = 0;
var set:Dictionary = new Dictionary();
const ITERATIONS:uint = 100;
for (i = 0; i < 100; i++)
{
set[i] = {value: i, count: 0};
}
for (i = 0; i < ITERATIONS; i++)
{
var n:uint = Math.random() * 100;
set[n].count++;
}
for (i = 0; i < 100; i++)
{
data.addItem({value: set[i].value, count: set[i].count});
}
}
]]>
</fx:Script>
<fx:Declarations>
<mx:SolidColor id="areaFill"
color="0x123456"
alpha=".3" />
<mx:SolidColorStroke id="areaStroke"
color="0x123456" />
</fx:Declarations>
<mx:AreaChart height="100%"
width="100%"
paddingLeft="5"
paddingRight="5"
showDataTips="true"
dataProvider="{data}">
<mx:verticalAxis>
<mx:LinearAxis title="count" />
</mx:verticalAxis>
<mx:horizontalAxis>
<mx:CategoryAxis categoryField="value"
title="value" />
</mx:horizontalAxis>
<mx:series>
<mx:AreaSeries yField="count"
areaStroke="{areaStroke}"
areaFill="{areaFill}">
</mx:AreaSeries>
</mx:series>
</mx:AreaChart>
</s:Application>
You may call random.org, but you will have to wait until the web site returns using an event listener.
function getRandomNum(min, max) {
var randTextURLRequest:URLRequest = new URLRequest("http://www.random.org/integers/?num=1&min=" + min + "&max=" + max + "&col=1&base=10&format=plain&rnd=new");
var randTextLoader:URLLoader = new URLLoader();
randTextLoader.addEventListener(Event.COMPLETE, randCompleteHandler);
randTextLoader.load(randTextURLRequest);
}
function randCompleteHandler(e:Event)
{
var randVal = Number(randTextLoader.data);
trace("Number " + randVal + " selected");
}
// You may then type in your code getRandomNum(1, 100)

Flex/AS3: a generic click-and-drag zoom example, working except for one issue

I adopted the code from this website,
http://blog.ninjacaptain.com/2010/03/flex-chart-zoom-window/
Note that the bug mentioned in the web blog concerning displaying datatips appears to have been fixed (at least as of 4.5.1 SDK). I'm able to see the datatips fine.
The code works well except for one issue. I've added the complete code below so you can just copy and paste as a new Flex application and run it.
The problem is simply when the user clicks once without dragging, which gives the following error (make sure you click it AS SOON AS the app first runs):
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.charts.chartClasses::CartesianDataCanvas/localToData()[E:\dev\4.5.1\frameworks\projects\charts\src\mx\charts\chartClasses\CartesianDataCanvas.as:580]
Is there a way to capture when the user clicks without dragging like this and call some function to handle it? Or, any way to avoid this error? Thanks for any comments/suggestions.
The code is:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
initialize="init()" width="600" height="520">
<fx:Script>
<![CDATA[
[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.graphics.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;
}
]]>
</fx:Script>
<s:VGroup>
<mx:Panel title="Line Chart">
<mx:LineChart id="chart1"
mouseDown="startDraw(event)"
mouseMove="showDraw(event)"
mouseUp="finishDraw(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()" />
</s:VGroup>
</s:Application>
UPDATE:
Here's the solution, in case it's useful to others. I've added an if statement to check for null dragStart and dragEnd values, as discussed in the answer below. Also, I've removed the drop shadow that flex places by default on the line series, so a warning doesn't appear if the zoom area the user selects is too small.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
initialize="init()" width="600" height="520">
<fx:Script>
<![CDATA[
[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.graphics.clear();
if (dragStart && dragEnd) { // Solution to original posted quesion
// 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];
}
// reset values for next time
dragStart=null;
dragEnd=null;
}
// resets the axis max/mins
private function resetZoom():void{
haxis.minimum = NaN;
haxis.maximum = NaN;
vaxis.minimum = NaN;
vaxis.maximum = NaN;
}
]]>
</fx:Script>
<s:VGroup>
<mx:Panel title="Line Chart">
<mx:LineChart id="chart1"
mouseDown="startDraw(event)"
mouseMove="showDraw(event)"
mouseUp="finishDraw(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:seriesFilters>
<fx:Array/>
</mx:seriesFilters>
</mx:LineChart>
</mx:Panel>
<mx:Button label="Reset Zoom" click="resetZoom()" />
</s:VGroup>
</s:Application>
You are getting this error because the variable dragEnd is never set if the user just clicks the mouse. The easiest way to prevent this would be check for null values inside your finishDraw function:
private function finishDraw(e:MouseEvent):void
{
zooming = false;
chartCanvas.clear();
if(dragStart && dragEnd)
{
//your stuff here
//...
}
//reset values for next time
dragStart=null;
dragEnd=null;
}
And this should avoid any further 1009 Errors there. Beware I am getting some Warnings if I drag a tiny small zoom window and then release the mouse because Flash has a limit on how big a DisplayObject can be, so you should validate also the dimensions of the zoom window.
Hope this helps!