DateTimeAxis.cs

Go to the documentation of this file.
00001 /*
00002 NPlot - A charting library for .NET
00003 
00004 DateTimeAxis.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 
00053 using System;
00054 using System.Drawing;
00055 using System.Collections;
00056 using System.Globalization;
00057 
00058 // TODO: More control over how labels are displayed.
00059 // TODO: SkipWeekends property.
00060 // TODO: Make a relative (as opposed to absolute) TimeAxis.
00061 
00062 namespace NPlot
00063 {
00067         public class DateTimeAxis : Axis
00068         {
00069 
00070 
00071                 #region Clone implementation
00076                 public override object Clone()
00077                 {
00078                         DateTimeAxis a = new DateTimeAxis();
00079                         // ensure that this isn't being called on a derived type. If it is, then oh no!
00080                         if (this.GetType() != a.GetType())
00081                         {
00082                                 throw new NPlotException( "Clone not defined in derived type. Help!" );
00083                         }
00084                         DoClone( this, a );
00085                         return a;
00086                 }
00087 
00088 
00094                 protected static void DoClone( DateTimeAxis b, DateTimeAxis a )
00095                 {
00096                         Axis.DoClone( b, a );
00097                 }
00098                 #endregion
00099 
00100                 private void Init()
00101                 {
00102                 }
00103 
00104 
00109                 public DateTimeAxis( Axis a )
00110                         : base( a )
00111                 {
00112                         this.Init();
00113                         this.NumberFormat = null;
00114                 }
00115 
00116 
00120                 public DateTimeAxis()
00121                         : base()
00122                 {
00123                         this.Init();
00124                 }
00125 
00126 
00132                 public DateTimeAxis( double worldMin, double worldMax )
00133                         : base( worldMin, worldMax )
00134                 {
00135                         this.Init();
00136                 }
00137 
00138 
00144                 public DateTimeAxis( long worldMin, long worldMax )
00145                         : base( (double)worldMin, (double)worldMax )
00146                 {
00147                         this.Init();
00148                 }
00149 
00150 
00156                 public DateTimeAxis( DateTime worldMin, DateTime worldMax )
00157                         : base( (double)worldMin.Ticks, (double)worldMax.Ticks )
00158                 {
00159                         this.Init();
00160                 }
00161 
00162 
00171                 protected override void DrawTicks( 
00172                         Graphics g, 
00173                         Point physicalMin, 
00174                         Point physicalMax, 
00175                         out object labelOffset,
00176                         out object boundingBox )
00177                 {
00178                         
00179                         // TODO: Look at offset and bounding box logic again here. why temp and other vars? 
00180 
00181                         Point tLabelOffset;
00182                         Rectangle tBoundingBox;
00183 
00184                         labelOffset = this.getDefaultLabelOffset( physicalMin, physicalMax );
00185                         boundingBox = null;
00186                 
00187                         ArrayList largeTicks;
00188                         ArrayList smallTicks;
00189                         this.WorldTickPositions( physicalMin, physicalMax, out largeTicks, out smallTicks );
00190 
00191                         // draw small ticks.
00192                         for (int i=0; i<smallTicks.Count; ++i)
00193                         {
00194                                 this.DrawTick( g, (double)smallTicks[i], 
00195                                         this.SmallTickSize, "", new Point(0, 0),
00196                                         physicalMin, physicalMax, 
00197                                         out tLabelOffset, out tBoundingBox );
00198                                 // assume label offset and bounding box unchanged by small tick bounds.
00199                         }
00200 
00201                         // draw large ticks.
00202                         for (int i=0; i<largeTicks.Count; ++i)
00203                         {
00204                                         
00205                                 DateTime tickDate = new DateTime( (long)((double)largeTicks[i]) );
00206                                 string label = "";
00207                                 
00208                                 if(this.NumberFormat == null || this.NumberFormat == String.Empty) 
00209                                 {
00210                                         if ( this.LargeTickLabelType_ == LargeTickLabelType.year )
00211                                         {
00212                                                 label = tickDate.Year.ToString();
00213                                         }
00214 
00215                                         else if ( this.LargeTickLabelType_ == LargeTickLabelType.month )
00216                                         {
00217                                                 label = tickDate.ToString("MMM");
00218                                                 label += " ";
00219                                                 label += tickDate.Year.ToString().Substring(2,2);
00220                                         }
00221 
00222                                         else if ( this.LargeTickLabelType_ == LargeTickLabelType.day )
00223                                         {
00224                                                 label = (tickDate.Day).ToString();
00225                                                 label += " ";
00226                                                 label += tickDate.ToString("MMM");
00227                                         }
00228                                 
00229                                         else if ( this.LargeTickLabelType_ == LargeTickLabelType.hourMinute )
00230                                         {
00231                                                 string minutes = tickDate.Minute.ToString();
00232                                                 if (minutes.Length == 1)
00233                                                 {
00234                                                         minutes = "0" + minutes;
00235                                                 }
00236                                                 label = tickDate.Hour.ToString() + ":" + minutes;
00237                                         }
00238                                         else if ( this.LargeTickLabelType_ == LargeTickLabelType.hourMinuteSeconds )
00239                                         {
00240                                                 string minutes = tickDate.Minute.ToString();
00241                                                 string seconds = tickDate.Second.ToString();
00242                                                 if (seconds.Length == 1)
00243                                                 {
00244                                                         seconds = "0" + seconds;
00245                                                 }
00246 
00247                                                 if (minutes.Length == 1)
00248                                                 {
00249                                                         minutes = "0" + minutes;
00250                                                 }
00251                                                 label = tickDate.Hour.ToString() + ":" + minutes + "." + seconds;       
00252                                         }
00253 
00254                                 }
00255                                 else 
00256                                 {
00257                                         label = tickDate.ToString(NumberFormat);
00258                                 }
00259 
00260                                 this.DrawTick( g, (double)largeTicks[i], 
00261                                         this.LargeTickSize, label, new Point( 0, 0 ),
00262                                         physicalMin, physicalMax, out tLabelOffset, out tBoundingBox );
00263 
00264                                 Axis.UpdateOffsetAndBounds( ref labelOffset, ref boundingBox, tLabelOffset, tBoundingBox );
00265                         }
00266 
00267                 }
00268 
00269 
00270                 private enum LargeTickLabelType 
00271                 {
00272                         none = 0,
00273                         year = 1,
00274                         month = 2,
00275                         day = 3,
00276                         hourMinute = 4,
00277                         hourMinuteSeconds = 5
00278                 }
00279 
00280 
00281                 // this gets set after a get LargeTickPositions.
00282                 private LargeTickLabelType LargeTickLabelType_;
00283 
00293                 internal override void WorldTickPositions_FirstPass(
00294                         Point physicalMin, 
00295                         Point physicalMax,
00296                         out ArrayList largeTickPositions,
00297                         out ArrayList smallTickPositions
00298                         )
00299                 {
00300                         smallTickPositions = null;
00301 
00302                         largeTickPositions = new ArrayList();
00303 
00304                         const int daysInMonth = 30;
00305 
00306                         TimeSpan timeLength = new TimeSpan( (long)(WorldMax-WorldMin));
00307                         DateTime worldMinDate = new DateTime( (long)this.WorldMin );
00308                         DateTime worldMaxDate = new DateTime( (long)this.WorldMax );
00309 
00310                         if(largeTickStep_ == TimeSpan.Zero) 
00311                         {
00312 
00313                                 // if less than 10 minutes, then large ticks on second spacings. 
00314 
00315                                 if ( timeLength < new TimeSpan(0,0,2,0,0) )
00316                                 {
00317                                         this.LargeTickLabelType_ = LargeTickLabelType.hourMinuteSeconds;
00318 
00319                                         double secondsSkip;
00320 
00321                                         if (timeLength < new TimeSpan( 0,0,0,10,0 ) )
00322                                                 secondsSkip = 1.0;
00323                                         else if ( timeLength < new TimeSpan(0,0,0,20,0) )
00324                                                 secondsSkip = 2.0;
00325                                         else if ( timeLength < new TimeSpan(0,0,0,50,0) )
00326                                                 secondsSkip = 5.0;
00327                                         else if ( timeLength < new TimeSpan(0,0,2,30,0) )
00328                                                 secondsSkip = 15.0;
00329                                         else 
00330                                                 secondsSkip = 30.0;
00331 
00332                                         int second = worldMinDate.Second;
00333                                         second -= second % (int)secondsSkip;                                    
00334 
00335                                         DateTime currentTickDate = new DateTime( 
00336                                                 worldMinDate.Year,
00337                                                 worldMinDate.Month, 
00338                                                 worldMinDate.Day,
00339                                                 worldMinDate.Hour,
00340                                                 second,0,0 );
00341 
00342                                         while ( currentTickDate < worldMaxDate )
00343                                         {
00344                                                 double world = (double)currentTickDate.Ticks;
00345 
00346                                                 if ( world >= this.WorldMin && world <= this.WorldMax )
00347                                                 {
00348                                                         largeTickPositions.Add( world );
00349                                                 }
00350 
00351                                                 currentTickDate = currentTickDate.AddSeconds( secondsSkip );
00352                                         }
00353                                 }
00354 
00355                                 // Less than 2 hours, then large ticks on minute spacings.
00356 
00357                                 else if ( timeLength < new TimeSpan(0,2,0,0,0) )
00358                                 {
00359                                         this.LargeTickLabelType_ = LargeTickLabelType.hourMinute;
00360 
00361                                         double minuteSkip;
00362 
00363                                         if ( timeLength < new TimeSpan(0,0,10,0,0) )
00364                                                 minuteSkip = 1.0;
00365                                         else if ( timeLength < new TimeSpan(0,0,20,0,0) )
00366                                                 minuteSkip = 2.0;
00367                                         else if ( timeLength < new TimeSpan(0,0,50,0,0) )
00368                                                 minuteSkip = 5.0;
00369                                         else if ( timeLength < new TimeSpan(0,2,30,0,0) )
00370                                                 minuteSkip = 15.0;
00371                                         else //( timeLength < new TimeSpan( 0,5,0,0,0) )
00372                                                 minuteSkip = 30.0;
00373 
00374                                         int minute = worldMinDate.Minute;
00375                                         minute -= minute % (int)minuteSkip;                                     
00376 
00377                                         DateTime currentTickDate = new DateTime( 
00378                                                 worldMinDate.Year,
00379                                                 worldMinDate.Month, 
00380                                                 worldMinDate.Day,
00381                                                 worldMinDate.Hour,
00382                                                 minute,0,0 );
00383 
00384                                         while ( currentTickDate < worldMaxDate )
00385                                         {
00386                                                 double world = (double)currentTickDate.Ticks;
00387 
00388                                                 if ( world >= this.WorldMin && world <= this.WorldMax )
00389                                                 {
00390                                                         largeTickPositions.Add( world );
00391                                                 }
00392 
00393                                                 currentTickDate = currentTickDate.AddMinutes( minuteSkip );
00394                                         }
00395                                 }
00396 
00397                                 // Less than 2 days, then large ticks on hour spacings.
00398 
00399                                 else if ( timeLength < new TimeSpan(2,0,0,0,0) )
00400                                 {
00401                                         this.LargeTickLabelType_ = LargeTickLabelType.hourMinute;
00402 
00403                                         double hourSkip;
00404                                         if ( timeLength < new TimeSpan(0,10,0,0,0) )
00405                                                 hourSkip = 1.0;
00406                                         else if ( timeLength < new TimeSpan(0,20,0,0,0) )
00407                                                 hourSkip = 2.0;
00408                                         else
00409                                                 hourSkip = 6.0;
00410 
00411 
00412                                         int hour = worldMinDate.Hour;
00413                                         hour -= hour % (int)hourSkip;                                   
00414 
00415                                         DateTime currentTickDate = new DateTime( 
00416                                                 worldMinDate.Year,
00417                                                 worldMinDate.Month, 
00418                                                 worldMinDate.Day,
00419                                                 hour,0,0,0 );
00420 
00421                                         while ( currentTickDate < worldMaxDate )
00422                                         {
00423                                                 double world = (double)currentTickDate.Ticks;
00424 
00425                                                 if ( world >= this.WorldMin && world <= this.WorldMax )
00426                                                 {
00427                                                         largeTickPositions.Add( world );
00428                                                 }
00429 
00430                                                 currentTickDate = currentTickDate.AddHours( hourSkip );
00431                                         }
00432 
00433                                 }
00434 
00435 
00436                                 // less than 5 months, then large ticks on day spacings.
00437 
00438                                 else if ( timeLength < new TimeSpan(daysInMonth*4,0,0,0,0))
00439                                 {
00440                                         this.LargeTickLabelType_ = LargeTickLabelType.day;
00441 
00442                                         double daySkip;
00443                                         if ( timeLength < new TimeSpan(10,0,0,0,0) )
00444                                                 daySkip = 1.0;
00445                                         else if (timeLength < new TimeSpan(20,0,0,0,0) )
00446                                                 daySkip = 2.0;
00447                                         else if (timeLength < new TimeSpan(7*10,0,0,0,0) )
00448                                                 daySkip = 7.0;
00449                                         else 
00450                                                 daySkip = 14.0;
00451 
00452                                         DateTime currentTickDate = new DateTime( 
00453                                                 worldMinDate.Year,
00454                                                 worldMinDate.Month, 
00455                                                 worldMinDate.Day );
00456 
00457                     if (daySkip == 2.0)
00458                     {
00459 
00460                         TimeSpan timeSinceBeginning = currentTickDate - DateTime.MinValue;
00461 
00462                         if (timeSinceBeginning.Days % 2 == 1)
00463                             currentTickDate = currentTickDate.AddDays(-1.0);
00464                     }
00465 
00466                     if (daySkip == 7 || daySkip == 14.0)
00467                     {
00468                         DayOfWeek dow = currentTickDate.DayOfWeek;
00469                         switch (dow)
00470                         {
00471                             case DayOfWeek.Monday:
00472                                 break;
00473                             case DayOfWeek.Tuesday:
00474                                 currentTickDate = currentTickDate.AddDays(-1.0);
00475                                 break;
00476                             case DayOfWeek.Wednesday:
00477                                 currentTickDate = currentTickDate.AddDays(-2.0);
00478                                 break;
00479                             case DayOfWeek.Thursday:
00480                                 currentTickDate = currentTickDate.AddDays(-3.0);
00481                                 break;
00482                             case DayOfWeek.Friday:
00483                                 currentTickDate = currentTickDate.AddDays(-4.0);
00484                                 break;
00485                             case DayOfWeek.Saturday:
00486                                 currentTickDate = currentTickDate.AddDays(-5.0);
00487                                 break;
00488                             case DayOfWeek.Sunday:
00489                                 currentTickDate = currentTickDate.AddDays(-6.0);
00490                                 break;
00491                         }
00492 
00493                     }
00494 
00495                     if (daySkip == 14.0f)
00496                     {
00497                         TimeSpan timeSinceBeginning = currentTickDate - DateTime.MinValue;
00498 
00499                         if ((timeSinceBeginning.Days / 7) % 2 == 1)
00500                         {
00501                             currentTickDate = currentTickDate.AddDays(-7.0);
00502                         }
00503                     }
00504 
00505                     while ( currentTickDate < worldMaxDate )
00506                                         {
00507                                                 double world = (double)currentTickDate.Ticks;
00508 
00509                                                 if ( world >= this.WorldMin && world <= this.WorldMax )
00510                                                 {
00511                                                         largeTickPositions.Add( world );
00512                                                 }
00513 
00514                                                 currentTickDate = currentTickDate.AddDays(daySkip);
00515                                         }
00516                                 }
00517 
00518 
00519                                         // else ticks on month or year spacings.
00520 
00521                                 else if ( timeLength >= new TimeSpan(daysInMonth*4,0,0,0,0) )
00522                                 {
00523 
00524                                         int monthSpacing = 0;
00525                         
00526                                         if ( timeLength.Days < daysInMonth*(12*3+6) )
00527                                         {
00528                                                 LargeTickLabelType_ = LargeTickLabelType.month;
00529 
00530                                                 if ( timeLength.Days < daysInMonth*10 )
00531                                                         monthSpacing = 1;
00532                                                 else if ( timeLength.Days < daysInMonth*(12*2) )
00533                                                         monthSpacing = 3;
00534                                                 else // if ( timeLength.Days < daysInMonth*(12*3+6) )
00535                                                         monthSpacing = 6;
00536                                         }
00537                                         else
00538                                         {
00539                                                 LargeTickLabelType_ = LargeTickLabelType.year;
00540 
00541                         if (timeLength.Days < daysInMonth * (12 * 6))
00542                             monthSpacing = 12;
00543                         else if (timeLength.Days < daysInMonth * (12 * 12))
00544                             monthSpacing = 24;
00545                         else if (timeLength.Days < daysInMonth * (12 * 30))
00546                             monthSpacing = 60;
00547                         else
00548                             monthSpacing = 120;
00549                         //LargeTickLabelType_ = LargeTickLabelType.none;
00550                                         }
00551 
00552                                         // truncate start
00553                                         DateTime currentTickDate = new DateTime( 
00554                                                 worldMinDate.Year,
00555                                                 worldMinDate.Month, 
00556                                                 1 );
00557                         
00558                                         if (monthSpacing > 1)
00559                                         {
00560                                                 currentTickDate = currentTickDate.AddMonths(
00561                                                         -(currentTickDate.Month-1)%monthSpacing );
00562                                         }
00563 
00564                                         // Align on 2 or 5 year boundaries if necessary.
00565                                         if (monthSpacing >= 24)
00566                                         {
00567                                                 currentTickDate = currentTickDate.AddYears(
00568                                                         -(currentTickDate.Year)%(monthSpacing/12) );                                            
00569                                         }
00570 
00571                                         //this.firstLargeTick_ = (double)currentTickDate.Ticks;
00572 
00573                                         if ( LargeTickLabelType_ != LargeTickLabelType.none )
00574                                         {
00575                                                 while ( currentTickDate < worldMaxDate )
00576                                                 {
00577                                                         double world = (double)currentTickDate.Ticks;
00578 
00579                                                         if ( world >= this.WorldMin && world <= this.WorldMax )
00580                                                         {
00581                                                                 largeTickPositions.Add( world );
00582                                                         }
00583 
00584                                                         currentTickDate = currentTickDate.AddMonths( monthSpacing );
00585                                                 }
00586                                         }
00587                                 }
00588                         }
00589                         else
00590                         {
00591                                 for (DateTime date = worldMinDate; date < worldMaxDate; date += largeTickStep_) 
00592                                 {
00593                                         largeTickPositions.Add((double)date.Ticks);
00594                                 }
00595                         }
00596         }
00597 
00598 
00611         internal override void WorldTickPositions_SecondPass(
00612             Point physicalMin,
00613             Point physicalMax,
00614             ArrayList largeTickPositions,
00615             ref ArrayList smallTickPositions
00616           )
00617         {
00618             if (largeTickPositions.Count < 2 || !(LargeTickLabelType_.Equals(LargeTickLabelType.year)))
00619             {
00620                 smallTickPositions = new ArrayList(); ;
00621             }
00622             else
00623             {
00624                 smallTickPositions = new ArrayList();
00625                 double diff = 0.5 * (((double)largeTickPositions[1]) - ((double)largeTickPositions[0]));
00626                 if (((double)largeTickPositions[0] - diff) > this.WorldMin)
00627                 {
00628                     smallTickPositions.Add((double)largeTickPositions[0] - diff);
00629                 }
00630                 for (int i = 0; i < largeTickPositions.Count - 1; i++)
00631                 {
00632                     smallTickPositions.Add(((double)largeTickPositions[i]) + diff);
00633                 }
00634                 if (((double)largeTickPositions[largeTickPositions.Count - 1] + diff) < this.WorldMax)
00635                 {
00636                     smallTickPositions.Add((double)largeTickPositions[largeTickPositions.Count - 1] + diff);
00637                 }
00638             }
00639         }
00640 
00641 
00646                 public TimeSpan LargeTickStep 
00647                 {
00648                         set 
00649                         {
00650                                 largeTickStep_ = value;
00651                         }
00652                         get 
00653                         {
00654                                 return largeTickStep_;
00655                         }
00656                 }
00657                 private TimeSpan largeTickStep_ = TimeSpan.Zero;
00658 
00659 
00660         }
00661 }

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