Axis.cs

Go to the documentation of this file.
00001 /*
00002 NPlot - A charting library for .NET
00003 
00004 Axis.cs
00005 Copyright (C) 2003
00006 Matt Howlett
00007 
00008 Redistribution and use of NPlot or parts there-of in source and
00009 binary forms, with or without modification, are permitted provided
00010 that the following conditions are met:
00011 
00012 1. Re-distributions in source form must retain at the head of each
00013    source file the above copyright notice, this list of conditions
00014    and the following disclaimer.
00015 
00016 2. Any product ("the product") that makes use NPlot or parts 
00017    there-of must either:
00018   
00019     (a) allow any user of the product to obtain a complete machine-
00020         readable copy of the corresponding source code for the 
00021         product and the version of NPlot used for a charge no more
00022         than your cost of physically performing source distribution,
00023             on a medium customarily used for software interchange, or:
00024 
00025     (b) reproduce the following text in the documentation, about 
00026         box or other materials intended to be read by human users
00027         of the product that is provided to every human user of the
00028         product: 
00029    
00030               "This product includes software developed as 
00031               part of the NPlot library project available 
00032               from: http://www.nplot.com/" 
00033 
00034         The words "This product" may optionally be replace with 
00035         the actual name of the product.
00036 
00037 ------------------------------------------------------------------------
00038 
00039 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00040 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00041 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00042 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00043 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00044 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00045 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00046 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00047 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00048 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00049 
00050 */
00051 
00052 using System.Drawing.Drawing2D;
00053 using System.Drawing;
00054 using System;
00055 using System.Collections;
00056 
00057 namespace NPlot
00058 {
00059 
00068         public class Axis : System.ICloneable
00069         {
00070         
00075                 public bool TicksCrossAxis
00076                 {
00077                         get
00078                         {
00079                                 return ticksCrossAxis_;
00080                         }
00081                         set
00082                         {
00083                                 ticksCrossAxis_ = value;
00084                         }
00085                 }
00086                 bool ticksCrossAxis_ = false;
00087 
00088 
00098                 public virtual double WorldMax
00099                 {
00100                         get
00101                         {
00102                                 return worldMax_;
00103                         }
00104                         set
00105                         {
00106                                 this.worldMax_ = value;
00107                 /*
00108                 if (this.WorldExtentsChanged != null)
00109                     this.WorldExtentsChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
00110                 if (this.WorldMaxChanged != null)
00111                     this.WorldMaxChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
00112                 */
00113             }
00114                 }
00115                 private double worldMax_;
00116                 
00117 
00127                 public virtual double WorldMin
00128                 {
00129                         get
00130                         {
00131                                 return this.worldMin_;
00132                         }
00133                         set
00134                         {
00135                                 this.worldMin_ = value;
00136                 /*
00137                 if (this.WorldExtentsChanged != null)
00138                     this.WorldExtentsChanged( this, new WorldValueChangedArgs( worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
00139                 if (this.WorldMinChanged != null)
00140                     this.WorldMinChanged( this, new WorldValueChangedArgs(worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
00141                 */
00142             }
00143                 }
00144                 private double worldMin_;
00145 
00146 
00151                 public int LargeTickSize
00152                 {
00153                         get
00154                         {
00155                                 return largeTickSize_;
00156                         }
00157                         set
00158                         {
00159                                 largeTickSize_ = value;
00160                         }
00161                 }
00162                 private int largeTickSize_;
00163 
00164 
00168                 public int SmallTickSize
00169                 {
00170                         get
00171                         {
00172                                 return smallTickSize_;
00173                         }
00174                         set
00175                         {
00176                                 smallTickSize_ = value;
00177                         }
00178                 }
00179                 private int smallTickSize_;
00180 
00181 
00185                 public string Label
00186                 {
00187                         get
00188                         {
00189                                 return label_;
00190                         }
00191                         set
00192                         {
00193                                 label_ = value;
00194                         }
00195                 }
00196                 private string label_;
00197 
00198 
00204                 public bool TickTextNextToAxis
00205                 {
00206                         get
00207                         {
00208                                 return tickTextNextToAxis_;
00209                         }
00210                         set
00211                         {
00212                                 tickTextNextToAxis_ = value;
00213                         }
00214                 }
00215                 bool tickTextNextToAxis_;
00216 
00217 
00222                 public bool Hidden
00223                 {
00224                         get
00225                         {
00226                                 return hidden_;
00227                         }
00228                         set
00229                         {
00230                                 hidden_ = value;
00231                         }
00232                 }
00233                 private bool hidden_;
00234 
00235 
00240                 public bool Reversed
00241                 {
00242                         get
00243                         {
00244                                 return reversed_;
00245                         }
00246                         set
00247                         {
00248                                 reversed_ = value;
00249                         }
00250                 }
00251                 private bool reversed_;
00252 
00253 
00257                 public bool HideTickText
00258                 {
00259                         get
00260                         {
00261                                 return hideTickText_;
00262                         }
00263                         set
00264                         {
00265                                 hideTickText_ = value;
00266                         }
00267                 }
00268                 private bool hideTickText_;
00269 
00270 
00274                 public Font TickTextFont
00275                 {
00276                         get
00277                         {
00278                                 return this.tickTextFont_;
00279                         }
00280                         set
00281                         {
00282                                 this.tickTextFont_ = value;
00283                                 UpdateScale();
00284                         }
00285                 }
00286                 private Font tickTextFont_;
00287                 private Font tickTextFontScaled_;
00288 
00289 
00293                 public Font LabelFont
00294                 {
00295                         get
00296                         {
00297                                 return labelFont_;
00298                         }
00299                         set
00300                         {
00301                                 labelFont_ = value;
00302                                 UpdateScale();
00303                         }
00304                 }
00305                 private Font labelFont_;
00306                 private Font labelFontScaled_;
00307 
00308 
00314                 public string NumberFormat
00315                 {
00316                         get
00317                         {
00318                                 return numberFormat_;
00319                         }
00320                         set
00321                         {
00322                                 numberFormat_ = value;
00323                         }
00324                 }
00325                 private string numberFormat_;
00326 
00327 
00333                 public int MinPhysicalLargeTickStep
00334                 {
00335                         get
00336                         {
00337                                 return minPhysicalLargeTickStep_;
00338                         }
00339                         set
00340                         {
00341                                 minPhysicalLargeTickStep_ = value;
00342                         }
00343                 }
00344                 private int minPhysicalLargeTickStep_ = 30;
00345 
00346 
00350                 public System.Drawing.Color AxisColor
00351                 {
00352                         get
00353                         {
00354                                 return linePen_.Color;
00355                         }
00356                         set
00357                         {
00358                                 linePen_ = new Pen( (Color)value );
00359                         }
00360                 }
00361 
00362 
00366                 public System.Drawing.Pen AxisPen
00367                 {
00368                         get
00369                         {
00370                                 return linePen_;
00371                         }
00372                         set
00373                         {
00374                                 linePen_ = value;
00375                         }
00376                 }
00377                 private System.Drawing.Pen linePen_;
00378 
00379 
00391                 public bool TicksIndependentOfPhysicalExtent
00392                 {
00393                         get
00394                         {
00395                                 return ticksIndependentOfPhysicalExtent_;
00396                         }
00397                         set
00398                         {
00399                                 ticksIndependentOfPhysicalExtent_ = value;
00400                         }
00401                 }
00402                 private bool ticksIndependentOfPhysicalExtent_ = false;
00403 
00404 
00408                 public bool FlipTicksLabel
00409                 {
00410                         get 
00411                         {
00412                                 return flipTicksLabel_; 
00413                         }
00414                         set 
00415                         { 
00416                                 flipTicksLabel_ = value; 
00417                         }
00418                 }
00419                 private bool flipTicksLabel_ = false;
00420 
00421 
00425                 public float TicksAngle
00426                 {
00427                         get
00428                         {
00429                                 return ticksAngle_;
00430                         }
00431                         set
00432                         {
00433                                 ticksAngle_ = value;
00434                         }
00435                 }
00436                 private float ticksAngle_ = (float)Math.PI / 2.0f;
00437 
00438 
00443                 public float TicksLabelAngle
00444                 {
00445                         get
00446                         {
00447                                 return ticksLabelAngle_;
00448                         }
00449                         set
00450                         {
00451                                 ticksLabelAngle_ = value;
00452                         }
00453                 }
00454                 private float ticksLabelAngle_ = 0.0f;
00455 
00456 
00460                 public Color LabelColor
00461                 {
00462                         set
00463                         {
00464                                 labelBrush_ = new SolidBrush( value );
00465                         }
00466                 }
00467                 
00468                 
00472                 public Brush LabelBrush
00473                 {
00474                         get
00475                         {
00476                                 return labelBrush_;
00477                         }
00478                         set
00479                         {
00480                                 labelBrush_ = value;
00481                         }
00482                 }
00483                 private Brush labelBrush_;
00484 
00485                 
00489                 public Color TickTextColor
00490                 {
00491                         set
00492                         {
00493                                 tickTextBrush_ = new SolidBrush( value );
00494                         }
00495                 }
00496 
00497 
00501                 public Brush TickTextBrush
00502                 {
00503                         get
00504                         {
00505                                 return tickTextBrush_;
00506                         }
00507                         set
00508                         {
00509                                 tickTextBrush_ = value;
00510                         }
00511                 }
00512                 private Brush tickTextBrush_;
00513 
00514 
00520                 public bool AutoScaleText
00521                 {
00522                         get
00523                         {
00524                                 return autoScaleText_;
00525                         }
00526                         set
00527                         {
00528                                 autoScaleText_ = value;
00529                         }
00530                 }
00531                 private bool autoScaleText_;
00532 
00533 
00539                 public bool AutoScaleTicks
00540                 {
00541                         get
00542                         {
00543                                 return autoScaleTicks_;
00544                         }
00545                         set
00546                         {
00547                                 autoScaleTicks_ = value;
00548                         }
00549                 }
00550                 private bool autoScaleTicks_;
00551 
00552 
00563                 public virtual object Clone()
00564                 {
00565                         // ensure that this isn't being called on a derived type. If that is the case
00566                         // then the derived type didn't override this method as it should have.
00567                         if (this.GetType() != typeof(Axis))
00568                         {
00569                                 throw new NPlotException( "Clone not defined in derived type." );
00570                         }
00571                         
00572                         Axis a = new Axis();
00573                         DoClone( this, a );
00574                         return a;
00575                 }
00576 
00577 
00583                 protected static void DoClone( Axis b, Axis a )
00584                 {
00585 
00586                         // value items
00587                         a.autoScaleText_ = b.autoScaleText_;
00588                         a.autoScaleTicks_ = b.autoScaleTicks_;
00589                         a.worldMax_ = b.worldMax_;
00590                         a.worldMin_ = b.worldMin_;
00591                         a.tickTextNextToAxis_ = b.tickTextNextToAxis_;
00592                         a.hidden_ = b.hidden_;
00593                         a.hideTickText_ = b.hideTickText_;
00594                         a.reversed_ = b.reversed_;
00595                         a.ticksAngle_ = b.ticksAngle_;
00596                         a.ticksLabelAngle_ = b.ticksLabelAngle_;
00597                         a.minPhysicalLargeTickStep_ = b.minPhysicalLargeTickStep_;
00598                         a.ticksIndependentOfPhysicalExtent_ = b.ticksIndependentOfPhysicalExtent_;
00599                         a.largeTickSize_ = b.largeTickSize_;
00600                         a.smallTickSize_ = b.smallTickSize_;
00601                         a.ticksCrossAxis_ = b.ticksCrossAxis_;
00602                         a.labelOffset_ = b.labelOffset_;
00603             a.labelOffsetAbsolute_ = b.labelOffsetAbsolute_;
00604             a.labelOffsetScaled_ = b.labelOffsetScaled_;
00605 
00606                         // reference items.
00607                         a.tickTextFont_ = (Font)b.tickTextFont_.Clone();
00608                         a.label_ = (string)b.label_.Clone();
00609                         if (b.numberFormat_ != null) 
00610                         {
00611                                 a.numberFormat_ = (string)b.numberFormat_.Clone();
00612                         }
00613                         else
00614                         {
00615                                 a.numberFormat_ = null;
00616                         }
00617 
00618                         a.labelFont_ = (Font)b.labelFont_.Clone();
00619                         a.linePen_ = (Pen)b.linePen_.Clone();
00620                         a.tickTextBrush_ = (Brush)b.tickTextBrush_.Clone();
00621                         a.labelBrush_ = (Brush)b.labelBrush_.Clone();
00622 
00623                         a.FontScale = b.FontScale;
00624                         a.TickScale = b.TickScale;
00625 
00626                 }
00627 
00628 
00629 
00634                 private void Init()
00635                 {
00636                         this.worldMax_ = double.NaN;
00637                         this.worldMin_ = double.NaN;
00638                         this.Hidden = false;
00639                         this.SmallTickSize = 2;
00640                         this.LargeTickSize = 6;
00641                         this.FontScale = 1.0f;
00642                         this.TickScale = 1.0f;
00643                         this.AutoScaleTicks = false;
00644                         this.AutoScaleText = false;
00645                         this.TickTextNextToAxis = true;
00646                         this.HideTickText = false;
00647                         this.TicksCrossAxis = false;
00648                         this.LabelOffset = 0.0f;
00649             this.LabelOffsetAbsolute = false;
00650             this.LabelOffsetScaled = true;
00651 
00652                         this.Label = "" ;
00653                         this.NumberFormat = null;
00654                         this.Reversed = false;
00655 
00656                         FontFamily fontFamily = new FontFamily( "Arial" );
00657                         this.TickTextFont = new Font( fontFamily, 10, FontStyle.Regular, GraphicsUnit.Pixel );
00658                         this.LabelFont = new Font( fontFamily, 12, FontStyle.Regular, GraphicsUnit.Pixel );
00659                         this.LabelColor = System.Drawing.Color.Black;
00660                         this.TickTextColor = System.Drawing.Color.Black;
00661                         this.linePen_ = new Pen( System.Drawing.Color.Black );
00662                         this.linePen_.Width = 1.0f;
00663                         this.FontScale = 1.0f;
00664 
00665                         // saves constructing these in draw method.
00666                         drawFormat_ = new StringFormat();
00667                         drawFormat_.Alignment = StringAlignment.Center;
00668                 }
00669 
00670                 StringFormat drawFormat_;
00671 
00672 
00676                 public Axis( )
00677                 {
00678                         this.Init();
00679                 }
00680 
00681 
00687                 public Axis( double worldMin, double worldMax )
00688                 {
00689                         this.Init();
00690                         this.WorldMin = worldMin;
00691                         this.WorldMax = worldMax;
00692                 }
00693 
00694 
00699                 public Axis( Axis a )
00700                 {
00701                         Axis.DoClone( a, this );
00702                 }
00703 
00704 
00710                 public bool OutOfRange( double coord )
00711                 {
00712                         if (double.IsNaN(WorldMin) || double.IsNaN(WorldMax))
00713                         {
00714                                 throw new NPlotException( "world min / max not set" );
00715                         }
00716 
00717                         if (coord > this.WorldMax || coord < this.WorldMin)
00718                         {
00719                                 return true;
00720                         }
00721                         else
00722                         {
00723                                 return false;
00724                         }
00725                 }
00726 
00727 
00734                 public void LUB( Axis a )
00735                 {
00736                         if (a == null)
00737                         {
00738                                 return;
00739                         }
00740 
00741                         // mins
00742                         if (!double.IsNaN(a.worldMin_))
00743                         {
00744                                 if (double.IsNaN(worldMin_))
00745                                 {
00746                                         WorldMin = a.WorldMin;
00747                                 }
00748                                 else
00749                                 {
00750                                         if (a.WorldMin < WorldMin)
00751                                         {
00752                                                 WorldMin = a.WorldMin;
00753                                         }
00754                                 }
00755                         }
00756 
00757                         // maxs.
00758                         if (!double.IsNaN(a.worldMax_))
00759                         {
00760                                 if (double.IsNaN(worldMax_))
00761                                 {
00762                                         WorldMax = a.WorldMax;
00763                                 }
00764                                 else
00765                                 {
00766                                         if (a.WorldMax > WorldMax)
00767                                         {
00768                                                 WorldMax = a.WorldMax;
00769                                         }
00770                                 }
00771                         }
00772                 }
00773 
00774 
00785                 public virtual PointF WorldToPhysical( 
00786                         double coord, 
00787                         PointF physicalMin, 
00788                         PointF physicalMax, 
00789                         bool clip )
00790                 {
00791 
00792                         // (1) account for reversed axis. Could be tricky and move
00793                         // this out, but would be a little messy.
00794 
00795                         PointF _physicalMin;
00796                         PointF _physicalMax;
00797 
00798                         if ( this.Reversed )
00799                         {
00800                                 _physicalMin = physicalMax;
00801                                 _physicalMax = physicalMin;
00802                         }
00803                         else
00804                         {
00805                                 _physicalMin = physicalMin;
00806                                 _physicalMax = physicalMax;
00807                         }
00808 
00809 
00810                         // (2) if want clipped value, return extrema if outside range.
00811                         
00812                         if ( clip )
00813                         {
00814                                 if ( WorldMin < WorldMax )
00815                                 {
00816                                         if ( coord > WorldMax )
00817                                         {
00818                                                 return _physicalMax;
00819                                         }
00820                                         if ( coord < WorldMin )
00821                                         {
00822                                                 return _physicalMin;
00823                                         }
00824                                 }
00825                                 else
00826                                 {
00827                                         if ( coord < WorldMax )
00828                                         {
00829                                                 return _physicalMax;
00830                                         }
00831                                         if ( coord > WorldMin )
00832                                         {
00833                                                 return _physicalMin;
00834                                         }
00835                                 }
00836                         }
00837 
00838 
00839                         // (3) we are inside range or don't want to clip.
00840 
00841                         double range = WorldMax - WorldMin;
00842                         double prop = (double)((coord - WorldMin) / range);
00843 
00844                         // Force clipping at bounding box largeClip times that of real bounding box 
00845                         // anyway. This is effectively at infinity.
00846                         const double largeClip = 100.0;
00847                         if (prop > largeClip && clip)
00848                                 prop = largeClip;
00849 
00850                         if (prop < -largeClip && clip)
00851                                 prop = -largeClip;
00852 
00853                         if (range == 0)
00854                         {
00855                                 if (coord >= WorldMin)
00856                                         prop = largeClip;
00857 
00858                                 if (coord < WorldMin)
00859                                         prop = -largeClip;
00860                         }
00861 
00862                         // calculate the physical coordinate.
00863                         PointF offset = new PointF( 
00864                                 (float)(prop * (_physicalMax.X - _physicalMin.X)),
00865                                 (float)(prop * (_physicalMax.Y - _physicalMin.Y)) );
00866 
00867                         return new Point( (int)(_physicalMin.X + offset.X), (int)(_physicalMin.Y + offset.Y) );
00868                 }
00869 
00870 
00880                 public virtual double PhysicalToWorld( 
00881                         PointF p, 
00882                         PointF physicalMin, 
00883                         PointF physicalMax,
00884                         bool clip )
00885                 {
00886 
00887                         // (1) account for reversed axis. Could be tricky and move
00888                         // this out, but would be a little messy.
00889 
00890                         PointF _physicalMin;
00891                         PointF _physicalMax;
00892 
00893                         if ( this.Reversed )
00894                         {
00895                                 _physicalMin = physicalMax;
00896                                 _physicalMax = physicalMin;
00897                         }
00898                         else
00899                         {
00900                                 _physicalMin = physicalMin;
00901                                 _physicalMax = physicalMax;
00902                         }
00903 
00904                         // normalised axis dir vector
00905                         float axis_X = _physicalMax.X - _physicalMin.X;
00906                         float axis_Y = _physicalMax.Y - _physicalMin.Y;
00907                         float len = (float)Math.Sqrt( axis_X * axis_X + axis_Y * axis_Y );
00908                         axis_X /= len;
00909                         axis_Y /= len;
00910 
00911                         // point relative to axis physical minimum.
00912                         PointF posRel = new PointF( p.X - _physicalMin.X, p.Y - _physicalMin.Y );
00913 
00914                         // dist of point projection on axis, normalised.
00915                         float prop = ( axis_X * posRel.X + axis_Y * posRel.Y ) / len;
00916 
00917                         double world = prop * (this.WorldMax - this.WorldMin) + this.WorldMin;
00918 
00919                         // if want clipped value, return extrema if outside range.
00920                         if (clip)
00921                         {
00922                                 world = Math.Max( world, this.WorldMin );
00923                                 world = Math.Min( world, this.WorldMax );
00924                         }
00925 
00926                         return world;
00927                 }
00928 
00929 
00938                 public object DrawLabel( 
00939                         Graphics g, 
00940                         Point offset, 
00941                         Point axisPhysicalMin, 
00942                         Point axisPhysicalMax )
00943                 {
00944 
00945                         if ( Label != "" )
00946                         {
00947         
00948                                 // first calculate any extra offset for axis label spacing.
00949                                 float extraOffsetAmount = this.LabelOffset;
00950                                 extraOffsetAmount += 2.0f; // empirically determed - text was too close to axis before this.
00951                                 
00952                                 if (this.AutoScaleText)
00953                                 {
00954                                         if (this.LabelOffsetScaled)
00955                                         {
00956                                                 extraOffsetAmount *= this.FontScale;
00957                                         }
00958                                 }
00959 
00960                                 // now extend offset.
00961                                 float offsetLength = (float)Math.Sqrt( offset.X*offset.X + offset.Y*offset.Y );
00962                                 if (offsetLength > 0.01)
00963                                 {
00964                                         float x_component = offset.X / offsetLength;
00965                                         float y_component = offset.Y / offsetLength;
00966 
00967                                         x_component *= extraOffsetAmount;
00968                                         y_component *= extraOffsetAmount;
00969 
00970                     if (this.LabelOffsetAbsolute)
00971                     {
00972                         offset.X = (int)x_component;
00973                         offset.Y = (int)y_component;
00974                     }
00975                     else
00976                     {
00977                         offset.X += (int)x_component;
00978                         offset.Y += (int)y_component;
00979                     }
00980                 }
00981                                 
00982                                 // determine angle of axis in degrees
00983                                 double theta = Math.Atan2(
00984                                         axisPhysicalMax.Y - axisPhysicalMin.Y,
00985                                         axisPhysicalMax.X - axisPhysicalMin.X );
00986                                 theta = theta * 180.0f / Math.PI;
00987 
00988                                 PointF average = new PointF(
00989                                         (axisPhysicalMax.X + axisPhysicalMin.X)/2.0f,
00990                                         (axisPhysicalMax.Y + axisPhysicalMin.Y)/2.0f );
00991 
00992                                 g.TranslateTransform( offset.X , offset.Y );    // this is done last.
00993                                 g.TranslateTransform( average.X, average.Y );
00994                                 g.RotateTransform( (float)theta );                              // this is done first.
00995 
00996                                 SizeF labelSize = g.MeasureString( Label, labelFontScaled_);
00997 
00998                                 //bounding box for label centered around zero.
00999                                 RectangleF drawRect = new RectangleF( 
01000                                         -labelSize.Width/2.0f,
01001                                         -labelSize.Height/2.0f,
01002                                         labelSize.Width,
01003                                         labelSize.Height );
01004                                 
01005                                 g.DrawString( 
01006                                         Label, 
01007                                         labelFontScaled_,
01008                                         labelBrush_, 
01009                                         drawRect,
01010                                         drawFormat_ );
01011 
01012                                 // now work out physical bounds of label. 
01013                                 Matrix m = g.Transform;
01014                                 PointF[] recPoints = new PointF[2];
01015                                 recPoints[0] = new PointF( -labelSize.Width/2.0f, -labelSize.Height/2.0f );
01016                                 recPoints[1] = new PointF( labelSize.Width/2.0f, labelSize.Height/2.0f );
01017                                 m.TransformPoints( recPoints );
01018 
01019                                 int x1 = (int)Math.Min( recPoints[0].X, recPoints[1].X );
01020                                 int x2 = (int)Math.Max( recPoints[0].X, recPoints[1].X );
01021                                 int y1 = (int)Math.Min( recPoints[0].Y, recPoints[1].Y );
01022                                 int y2 = (int)Math.Max( recPoints[0].Y, recPoints[1].Y );
01023 
01024                                 g.ResetTransform();
01025 
01026                                 // and return label bounding box.
01027                                 return new Rectangle( x1, y1, (x2-x1), (y2-y1) );
01028                         }
01029 
01030                         return null;
01031                 }
01032 
01033 
01046                 public virtual void DrawTick( 
01047                         Graphics g, 
01048                         double w,
01049                         float size,
01050                         string text,
01051                         Point textOffset,
01052                         Point axisPhysMin,
01053                         Point axisPhysMax,
01054                         out Point labelOffset,
01055                         out Rectangle boundingBox )
01056                 {
01057 
01058                         // determine physical location where tick touches axis. 
01059                         PointF tickStart = WorldToPhysical( w, axisPhysMin, axisPhysMax, true );
01060 
01061                         // determine offset from start point.
01062                         PointF axisDir = Utils.UnitVector( axisPhysMin, axisPhysMax );
01063 
01064                         // rotate axis dir clockwise by angle radians to get tick direction.
01065                         float x1 = (float)(Math.Cos( -this.TicksAngle ) * axisDir.X + Math.Sin( -this.TicksAngle ) * axisDir.Y);
01066                         float y1 = (float)(-Math.Sin( -this.TicksAngle ) * axisDir.X + Math.Cos( -this.TicksAngle ) * axisDir.Y);
01067 
01068                         // now get the scaled tick vector.
01069                         PointF tickVector = new PointF( this.TickScale * size * x1, this.TickScale * size * y1 );
01070 
01071                         if (this.TicksCrossAxis)
01072                         {
01073                                 tickStart = new PointF(
01074                                         tickStart.X - tickVector.X / 2.0f,
01075                                         tickStart.Y - tickVector.Y / 2.0f );
01076                         }
01077 
01078                         // and the end point [point off axis] of tick mark.
01079                         PointF tickEnd = new PointF( tickStart.X + tickVector.X, tickStart.Y + tickVector.Y );
01080 
01081                         // and draw it!
01082                         if (g != null)
01083                                 g.DrawLine( this.linePen_, (int)tickStart.X, (int)tickStart.Y, (int)tickEnd.X, (int)tickEnd.Y );
01084                         // note: casting to int for tick positions was necessary to ensure ticks drawn where we wanted
01085                         // them. Not sure of the reason.
01086 
01087                         // calculate bounds of tick.
01088                         int minX = (int)Math.Min( tickStart.X, tickEnd.X );
01089                         int minY = (int)Math.Min( tickStart.Y, tickEnd.Y );
01090                         int maxX = (int)Math.Max( tickStart.X, tickEnd.X );
01091                         int maxY = (int)Math.Max( tickStart.Y, tickEnd.Y );
01092                         boundingBox = new Rectangle( minX, minY, maxX-minX, maxY-minY );
01093                         
01094                         // by default, label offset from axis is 0. TODO: revise this.
01095                         labelOffset = new Point( 
01096                                 -(int)tickVector.X, 
01097                                 -(int)tickVector.Y );
01098 
01099                         // ------------------------
01100 
01101                         // now draw associated text.
01102 
01103                         // **** TODO ****
01104                         // The following code needs revising. A few things are hard coded when
01105                         // they should not be. Also, angled tick text currently just works for
01106                         // the bottom x-axis. Also, it's a bit hacky.
01107 
01108                         if (text != "" && !HideTickText && g != null )
01109                         {
01110                                 SizeF textSize = g.MeasureString( text, tickTextFontScaled_ );
01111 
01112                                 // determine the center point of the tick text.
01113                                 float textCenterX;
01114                                 float textCenterY;
01115 
01116                                 // if text is at pointy end of tick.
01117                                 if (!this.TickTextNextToAxis)
01118                                 {
01119                                         // offset due to tick.
01120                                         textCenterX = tickStart.X + tickVector.X*1.2f;
01121                                         textCenterY = tickStart.Y + tickVector.Y*1.2f;
01122 
01123                                         // offset due to text box size.
01124                                         textCenterX += 0.5f * x1 * textSize.Width;
01125                                         textCenterY += 0.5f * y1 * textSize.Height;
01126                                 }
01127                                         // else it's next to the axis.
01128                                 else
01129                                 {
01130                                         // start location.
01131                                         textCenterX = tickStart.X;
01132                                         textCenterY = tickStart.Y;
01133 
01134                                         // offset due to text box size.
01135                                         textCenterX -= 0.5f * x1 * textSize.Width;
01136                                         textCenterY -= 0.5f * y1 * textSize.Height;
01137 
01138                                         // bring text away from the axis a little bit.
01139                                         textCenterX -= x1*(2.0f+FontScale);
01140                                         textCenterY -= y1*(2.0f+FontScale);
01141                                 }
01142 
01143                                 // If tick text is angled.. 
01144                                 if (this.TicksLabelAngle != 0.0f)
01145                                 {
01146 
01147                                         // determine the point we want to rotate text about.
01148                                         
01149                                         PointF textScaledTickVector = new PointF( this.TickScale * x1 * (textSize.Height/2.0f), this.TickScale * y1 * (textSize.Height/2.0f) );
01150 
01151                                         PointF rotatePoint;
01152                                         if (this.TickTextNextToAxis) 
01153                                         {
01154                                                 rotatePoint = new PointF( tickStart.X - textScaledTickVector.X, tickStart.Y - textScaledTickVector.Y );
01155                                         }
01156                                         else 
01157                                         {
01158                                                 rotatePoint = new PointF( tickEnd.X + textScaledTickVector.X, tickEnd.Y + textScaledTickVector.Y );
01159                                         }
01160  
01161                                         float actualAngle;
01162                                         if (flipTicksLabel_) 
01163                                         {
01164                                                 double radAngle = (Math.PI / 180) * this.TicksLabelAngle;
01165                                                 rotatePoint.X += textSize.Width * (float)Math.Cos(radAngle);
01166                                                 rotatePoint.Y += textSize.Width * (float)Math.Sin(radAngle);
01167                                                 actualAngle = this.TicksLabelAngle + 180;
01168                                         }
01169                                         else 
01170                                         {
01171                                                 actualAngle = this.TicksLabelAngle;
01172                                         }
01173                                         
01174 
01175                                         g.TranslateTransform( rotatePoint.X, rotatePoint.Y );
01176 
01177                                         g.RotateTransform( actualAngle );
01178                                         
01179                                         Matrix m = g.Transform;
01180                                         PointF[] recPoints = new PointF[2];
01181                                         recPoints[0] = new PointF( 0.0f, -(textSize.Height / 2) );
01182                                         recPoints[1] = new PointF( textSize.Width, textSize.Height );
01183                                         m.TransformPoints( recPoints );
01184 
01185                                         float t_x1 = Math.Min( recPoints[0].X, recPoints[1].X );
01186                                         float t_x2 = Math.Max( recPoints[0].X, recPoints[1].X );
01187                                         float t_y1 = Math.Min( recPoints[0].Y, recPoints[1].Y );
01188                                         float t_y2 = Math.Max( recPoints[0].Y, recPoints[1].Y );
01189                                         
01190                                         boundingBox = Rectangle.Union(boundingBox, new Rectangle( (int)t_x1, (int)t_y1, (int)(t_x2-t_x1), (int)(t_y2-t_y1) ) );
01191                                         RectangleF drawRect = new RectangleF( 0.0f, -(textSize.Height / 2), textSize.Width, textSize.Height );
01192 
01193                                         g.DrawString( 
01194                                                 text,
01195                                                 tickTextFontScaled_,
01196                                                 tickTextBrush_, 
01197                                                 drawRect,
01198                                                 drawFormat_ );
01199 
01200                                         t_x2 -= tickStart.X;
01201                                         t_y2 -= tickStart.Y;
01202                                         t_x2 *= 1.25f;
01203                                         t_y2 *= 1.25f;
01204 
01205                                         labelOffset = new Point( (int)t_x2, (int)t_y2 );
01206 
01207                                         g.ResetTransform();
01208 
01209                                         //g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );
01210 
01211                                 }
01212                                 else
01213                                 {
01214 
01215                                         float bx1 = (textCenterX - textSize.Width/2.0f);
01216                                         float by1 = (textCenterY - textSize.Height/2.0f);
01217                                         float bx2 = textSize.Width;
01218                                         float by2 = textSize.Height;
01219 
01220                                         RectangleF drawRect = new RectangleF( bx1, by1, bx2, by2 );
01221                                         Rectangle drawRect_int = new Rectangle( (int)bx1, (int)by1, (int)bx2, (int)by2 );
01222                                         // g.DrawRectangle( new Pen(Color.Green), bx1, by1, bx2, by2 );
01223 
01224                                         boundingBox = Rectangle.Union( boundingBox, drawRect_int );
01225 
01226                                         // g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );
01227 
01228                                         g.DrawString( 
01229                                                 text,
01230                                                 tickTextFontScaled_,
01231                                                 tickTextBrush_,
01232                                                 drawRect,
01233                                                 drawFormat_ );
01234 
01235                                         textCenterX -= tickStart.X;
01236                                         textCenterY -= tickStart.Y;
01237                                         textCenterX *= 2.3f;
01238                                         textCenterY *= 2.3f;
01239 
01240                                         labelOffset = new Point( (int)textCenterX, (int)textCenterY );
01241                                 }
01242                         } 
01243 
01244                 }
01245 
01246 
01257                 public virtual void Draw( 
01258                         System.Drawing.Graphics g,
01259                         Point physicalMin,
01260                         Point physicalMax, 
01261                         out Rectangle boundingBox )
01262                 {
01263                         // calculate the bounds of the axis line only.
01264                         int x1 = Math.Min( physicalMin.X, physicalMax.X );
01265                         int x2 = Math.Max( physicalMin.X, physicalMax.X );
01266                         int y1 = Math.Min( physicalMin.Y, physicalMax.Y );
01267                         int y2 = Math.Max( physicalMin.Y, physicalMax.Y );
01268                         Rectangle bounds = new Rectangle( x1, y1, x2-x1, y2-y1 );
01269 
01270                         if (!Hidden)
01271                         {
01272                                 
01273                                 // (1) Draw the axis line.
01274                                 g.DrawLine( this.linePen_, physicalMin.X, physicalMin.Y, physicalMax.X, physicalMax.Y );
01275 
01276                                 // (2) draw tick marks (subclass responsibility). 
01277 
01278                                 object labelOffset;
01279                                 object tickBounds;
01280                                 this.DrawTicks( g, physicalMin, physicalMax, out labelOffset, out tickBounds );
01281 
01282                                 // (3) draw the axis label
01283                                 object labelBounds = null;
01284                                 if (!this.HideTickText)
01285                                 {
01286                                         labelBounds = this.DrawLabel( g, (Point)labelOffset, physicalMin, physicalMax );
01287                                 }
01288 
01289                                 // (4) merge bounds and return.
01290                                 if (labelBounds != null)
01291                                         bounds = Rectangle.Union( bounds, (Rectangle)labelBounds );
01292 
01293                                 if (tickBounds != null)
01294                                         bounds = Rectangle.Union( bounds, (Rectangle)tickBounds );
01295 
01296                         }
01297 
01298                         boundingBox = bounds;
01299                 }
01300 
01301 
01311                 protected static void UpdateOffsetAndBounds( 
01312                         ref object labelOffset, ref object boundingBox, 
01313                         Point mergeLabelOffset, Rectangle mergeBoundingBox )
01314                 {
01315                         // determining largest label offset and use it.
01316                         Point lo = (Point)labelOffset;
01317                         double norm1 = Math.Sqrt( lo.X*lo.X + lo.Y*lo.Y );
01318                         double norm2 = Math.Sqrt( mergeLabelOffset.X*mergeLabelOffset.X + mergeLabelOffset.Y*mergeLabelOffset.Y );
01319                         if (norm1 < norm2)
01320                         {
01321                                 labelOffset = mergeLabelOffset;
01322                         }
01323 
01324                         // determining bounding box.
01325                         Rectangle b = mergeBoundingBox;
01326                         if (boundingBox == null)
01327                         {
01328                                 boundingBox = b;
01329                         }
01330                         else
01331                         {
01332                                 boundingBox = Rectangle.Union( (Rectangle)boundingBox, b );
01333                         }
01334                 }
01335 
01336 
01345                 protected virtual void DrawTicks( 
01346                         Graphics g, 
01347                         Point physicalMin, 
01348                         Point physicalMax, 
01349                         out object labelOffset,
01350                         out object boundingBox )
01351                 {
01352                         labelOffset = null;
01353                         boundingBox = null;
01354                         // do nothing. This class is not abstract because a subclass may
01355                         // want to override the Axis.Draw method to one that doesn't 
01356                         // require DrawTicks.
01357                 }
01358 
01359 
01360 
01364                 public double WorldLength
01365                 {
01366                         get
01367                         {
01368                                 return Math.Abs( worldMax_ - worldMin_ );
01369                         }
01370                 }
01371 
01385                 internal virtual void WorldTickPositions_FirstPass(
01386                         Point physicalMin, 
01387                         Point physicalMax,
01388                         out ArrayList largeTickPositions,
01389                         out ArrayList smallTickPositions
01390                         )
01391                 {
01392                         largeTickPositions = new ArrayList();
01393                         smallTickPositions = null;
01394                 }
01395 
01396 
01408                 internal virtual void WorldTickPositions_SecondPass( 
01409                         Point physicalMin,
01410                         Point physicalMax,
01411                         ArrayList largeTickPositions, 
01412                         ref ArrayList smallTickPositions )
01413                 {
01414                         if (smallTickPositions == null)
01415                                 smallTickPositions = new ArrayList();
01416                 }
01417 
01418 
01426                 public void WorldTickPositions(
01427                         Point physicalMin,
01428                         Point physicalMax,
01429                         out ArrayList largeTickPositions,
01430                         out ArrayList smallTickPositions
01431                         )
01432                 {
01433                         WorldTickPositions_FirstPass( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions );
01434                         WorldTickPositions_SecondPass( physicalMin, physicalMax, largeTickPositions, ref smallTickPositions );
01435                 }
01436 
01437 
01447                 public void IncreaseRange( double percent )
01448                 {
01449                         double range = WorldMax - WorldMin;
01450                         
01451                         if ( !Utils.DoubleEqual( range, 0.0 ) )
01452                         {
01453                                 range *= percent;
01454                         }
01455                         else
01456                         {
01457                                 // arbitrary number. 
01458                                 // TODO make this configurable.
01459                                 range = 0.01;
01460                         }
01461 
01462                         WorldMax += range;
01463                         WorldMin -= range;
01464                 }
01465 
01466 
01471                 internal float FontScale 
01472                 {
01473                         get 
01474                         {
01475                                 return fontScale_;
01476                         }
01477  
01478                         set 
01479                         {
01480                                 fontScale_ = value;
01481                                 UpdateScale();
01482                         }
01483                 }
01484                 private float fontScale_;
01485 
01486 
01491                 internal float TickScale 
01492                 {
01493                         get 
01494                         {
01495                                 return tickScale_;
01496                         }
01497                         set 
01498                         {
01499                                 tickScale_ = value;
01500                         }
01501                 }
01502                 private float tickScale_;
01503 
01504 
01505                 private void UpdateScale()      
01506                 {
01507                         if (labelFont_ != null)
01508                                 this.labelFontScaled_ = Utils.ScaleFont( labelFont_, FontScale );
01509                         
01510                         if (tickTextFont_ != null)
01511                                 this.tickTextFontScaled_ = Utils.ScaleFont( tickTextFont_, FontScale );
01512                 }
01513 
01514 
01518                 public virtual bool IsLinear
01519                 {
01520                         get
01521                         {
01522                                 return true;
01523                         }
01524                 }
01525 
01526                 private float labelOffset_ = 0;
01535                 public float LabelOffset
01536                 {
01537                         get
01538                         {
01539                                 return labelOffset_;
01540                         }
01541                         set
01542                         {
01543                                 labelOffset_ = value;
01544                         }
01545                 }
01546 
01547         private bool labelOffsetAbsolute_ = false;
01555         public bool LabelOffsetAbsolute
01556         {
01557             get
01558             {
01559                 return labelOffsetAbsolute_;
01560             }
01561             set
01562             {
01563                 labelOffsetAbsolute_ = value;
01564             }
01565         }
01566 
01567         private bool labelOffsetScaled_ = true;
01572                 public bool LabelOffsetScaled
01573                 {
01574                         get
01575                         {
01576                                 return labelOffsetScaled_;
01577                         }
01578                         set
01579                         {
01580                                 labelOffsetScaled_ = value;
01581                         }
01582                 }
01583 
01584 
01592                 protected Point getDefaultLabelOffset( Point physicalMin, Point physicalMax )
01593                 {
01594                         System.Drawing.Rectangle tBoundingBox;
01595                         System.Drawing.Point tLabelOffset;
01596 
01597                         this.DrawTick( null, this.WorldMax, this.LargeTickSize, 
01598                                 "",
01599                                 new Point(0,0),
01600                                 physicalMin, physicalMax,
01601                                 out tLabelOffset, out tBoundingBox );
01602 
01603                         return tLabelOffset;
01604                 }
01605 
01606 
01610         public Color Color
01611         {
01612             set
01613             {
01614                 this.AxisColor = value;
01615                 this.TickTextColor = value;
01616                 this.LabelColor = value;
01617             }
01618         }
01619 
01620 
01621         /*
01622 
01623         // finish implementation of this at some point.
01624 
01625         public class WorldValueChangedArgs
01626         {
01627             public WorldValueChangedArgs( double value, MinMaxType minOrMax )
01628             {
01629                 Value = value;
01630                 MinOrMax = minOrMax;
01631             }
01632 
01633             public double Value;
01634 
01635             public enum MinMaxType
01636             {
01637                 Min = 0,
01638                 Max = 1
01639             }
01640 
01641             public MinMaxType MinOrMax;
01642         }
01643 
01644 
01645         public delegate void WorldValueChangedHandler( object sender, WorldValueChangedArgs e );
01646 
01647         public event WorldValueChangedHandler WorldMinChanged;
01648         public event WorldValueChangedHandler WorldMaxChanged;
01649         public event WorldValueChangedHandler WorldExtentsChanged;
01650 
01651         */
01652 
01653     }
01654 }

Generated on Sat Nov 5 01:04:05 2005 for NPlot by  doxygen 1.4.5