1 Reply Latest reply on Mar 2, 2007 8:52 AM by beckerb4

    Transforming from data to local coords inacurate on Log scale

    beckerb4 Level 1
      This post related to transforming between corrdinate spaces using Flex 2 Charting.
      I found that transforming from data coordinates to local coordinates using chart.transformCache or .dataToLocal is inacurate on a Log scale. Furthermore, the 2 methods of conversion give different answers. Which approach is recommended to transfrom from data to local coords?

      The two ways I have tried are
      var pp:Array = [{dx: dataXPos, dy: dataYPos}];
      dataTransform.transformCache(pp, "dx", "x", "dy", "y");
      p = pp[0]; // local/screen coords are (p.x, p.y)
      and
      var q:Poin = this.chart.dataToLocal(dataXPos, dataYPos);


      The first method seems to give the correct answer on a LinearAxis, and the second methods appears to give a somewhat inaccurate answer on a LinearAxis (its off by about 5% or so).

      On a LogScale axis, the first method gives a wildly inaccurate answer (off by a large factor), and the second method seems to give an accurate answer but offset by some constant factor. In other words if I subtract some constant like 65 from the result, the answer seems to be correct.

      Below is a program that demonstrates these isasues. Any help in understanding what is going on would be appreciated.
      The program shows a bubbleChart with some referenceLines. The ReferenceLine class figures out where to draw the line based on the data position passed in. I have alerts to show the results of all the different transformation methods.

      <?xml version="1.0" encoding="utf-8"?>
      <mx:Application
      xmlns:mx=" http://www.adobe.com/2006/mxml"
      layout="absolute" pageTitle="Experiments in Flex Charting"
      xmlns:e="generator.views.elements.*"
      width="100%" height="100%" creationComplete="init()" >

      <mx:Script> <![CDATA[
      import mx.formatters.NumberFormatter;
      import mx.collections.*;
      import mx.charts.*
      import generator.views.elements.ReferenceLine;

      // test case to show missing single bubble in bubble chart

      [Bindable]
      private var expenses:ArrayCollection = new ArrayCollection([
      {Month: "Jan", Profit: 200, Expenses: 1.12, Amount: 970},
      {Month: "Feb", Profit: 2100, Expenses: 5.6, Amount: 850},
      {Month: "Mar", Profit: 900, Expenses: 10.725, Amount: 1450},
      {Month: "Apr", Profit: 1700, Expenses: 100.02, Amount: 1050}
      ]);

      private function init():void
      {
      var refLineY1:ReferenceLine = new ReferenceLine("refLineY1", 10, "10", true, horzAxis);
      var refLineY2:ReferenceLine = new ReferenceLine("refLineY2", 100, "100", true, horzAxis);
      var refLineX:ReferenceLine = new ReferenceLine("refLineX", 1000, "1,000", false, vertAxis);
      bubble2.backgroundElements = [refLineX, refLineY1, refLineY2];
      }
      ]]>
      </mx:Script>

      <mx:HBox height="100%" width="100%">
      <mx:BubbleChart id="bubble2" paddingLeft="5" height="100%" width="100%"
      paddingRight="5" dataProvider="{expenses}" showDataTips="true">
      <mx:horizontalAxis>
      <mx:LogAxis id="horzAxis" baseAtZero="false" title="Profit" />
      </mx:horizontalAxis>
      <mx:verticalAxis>
      <mx:LinearAxis id="vertAxis" baseAtZero="false" title="Expenses"/>
      </mx:verticalAxis>
      <mx:series>
      <mx:Array>
      <mx:BubbleSeries xField="Expenses" yField="Profit" name="Expenses/Profit" radiusField="Amount" displayName="Profit2" />
      </mx:Array>
      </mx:series>
      </mx:BubbleChart>
      </mx:HBox>


      </mx:Application>
        • 1. Re: Transforming from data to local coords inacurate on Log scale
          beckerb4 Level 1
          ----------------------------------------------------------------- ReferenceLine.as ------------------------------
          package generator.views.elements {

          import mx.charts.chartClasses.*;
          import mx.charts.*;
          import flash.display.*;
          import flash.events.*;
          import flash.geom.*;
          import mx.controls.*;


          /**
          * Draws a horizontal or vertical reference line at the specified position.
          *
          * @author Barry Becker
          */
          public class ReferenceLine extends ChartElement {

          /** the current position of the crosshairs */
          private var position:Number;

          /** the label for the reference line (Avg or percentile value) */
          private var referenceLabel:Label;

          /** vertical line if true. */
          private var isVertical:Boolean = true;

          private var axis:NumericAxis;

          /**
          * Constructor
          * @param value - specified line postion.
          * @param isVertical - vertical or horizontal ref line.
          */
          public function ReferenceLine(labelText:String, value:Number, formattedValue:String, isVertical:Boolean, axis:NumericAxis):void
          {
          this.isVertical = isVertical;
          this.position = value;
          this.axis = axis;

          /* create our labels and tooltips for the elements. */
          referenceLabel = new Label();
          referenceLabel.text = labelText;

          var tip:String = labelText + " = " + formattedValue;
          referenceLabel.toolTip = tip;
          this.toolTip = tip;

          addChild(referenceLabel);
          }

          /**
          * Draw the reference line and its label.
          */
          override protected function updateDisplayList(unscaledWidth:Number,
          unscaledHeight:Number):void
          {
          super.updateDisplayList(unscaledWidth, unscaledHeight);

          var g:Graphics = graphics;
          g.clear();

          // draw the line. This should somehow come from css.
          g.lineStyle(1, 0x005364, 0.5);

          var labelWidth:int = referenceLabel.measuredWidth;
          var labelHeight:int = referenceLabel.measuredHeight;
          referenceLabel.setActualSize(labelWidth, labelHeight);
          var p:Object;
          var pp:Array;
          var q:Point

          if (isVertical)
          {
          // transform from data to screen coords
          pp = [{dx: position, dy: 0}];
          dataTransform.transformCache(pp, "dx", "x", "dy", "y");
          p = pp[0];

          // compare transformCache result with dataToLocal
          var testP:Point = this.chart.dataToLocal(position, 0);
          Alert.show("transformCache x= "+p.x +" dataToLocal x="+testP.x);

          if (axis is LogAxis)
          {
          q = this.chart.dataToLocal(position, 0);
          //Alert.show("data x="+position+ " coord x="+p.x +" q.x="+q.x);
          // Hack. Why does this work? There must be a flex bug when using log scales.

          // compare transformCache result with dataToLocal. Why is a fudgeFactor needed to get
          // the line to appear where it should on the axis?
          var fudgeFactor:int = 74.0;
          Alert.show("log transformCache x= "+p.x +" dataToLocal x="+q.x + " expected="+(q.x - fudgeFactor));

          p.x = q.x - fudgeFactor;
          }
          g.moveTo(p.x, 0);
          g.lineTo(p.x, unscaledHeight);

          // adjust the label if the line is at an extreme
          var xpos:int = (p.x < (unscaledWidth - labelWidth)) ? (p.x + 1) : (p.x - labelWidth - 4)
          referenceLabel.move(xpos, 0);
          }
          else // horizontal
          {
          // transform from data to screen coords
          //p = this.chart.dataToLocal(0, position);
          pp = [{dx: 0, dy: position}];
          dataTransform.transformCache(pp, "dx", "x", "dy", "y");
          p = pp[0];

          if (axis is LogAxis) {
          q = this.chart.dataToLocal(position, 0);
          //Alert.show("data x="+position+ " coord x="+p.x +" q.x="+q.x);
          // Hack. Why does this work? There must be a flex bug when using log scales.
          p.y = q.y;
          }

          g.moveTo(0, p.y);
          g.lineTo(unscaledWidth, p.y);

          // adjust the label if the line is at an extreme
          var ypos:int = (p.y < (unscaledHeight - labelHeight - 4)) ? p.y : (p.y - labelWidth - 4)
          referenceLabel.move(unscaledWidth - labelWidth, ypos);
          }
          }


          /**
          * Since we store our selection in data coordinates,
          * we need to redraw when the mapping between data coordinates and screen coordinates changes.
          */
          override public function mappingChanged():void
          {
          invalidateDisplayList();
          }

          }
          }