package flare.vis.axis { import flare.animate.Transitioner; import flare.display.TextSprite; import flare.scale.IScaleMap; import flare.scale.LinearScale; import flare.scale.Scale; import flare.scale.ScaleType; import flare.util.Sort; import flare.util.Stats; import flare.util.Strings; import flare.util.Vectors; import flash.display.DisplayObject; import flash.display.Sprite; import flash.geom.Point; import flash.text.TextFormat; import flash.utils.Dictionary; /** * A metric data axis consisting of axis labels and gridlines. * *
Axis labels can be configured both in terms of text formatting,
* orientation, and position. Use the labelOffsetX
or
* labelOffsetY
property to adjust label positioning. For
* example, labelOffsetX = -10;
places the anchor point for
* the label ten pixels to the left of the data bounds, whereas
* labelOffsetX = 10;
will place the point 10 pixels to the
* right of the data bounds. One could simultaneously adjust the
* horizontalAnchor
property to align the labels as desired.
*
Similarly, axis gridlines can also be configured. The
* lineCapX1
, lineCapX2
, lineCapY1
,
* and lineCapY2
properties determine by how much the
* grid lines should exceed the data bounds. For example,
* lineCapX1 = 5
causes the grid line to extend an extra
* 5 pixels to the left. Each of these values should be greater than or
* equal to zero.
axisScale
. If null,
* the formatting pattern for the axisScale
is used. */
public function get labelFormat():String {
return _labelFormat==null ? null
: _labelFormat.substring(3, _labelFormat.length-1);
}
public function set labelFormat(fmt:String):void {
_labelFormat = "{0:"+fmt+"}"; updateLabels();
}
/** The number of labels and gridlines to generate by default. If this
* number is zero or less (default -1), the number of labels will be
* automatically determined from the current scale and size. */
public function get numLabels():int {
// if set positive, return number
if (_numLabels > 0) return _numLabels;
// if ordinal return all labels
if (ScaleType.isOrdinal(axisScale.scaleType)) return -1;
// otherwise determine based on axis size (random hack...)
var lx:Number = _xb-_xa; if (lx<0) lx = -lx;
var ly:Number = _yb-_ya; if (ly<0) ly = -ly;
lx = (lx > ly ? lx : ly);
return lx > 200 ? 10 : lx < 20 ? 1 : int(lx/20);
}
public function set numLabels(n:int):void { _numLabels = n; }
/** TextFormat (font, size, style) for axis title text. */
public function get axisTitle():String { return _axisTitleText; }
public function set axisTitle(t:String):void { _axisTitleText = t; updateLabels(); }
/** TextFormat (font, size, style) for axis title text. */
public function get titleTextFormat():TextFormat { return _titleTextFormat; }
public function set titleTextFormat(f:TextFormat):void { _titleTextFormat = f; updateLabels(); }
/** The text rendering mode to use for title TextSprite.
* @see flare.display.TextSprite. */
public function get titleTextMode():int { return _titleTextMode; }
public function set titleTextMode(m:int):void { _titleTextMode = m; updateLabels(); }
/** The horizontal anchor point for axis labels.
* @see flare.display.TextSprite. */
public function get horizontalAnchor():int { return _anchorH; }
public function set horizontalAnchor(a:int):void { _anchorH = a; updateLabels(); }
/** The vertical anchor point for axis labels.
* @see flare.display.TextSprite. */
public function get verticalAnchor():int { return _anchorV; }
public function set verticalAnchor(a:int):void { _anchorV = a; updateLabels(); }
/** The x-coordinate of the axis origin. */
public function get originX():Number {
return (ScaleType.isQuantitative(axisScale.scaleType) ? X(0) : x1);
}
/** The y-coordinate of the axis origin. */
public function get originY():Number {
return (ScaleType.isQuantitative(axisScale.scaleType) ? Y(0) : y1);
}
// -- Initialization --------------------------------------------------
/**
* Creates a new Axis.
* @param axisScale the axis scale to use. If null, a linear scale
* is assumed.
*/
public function Axis(axisScale:Scale=null)
{
this.axisScale = axisScale ? axisScale : new LinearScale();
_prevScale = this.axisScale;
initializeChildren();
}
/**
* Initializes the child container sprites for labels and grid lines.
*/
protected function initializeChildren():void
{
addChild(new Sprite()); // add gridlines
addChild(new Sprite()); // add labels
addChild(new TextSprite()); // add title
}
// -- Updates ---------------------------------------------------------
/**
* Updates this axis, performing filtering and layout as needed.
* @param trans a Transitioner for collecting value updates
* @return the input transitioner.
*/
public function update(trans:Transitioner):Transitioner
{
var t:Transitioner = (trans!=null ? trans : Transitioner.DEFAULT);
// compute directions and offsets
_xd = lineLengthX < 0 ? -1 : 1;
_yd = lineLengthY < 0 ? -1 : 1;
_xlo = _xd*labelOffsetX + (labelOffsetX>0 ? lineLengthX : 0);
_ylo = -_yd*labelOffsetY + (labelOffsetY<0 ? lineLengthY : 0);
// run updates
filter(t);
layout(t);
updateLabels(); // TODO run through transitioner?
updateGridLines(); // TODO run through transitioner?
updateTitle();
return trans;
}
// -- Lookups ---------------------------------------------------------
/**
* Returns the horizontal offset along the axis for the input value.
* @param value an input data value
* @return the horizontal offset along the axis corresponding to the
* input value. This is the x-position minus x1
.
*/
public function offsetX(value:Object):Number
{
return axisScale.interpolate(value) * (_xb - _xa);
}
/**
* Returns the vertical offset along the axis for the input value.
* @param value an input data value
* @return the vertical offset along the axis corresponding to the
* input value. This is the y-position minus y1
.
*/
public function offsetY(value:Object):Number
{
return axisScale.interpolate(value) * (_yb - _ya);
}
/** @inheritDoc */
public function X(value:Object):Number
{
return _xa + offsetX(value);
}
/** @inheritDoc */
public function Y(value:Object):Number
{
return _ya + offsetY(value);
}
/** @inheritDoc */
public function value(x:Number, y:Number, stayInBounds:Boolean=true):Object
{
// project the input point onto the axis line
// (P-A).(B-A) / |B-A|^2 == fractional projection onto axis line
var dx:Number = (_xb-_xa);
var dy:Number = (_yb-_ya);
var f:Number = ((x-_xa)*dx + (y-_ya)*dy) / (dx*dx + dy*dy);
// correct bounds, if desired
if (stayInBounds) {
if (f < 0) f = 0;
if (f > 1) f = 1;
}
// lookup and return value
return axisScale.lookup(f);
}
/**
* Clears the previous axis scale used, if cached.
*/
public function clearPreviousScale():void
{
_prevScale = axisScale;
}
// -- Filter ----------------------------------------------------------
/**
* Performs filtering, determining which axis labels and grid lines
* should be visible.
* @param trans a Transitioner for collecting value updates.
*/
protected function filter(trans:Transitioner) : void
{
var ordinal:uint = 0, i:uint, idx:int = -1, val:Object;
var label:AxisLabel = null;
var gline:AxisGridLine = null;
var nl:uint = labels.numChildren;
var ng:uint = gridLines.numChildren;
var keepLabels:Vector.