00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
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
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
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
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
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
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
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
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
00309 bool shouldCullMiddle;
00310 double tickDist = this.DetermineLargeTickStep(
00311 Utils.Distance(physicalMin, physicalMax),
00312 out shouldCullMiddle );
00313
00314
00315
00316 double first = 0.0f;
00317
00318 if (!double.IsNaN(largeTickValue_))
00319 {
00320
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
00338 if ((first - tickDist) >= adjustedMin)
00339 {
00340 first -= tickDist;
00341 }
00342 }
00343
00344
00345
00346
00347 largeTickPositions = new ArrayList();
00348
00349 if (tickDist < 0.0)
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
00363
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
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
00420
00421
00422 double adjustedMax = this.AdjustedWorldValue( WorldMax );
00423 double adjustedMin = this.AdjustedWorldValue( WorldMin );
00424 double range = adjustedMax - adjustedMin;
00425
00426
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
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
00457 mantissaIndex += 1;
00458 if (mantissaIndex == Mantissas.Length)
00459 {
00460 mantissaIndex = 0;
00461 exponent += 1.0;
00462 }
00463
00464 if (!TicksIndependentOfPhysicalExtent)
00465 {
00466
00467
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
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
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 }