0
votes

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>
1

1 Answers

1
votes

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!