/* * NPlot - A charting library for .NET * * PlotSurface2D.cs * Copyright (C) 2003-2006 Matt Howlett and others. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of NPlot nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ // #define DEBUG_BOUNDING_BOXES using System; using System.Collections; using System.Drawing; using System.Drawing.Drawing2D; namespace NPlot { /// /// Implements the surface on which IDrawables are drawn. Is extended /// by Bitmap.PlotSurface2D, Windows.PlotSurface2D etc. TODO: better explanation. /// public class PlotSurface2D : IPlotSurface2D { /// /// Possible positions of the X axis. /// public enum XAxisPosition { /// /// X axis is on the top. /// Top = 1, //Center = 2, /// /// X axis is on the bottom. /// Bottom = 3, } /// /// Possible positions of the Y axis. /// public enum YAxisPosition { /// /// Y axis on the left. /// Left = 1, // Center /// /// Y axis on the right. /// Right = 3, } private readonly StringFormat titleDrawFormat_; private bool autoScaleAutoGeneratedAxes_; private bool autoScaleTitle_; private ArrayList axesConstraints_; private object bbTitleCache_; private object bbXAxis1Cache_; private object bbXAxis2Cache_; private object bbYAxis1Cache_; private object bbYAxis2Cache_; private ArrayList drawables_; private int legendZOrder_ = -1; private Legend legend_; private SortedList ordering_; private PhysicalAxis pXAxis1Cache_; private PhysicalAxis pXAxis2Cache_; private PhysicalAxis pYAxis1Cache_; private PhysicalAxis pYAxis2Cache_; private int padding_; private object plotAreaBoundingBoxCache_; private IRectangleBrush plotBackBrush_; private object plotBackColor_; private System.Drawing.Bitmap plotBackImage_; private SmoothingMode smoothingMode_; private Brush titleBrush_; private Font titleFont_; private string title_; private int uniqueCounter_; private Axis xAxis1_; private Axis xAxis2_; private ArrayList xAxisPositions_; private Axis yAxis1_; private Axis yAxis2_; private ArrayList yAxisPositions_; private ArrayList zPositions_; /// /// Default constructor. /// public PlotSurface2D() { // only create this once. titleDrawFormat_ = new StringFormat(); titleDrawFormat_.Alignment = StringAlignment.Center; Init(); } /// /// The physical bounding box of the last drawn plot surface area is available here. /// public Rectangle PlotAreaBoundingBoxCache { get { if (plotAreaBoundingBoxCache_ == null) { return Rectangle.Empty; } else { return (Rectangle) plotAreaBoundingBoxCache_; } } } /// /// The physical XAxis1 that was last drawn. /// public PhysicalAxis PhysicalXAxis1Cache { get { return pXAxis1Cache_; } } /// /// The physical YAxis1 that was last drawn. /// public PhysicalAxis PhysicalYAxis1Cache { get { return pYAxis1Cache_; } } /// /// The physical XAxis2 that was last drawn. /// public PhysicalAxis PhysicalXAxis2Cache { get { return pXAxis2Cache_; } } /// /// The physical YAxis2 that was last drawn. /// public PhysicalAxis PhysicalYAxis2Cache { get { return pYAxis2Cache_; } } /// /// The bottom abscissa axis. /// public Axis XAxis1 { get { return xAxis1_; } set { xAxis1_ = value; } } /// /// The left ordinate axis. /// public Axis YAxis1 { get { return yAxis1_; } set { yAxis1_ = value; } } /// /// The top abscissa axis. /// public Axis XAxis2 { get { return xAxis2_; } set { xAxis2_ = value; } } /// /// The right ordinate axis. /// public Axis YAxis2 { get { return yAxis2_; } set { yAxis2_ = value; } } /// /// The chart title. /// public string Title { get { return title_; } set { title_ = value; } } /// /// The plot title font. /// public Font TitleFont { get { return titleFont_; } set { titleFont_ = value; } } /// /// The distance in pixels to leave between of the edge of the bounding rectangle /// supplied to the Draw method, and the markings that make up the plot. /// public int Padding { get { return padding_; } set { padding_ = value; } } /// /// Sets the title to be drawn using a solid brush of this color. /// public Color TitleColor { set { titleBrush_ = new SolidBrush(value); } } /// /// The brush used for drawing the title. /// public Brush TitleBrush { get { return titleBrush_; } set { titleBrush_ = value; } } /// /// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush /// public Color PlotBackColor { set { plotBackColor_ = value; plotBackBrush_ = null; plotBackImage_ = null; } } /// /// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush /// public System.Drawing.Bitmap PlotBackImage { set { plotBackImage_ = value; plotBackColor_ = null; plotBackBrush_ = null; } } /// /// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush /// public IRectangleBrush PlotBackBrush { set { plotBackBrush_ = value; plotBackColor_ = null; plotBackImage_ = null; } } /// /// Smoothing mode to use when drawing plots. /// public SmoothingMode SmoothingMode { get { return smoothingMode_; } set { smoothingMode_ = value; } } /// /// Adds a drawable object to the plot surface with z-order 0. If the object is an IPlot, /// the PlotSurface2D axes will also be updated. /// /// The IDrawable object to add to the plot surface. public void Add(IDrawable p) { Add(p, 0); } /// /// Adds a drawable object to the plot surface. If the object is an IPlot, /// the PlotSurface2D axes will also be updated. /// /// The IDrawable object to add to the plot surface. /// The z-ordering when drawing (objects with lower numbers are drawn first) public void Add(IDrawable p, int zOrder) { Add(p, XAxisPosition.Bottom, YAxisPosition.Left, zOrder); } /// /// Adds a drawable object to the plot surface against the specified axes with /// z-order of 0. If the object is an IPlot, the PlotSurface2D axes will also /// be updated. /// /// the IDrawable object to add to the plot surface /// the x-axis to add the plot against. /// the y-axis to add the plot against. public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp) { Add(p, xp, yp, 0); } /// /// Adds a drawable object to the plot surface against the specified axes. If /// the object is an IPlot, the PlotSurface2D axes will also be updated. /// /// the IDrawable object to add to the plot surface /// the x-axis to add the plot against. /// the y-axis to add the plot against. /// The z-ordering when drawing (objects with lower numbers are drawn first) public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp, int zOrder) { drawables_.Add(p); xAxisPositions_.Add(xp); yAxisPositions_.Add(yp); zPositions_.Add((double) zOrder); // fraction is to make key unique. With 10 million plots at same z, this buggers up.. double fraction = (double) (++uniqueCounter_)/10000000.0f; ordering_.Add(zOrder + fraction, drawables_.Count - 1); // if p is just an IDrawable, then it can't affect the axes. if (p is IPlot) { UpdateAxes(false); } } /// /// Clears the plot and resets all state to the default. /// public void Clear() { Init(); } /// /// Legend to use. If this property is null [default], then the plot /// surface will have no corresponding legend. /// public Legend Legend { get { return legend_; } set { legend_ = value; } } /// /// Add an axis constraint to the plot surface. Axes constraints give you /// control over where NPlot positions each axes, and the world - pixel /// ratio. /// /// The axis constraint to add. public void AddAxesConstraint(AxesConstraint constraint) { axesConstraints_.Add(constraint); } /// /// Whether or not the title will be scaled according to size of the plot surface. /// public bool AutoScaleTitle { get { return autoScaleTitle_; } set { autoScaleTitle_ = value; } } /// /// When plots are added to the plot surface, the axes they are attached to /// are immediately modified to reflect data of the plot. If /// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will /// be turned in to auto scaling ones if they are not already [tick marks, /// tick text and label size scaled to size of plot surface]. If false, /// axes will not be autoscaling. /// public bool AutoScaleAutoGeneratedAxes { get { return autoScaleAutoGeneratedAxes_; } set { autoScaleAutoGeneratedAxes_ = value; } } /// /// Remove a drawable object. /// Note that axes are not updated. /// /// Drawable to remove. /// if true, the axes are updated. public void Remove(IDrawable p, bool updateAxes) { int index = drawables_.IndexOf(p); if (index < 0) return; drawables_.RemoveAt(index); xAxisPositions_.RemoveAt(index); yAxisPositions_.RemoveAt(index); zPositions_.RemoveAt(index); if (updateAxes) { UpdateAxes(true); } RefreshZOrdering(); } /// /// Gets an array list containing all drawables currently added to the PlotSurface2D. /// public ArrayList Drawables { get { return drawables_; } } /// /// Setting this value determines the order (relative to IDrawables added to the plot surface) /// that the legend is drawn. /// public int LegendZOrder { get { return legendZOrder_; } set { legendZOrder_ = value; } } /// /// Performs a hit test with the given point and returns information /// about the object being hit. /// /// The point to test. /// public ArrayList HitTest(Point p) { ArrayList a = new ArrayList(); // this is the case if PlotSurface has been cleared. if (bbXAxis1Cache_ == null) { return a; } else if (bbXAxis1Cache_ != null && ((Rectangle) bbXAxis1Cache_).Contains(p)) { a.Add(xAxis1_); return a; } else if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_).Contains(p)) { a.Add(yAxis1_); return a; } else if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_).Contains(p)) { a.Add(xAxis2_); return a; } else if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_).Contains(p)) { a.Add(yAxis2_); return a; } else if (bbTitleCache_ != null && ((Rectangle) bbTitleCache_).Contains(p)) { a.Add(this); return a; } else if (plotAreaBoundingBoxCache_ != null && ((Rectangle) plotAreaBoundingBoxCache_).Contains(p)) { a.Add(this); return a; } return a; } private void Init() { drawables_ = new ArrayList(); xAxisPositions_ = new ArrayList(); yAxisPositions_ = new ArrayList(); zPositions_ = new ArrayList(); ordering_ = new SortedList(); FontFamily fontFamily = new FontFamily("Arial"); TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel); padding_ = 10; title_ = ""; autoScaleTitle_ = false; autoScaleAutoGeneratedAxes_ = false; xAxis1_ = null; xAxis2_ = null; yAxis1_ = null; yAxis2_ = null; pXAxis1Cache_ = null; pYAxis1Cache_ = null; pXAxis2Cache_ = null; pYAxis2Cache_ = null; titleBrush_ = new SolidBrush(Color.Black); plotBackColor_ = Color.White; legend_ = null; smoothingMode_ = SmoothingMode.None; axesConstraints_ = new ArrayList(); } private float DetermineScaleFactor(int w, int h) { float diag = (float) Math.Sqrt(w*w + h*h); float scaleFactor = (diag/1400.0f)*2.4f; if (scaleFactor > 1.0f) { return scaleFactor; } else { return 1.0f; } } private void UpdateAxes(bool recalculateAll) { if (drawables_.Count != xAxisPositions_.Count || drawables_.Count != yAxisPositions_.Count) { throw new NPlotException("plots and axis position arrays our of sync"); } int position = 0; // if we're not recalculating axes using all iplots then set // position to last one in list. if (!recalculateAll) { position = drawables_.Count - 1; if (position < 0) position = 0; } if (recalculateAll) { xAxis1_ = null; yAxis1_ = null; xAxis2_ = null; yAxis2_ = null; } for (int i = position; i < drawables_.Count; ++i) { // only update axes if this drawable is an IPlot. if (!(drawables_[position] is IPlot)) continue; IPlot p = (IPlot) drawables_[position]; XAxisPosition xap = (XAxisPosition) xAxisPositions_[position]; YAxisPosition yap = (YAxisPosition) yAxisPositions_[position]; if (xap == XAxisPosition.Bottom) { if (xAxis1_ == null) { xAxis1_ = p.SuggestXAxis(); if (xAxis1_ != null) { xAxis1_.TicksAngle = -(float) Math.PI/2.0f; } } else { xAxis1_.LUB(p.SuggestXAxis()); } if (xAxis1_ != null) { xAxis1_.MinPhysicalLargeTickStep = 50; if (AutoScaleAutoGeneratedAxes) { xAxis1_.AutoScaleText = true; xAxis1_.AutoScaleTicks = true; xAxis1_.TicksIndependentOfPhysicalExtent = true; } else { xAxis1_.AutoScaleText = false; xAxis1_.AutoScaleTicks = false; xAxis1_.TicksIndependentOfPhysicalExtent = false; } } } if (xap == XAxisPosition.Top) { if (xAxis2_ == null) { xAxis2_ = p.SuggestXAxis(); if (xAxis2_ != null) { xAxis2_.TicksAngle = (float) Math.PI/2.0f; } } else { xAxis2_.LUB(p.SuggestXAxis()); } if (xAxis2_ != null) { xAxis2_.MinPhysicalLargeTickStep = 50; if (AutoScaleAutoGeneratedAxes) { xAxis2_.AutoScaleText = true; xAxis2_.AutoScaleTicks = true; xAxis2_.TicksIndependentOfPhysicalExtent = true; } else { xAxis2_.AutoScaleText = false; xAxis2_.AutoScaleTicks = false; xAxis2_.TicksIndependentOfPhysicalExtent = false; } } } if (yap == YAxisPosition.Left) { if (yAxis1_ == null) { yAxis1_ = p.SuggestYAxis(); if (yAxis1_ != null) { yAxis1_.TicksAngle = (float) Math.PI/2.0f; } } else { yAxis1_.LUB(p.SuggestYAxis()); } if (yAxis1_ != null) { if (AutoScaleAutoGeneratedAxes) { yAxis1_.AutoScaleText = true; yAxis1_.AutoScaleTicks = true; yAxis1_.TicksIndependentOfPhysicalExtent = true; } else { yAxis1_.AutoScaleText = false; yAxis1_.AutoScaleTicks = false; yAxis1_.TicksIndependentOfPhysicalExtent = false; } } } if (yap == YAxisPosition.Right) { if (yAxis2_ == null) { yAxis2_ = p.SuggestYAxis(); if (yAxis2_ != null) { yAxis2_.TicksAngle = -(float) Math.PI/2.0f; } } else { yAxis2_.LUB(p.SuggestYAxis()); } if (yAxis2_ != null) { if (AutoScaleAutoGeneratedAxes) { yAxis2_.AutoScaleText = true; yAxis2_.AutoScaleTicks = true; yAxis2_.TicksIndependentOfPhysicalExtent = true; } else { yAxis2_.AutoScaleText = false; yAxis2_.AutoScaleTicks = false; yAxis2_.TicksIndependentOfPhysicalExtent = false; } } } } } private void DetermineAxesToDraw(out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2) { xAxis1 = xAxis1_; xAxis2 = xAxis2_; yAxis1 = yAxis1_; yAxis2 = yAxis2_; if (xAxis1_ == null) { if (xAxis2_ == null) { throw new NPlotException("Error: No X-Axis specified"); } xAxis1 = (Axis) xAxis2_.Clone(); xAxis1.HideTickText = true; xAxis1.TicksAngle = -(float) Math.PI/2.0f; } if (xAxis2_ == null) { // don't need to check if xAxis1_ == null, as case already handled above. xAxis2 = (Axis) xAxis1_.Clone(); xAxis2.HideTickText = true; xAxis2.TicksAngle = (float) Math.PI/2.0f; } if (yAxis1_ == null) { if (yAxis2_ == null) { throw new NPlotException("Error: No Y-Axis specified"); } yAxis1 = (Axis) yAxis2_.Clone(); yAxis1.HideTickText = true; yAxis1.TicksAngle = (float) Math.PI/2.0f; } if (yAxis2_ == null) { // don't need to check if yAxis1_ == null, as case already handled above. yAxis2 = (Axis) yAxis1_.Clone(); yAxis2.HideTickText = true; yAxis2.TicksAngle = -(float) Math.PI/2.0f; } } private void DeterminePhysicalAxesToDraw(Rectangle bounds, Axis xAxis1, Axis xAxis2, Axis yAxis1, Axis yAxis2, out PhysicalAxis pXAxis1, out PhysicalAxis pXAxis2, out PhysicalAxis pYAxis1, out PhysicalAxis pYAxis2) { Rectangle cb = bounds; pXAxis1 = new PhysicalAxis(xAxis1, new Point(cb.Left, cb.Bottom), new Point(cb.Right, cb.Bottom)); pYAxis1 = new PhysicalAxis(yAxis1, new Point(cb.Left, cb.Bottom), new Point(cb.Left, cb.Top)); pXAxis2 = new PhysicalAxis(xAxis2, new Point(cb.Left, cb.Top), new Point(cb.Right, cb.Top)); pYAxis2 = new PhysicalAxis(yAxis2, new Point(cb.Right, cb.Bottom), new Point(cb.Right, cb.Top)); int bottomIndent = padding_; if (!pXAxis1.Axis.Hidden) { // evaluate its bounding box Rectangle bb = pXAxis1.GetBoundingBox(); // finally determine its indentation from the bottom bottomIndent = bottomIndent + bb.Bottom - cb.Bottom; } int leftIndent = padding_; if (!pYAxis1.Axis.Hidden) { // evaluate its bounding box Rectangle bb = pYAxis1.GetBoundingBox(); // finally determine its indentation from the left leftIndent = leftIndent - bb.Left + cb.Left; } int topIndent = padding_; float scale = DetermineScaleFactor(bounds.Width, bounds.Height); int titleHeight; if (AutoScaleTitle) { titleHeight = Utils.ScaleFont(titleFont_, scale).Height; } else { titleHeight = titleFont_.Height; } //count number of new lines in title. int nlCount = 0; for (int i = 0; i < title_.Length; ++i) { if (title_[i] == '\n') nlCount += 1; } titleHeight = (int) ((nlCount*0.75 + 1.0f)*titleHeight); if (!pXAxis2.Axis.Hidden) { // evaluate its bounding box Rectangle bb = pXAxis2.GetBoundingBox(); topIndent = topIndent - bb.Top + cb.Top; // finally determine its indentation from the top // correct top indendation to take into account plot title if (title_ != "") { topIndent += (int) (titleHeight*1.3f); } } int rightIndent = padding_; if (!pYAxis2.Axis.Hidden) { // evaluate its bounding box Rectangle bb = pYAxis2.GetBoundingBox(); // finally determine its indentation from the right rightIndent = (rightIndent + bb.Right - cb.Right); } // now we have all the default calculated positions and we can proceed to // "move" the axes to their right places // primary axes (bottom, left) pXAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent); pXAxis1.PhysicalMax = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent); pYAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent); pYAxis1.PhysicalMax = new Point(cb.Left + leftIndent, cb.Top + topIndent); // secondary axes (top, right) pXAxis2.PhysicalMin = new Point(cb.Left + leftIndent, cb.Top + topIndent); pXAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent); pYAxis2.PhysicalMin = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent); pYAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent); } /// /// Draw the the PlotSurface2D and all contents [axes, drawables, and legend] on the /// supplied graphics surface. /// /// The graphics surface on which to draw. /// /// A bounding box on this surface that denotes the area on the /// surface to confine drawing to. /// public void Draw(Graphics g, Rectangle bounds) { // determine font sizes and tick scale factor. float scale = DetermineScaleFactor(bounds.Width, bounds.Height); // if there is nothing to plot, return. if (drawables_.Count == 0) { // draw title float x_center = (bounds.Left + bounds.Right)/2.0f; float y_center = (bounds.Top + bounds.Bottom)/2.0f; Font scaled_font; if (AutoScaleTitle) { scaled_font = Utils.ScaleFont(titleFont_, scale); } else { scaled_font = titleFont_; } g.DrawString(title_, scaled_font, titleBrush_, new PointF(x_center, y_center), titleDrawFormat_); return; } // determine the [non physical] axes to draw based on the axis properties set. Axis xAxis1 = null; Axis xAxis2 = null; Axis yAxis1 = null; Axis yAxis2 = null; DetermineAxesToDraw(out xAxis1, out xAxis2, out yAxis1, out yAxis2); // apply scale factor to axes as desired. if (xAxis1.AutoScaleTicks) xAxis1.TickScale = scale; if (xAxis1.AutoScaleText) xAxis1.FontScale = scale; if (yAxis1.AutoScaleTicks) yAxis1.TickScale = scale; if (yAxis1.AutoScaleText) yAxis1.FontScale = scale; if (xAxis2.AutoScaleTicks) xAxis2.TickScale = scale; if (xAxis2.AutoScaleText) xAxis2.FontScale = scale; if (yAxis2.AutoScaleTicks) yAxis2.TickScale = scale; if (yAxis2.AutoScaleText) yAxis2.FontScale = scale; // determine the default physical positioning of those axes. PhysicalAxis pXAxis1 = null; PhysicalAxis pYAxis1 = null; PhysicalAxis pXAxis2 = null; PhysicalAxis pYAxis2 = null; DeterminePhysicalAxesToDraw( bounds, xAxis1, xAxis2, yAxis1, yAxis2, out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2); float oldXAxis2Height = pXAxis2.PhysicalMin.Y; // Apply axes constraints for (int i = 0; i < axesConstraints_.Count; ++i) { ((AxesConstraint) axesConstraints_[i]).ApplyConstraint( pXAxis1, pYAxis1, pXAxis2, pYAxis2); } ///////////////////////////////////////////////////////////////////////// // draw legend if have one. // Note: this will update axes if necessary. Point legendPosition = new Point(0, 0); if (legend_ != null) { legend_.UpdateAxesPositions( pXAxis1, pYAxis1, pXAxis2, pYAxis2, drawables_, scale, padding_, bounds, out legendPosition); } float newXAxis2Height = pXAxis2.PhysicalMin.Y; float titleExtraOffset = oldXAxis2Height - newXAxis2Height; // now we are ready to define the bounding box for the plot area (to use in clipping // operations. plotAreaBoundingBoxCache_ = new Rectangle( Math.Min(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X), Math.Min(pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y), Math.Abs(pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1), Math.Abs(pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1) ); bbXAxis1Cache_ = pXAxis1.GetBoundingBox(); bbXAxis2Cache_ = pXAxis2.GetBoundingBox(); bbYAxis1Cache_ = pYAxis1.GetBoundingBox(); bbYAxis2Cache_ = pYAxis2.GetBoundingBox(); // Fill in the background. if (plotBackColor_ != null) { g.FillRectangle( new SolidBrush((Color) plotBackColor_), (Rectangle) plotAreaBoundingBoxCache_); } else if (plotBackBrush_ != null) { g.FillRectangle( plotBackBrush_.Get((Rectangle) plotAreaBoundingBoxCache_), (Rectangle) plotAreaBoundingBoxCache_); } else if (plotBackImage_ != null) { g.DrawImage( Utils.TiledImage(plotBackImage_, new Size( ((Rectangle) plotAreaBoundingBoxCache_).Width, ((Rectangle) plotAreaBoundingBoxCache_).Height)), (Rectangle) plotAreaBoundingBoxCache_); } // draw title float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X)/2.0f; float yt = bounds.Top + padding_ - titleExtraOffset; Font scaledFont; if (AutoScaleTitle) { scaledFont = Utils.ScaleFont(titleFont_, scale); } else { scaledFont = titleFont_; } g.DrawString(title_, scaledFont, titleBrush_, new PointF(xt, yt), titleDrawFormat_); //count number of new lines in title. int nlCount = 0; for (int i = 0; i < title_.Length; ++i) { if (title_[i] == '\n') nlCount += 1; } SizeF s = g.MeasureString(title_, scaledFont); bbTitleCache_ = new Rectangle((int) (xt - s.Width/2), (int) (yt), (int) (s.Width), (int) (s.Height)*(nlCount + 1)); // draw drawables.. SmoothingMode smoothSave = g.SmoothingMode; g.SmoothingMode = smoothingMode_; bool legendDrawn = false; for (int i_o = 0; i_o < ordering_.Count; ++i_o) { int i = (int) ordering_.GetByIndex(i_o); double zOrder = (double) ordering_.GetKey(i_o); if (zOrder > legendZOrder_) { // draw legend. if (!legendDrawn && legend_ != null) { legend_.Draw(g, legendPosition, drawables_, scale); legendDrawn = true; } } IDrawable drawable = (IDrawable) drawables_[i]; XAxisPosition xap = (XAxisPosition) xAxisPositions_[i]; YAxisPosition yap = (YAxisPosition) yAxisPositions_[i]; PhysicalAxis drawXAxis; PhysicalAxis drawYAxis; if (xap == XAxisPosition.Bottom) { drawXAxis = pXAxis1; } else { drawXAxis = pXAxis2; } if (yap == YAxisPosition.Left) { drawYAxis = pYAxis1; } else { drawYAxis = pYAxis2; } // set the clipping region.. (necessary for zoom) g.Clip = new Region((Rectangle) plotAreaBoundingBoxCache_); // plot. drawable.Draw(g, drawXAxis, drawYAxis); // reset it.. g.ResetClip(); } if (!legendDrawn && legend_ != null) { legend_.Draw(g, legendPosition, drawables_, scale); } // cache the physical axes we used on this draw; pXAxis1Cache_ = pXAxis1; pYAxis1Cache_ = pYAxis1; pXAxis2Cache_ = pXAxis2; pYAxis2Cache_ = pYAxis2; g.SmoothingMode = smoothSave; // now draw axes. Rectangle axisBounds; pXAxis1.Draw(g, out axisBounds); pXAxis2.Draw(g, out axisBounds); pYAxis1.Draw(g, out axisBounds); pYAxis2.Draw(g, out axisBounds); #if DEBUG_BOUNDING_BOXES g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis1Cache_ ); g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis2Cache_ ); g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis1Cache_ ); g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis2Cache_ ); g.DrawRectangle( new Pen(Color.Red,5.0F),(Rectangle) plotAreaBoundingBoxCache_); //if(this.ShowLegend)g.DrawRectangle( new Pen(Color.Chocolate, 3.0F), (Rectangle) bbLegendCache_); g.DrawRectangle( new Pen(Color.DeepPink,2.0F), (Rectangle) bbTitleCache_); #endif } /// /// If a plot is removed, then the ordering_ list needs to be /// recalculated. /// private void RefreshZOrdering() { uniqueCounter_ = 0; ordering_ = new SortedList(); for (int i = 0; i < zPositions_.Count; ++i) { double zpos = Convert.ToDouble(zPositions_[i]); double fraction = (double) (++uniqueCounter_)/10000000.0f; double d = zpos + fraction; ordering_.Add(d, i); } } /// /// Returns the x-axis associated with a given plot. /// /// the plot to get associated x-axis. /// the axis associated with the plot. public Axis WhichXAxis(IPlot plot) { int index = drawables_.IndexOf(plot); XAxisPosition p = (XAxisPosition) xAxisPositions_[index]; if (p == XAxisPosition.Bottom) return xAxis1_; else return xAxis2_; } /// /// Returns the y-axis associated with a given plot. /// /// the plot to get associated y-axis. /// the axis associated with the plot. public Axis WhichYAxis(IPlot plot) { int index = drawables_.IndexOf(plot); YAxisPosition p = (YAxisPosition) yAxisPositions_[index]; if (p == YAxisPosition.Left) return yAxis1_; else return yAxis2_; } } }