• ZedGraph源码学习(三)


    我们先看下一段代码,这行代码是说在轴上对应的值转化成它对应的像素值。

    		public float Transform( double x )
    		{
    			// Must take into account Log, and Reverse Axes
    			double denom = ( _maxLinTemp - _minLinTemp );
    			double ratio;
    			if ( denom > 1e-100 )
    				ratio = ( Linearize( x ) - _minLinTemp ) / denom;
    			else
    				ratio = 0;
    
    			if ( _isReverse == ( _ownerAxis is XAxis || _ownerAxis is X2Axis ) )
    				return (float) ( _maxPix - ( _maxPix - _minPix ) * ratio );
    			else
    				return (float) ( _minPix + ( _maxPix - _minPix ) * ratio );
    		}

    这段代码很好理解,我们假设这是X轴,轴没有反转,X轴上的坐标最小值minLinTemp,对应像素是minpix,坐标最大值是maxLinTemp,对应像素是maxpix,现有一点在x轴的坐标是xlin,那么它对应的像素是xpix,它们满足这样一个关系,(xlin-minLinTemp)/(maxLinTemp-minLinTemp)=(xpix-minpix)/(maxpix-minpix),根据这个关系我们能得到对应的xpix的关系。如果是Y轴,因为我们坐标系上的Y轴和在设备上的坐标系是反着来的,那么它们的关系要改着这样了,(xlin-minLinTemp)/(maxLinTemp-minLinTemp)=(maxpix-xpix)/(maxpix-minpix).

    然后我们查看一下Chart图上的元素是如何绘制的,我们这里看线条的绘制过程。线条的元素LineItem里会封装一个Line的对象,这个对象主要是负责具体画什么样的线,如直线,基数样条等,我们看一下普通的基数样条的绘制代码。

    public void DrawCurve( Graphics g, GraphPane pane,
                                    CurveItem curve, float scaleFactor )
    		{
    			Line source = this;
    			if ( curve.IsSelected )
    				source = Selection.Line;
    
    			// switch to int to optimize drawing speed (per Dale-a-b)
    			int	tmpX, tmpY,
    					lastX = int.MaxValue,
    					lastY = int.MaxValue;
    
    			double curX, curY, lowVal;
    			PointPair curPt, lastPt = new PointPair();
    
    			bool lastBad = true;
    			IPointList points = curve.Points;
    			ValueHandler valueHandler = new ValueHandler( pane, false );
    			Axis yAxis = curve.GetYAxis( pane );
    			Axis xAxis = curve.GetXAxis( pane );
    
    			bool xIsLog = xAxis._scale.IsLog;
    			bool yIsLog = yAxis._scale.IsLog;
    
    			// switch to int to optimize drawing speed (per Dale-a-b)
    			int minX = (int)pane.Chart.Rect.Left;
    			int maxX = (int)pane.Chart.Rect.Right;
    			int minY = (int)pane.Chart.Rect.Top;
    			int maxY = (int)pane.Chart.Rect.Bottom;
    
    			using ( Pen pen = source.GetPen( pane, scaleFactor ) )
    			{
    				if ( points != null && !_color.IsEmpty && this.IsVisible )
    				{
    					//bool lastOut = false;
    					bool isOut;
    
    					bool isOptDraw = _isOptimizedDraw && points.Count > 1000;
    
    					// (Dale-a-b) we'll set an element to true when it has been drawn	
    					bool[,] isPixelDrawn = null;
    					
    					if ( isOptDraw )
    						isPixelDrawn = new bool[maxX + 1, maxY + 1]; 
    					
    					// Loop over each point in the curve
    					for ( int i = 0; i < points.Count; i++ )
    					{
    						curPt = points[i];
    						if ( pane.LineType == LineType.Stack )
    						{
    							if ( !valueHandler.GetValues( curve, i, out curX, out lowVal, out curY ) )
    							{
    								curX = PointPair.Missing;
    								curY = PointPair.Missing;
    							}
    						}
    						else
    						{
    							curX = curPt.X;
    							curY = curPt.Y;
    						}
    
    						// Any value set to double max is invalid and should be skipped
    						// This is used for calculated values that are out of range, divide
    						//   by zero, etc.
    						// Also, any value <= zero on a log scale is invalid
    						if ( curX == PointPair.Missing ||
    								curY == PointPair.Missing ||
    								System.Double.IsNaN( curX ) ||
    								System.Double.IsNaN( curY ) ||
    								System.Double.IsInfinity( curX ) ||
    								System.Double.IsInfinity( curY ) ||
    								( xIsLog && curX <= 0.0 ) ||
    								( yIsLog && curY <= 0.0 ) )
    						{
    							// If the point is invalid, then make a linebreak only if IsIgnoreMissing is false
    							// LastX and LastY are always the last valid point, so this works out
    							lastBad = lastBad || !pane.IsIgnoreMissing;
    							isOut = true;
    						}
    						else
    						{
    							// Transform the current point from user scale units to
    							// screen coordinates
    							tmpX = (int) xAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curX );
    							tmpY = (int) yAxis.Scale.Transform( curve.IsOverrideOrdinal, i, curY );
    
    							// Maintain an array of "used" pixel locations to avoid duplicate drawing operations
    							// contributed by Dale-a-b
    							if ( isOptDraw && tmpX >= minX && tmpX <= maxX &&
    										tmpY >= minY && tmpY <= maxY ) // guard against the zoom-in case
    							{
    								if ( isPixelDrawn[tmpX, tmpY] )
    									continue;
    								isPixelDrawn[tmpX, tmpY] = true;
    							}
    
    							isOut = ( tmpX < minX && lastX < minX ) || ( tmpX > maxX && lastX > maxX ) ||
    								( tmpY < minY && lastY < minY ) || ( tmpY > maxY && lastY > maxY );
    
    							if ( !lastBad )
    							{
    								try
    								{
    									// GDI+ plots the data wrong and/or throws an exception for
    									// outrageous coordinates, so we do a sanity check here
    									if ( lastX > 5000000 || lastX < -5000000 ||
    											lastY > 5000000 || lastY < -5000000 ||
    											tmpX > 5000000 || tmpX < -5000000 ||
    											tmpY > 5000000 || tmpY < -5000000 )
    										InterpolatePoint( g, pane, curve, lastPt, scaleFactor, pen,
    														lastX, lastY, tmpX, tmpY );
    									else if ( !isOut )
    									{
    										if ( !curve.IsSelected && this._gradientFill.IsGradientValueType )
    										{
    											using ( Pen tPen = GetPen( pane, scaleFactor, lastPt ) )
    											{
    												if ( this.StepType == StepType.NonStep )
    												{
    													g.DrawLine( tPen, lastX, lastY, tmpX, tmpY );
    												}
    												else if ( this.StepType == StepType.ForwardStep )
    												{
    													g.DrawLine( tPen, lastX, lastY, tmpX, lastY );
    													g.DrawLine( tPen, tmpX, lastY, tmpX, tmpY );
    												}
    												else if ( this.StepType == StepType.RearwardStep )
    												{
    													g.DrawLine( tPen, lastX, lastY, lastX, tmpY );
    													g.DrawLine( tPen, lastX, tmpY, tmpX, tmpY );
    												}
    												else if ( this.StepType == StepType.ForwardSegment )
    												{
    													g.DrawLine( tPen, lastX, lastY, tmpX, lastY );
    												}
    												else
    												{
    													g.DrawLine( tPen, lastX, tmpY, tmpX, tmpY );
    												}
    											}
    										}
    										else
    										{
    											if ( this.StepType == StepType.NonStep )
    											{
    												g.DrawLine( pen, lastX, lastY, tmpX, tmpY );
    											}
    											else if ( this.StepType == StepType.ForwardStep )
    											{
    												g.DrawLine( pen, lastX, lastY, tmpX, lastY );
    												g.DrawLine( pen, tmpX, lastY, tmpX, tmpY );
    											}
    											else if ( this.StepType == StepType.RearwardStep )
    											{
    												g.DrawLine( pen, lastX, lastY, lastX, tmpY );
    												g.DrawLine( pen, lastX, tmpY, tmpX, tmpY );
    											}
    											else if ( this.StepType == StepType.ForwardSegment )
    											{
    												g.DrawLine( pen, lastX, lastY, tmpX, lastY );
    											}
    											else if ( this.StepType == StepType.RearwardSegment )
    											{
    												g.DrawLine( pen, lastX, tmpY, tmpX, tmpY );
    											}
    										}
    									}
    
    								}
    								catch
    								{
    									InterpolatePoint( g, pane, curve, lastPt, scaleFactor, pen,
    												lastX, lastY, tmpX, tmpY );
    								}
    
    							}
    
    							lastPt = curPt;
    							lastX = tmpX;
    							lastY = tmpY;
    							lastBad = false;
    							//lastOut = isOut;
    						}
    					}
    				}
    			}
    		}

    代码有些多,但是过程还是很清晰的,前面一部分代码都是来获取相关数据,然后遍历对应CurveItem里的IPointList数据,把每个Point数据(是我们看到的数据)对应当前CurveItem里的X轴与Y轴上的像素值(给机器来看的),这里用到的转换就是上面所说的那部分代码,根据我们选择StepType不同来绘制每二点决定的线。这样就绘制出了我们要的线条,别的元素具体上不同,但是都是这种逻辑。其实如果要多了解这一方面的逻辑,可以看下有关坐标系统和转换的知识。

    PS:读源码有个地方要注意,不要只是跟着代码往下来,看一会不是分支过多就是跑题了,看的时候你要自己跟着想,如果是我来实现这一功能,应该怎么来实现,然后和看的代码比对。

  • 相关阅读:
    “王者对战”之 MySQL 8 vs PostgreSQL 10
    PostgreSQL 进程结构
    Linux core dump 诊断进程奔溃退出
    linux下core dump--转载
    2.4 等比数列
    2.3 等差数列的前n项和
    2.2 等差数列
    1.1.1 三角形正弦定理
    调整颜色
    去括号法则
  • 原文地址:https://www.cnblogs.com/zhouxin/p/2477548.html
Copyright © 2020-2023  润新知