LinearAxis.cs

Go to the documentation of this file.
00001 /*
00002 NPlot - A charting library for .NET
00003 
00004 LinearAxis.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;
00053 using System.Collections;
00054 using System;
00055 using System.Text;
00056 using System.Diagnostics;
00057 
00058 namespace NPlot
00059 {
00063         public class LinearAxis : Axis, System.ICloneable
00064         {
00065 
00070                 public override object Clone()
00071                 {
00072                         LinearAxis a = new LinearAxis();
00073                         // ensure that this isn't being called on a derived type. If it is, then oh no!
00074                         if (this.GetType() != a.GetType())
00075                         {
00076                                 throw new NPlotException( "Clone not defined in derived type. Help!" );
00077                         }
00078                         this.DoClone( this, a );
00079                         return a;
00080                 }
00081 
00082 
00086                 protected void DoClone( LinearAxis b, LinearAxis a )
00087                 {
00088                         Axis.DoClone( b, a );
00089 
00090                         a.numberSmallTicks_ = b.numberSmallTicks_;
00091                         a.largeTickValue_ = b.largeTickValue_;
00092                         a.largeTickStep_ = b.largeTickStep_;
00093 
00094                         a.offset_ = b.offset_;
00095                         a.scale_ = b.scale_;
00096                 }
00097 
00098 
00103                 public LinearAxis( Axis a )
00104                         : base( a )
00105                 {
00106                         Init();
00107                 }
00108 
00109 
00113                 public LinearAxis()
00114                         : base()
00115                 {
00116                         Init();
00117                 }
00118 
00119 
00125                 public LinearAxis( double worldMin, double worldMax )
00126                         : base( worldMin, worldMax )
00127                 {
00128                         Init();
00129                 }
00130 
00131 
00132                 private void Init() 
00133                 {
00134                         this.NumberFormat = "{0:g5}";
00135                 }
00136 
00137 
00146                 protected override void DrawTicks(
00147                         Graphics g, 
00148                         Point physicalMin, 
00149                         Point physicalMax, 
00150                         out object labelOffset,
00151                         out object boundingBox )
00152                 {
00153 
00154                         Point tLabelOffset;
00155                         Rectangle tBoundingBox;
00156 
00157                         labelOffset = this.getDefaultLabelOffset( physicalMin, physicalMax );
00158                         boundingBox = null;
00159 
00160                         ArrayList largeTickPositions;
00161                         ArrayList smallTickPositions;
00162                         this.WorldTickPositions( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions );
00163 
00164                         labelOffset = new Point( 0, 0 );
00165                         boundingBox = null;
00166 
00167                         if (largeTickPositions.Count > 0)
00168                         {
00169                                 for (int i = 0; i < largeTickPositions.Count; ++i)
00170                                 {
00171                                         double labelNumber = (double)largeTickPositions[i];
00172 
00173                                         // TODO: Find out why zero is sometimes significantly not zero [seen as high as 10^-16].
00174                                         if (Math.Abs(labelNumber) < 0.000000000000001)
00175                                         {
00176                                                 labelNumber = 0.0;
00177                                         }
00178 
00179                                         StringBuilder label = new StringBuilder();
00180                                         label.AppendFormat(this.NumberFormat, labelNumber);
00181 
00182                                         this.DrawTick( g, ((double)largeTickPositions[i]), 
00183                                                 this.LargeTickSize, label.ToString(),
00184                                                 new Point(0,0), physicalMin, physicalMax, 
00185                                                 out tLabelOffset, out tBoundingBox );
00186                                         
00187                                         Axis.UpdateOffsetAndBounds( ref labelOffset, ref boundingBox, 
00188                                                 tLabelOffset, tBoundingBox );
00189 
00190                                 }
00191                         }
00192 
00193                         for (int i = 0; i<smallTickPositions.Count; ++i)
00194                         {
00195                                 this.DrawTick( g, ((double)smallTickPositions[i]), 
00196                                         this.SmallTickSize, "", 
00197                                         new Point(0, 0), physicalMin, physicalMax, 
00198                                         out tLabelOffset, out tBoundingBox );
00199 
00200                                 // assume bounding box and label offset unchanged by small tick bounds.
00201                         }
00202 
00203                 }
00204 
00205 
00215                 internal override void WorldTickPositions_SecondPass( 
00216                         Point physicalMin,
00217                         Point physicalMax,
00218                         ArrayList largeTickPositions, 
00219                         ref ArrayList smallTickPositions )
00220                 {
00221 
00222                         // return if already generated.
00223                         if (smallTickPositions != null)
00224                                 return;
00225 
00226                         int physicalAxisLength = Utils.Distance( physicalMin, physicalMax );
00227 
00228                         double adjustedMax = this.AdjustedWorldValue( WorldMax );
00229                         double adjustedMin = this.AdjustedWorldValue( WorldMin );
00230 
00231                         smallTickPositions = new ArrayList();
00232 
00233                         // TODO: Can optimize this now.
00234                         bool shouldCullMiddle;
00235                         double bigTickSpacing = this.DetermineLargeTickStep( physicalAxisLength, out shouldCullMiddle );
00236 
00237                         int nSmall = this.DetermineNumberSmallTicks( bigTickSpacing );
00238                         double smallTickSpacing = bigTickSpacing / (double)nSmall;
00239 
00240                         // if there is at least one big tick
00241                         if (largeTickPositions.Count > 0)
00242                         {
00243                                 double pos1 = (double)largeTickPositions[0];
00244                                 while (pos1 > adjustedMin)
00245                                 {
00246                                         pos1 -= smallTickSpacing;
00247                                         smallTickPositions.Add( pos1 );
00248                                 }
00249                         }
00250 
00251                         for (int i = 0; i < largeTickPositions.Count; ++i )
00252                         {
00253                                 for (int j = 1; j < nSmall; ++j )
00254                                 {
00255                                         double pos = (double)largeTickPositions[i] + ((double)j) * smallTickSpacing;
00256                                         if (pos <= adjustedMax)
00257                                         {
00258                                                 smallTickPositions.Add( pos );
00259                                         }
00260                                 }
00261                         }
00262 
00263                 }
00264 
00271                 public double AdjustedWorldValue( double world )
00272                 {
00273                         return world * this.scale_ + this.offset_;
00274                 }
00275 
00276 
00290                 internal override void WorldTickPositions_FirstPass(
00291                         Point physicalMin, 
00292                         Point physicalMax,
00293                         out ArrayList largeTickPositions,
00294                         out ArrayList smallTickPositions
00295                         )
00296                 {
00297 
00298                         // (1) error check
00299 
00300                         if ( double.IsNaN(WorldMin) || double.IsNaN(WorldMax) )
00301                         {
00302                                 throw new NPlotException( "world extent of axis not set." );
00303                         }
00304                         
00305                         double adjustedMax = this.AdjustedWorldValue( WorldMax );
00306                         double adjustedMin = this.AdjustedWorldValue( WorldMin );
00307 
00308                         // (2) determine distance between large ticks.
00309                         bool shouldCullMiddle;
00310                         double tickDist = this.DetermineLargeTickStep( 
00311                                 Utils.Distance(physicalMin, physicalMax),
00312                                 out shouldCullMiddle );
00313 
00314                         // (3) determine starting position.
00315                 
00316                         double first = 0.0f;
00317 
00318                         if (!double.IsNaN(largeTickValue_)) 
00319                         {
00320                                 // this works for both case when largTickValue_ lt or gt adjustedMin.
00321                                 first = largeTickValue_ + (Math.Ceiling((adjustedMin-largeTickValue_)/tickDist))*tickDist;
00322                         }
00323 
00324                         else
00325                         {
00326                                 if( adjustedMin > 0.0 )
00327                                 {
00328                                         double nToFirst = Math.Floor(adjustedMin / tickDist) + 1.0f;
00329                                         first = nToFirst * tickDist;
00330                                 }
00331                                 else
00332                                 {
00333                                         double nToFirst = Math.Floor(-adjustedMin/tickDist) - 1.0f;
00334                                         first = -nToFirst * tickDist;
00335                                 }
00336 
00337                                 // could miss one, if first is just below zero.
00338                                 if ((first - tickDist) >= adjustedMin)
00339                                 {
00340                                         first -= tickDist;
00341                                 }
00342                         }
00343 
00344 
00345                         // (4) now make list of large tick positions.
00346                         
00347                         largeTickPositions = new ArrayList();
00348 
00349                         if (tickDist < 0.0) // some sanity checking. TODO: remove this.
00350                                 throw new NPlotException( "Tick dist is negative" );
00351 
00352                         double position = first;
00353                         int safetyCount = 0;
00354                         while (
00355                                 (position <= adjustedMax) && 
00356                                 (++safetyCount < 5000) )
00357                         {
00358                                 largeTickPositions.Add( position );
00359                                 position += tickDist;
00360                         }
00361 
00362                         // (5) if the physical extent is too small, and the middle 
00363                         // ticks should be turned into small ticks, then do this now.
00364                         smallTickPositions = null;
00365                         if (shouldCullMiddle)
00366                         {
00367                                 smallTickPositions = new ArrayList();
00368 
00369                                 if (largeTickPositions.Count > 2)
00370                                 {
00371                                         for (int i=1; i<largeTickPositions.Count-1; ++i)
00372                                         {
00373                                                 smallTickPositions.Add( largeTickPositions[i] );
00374                                         }
00375                                 }
00376 
00377                                 ArrayList culledPositions = new ArrayList();
00378                                 culledPositions.Add( largeTickPositions[0] );
00379                                 culledPositions.Add( largeTickPositions[largeTickPositions.Count-1] );
00380                                 largeTickPositions = culledPositions;
00381                         }
00382 
00383                 }
00384 
00385 
00398                 private double DetermineLargeTickStep( float physicalLength, out bool shouldCullMiddle )
00399                 {
00400                         shouldCullMiddle = false;
00401 
00402                         if ( double.IsNaN(WorldMin) || double.IsNaN(WorldMax) )
00403                         {
00404                                 throw new NPlotException( "world extent of axis not set." );
00405                         }
00406 
00407                         // if the large tick has been explicitly set, then return this.
00408                         if ( !double.IsNaN(largeTickStep_) )
00409                         {
00410                                 if ( largeTickStep_ <= 0.0f )
00411                                 {
00412                                         throw new NPlotException( 
00413                                                 "can't have negative or zero tick step - reverse WorldMin WorldMax instead."
00414                                         );
00415                                 }
00416                                 return largeTickStep_;
00417                         }
00418 
00419                         // otherwise we need to calculate the large tick step ourselves.
00420 
00421                         // adjust world max and min for offset and scale properties of axis.
00422                         double adjustedMax = this.AdjustedWorldValue( WorldMax );
00423                         double adjustedMin = this.AdjustedWorldValue( WorldMin );
00424                         double range = adjustedMax - adjustedMin;
00425 
00426                         // if axis has zero world length, then return arbitrary number.
00427                         if ( Utils.DoubleEqual( adjustedMax, adjustedMin ) )
00428                         {
00429                                 return 1.0f;
00430                         }
00431 
00432                         double approxTickStep;
00433                         if (TicksIndependentOfPhysicalExtent)
00434                         {
00435                                 approxTickStep = range / 6.0f;
00436                         }
00437                         else
00438                         {
00439                                 approxTickStep = (MinPhysicalLargeTickStep / physicalLength) * range;
00440                         }
00441 
00442                         double exponent = Math.Floor( Math.Log10( approxTickStep ) );
00443                         double mantissa = Math.Pow( 10.0, Math.Log10( approxTickStep ) - exponent );
00444 
00445                         // determine next whole mantissa below the approx one.
00446                         int mantissaIndex = Mantissas.Length-1;
00447                         for (int i=1; i<Mantissas.Length; ++i)
00448                         {
00449                                 if (mantissa < Mantissas[i])
00450                                 {
00451                                         mantissaIndex = i-1;
00452                                         break;
00453                                 }
00454                         }
00455                         
00456                         // then choose next largest spacing. 
00457                         mantissaIndex += 1;
00458                         if (mantissaIndex == Mantissas.Length)
00459                         {
00460                                 mantissaIndex = 0;
00461                                 exponent += 1.0;
00462                         }
00463 
00464                         if (!TicksIndependentOfPhysicalExtent)
00465                         {
00466                                 // now make sure that the returned value is such that at least two 
00467                                 // large tick marks will be displayed.
00468                                 double tickStep = Math.Pow( 10.0, exponent ) * Mantissas[mantissaIndex];
00469                                 float physicalStep = (float)((tickStep / range) * physicalLength);
00470 
00471                                 while (physicalStep > physicalLength/2)
00472                                 {
00473                                         shouldCullMiddle = true;
00474 
00475                                         mantissaIndex -= 1;
00476                                         if (mantissaIndex == -1)
00477                                         {
00478                                                 mantissaIndex = Mantissas.Length-1;
00479                                                 exponent -= 1.0;
00480                                         }
00481 
00482                                         tickStep = Math.Pow( 10.0, exponent ) * Mantissas[mantissaIndex];
00483                                         physicalStep = (float)((tickStep / range) * physicalLength);
00484                                 }
00485                         }
00486 
00487                         // and we're done.
00488                         return Math.Pow( 10.0, exponent ) * Mantissas[mantissaIndex];
00489 
00490                 }
00491 
00492 
00499                 private int DetermineNumberSmallTicks( double bigTickDist )
00500                 {
00501 
00502                         if (this.numberSmallTicks_ != null)
00503                         {
00504                                 return (int)this.numberSmallTicks_+1;
00505                         }
00506 
00507                         if (this.SmallTickCounts.Length != this.Mantissas.Length)
00508                         {
00509                                 throw new NPlotException( "Mantissa.Length != SmallTickCounts.Length" );
00510                         }
00511 
00512                         if (bigTickDist > 0.0f)
00513                         {
00514 
00515                                 double exponent = Math.Floor( Math.Log10( bigTickDist ) );
00516                                 double mantissa = Math.Pow( 10.0, Math.Log10( bigTickDist ) - exponent );
00517 
00518                                 for (int i=0; i<Mantissas.Length; ++i)
00519                                 {
00520                                         if ( Math.Abs(mantissa-Mantissas[i]) < 0.001 )
00521                                         {
00522                                                 return SmallTickCounts[i]+1;
00523                                         }
00524                                 }
00525 
00526                         }
00527                                 
00528                         return 0;
00529 
00530                 }
00531 
00532 
00537                 public double LargeTickStep
00538                 {
00539                         set
00540                         {
00541                                 largeTickStep_ = value;
00542                         }
00543                         get
00544                         {
00545                                 return largeTickStep_;
00546                         }
00547                 }
00551                 private double largeTickStep_ = double.NaN;
00552 
00553 
00558                 public double LargeTickValue
00559                 {
00560                         set
00561                         {
00562                                 largeTickValue_ = value;
00563                         }
00564                         get
00565                         {
00566                                 return largeTickValue_;
00567                         }
00568                 }
00569                 private double largeTickValue_ = double.NaN;
00570 
00574                 public int NumberOfSmallTicks
00575                 {
00576                         set
00577                         {
00578                                 numberSmallTicks_ = value;
00579                         }
00580                         get
00581                         {
00582                                 // TODO: something better here.
00583                                 return (int)numberSmallTicks_;
00584                         }
00585                 }
00586                 private object numberSmallTicks_ = null;
00587 
00593                 public double Scale
00594                 {
00595                         get
00596                         {
00597                                 return scale_;
00598                         }
00599                         set
00600                         {
00601                                 scale_ = value;
00602                         }
00603                 }
00604 
00610                 public double Offset
00611                 {
00612                         get
00613                         {
00614                                 return offset_;
00615                         }
00616                         set
00617                         {
00618                                 offset_ = value;
00619                         }
00620                 }
00621 
00629                 public float ApproxNumberLargeTicks = 3.0f;
00630 
00636                 public double[] Mantissas = {1.0, 2.0, 5.0};
00637 
00643                 public int[] SmallTickCounts = {4, 1, 4};
00644 
00645 
00646                 private double offset_ = 0.0;
00647 
00648                 private double scale_ = 1.0;
00649         }
00650 }

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