ZedGraph源码学习(二)
昨天大致介绍下了PaneBase,只是介绍了界面伸缩的一些实现,和我开始说的Pane里包含了所有要绘制的元素不符,其实这些部分的实现,全交给了它的子类GraphPane来实现,下面来让我们看看这个类的一些实现。
这里我先说下在GraphPane里的坐标轴,X轴的情况不复杂,分别为下
轴(xAxis)与上面的x轴(x2Axis),Y轴则有些不同,它分为左边的Y轴集合(yAxisList),右边的Y轴集合(y2AxisList).还有一个比较重要的属性就是昨天所说的所需要画的所有的元素CurveItem集合CurveList。
我们先看下如何轴上的数据是如何初始化的。
在AxisChange()这个函数里,首先会调用GraphPane里的CurveList的GetRange()方法,我们先看下这个方法的基本实现。
public void GetRange( bool bIgnoreInitial, bool isBoundedRanges, GraphPane pane ) { double tXMinVal, tXMaxVal, tYMinVal, tYMaxVal; InitScale( pane.XAxis.Scale, isBoundedRanges ); InitScale( pane.X2Axis.Scale, isBoundedRanges ); foreach ( YAxis axis in pane.YAxisList ) InitScale( axis.Scale, isBoundedRanges ); foreach ( Y2Axis axis in pane.Y2AxisList ) InitScale( axis.Scale, isBoundedRanges ); maxPts = 1; // Loop over each curve in the collection and examine the data ranges foreach ( CurveItem curve in this ) { if ( curve.IsVisible ) { // For stacked types, use the GetStackRange() method which accounts for accumulated values // rather than simple curve values. if ( ( ( curve is BarItem ) && ( pane._barSettings.Type == BarType.Stack || pane._barSettings.Type == BarType.PercentStack ) ) || ( ( curve is LineItem ) && pane.LineType == LineType.Stack ) ) { GetStackRange( pane, curve, out tXMinVal, out tYMinVal, out tXMaxVal, out tYMaxVal ); } else { // Call the GetRange() member function for the current // curve to get the min and max values curve.GetRange( out tXMinVal, out tXMaxVal, out tYMinVal, out tYMaxVal, bIgnoreInitial, true, pane ); } // isYOrd is true if the Y axis is an ordinal type Scale yScale = curve.GetYAxis( pane ).Scale; Scale xScale = curve.GetXAxis( pane ).Scale; bool isYOrd = yScale.IsAnyOrdinal; // isXOrd is true if the X axis is an ordinal type bool isXOrd = xScale.IsAnyOrdinal; // For ordinal Axes, the data range is just 1 to Npts if ( isYOrd && !curve.IsOverrideOrdinal ) { tYMinVal = 1.0; tYMaxVal = curve.NPts; } if ( isXOrd && !curve.IsOverrideOrdinal ) { tXMinVal = 1.0; tXMaxVal = curve.NPts; } // Bar types always include the Y=0 value if ( curve.IsBar ) { if ( pane._barSettings.Base == BarBase.X || pane._barSettings.Base == BarBase.X2 ) { // Only force z=0 for BarItems, not HiLowBarItems if ( ! (curve is HiLowBarItem) ) { if ( tYMinVal > 0 ) tYMinVal = 0; else if ( tYMaxVal < 0 ) tYMaxVal = 0; } // for non-ordinal axes, expand the data range slightly for bar charts to // account for the fact that the bar clusters have a width if ( !isXOrd ) { tXMinVal -= pane._barSettings._clusterScaleWidth / 2.0; tXMaxVal += pane._barSettings._clusterScaleWidth / 2.0; } } else { // Only force z=0 for BarItems, not HiLowBarItems if ( !( curve is HiLowBarItem ) ) { if ( tXMinVal > 0 ) tXMinVal = 0; else if ( tXMaxVal < 0 ) tXMaxVal = 0; } // for non-ordinal axes, expand the data range slightly for bar charts to // account for the fact that the bar clusters have a width if ( !isYOrd ) { tYMinVal -= pane._barSettings._clusterScaleWidth / 2.0; tYMaxVal += pane._barSettings._clusterScaleWidth / 2.0; } } } // determine which curve has the maximum number of points if ( curve.NPts > maxPts ) maxPts = curve.NPts; // If the min and/or max values from the current curve // are the absolute min and/or max, then save the values // Also, differentiate between Y and Y2 values if ( tYMinVal < yScale._rangeMin ) yScale._rangeMin = tYMinVal; if ( tYMaxVal > yScale._rangeMax ) yScale._rangeMax = tYMaxVal; if ( tXMinVal < xScale._rangeMin ) xScale._rangeMin = tXMinVal; if ( tXMaxVal > xScale._rangeMax ) xScale._rangeMax = tXMaxVal; } } pane.XAxis.Scale.SetRange( pane, pane.XAxis ); pane.X2Axis.Scale.SetRange( pane, pane.X2Axis ); foreach ( YAxis axis in pane.YAxisList ) axis.Scale.SetRange( pane, axis ); foreach ( Y2Axis axis in pane.Y2AxisList ) axis.Scale.SetRange( pane, axis ); }
在这里我们可以看到,这个方法是先初始化所有的X与Y轴上的一些基本数据,如最小值,最大值,步长等数据。然后遍历所有CurveList的所有数据,根据我们要画的不同的元素分别得到X与Y轴的最小值和最大值。
然后判断当前Curve所对应的轴的的X轴与Y轴是否有序。这个有序我大致说下意思,大家可以看下有些坐标轴,它的轴上不是一些数字,而是一些标示,一般是些描述,那么这种我们称之有序,而那种轴上是一些数字有规律排列,我们则不称有序。这个大家要查看具体的可以查找enum类AxisType的各个属性值的具体描述,这里我不多做说明。
X轴和Y轴通过它们对就的Curve得到它们对应的要显示的最大值和最小值后,这段逻辑我们也可以看到,假设二个Curve对应的是同一个X与Y轴,那么最小值和最大值分别取二个Curve里的最小值和最大值,所以如果多个Curve的话,并且它们同X轴或是同Y轴的最大值和最小值差值很大的话,请考虑分开它们的坐标轴是一个好的选择。确定了对应轴要显示的最大与最小数据(RangeMin与RangeMax),在这里我有个疑问,这里只是确定轴所要显示的最大值的最小值,但是我们看到的轴图,都会多显示一些数据并且还有相应的的步长都没确定下来。
然后我们回到AxisChange()里,可以看到会调用一个PickScale()的方法,这里前面一部分代码会遍历Pane上所有的轴然后调用每一个轴上的scale上的PickScale()方法,跟进去看发现还是很明白的。我们先假设是最普通的笛卡尔坐标轴,就是LineraScale类型,会先调用父类Scale类里的PickScale()方法,在这方法里我们能看到分别有二个参数minGrace与maxGrace,通过查看这二个属性的描述,我们明白了这二个参数的意思,如果我们要显示的最小值RangeMin为5,最大值RangeMax=40,则显示的Range=RangeMax-RangeMin=35那么我们在轴上显示最小值Min应该是是RangeMin-RangeMin*Range=1.5,同理Max应该是43.5。我们清楚了父类Scale的这个方法就是确定坐标轴的min与max值。
然后我们回到本身的PickScale()方法里,这里下走,可以看到如果有句代码min/(max-min)<0.25,则min=0,这里的意思就是如果min相对于整个要显示的范围太少,则把最小值设为零,我们看上面,1.5/(43.5-1.5)肯定没有0.25大,于是最小值min则为零了.好了,min与max显示完善了,那么步长了,别急,在下面的_majorStep = CalcStepSize( _max - _min, targetSteps );在CalcStepSize这个函数里,如下代码所示。
protected static double CalcStepSize( double range, double targetSteps ) { // Calculate an initial guess at step size double tempStep = range / targetSteps; // Get the magnitude of the step size double mag = Math.Floor( Math.Log10( tempStep ) ); double magPow = Math.Pow( (double) 10.0, mag ); // Calculate most significant digit of the new step size double magMsd = ( (int) ( tempStep / magPow + .5 ) ); // promote the MSD to either 1, 2, or 5 if ( magMsd > 5.0 ) magMsd = 10.0; else if ( magMsd > 2.0 ) magMsd = 5.0; else if ( magMsd > 1.0 ) magMsd = 2.0; return magMsd * magPow; }
这代码主要是为了选择一些能除2,5,10的,大致结果是Range11-17时选择2,18-38时为5,39-104为10,这个范围的10的N次倍的结果也是N次,如200就是50,2就是0.5,2000就是50.设置好主步长和,然后就是副步长。里面还有些函数如CalcMaxLabels这些主要是确定轴上最多能放多少个标识点,这个如果后面讲到scale我会具体说。
回到AxisChange()里,然后会对要生成的chart图通过CalcChartRect()来计算绘图的方位,在这个函数里,会除去对应标题,轴上数据显示所要占的空间,点到只是绘制的可用面积与方位。到这后GraphPane初始化它上面的各个对象的数据,然后这些对象就能通过这些数据来绘制它们自己而显示成我们所看到的各个精彩的图表控件。后面我会讲各个元素的绘制过程。