root/plot/Pybrary.Plot/DateTimeAxis.cs

Revision 746, 11.7 kB (checked in by mfenniak, 2 years ago)

--

Line 
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Drawing;
5 using System.Drawing.Drawing2D;
6 using System.Globalization;
7
8 namespace Pybrary.Plot
9 {
10     public class DateTimeAxis : Axis, XAxis
11     {
12         // priority of scales - zoomed, user, data, unscaled
13
14         private DateTime? zoomedMinimum = null;
15         private DateTime? zoomedMaximum = null;
16         private DateTime? userMinimum = null;
17         private DateTime? userMaximum = null;
18         private DateTime unscaledMinimum = new DateTime(2000, 1, 1);
19         private DateTime unscaledMaximum = new DateTime(2000, 12, 31);
20         private float minorTickLength = 0.025f; // in inches
21         private PenDescription minorTickPen = new PenDescription(Color.Black, 1f / 96);
22
23         private Plot parent;
24         private bool horizontalMonthLabels = false;
25         private bool verticalMonthLabels = false;
26         private bool quarterLabels = false;
27         private bool dailyTicks = false;
28
29         private GregorianCalendar cal = new GregorianCalendar();
30
31         public DateTimeAxis(Plot parent)
32         {
33             this.parent = parent;
34         }
35
36         public float CalculateHeight(Graphics g, float maximumWidth)
37         {
38             float height = 0;
39
40             dailyTicks = horizontalMonthLabels = verticalMonthLabels =
41                 quarterLabels = false;
42
43             using (Font f = labelFont.CreateFont())
44             {
45                 horizontalMonthLabels = true;
46                 SizeF labelSize = g.MeasureString("Mar '05", f);
47                 int numLabels = calculateNumLabels();
48
49                 float estimatedWidth = (labelSize.Width + 0.3f) * numLabels;
50                 if (estimatedWidth < maximumWidth)
51                 {
52                     // monthly horizontal labels
53                     height += labelSize.Height;
54
55                     // do we also have room for daily ticks?
56                     int numDays = (ScaleMaximum - ScaleMinimum).Days;
57                     dailyTicks = (0.05f * numDays) < maximumWidth;
58                 }
59                 else
60                 {
61                     // our total width is going to be too wide.  fallback #1 -
62                     // vertically drawn labels
63                     horizontalMonthLabels = false;
64                     verticalMonthLabels = true;
65
66                     estimatedWidth = (labelSize.Height + 0.3f) * numLabels;
67                     if (estimatedWidth < maximumWidth)
68                     {
69                         // vertical labels will work fine.
70                         height += labelSize.Width;
71                     }
72                     else
73                     {
74                         verticalMonthLabels = false;
75                         quarterLabels = true;
76
77                         // still too wide - switch to "Q1 '04", "Q2 '04", ect.
78                         labelSize = g.MeasureString("Q1 '05", f);
79                         height += labelSize.Width;
80                         quarterLabels = true;
81
82                         // future work - switch to just the year, then the decade
83                         // if ever necessary
84                     }
85                 }
86             }
87
88             // tick marks
89             height += tickLength;
90
91             return height;
92         }
93
94         private int calculateNumLabels()
95         {
96             DateTime v = ScaleMinimum;
97             int i;
98             for (i = 0; ; i++)
99             {
100                 if (v > ScaleMaximum)
101                     break;
102                 v = cal.AddMonths(v, quarterLabels ? 3 : 1);
103             }
104             return i;
105         }
106
107         public void DrawX(Graphics g, AdvancedRect area, AdvancedRect plotArea)
108         {
109             drawArea = area;
110
111             //using (Brush br = new SolidBrush(Color.Green))
112             //    g.FillRectangle(br, area.Rect);
113             GraphicsState _s = g.Save();
114
115             using (Brush br = labelFont.CreateBrush())
116             using (Font f = labelFont.CreateFont())
117             using (Pen p = tickPen.CreatePen())
118             using (Pen pgrid = gridlinePen.CreatePen())
119             using (Pen pminor = minorTickPen.CreatePen())
120             {
121                 float tick = tickLength;
122                 float dayTick = minorTickLength;
123                 DateTime v;
124                 if (verticalMonthLabels || horizontalMonthLabels)
125                     v = new DateTime(ScaleMinimum.Year, ScaleMinimum.Month, 1);
126                 else // quarterLabels
127                     // set v to beginning of quarter which ScaleMinimum is in
128                     v = new DateTime(ScaleMinimum.Year, (((ScaleMinimum.Month - 1) / 3) * 3) + 1, 1);
129                 for (int i = 0; i < calculateNumLabels(); i++)
130                 {
131                     float x1 = DataToCoordinate(v, area);
132                     x1 = Math.Max(x1, area.TopLeft.X);
133
134                     DateTime v2 = cal.AddMonths(v, quarterLabels ? 3 : 1);
135                     float x2 = DataToCoordinate(v2, area);
136                     x2 = Math.Min(x2, area.BottomRight.X);
137
138                     g.DrawLine(p, x1, area.TopLeft.Y, x1, area.TopLeft.Y + tick);
139                     if (gridlinesEnabled && x1 != area.TopLeft.X && x1 != area.BottomRight.X)
140                         g.DrawLine(pgrid, x1, plotArea.TopLeft.Y, x1, plotArea.BottomRight.Y);
141
142                     if (dailyTicks)
143                     {
144                         int days = cal.GetDaysInMonth(v.Year, v.Month);
145                         for (int d = 1; d <= days; d++)
146                         {
147                             float xd = DataToCoordinate(new DateTime(v.Year, v.Month, d), area);
148                             if (xd > area.TopLeft.X && xd < area.BottomRight.X)
149                                 g.DrawLine(pminor, xd, area.TopLeft.Y, xd, area.TopLeft.Y + dayTick);
150                         }
151                     }
152                     else if (quarterLabels)
153                     {
154                         for (int d = 1; d < 3; d++)
155                         {
156                             float xd = DataToCoordinate(new DateTime(v.Year, v.Month + d, 1), area);
157                             if (xd > area.TopLeft.X && xd < area.BottomRight.X)
158                                 g.DrawLine(pminor, xd, area.TopLeft.Y, xd, area.TopLeft.Y + dayTick);
159                         }
160                     }
161
162                     StringFormat form = new StringFormat();
163                     if (verticalMonthLabels || quarterLabels)
164                         form.FormatFlags = StringFormatFlags.DirectionVertical;
165
166                     string txt;
167                     if (verticalMonthLabels || horizontalMonthLabels)
168                         txt = String.Format("{0:MMM \\'yy}", v);
169                     else
170                         txt = String.Format("Q{0} {1:\\'yy}", (v.Month / 3) + 1, v);
171
172                     SizeF sz = g.MeasureString(txt, f, 100, form);
173                     g.DrawString(txt, f, br, ((x1 + x2) / 2) - (sz.Width / 2), area.TopLeft.Y + tick, form);
174
175                     v = v2;
176                 }
177
178                 // one final tick to signify end of last visible month
179                 float xL = DataToCoordinate(v, area);
180                 if (xL < area.BottomRight.X)
181                 {
182                     g.DrawLine(p, xL, area.TopLeft.Y, xL, area.TopLeft.Y + tick);
183                     if (gridlinesEnabled)
184                         g.DrawLine(pgrid, xL, plotArea.TopLeft.Y, xL, plotArea.BottomRight.Y);
185                 }
186             }
187
188             g.Restore(_s);
189         }
190
191         public float DataToCoordinate(DateTime v, AdvancedRect rect)
192         {
193             return DataToCoordinate(asDouble(v), rect);
194         }
195
196         public float DataToCoordinate(double v, AdvancedRect rect)
197         {
198             double r = (v - asDouble(ScaleMinimum)) / (asDouble(ScaleMaximum) - asDouble(ScaleMinimum));
199             return (float)((rect.Width * r) + rect.TopLeft.X);
200         }
201
202         public double CoordinateToData(float x, AdvancedRect rect)
203         {
204             double r = ((x - rect.TopLeft.X) / rect.Width);
205             return (asDouble(ScaleMaximum) - asDouble(ScaleMinimum)) * r + asDouble(ScaleMinimum);
206         }
207
208         private double asDouble(DateTime v)
209         {
210             return v.ToOADate();
211         }
212
213         private DateTime asDateTime(double d)
214         {
215             return DateTime.FromOADate(d);
216         }
217
218         private double? asNullableDouble(DateTime? v)
219         {
220             if (v == null)
221                 return null;
222             return asDouble(v.Value);
223         }
224
225         private DateTime? asNullableDateTime(double? v)
226         {
227             if (v == null)
228                 return null;
229             return asDateTime(v.Value);
230         }
231
232         public DateTime ScaleMaximum
233         {
234             get
235             {
236                 if (zoomedMaximum.HasValue)
237                     return zoomedMaximum.Value;
238                 if (userMaximum.HasValue)
239                     return userMaximum.Value;
240                 if (parent.Series.MaxX != null)
241                 {
242                     DateTime maxX = asDateTime(parent.Series.MaxX.Value);
243                     // scale to the end of the month of maxX;
244                     return new DateTime(maxX.Year, maxX.Month, cal.GetDaysInMonth(maxX.Year, maxX.Month));
245                 }
246                 return unscaledMaximum;
247             }
248         }
249
250         public DateTime ScaleMinimum
251         {
252             get
253             {
254                 if (zoomedMinimum.HasValue)
255                     return zoomedMinimum.Value;
256                 if (userMinimum.HasValue)
257                     return userMinimum.Value;
258                 if (parent.Series.MinX != null)
259                 {
260                     DateTime minX = asDateTime(parent.Series.MinX.Value);
261                     // scale to the end of the month of maxX;
262                     return new DateTime(minX.Year, minX.Month, 1);
263                 }
264                 return unscaledMinimum;
265             }
266         }
267
268         public DateTime? UserMaximum2
269         {
270             get
271             {
272                 return userMaximum;
273             }
274             set
275             {
276                 userMaximum = value;
277                 raiseEvent();
278             }
279         }
280
281         public DateTime? UserMinimum2
282         {
283             get
284             {
285                 return userMinimum;
286             }
287             set
288             {
289                 userMinimum = value;
290                 raiseEvent();
291             }
292         }
293
294         public double? UserMaximum
295         {
296             get
297             {
298                 return asNullableDouble(userMaximum);
299             }
300             set
301             {
302                 userMaximum = asNullableDateTime(value);
303                 raiseEvent();
304             }
305         }
306
307         public double? UserMinimum
308         {
309             get
310             {
311                 return asNullableDouble(userMinimum);
312             }
313             set
314             {
315                 userMinimum = asNullableDateTime(value);
316                 raiseEvent();
317             }
318         }
319
320         public double? ZoomedMaximum
321         {
322             get
323             {
324                 return asNullableDouble(zoomedMaximum);
325             }
326             set
327             {
328                 zoomedMaximum = asNullableDateTime(value);
329                 raiseEvent();
330             }
331         }
332
333         public double? ZoomedMinimum
334         {
335             get
336             {
337                 return asNullableDouble(zoomedMinimum);
338             }
339             set
340             {
341                 zoomedMinimum = asNullableDateTime(value);
342                 raiseEvent();
343             }
344         }
345     }
346 }
Note: See TracBrowser for help on using the browser.