LogAxis.cs

Go to the documentation of this file.
00001 /*
00002 NPlot - A charting library for .NET
00003 
00004 LogAxis.cs
00005 Copyright (C) 2003
00006 Paolo Pierini, 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;
00053 using System.Drawing;
00054 using System.Collections;
00055 using System.Text;
00056 
00057 namespace NPlot
00058 {
00062         public class LogAxis : Axis
00063         {
00064 
00069                 public override object Clone()
00070                 {
00071                         LogAxis a = new LogAxis();
00072                         if (this.GetType() != a.GetType())
00073                         {
00074                                 throw new NPlotException("Clone not defined in derived type. Help!");
00075                         }
00076                         this.DoClone( this, a );
00077                         return a;
00078                 }
00079 
00080 
00086                 protected void DoClone(LogAxis b, LogAxis a)
00087                 {
00088                         Axis.DoClone(b,a);
00089                         // add specific elemtents of the class for the deep copy of the object
00090                         a.numberSmallTicks_ = b.numberSmallTicks_;
00091                         a.largeTickValue_ = b.largeTickValue_;
00092                         a.largeTickStep_ = b.largeTickStep_;
00093                 }
00094 
00095 
00099                 public LogAxis()
00100                         : base()
00101                 {
00102                         Init();
00103                 }
00104 
00105 
00110                 public LogAxis(Axis a)
00111                         : base(a)
00112                 {
00113                         Init();
00114                 }
00115 
00116 
00122                 public LogAxis(double worldMin, double worldMax)
00123                         : base( worldMin, worldMax )
00124                 {
00125                         Init();
00126                 }
00127 
00128 
00129                 private void Init() 
00130                 {
00131                         this.NumberFormat = "{0:g5}";
00132                 }
00133 
00134 
00145                 protected override void DrawTicks(
00146                         Graphics g, 
00147                         Point physicalMin, 
00148                         Point physicalMax, 
00149                         out object labelOffset,
00150                         out object boundingBox )
00151                 {
00152 
00153                         Point tLabelOffset;
00154                         Rectangle tBoundingBox;
00155 
00156                         labelOffset = this.getDefaultLabelOffset( physicalMin, physicalMax );
00157                         boundingBox = null;
00158 
00159                         ArrayList largeTickPositions;
00160                         ArrayList smallTickPositions;
00161                         this.WorldTickPositions( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions );
00162 
00163                         Point offset = new Point( 0, 0 );
00164                         object bb = null;
00165                         // Missed this protection
00166                         if (largeTickPositions.Count > 0)
00167                         {
00168                                 for (int i=0; i<largeTickPositions.Count; ++i)
00169                                 {
00170                                         StringBuilder label = new StringBuilder();
00171                                         // do google search for "format specifier writeline" for help on this.
00172                                         label.AppendFormat(this.NumberFormat, (double)largeTickPositions[i]);
00173                                         this.DrawTick( g, (double)largeTickPositions[i], this.LargeTickSize, label.ToString(),
00174                                                 new Point(0,0), physicalMin, physicalMax, out tLabelOffset, out tBoundingBox );
00175 
00176                                         Axis.UpdateOffsetAndBounds( ref labelOffset, ref boundingBox, tLabelOffset, tBoundingBox );
00177                                 }
00178                         }
00179                         else
00180                         {
00181                                 // just get the axis bounding box)
00182                                 PointF dir = Utils.UnitVector(physicalMin,physicalMax);
00183                                 Rectangle rr = new Rectangle( physicalMin.X,
00184                                         (int)((physicalMax.X-physicalMin.X)*dir.X),
00185                                         physicalMin.Y,
00186                                         (int)((physicalMax.Y-physicalMin.Y)*dir.Y) );
00187                                 bb = rr;
00188                         }
00189                         
00190 
00191                         // missed protection for zero ticks
00192                         if (smallTickPositions.Count > 0)
00193                         {
00194                                 for (int i=0; i<smallTickPositions.Count; ++i)
00195                                 {
00196                                         this.DrawTick( g, (double)smallTickPositions[i], this.SmallTickSize,
00197                                                 "", new Point(0,0), physicalMin, physicalMax, out tLabelOffset, out tBoundingBox );
00198                                         // ignore r for now - assume bb unchanged by small tick bounds.
00199                                 }
00200                         }
00201 
00202                 }
00203 
00204 
00213                 internal override void WorldTickPositions_SecondPass( 
00214                         Point physicalMin,
00215                         Point physicalMax,
00216                         ArrayList largeTickPositions, 
00217                         ref ArrayList smallTickPositions )
00218                 {
00219 
00220                         if (smallTickPositions != null)
00221                         {
00222                                 throw new NPlotException( "not expecting smallTickPositions to be set already." );
00223                         }
00224 
00225                         smallTickPositions = new ArrayList();
00226 
00227                         // retrieve the spacing of the big ticks. Remember this is decades!
00228                         double bigTickSpacing = this.DetermineTickSpacing();
00229                         int nSmall = this.DetermineNumberSmallTicks( bigTickSpacing );
00230 
00231                         // now we have to set the ticks
00232                         // let us start with the easy case where the major tick distance
00233                         // is larger than a decade
00234                         if ( bigTickSpacing > 1.0f )
00235                         {
00236                                 if (largeTickPositions.Count > 0)
00237                                 {
00238                                         // deal with the smallticks preceding the
00239                                         // first big tick
00240                                         double pos1 = (double)largeTickPositions[0];
00241                                         while (pos1 > this.WorldMin)
00242                                         {
00243                                                 pos1 = pos1 / 10.0f;
00244                                                 smallTickPositions.Add( pos1 );
00245                                         }
00246                                         // now go on for all other Major ticks
00247                                         for (int i=0; i<largeTickPositions.Count; ++i )
00248                                         {
00249                                                 double pos = (double)largeTickPositions[i];
00250                                                 for (int j=1; j<=nSmall; ++j )
00251                                                 {
00252                                                         pos=pos*10.0F;
00253                                                         // check to see if we are still in the range
00254                                                         if (pos < WorldMax)
00255                                                         {
00256                                                                 smallTickPositions.Add( pos );
00257                                                         }
00258                                                 }
00259                                         }
00260                                 }
00261                         }
00262                         else
00263                         {
00264                                 // guess what...
00265                                 double [] m = { 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f };
00266                                 // Then we deal with the other ticks
00267                                 if (largeTickPositions.Count > 0)
00268                                 {
00269                                         // first deal with the smallticks preceding the first big tick
00270                                         // positioning before the first tick
00271                                         double pos1=(double)largeTickPositions[0]/10.0f;
00272                                         for (int i=0; i<m.Length; i++)
00273                                         {
00274                                                 double pos=pos1*m[i];
00275                                                 if (pos>this.WorldMin)
00276                                                 {
00277                                                         smallTickPositions.Add(pos);
00278                                                 }
00279                                         }
00280                                         // now go on for all other Major ticks
00281                                         for (int i=0; i<largeTickPositions.Count; ++i )
00282                                         {
00283                                                 pos1=(double)largeTickPositions[i];
00284                                                 for (int j=0; j<m.Length; ++j )
00285                                                 {
00286                                                         double pos=pos1*m[j];
00287                                                         // check to see if we are still in the range
00288                                                         if (pos < WorldMax)
00289                                                         {
00290                                                                 smallTickPositions.Add( pos );
00291                                                         }
00292                                                 }
00293                                         }
00294                                 }
00295                                 else
00296                                 {
00297                                         // probably a minor tick would anyway fall in the range
00298                                         // find the decade preceding the minimum
00299                                         double dec=Math.Floor(Math.Log10(WorldMin));
00300                                         double pos1=Math.Pow(10.0,dec);
00301                                         for (int i=0; i<m.Length; i++)
00302                                         {
00303                                                 double pos=pos1*m[i];
00304                                                 if (pos>this.WorldMin && pos< this.WorldMax )
00305                                                 {
00306                                                         smallTickPositions.Add(pos);
00307                                                 }
00308                                         }
00309                                 }
00310                         }
00311 
00312                 }
00313 
00314                 private static double m_d5Log = -Math.Log10(0.5);   // .30103
00315                 private static double m_d5RegionPos = Math.Abs(m_d5Log + ((1 - m_d5Log) / 2)); //          ' .6505
00316                 private static double m_d5RegionNeg = Math.Abs(m_d5Log / 2); //    '.1505
00317 
00318                 private void CalcGrids( double dLenAxis, int nNumDivisions, ref double dDivisionInterval)
00319                 {
00320                         double dMyInterval  = dLenAxis / nNumDivisions;
00321                         double dPower = Math.Log10(dMyInterval);
00322                         dDivisionInterval = 10 ^ (int)dPower;
00323                         double dFixPower = dPower - (int)dPower;
00324                         double d5Region = Math.Abs(dPower - dFixPower);
00325                         double dMyMult;
00326                         if (dPower < 0)
00327                         {
00328                                 d5Region = -(dPower - dFixPower);
00329                                 dMyMult = 0.5;
00330                         }
00331                         else
00332                         {
00333                                 d5Region = 1 - (dPower - dFixPower);
00334                                 dMyMult = 5;
00335                         }
00336                         if ((d5Region >= m_d5RegionNeg) && (d5Region <= m_d5RegionPos))
00337                         {
00338                                 dDivisionInterval = dDivisionInterval * dMyMult;
00339                         }
00340                 }
00341 
00349                 internal override void WorldTickPositions_FirstPass(
00350                         Point physicalMin, 
00351                         Point physicalMax,
00352                         out ArrayList largeTickPositions,
00353                         out ArrayList smallTickPositions
00354                         )
00355                 {
00356 
00357                         smallTickPositions = null;
00358                         largeTickPositions = new ArrayList();
00359 
00360                         if ( double.IsNaN(WorldMin) || double.IsNaN(WorldMax) )
00361                         {
00362                                 throw new NPlotException( "world extent of axis not set." );
00363                         }
00364 
00365                         double roundTickDist = this.DetermineTickSpacing( );
00366 
00367                         // now determine first tick position.
00368                         double first = 0.0f;
00369 
00370                         // if the user hasn't specified a large tick position.
00371                         if (double.IsNaN(largeTickValue_))
00372                         {
00373                                 if( WorldMin > 0.0 )
00374                                 {
00375 
00376                                         double nToFirst = Math.Floor(Math.Log10(WorldMin) / roundTickDist)+1.0f;
00377                                         first = nToFirst * roundTickDist;
00378                                 }
00379 
00380                                 // could miss one, if first is just below zero.
00381                                 if (first-roundTickDist >= Math.Log10(WorldMin))
00382                                 {
00383                                         first -= roundTickDist;
00384                                 }
00385                         }
00386                         
00387                         // the user has specified one place they would like a large tick placed.
00388                         else
00389                         {
00390                                 first = Math.Log10( this.LargeTickValue );
00391 
00392                                 // TODO: check here not too much different.
00393                                 // could result in long loop.
00394                                 while (first < Math.Log10(WorldMin))
00395                                 {
00396                                         first += roundTickDist;
00397                                 }
00398 
00399                                 while (first > Math.Log10(WorldMin)+roundTickDist)
00400                                 {
00401                                         first -= roundTickDist;
00402                                 }
00403                         }
00404 
00405                         double mark = first;
00406                         while (mark <= Math.Log10(WorldMax))
00407                         {
00408                                 // up to here only logs are dealt with, but I want to return
00409                                 // a real value in the arraylist
00410                                 double val;
00411                                 val = Math.Pow( 10.0, mark );
00412                                 largeTickPositions.Add( val );
00413                                 mark += roundTickDist;
00414                         }
00415 
00416                 }
00417 
00418 
00423                 private double DetermineTickSpacing( )
00424                 {
00425                         if ( double.IsNaN(WorldMin) || double.IsNaN(WorldMax) )
00426                         {
00427                                 throw new NPlotException( "world extent of axis is not set." );
00428                         }
00429 
00430                         // if largeTickStep has been set, it is used
00431                         if ( !double.IsNaN( this.largeTickStep_) )
00432                         {
00433                                 if ( this.largeTickStep_ <= 0.0f )
00434                                 {
00435                                         throw new NPlotException( "can't have negative tick step - reverse WorldMin WorldMax instead." );
00436                                 }
00437 
00438                                 return this.largeTickStep_;
00439                         }
00440 
00441                         double MagRange = (double)(Math.Floor(Math.Log10(WorldMax)) - Math.Floor(Math.Log10(WorldMin))+1.0);
00442 
00443                         if ( MagRange > 0.0 )
00444                         {
00445                                 // for now, a simple logic
00446                                 // start with a major tick every order of magnitude, and
00447                                 // increment if in order not to have more than 10 ticks in
00448                                 // the plot.
00449                                 double roundTickDist=1.0F;
00450                                 int nticks=(int)(MagRange/roundTickDist);
00451                                 while (nticks > 10)
00452                                 {
00453                                         roundTickDist++;
00454                                         nticks=(int)(MagRange/roundTickDist);
00455                                 }
00456                                 return roundTickDist;
00457                         }
00458                         else
00459                         {
00460                                 return 0.0f;
00461                         }
00462                 }
00463 
00464 
00470                 private int DetermineNumberSmallTicks( double bigTickDist )
00471                 {
00472                         // if the big ticks is more than one decade, the
00473                         // small ticks are every decade, I don't let the user set it.
00474                         if (this.numberSmallTicks_ != null && bigTickDist == 1.0f)
00475                         {
00476                                 return (int)this.numberSmallTicks_+1;
00477                         }
00478 
00479                         // if we are plotting every decade, we have to
00480                         // put the log ticks. As a start, I put every
00481                         // small tick (.2,.3,.4,.5,.6,.7,.8,.9)
00482                         if (bigTickDist == 1.0f)
00483                         {
00484                                 return 8;
00485                         }
00486                                 // easy, put a tick every missed decade
00487                         else if (bigTickDist > 1.0f)
00488                         {
00489                                 return (int)bigTickDist - 1;
00490                         }
00491                         else
00492                         {
00493                                 throw new NPlotException("Wrong Major tick distance setting");
00494                         }
00495                 }
00496 
00497 
00501                 public double LargeTickStep
00502                 {
00503                         set
00504                         {
00505                                 largeTickStep_ = value;
00506                         }
00507                         get
00508                         {
00509                                 return largeTickStep_;
00510                         }
00511                 }
00512 
00513 
00517                 public double LargeTickValue
00518                 {
00519                         set
00520                         {
00521                                 largeTickValue_ = value;
00522                         }
00523                         get
00524                         {
00525                                 return largeTickValue_;
00526                         }
00527                 }
00528 
00529 
00533                 public int NumberSmallTicks
00534                 {
00535                         set
00536                         {
00537                                 numberSmallTicks_ = value;
00538                         }
00539                 }
00540 
00541 
00542                 // Private members
00543                 private object numberSmallTicks_;
00544                 private double largeTickValue_ = double.NaN;
00545                 private double largeTickStep_ = double.NaN;
00546 
00556                 public override PointF WorldToPhysical( 
00557                         double coord,
00558                         PointF physicalMin, 
00559                         PointF physicalMax,
00560                         bool clip )
00561                 {
00562                         // if want clipped value, return extrema if outside range.
00563                         if (clip)
00564                         {
00565                                 if (coord > WorldMax)
00566                                 {
00567                                         return physicalMax;
00568                                 }
00569                                 if (coord < WorldMin)
00570                                 {
00571                                         return physicalMin;
00572                                 }
00573                         }
00574 
00575                         if (coord < 0.0f)
00576                         {
00577                                 throw new NPlotException( "Cannot have negative values for data using Log Axis" );
00578                         }
00579 
00580                         // inside range or don't want to clip.
00581                         double lrange = (double)(Math.Log10(WorldMax) - Math.Log10(WorldMin));
00582                         double prop = (double)((Math.Log10(coord) - Math.Log10(WorldMin)) / lrange);
00583                         PointF offset = new PointF( (float)(prop * (physicalMax.X - physicalMin.X)),
00584                                 (float)(prop * (physicalMax.Y - physicalMin.Y)) );
00585 
00586                         return new PointF( physicalMin.X + offset.X, physicalMin.Y + offset.Y );
00587                 }
00588 
00589 
00599                 public override double PhysicalToWorld( PointF p, PointF physicalMin, PointF physicalMax, bool clip )
00600                 {
00601                         // use the base method to do the projection on the axis.
00602                         double t = base.PhysicalToWorld( p, physicalMin, physicalMax, clip );
00603 
00604                         // now reconstruct phys dist prop along this assuming linear scale as base method did.
00605                         double v = (t - this.WorldMin) / (this.WorldMax - this.WorldMin);
00606 
00607                         double ret = WorldMin*Math.Pow( WorldMax / WorldMin, v );
00608 
00609                         // if want clipped value, return extrema if outside range.
00610                         if (clip)
00611                         {
00612                                 ret = Math.Max( ret, WorldMin );
00613                                 ret = Math.Min( ret, WorldMax );
00614                         }
00615 
00616                         return ret;
00617 
00618                 }
00619 
00620 
00624                 public override double WorldMin
00625                 {
00626                         get
00627                         {
00628                                 return (double)base.WorldMin;
00629                         }
00630                         set
00631                         {
00632                                 if (value > 0.0f)
00633                                 {
00634                                         base.WorldMin = value;
00635                                 }
00636                                 else
00637                                 {
00638                                         throw new NPlotException("Cannot have negative values in Log Axis");
00639                                 }
00640                         }
00641                 }
00642 
00643 
00647                 public override double WorldMax
00648                 {
00649                         get
00650                         {
00651                                 return (double)base.WorldMax;
00652                         }
00653                         set
00654                         {
00655                                 if (value > 0.0F)
00656                                 {
00657                                         base.WorldMax = value;
00658                                 }
00659                                 else
00660                                 {
00661                                         throw new NPlotException("Cannot have negative values in Log Axis");
00662                                 }
00663                         }
00664                 }
00665 
00669                 public override bool IsLinear
00670                 {
00671                         get
00672                         {
00673                                 return false;
00674                         }
00675                 }
00676 
00677         }
00678 }

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