我們先看下一段代碼,這行代碼是說在軸上對應的值轉化成它對應的像素值。
public float Transform( double x ){// Must take into account Log, and Reverse Axesdouble denom = ( _maxLinTemp - _minLinTemp );double ratio;if ( denom > 1e-100 )ratio = ( Linearize( x ) - _minLinTemp ) / denom;elseratio = 0;if ( _isReverse == ( _ownerAxis is XAxis || _ownerAxis is X2Axis ) )return (float) ( _maxPix - ( _maxPix - _minPix ) * ratio );elsereturn (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)inttmpX, 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 drawnbool[,] isPixelDrawn = null;if ( isOptDraw )isPixelDrawn = new bool[maxX + 1, maxY + 1]; // Loop over each point in the curvefor ( 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 invalidif ( 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 outlastBad = lastBad || !pane.IsIgnoreMissing;isOut = true;}else{// Transform the current point from user scale units to// screen coordinatestmpX = (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-bif ( 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 hereif ( 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:讀源碼有個地方要注意,不要只是跟著代碼往下來,看一會不是分支過多就是跑題了,看的時候你要自己跟著想,如果是我來實現這一功能,應該怎麼來實現,然後和看的代碼比對。