首页 > 代码库 > Linechart + Datagrid 互动展示数据 (Linechart自定义数据点选择线)

Linechart + Datagrid 互动展示数据 (Linechart自定义数据点选择线)

如上图示,在linechart中添加红色Y线,拖动该线的过程中,经过数据点时,会实时更新datagrid中对应的X、Y值数据。


实现要点:

1.linechart添加Y线

继承mx.charts.chartClasses.ChartElement,自定义Y线。

package
{
	import flash.display.Graphics;
	import flash.geom.Point;
	import flash.text.TextField;
	
	import mx.charts.chartClasses.CartesianChart;
	import mx.charts.chartClasses.CartesianTransform;
	import mx.charts.chartClasses.ChartElement;
	import mx.charts.chartClasses.ChartState;
	import mx.charts.chartClasses.IAxis;
	
	public class DashedLines extends ChartElement
	{
		public function DashedLines()
		{
			super();
		}
		
		private var _yValue:Number = NaN;
		private var _xValue:Date = null;

		public function get xValue():Date
		{
			return _xValue;
		}
		public function set xValue(value:Date):void
		{
			_xValue = value;
			invalidateDisplayList();
		}

		public function get yValue():Number
		{
			return _yValue;
		}
		
		public function set yValue(value:Number):void
		{
			_yValue = value;
			invalidateDisplayList();
		}
		
		/**
		 * 实线部分的长度
		 * @default 10
		 */
		public var length:Number = 10;
		
		/**
		 * 空白部分的长度
		 * @default 5
		 */
		public var gap:Number = 0;
		
		/**
		 * 线条的宽度
		 * @default 1
		 */
		public var lineThickness:Number = 3;
		
		/**
		 * 线条的颜色
		 * @default 黑色
		 */
		public var lineColor:uint = 0;
		
		private var _displayName:String;
		
		/**
		 * 该线所对应的数值名称(平均值,最大值等等)
		 * @default 
		 */
		public function get displayName():String
		{
			return _displayName;
		}
		
		/**
		 * @private
		 */
		public function set displayName(value:String):void
		{
			_displayName = value;
			invalidateDisplayList();
		}
		
		protected var label:TextField;
		
		override protected function createChildren():void
		{
			super.createChildren();
			
			if(!label)
			{
				label = new TextField();
				label.mouseEnabled = false;
				addChild(label);
			}
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			
			if (!chart||
				chart.chartState == ChartState.PREPARING_TO_HIDE_DATA ||
				chart.chartState == ChartState.HIDING_DATA)
			{
				return;
			}
			
			var g:Graphics = this.graphics;
			g.clear();
			
			// 如果没有设置数据,不显示
			if(xValue == null)
			{
				return;
			}
			
			var w:Number = unscaledWidth;
			var h:Number = unscaledHeight;
			var vAxis:IAxis = CartesianChart(this.chart).verticalAxis;
			var x:Number = dataToLocal(xValue,0).x;
			var pFrom:Point = new Point(x,0);
			var pTo:Point = new Point(x,h);
			
			GraphicUtils.drawDashed(g, pFrom, pTo, this.length, this.gap, this.lineThickness, this.lineColor);
			
			label.text = (displayName ? (displayName + " : ") : "") + xValue;
			label.x = x > 21 ? x - 21 : x + 1;
			label.y = 1;
		}
		
		// 这个方法复制自LineSeries
		override public function dataToLocal(... dataValues):Point
		{
			var data:Object = {};
			var da:Array /* of Object */ = [ data ];
			var n:int = dataValues.length;
			
			if (n > 0)
			{
				data["d0"] = dataValues[0];
				dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).
					mapCache(da, "d0", "v0");
			}
			
			if (n > 1)
			{
				data["d1"] = dataValues[1];
				dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).
					mapCache(da, "d1", "v1");           
			}
			
			dataTransform.transformCache(da,"v0","s0","v1","s1");
			return new Point(data.s0 + this.x,
				data.s1 + this.y);
		}
	}
}


该类中主要用到dataTransform,他提供了数据点与坐标空间的对应转换,主类中通过xValue将Y线处的x值传入,通过该方法转换为Y线需要绘制的坐标位置。

该类用到工具类GraphicUtils

package
{
	import flash.display.Graphics;
	import flash.geom.Point;
	
	/**
	 * 一些绘图相关的方法
	 * @author lip
	 */
	public class GraphicUtils
	{
		public function GraphicUtils()
		{
		}
		
		
		/**
		 * 画虚线
		 * @param graphics 你懂的
		 * @param pFrom 起点
		 * @param pTo 终点
		 * @param length 实线段的长度
		 * @param gap 实线段的间距
		 * @param thickness 线的宽度
		 * @param color 线的颜色
		 */
		public static function drawDashed(graphics:Graphics, pFrom:Point, pTo:Point, length:Number = 5, gap:Number = 5, thickness:Number = 1, color:uint = 0):void
		{
			var max:Number = Point.distance(pFrom, pTo);
			var l:Number = 0;
			var p3:Point;
			var p4:Point;
			graphics.lineStyle(thickness, color);
			while (l < max)
			{
				p3 = Point.interpolate(pTo, pFrom, l / max);
				l += length;
				if (l > max)
					l = max;
				p4 = Point.interpolate(pTo, pFrom, l / max);
				graphics.moveTo(p3.x, p3.y)
				graphics.lineTo(p4.x, p4.y)
				l += gap;
			}
		}
	}
}

2、datagrid对应样式的定义

   请参考博文:datagrid样式


3、组件主类

<?xml version="1.0" encoding="utf-8"?>
<s:BorderContainer 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:customcomponent="com.bjrz.view.centerview.customcomponent.*"
				   xmlns:amcharts="http://www.amcharts.com/com_internal"
				   width="100%" height="100%"
				   creationComplete="generateChartData()" xmlns:local="*">
	<fx:Script>
		<![CDATA[
			import mx.charts.ChartItem;
			import mx.charts.chartClasses.IAxis;
			import mx.charts.chartClasses.Series;
			import mx.charts.series.items.LineSeriesItem;
			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.formatters.DateFormatter;
			[Bindable]
			private var attributes:ArrayCollection =   new ArrayCollection(   
				[{lineColor:"0xFF6600", dataItem:"label",time:"北京", currentValue:"label",unit:"北京", show:"true",describe:"北京"},
				{lineColor:"0x00ff00", dataItem:"label",time:"北京", currentValue:"label",unit:"北京", show:"true",describe:"北京"}]);
			private var dateFormat:DateFormatter = new DateFormatter();
			private function generateChartData():void
			{
				dateFormat.formatString = "HH:NN:SS";   
			}  
			[Bindable]
			private var expensesAC:ArrayCollection = new ArrayCollection( [
				{ Month: "Tue May 20 16:23:50 GMT+0800 2014", Profit: 2000, Expenses: 1500, Amount: 450 },
				{ Month: "Tue May 20 16:23:51 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:23:52 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:23:53 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:23:54 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:23:55 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:23:56 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:23:57 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:23:58 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:23:59 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:00 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:01 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:02 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:03 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:04 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:05 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:06 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:07 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:08 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:09 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:10 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:11 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:12 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:13 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:14 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:15 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:16 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:17 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:18 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:19 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 }]);
			[Bindable]
			private var expensesAC2:ArrayCollection = new ArrayCollection( [
				{ Month: "Tue May 20 16:23:50 GMT+0800 2014", Profit: 2100, Expenses: 1500, Amount: 460 },
				{ Month: "Tue May 20 16:23:51 GMT+0800 2014", Profit: 1100, Expenses: 200, Amount: 660 },
				{ Month: "Tue May 20 16:23:52 GMT+0800 2014", Profit: 1100, Expenses: 500, Amount: 340 },
				{ Month: "Tue May 20 16:23:53 GMT+0800 2014", Profit: 1100, Expenses: 1200, Amount: 960 },
				{ Month: "Tue May 20 16:23:54 GMT+0800 2014", Profit: 2100, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:23:55 GMT+0800 2014", Profit: 1100, Expenses: 200, Amount: 630 },
				{ Month: "Tue May 20 16:23:56 GMT+0800 2014", Profit: 1100, Expenses: 500, Amount: 360 },
				{ Month: "Tue May 20 16:23:57 GMT+0800 2014", Profit: 1100, Expenses: 1200, Amount: 930 },
				{ Month: "Tue May 20 16:23:58 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:23:59 GMT+0800 2014", Profit: 1100, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:00 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 320 },
				{ Month: "Tue May 20 16:24:01 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 300 },
				{ Month: "Tue May 20 16:24:02 GMT+0800 2014", Profit: 2410, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:03 GMT+0800 2014", Profit: 1010, Expenses: 200, Amount: 650 },
				{ Month: "Tue May 20 16:24:04 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 360 },
				{ Month: "Tue May 20 16:24:05 GMT+0800 2014", Profit: 1100, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:06 GMT+0800 2014", Profit: 2100, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:07 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:08 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 360 },
				{ Month: "Tue May 20 16:24:09 GMT+0800 2014", Profit: 1800, Expenses: 1200, Amount: 900 },
				{ Month: "Tue May 20 16:24:10 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 570 },
				{ Month: "Tue May 20 16:24:11 GMT+0800 2014", Profit: 1100, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:12 GMT+0800 2014", Profit: 1100, Expenses: 500, Amount: 300 },
				{ Month: "Tue May 20 16:24:13 GMT+0800 2014", Profit: 1100, Expenses: 1200, Amount: 940 },
				{ Month: "Tue May 20 16:24:14 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 540 },
				{ Month: "Tue May 20 16:24:15 GMT+0800 2014", Profit: 1000, Expenses: 200, Amount: 600 },
				{ Month: "Tue May 20 16:24:16 GMT+0800 2014", Profit: 1500, Expenses: 500, Amount: 340 },
				{ Month: "Tue May 20 16:24:17 GMT+0800 2014", Profit: 1100, Expenses: 1200, Amount: 940 },
				{ Month: "Tue May 20 16:24:18 GMT+0800 2014", Profit: 2400, Expenses: 575, Amount: 500 },
				{ Month: "Tue May 20 16:24:19 GMT+0800 2014", Profit: 1100, Expenses: 200, Amount: 640 }]);
			protected function dashedlines1_mouseDownHandler(event:MouseEvent):void
			{
				this.addEventListener(MouseEvent.MOUSE_MOVE,dashedLine_mouseMoveHandler);
				this.addEventListener(MouseEvent.MOUSE_UP,dashedlines1_mouseUpHandler);
			}
			
			protected function dashedlines1_mouseUpHandler(event:MouseEvent):void
			{
				this.removeEventListener(MouseEvent.MOUSE_MOVE,dashedLine_mouseMoveHandler);
				this.removeEventListener(MouseEvent.MOUSE_UP,dashedlines1_mouseUpHandler);
			}
			[Bindable]
			private var linePosition:Number = 2;
			
			protected function dashedLine_mouseMoveHandler(event:MouseEvent):void
			{
				dashedLine.xValue = http://www.mamicode.com/new Date(Number((((linechart.mouseX - 50)*((hAxis.maximum as Date).time-(hAxis.minimum as Date).time)/(linechart.width-50)>
该类中,主要方法是针对Y线的拖拽处理。基本拖拽处理不再赘述。在mouseMove的处理中,需要对xValue执行重新赋值,以便刷新绘制Y线。

因为linechart只提供了数据点转坐标空间,没有找到坐标空间转数据点的方法。所以需要将鼠标位置mouseX通过计算转化为数据点x,然后作为xValue传入。

同时,根据数据点x获取对应的y值。这是出现一个问题: 

mouseX是连续的,而数据点非连续。

因为我的需求中linechart数据点十分密集,极近于连续点。所以我采用的方法是,将在计算转化的时候,取近似值。只将时间为整秒的数据做转化传入。这样并不影响最终使用效果。如果有更好的处理方式,望留言告知。