Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Visualization/3.2/LineChart.cs @ 1591

Last change on this file since 1591 was 1561, checked in by dwagner, 16 years ago

Added functionality: Checkbox to enable/disable Markers for every datapoint added to OptionsDialog. (#581)

File size: 22.5 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Drawing;
4using System.Drawing.Drawing2D;
5using System.Windows.Forms;
6using HeuristicLab.Core;
7using HeuristicLab.Visualization.Legend;
8using HeuristicLab.Visualization.Options;
9using HeuristicLab.Visualization.Test;
10
11namespace HeuristicLab.Visualization {
12  public partial class LineChart : ViewBase {
13    private readonly IChartDataRowsModel model;
14    private readonly Canvas canvas;
15
16    private readonly TextShape titleShape = new TextShape("Title");
17    private readonly LegendShape legendShape = new LegendShape();
18    private readonly XAxis xAxis = new XAxis();
19    private readonly List<RowEntry> rowEntries = new List<RowEntry>();
20
21    private readonly Dictionary<IDataRow, RowEntry> rowToRowEntry = new Dictionary<IDataRow, RowEntry>();
22
23    private readonly ViewSettings viewSettings;
24
25    private readonly WorldShape userInteractionShape = new WorldShape();
26    private readonly RectangleShape rectangleShape = new RectangleShape(0, 0, 0, 0, Color.FromArgb(50, 0, 0, 255));
27    private IMouseEventListener mouseEventListener;
28
29    private const int YAxisWidth = 100;
30    private const int XAxisHeight = 40;
31
32    /// <summary>
33    /// This constructor shouldn't be called. Only required for the designer.
34    /// </summary>
35    public LineChart() {
36      InitializeComponent();
37    }
38
39    /// <summary>
40    /// Initializes the chart.
41    /// </summary>
42    /// <param name="model">Referenz to the model, for data</param>
43    public LineChart(IChartDataRowsModel model) : this() {
44      if (model == null) {
45        throw new NullReferenceException("Model cannot be null.");
46      }
47
48      canvas = canvasUI.Canvas;
49
50      this.model = model;
51      viewSettings = model.ViewSettings;
52      viewSettings.OnUpdateSettings += UpdateViewSettings;
53
54      Item = model;
55
56      UpdateLayout();
57      canvasUI.Resize += delegate { UpdateLayout(); };
58
59      ZoomToFullView();
60    }
61
62    /// <summary>
63    /// updates the view settings
64    /// </summary>
65    private void UpdateViewSettings() {
66      titleShape.Font = viewSettings.TitleFont;
67      titleShape.Color = viewSettings.TitleColor;
68
69      legendShape.Font = viewSettings.LegendFont;
70      legendShape.Color = viewSettings.LegendColor;
71
72      xAxis.Font = viewSettings.XAxisFont;
73      xAxis.Color = viewSettings.XAxisColor;
74
75      SetLegendPosition();
76
77      canvasUI.Invalidate();
78    }
79
80    /// <summary>
81    /// Layout management - arranges the inner shapes.
82    /// </summary>
83    private void UpdateLayout() {
84      canvas.ClearShapes();
85
86      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
87        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
88        if (yAxisDescriptor.ShowGrid) {
89          info.Grid.Color = yAxisDescriptor.GridColor;
90          canvas.AddShape(info.Grid);
91        }
92      }
93
94      foreach (RowEntry rowEntry in rowEntries) {
95        canvas.AddShape(rowEntry.LinesShape);
96      }
97
98      xAxis.ShowLabel = model.ShowXAxisLabel;
99      xAxis.Label = model.XAxisLabel;
100
101      canvas.AddShape(xAxis);
102
103      int yAxesWidthLeft = 0;
104      int yAxesWidthRight = 0;
105
106      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
107        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
108        if (yAxisDescriptor.ShowYAxis) {
109          canvas.AddShape(info.YAxis);
110          info.YAxis.ShowLabel = yAxisDescriptor.ShowYAxisLabel;
111          info.YAxis.Label = yAxisDescriptor.Label;
112          info.YAxis.Position = yAxisDescriptor.Position;
113          switch (yAxisDescriptor.Position) {
114            case AxisPosition.Left:
115              yAxesWidthLeft += YAxisWidth;
116              break;
117            case AxisPosition.Right:
118              yAxesWidthRight += YAxisWidth;
119              break;
120            default:
121              throw new NotImplementedException();
122          }
123        }
124      }
125
126      canvas.AddShape(titleShape);
127      canvas.AddShape(legendShape);
128
129      canvas.AddShape(userInteractionShape);
130
131      titleShape.X = 10;
132      titleShape.Y = canvasUI.Height - 10;
133
134      RectangleD linesAreaBoundingBox = new RectangleD(yAxesWidthLeft,
135                                                       XAxisHeight,
136                                                       canvasUI.Width - yAxesWidthRight,
137                                                       canvasUI.Height);
138
139      foreach (RowEntry rowEntry in rowEntries) {
140        rowEntry.LinesShape.BoundingBox = linesAreaBoundingBox;
141      }
142
143      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
144        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
145        info.Grid.BoundingBox = linesAreaBoundingBox;
146      }
147
148      int yAxisLeft = 0;
149      int yAxisRight = (int)linesAreaBoundingBox.X2;
150
151      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
152        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
153        if (yAxisDescriptor.ShowYAxis) {
154          switch (yAxisDescriptor.Position) {
155            case AxisPosition.Left:
156              info.YAxis.BoundingBox = new RectangleD(yAxisLeft,
157                                                      linesAreaBoundingBox.Y1,
158                                                      yAxisLeft + YAxisWidth,
159                                                      linesAreaBoundingBox.Y2);
160              yAxisLeft += YAxisWidth;
161              break;
162            case AxisPosition.Right:
163              info.YAxis.BoundingBox = new RectangleD(yAxisRight,
164                                                      linesAreaBoundingBox.Y1,
165                                                      yAxisRight + YAxisWidth,
166                                                      linesAreaBoundingBox.Y2);
167              yAxisRight += YAxisWidth;
168              break;
169            default:
170              throw new NotImplementedException();
171          }
172        }
173      }
174
175      userInteractionShape.BoundingBox = linesAreaBoundingBox;
176      userInteractionShape.ClippingArea = new RectangleD(0, 0, userInteractionShape.BoundingBox.Width, userInteractionShape.BoundingBox.Height);
177
178      xAxis.BoundingBox = new RectangleD(linesAreaBoundingBox.X1,
179                                         0,
180                                         linesAreaBoundingBox.X2,
181                                         linesAreaBoundingBox.Y1);
182
183      SetLegendPosition();
184    }
185
186    private readonly Dictionary<YAxisDescriptor, YAxisInfo> yAxisInfos = new Dictionary<YAxisDescriptor, YAxisInfo>();
187
188    private YAxisInfo GetYAxisInfo(YAxisDescriptor yAxisDescriptor) {
189      YAxisInfo info;
190
191      if (!yAxisInfos.TryGetValue(yAxisDescriptor, out info)) {
192        info = new YAxisInfo();
193        yAxisInfos[yAxisDescriptor] = info;
194      }
195
196      return info;
197    }
198
199    /// <summary>
200    /// sets the legend position
201    /// </summary>
202    private void SetLegendPosition() {
203      switch (viewSettings.LegendPosition) {
204        case LegendPosition.Bottom:
205          setLegendBottom();
206          break;
207
208        case LegendPosition.Top:
209          setLegendTop();
210          break;
211
212        case LegendPosition.Left:
213          setLegendLeft();
214          break;
215
216        case LegendPosition.Right:
217          setLegendRight();
218          break;
219      }
220    }
221
222    public void setLegendRight() {
223      // legend right
224      legendShape.BoundingBox = new RectangleD(canvasUI.Width - legendShape.GetMaxLabelLength(), 10, canvasUI.Width, canvasUI.Height - 50);
225      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
226      legendShape.Row = false;
227      legendShape.CreateLegend();
228    }
229
230    public void setLegendLeft() {
231      // legend left
232      legendShape.BoundingBox = new RectangleD(10, 10, canvasUI.Width, canvasUI.Height - 50);
233      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
234      legendShape.Row = false;
235      legendShape.CreateLegend();
236
237      canvasUI.Invalidate();
238    }
239
240    public void setLegendTop() {
241      // legend top
242      legendShape.BoundingBox = new RectangleD(100, canvasUI.Height - canvasUI.Height, canvasUI.Width, canvasUI.Height - 10);
243      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
244      legendShape.Row = true;
245      legendShape.Top = true;
246      legendShape.CreateLegend();
247    }
248
249    public void setLegendBottom() {
250      // legend bottom
251      legendShape.BoundingBox = new RectangleD(100, 10, canvasUI.Width, canvasUI.Height/*legendShape.GetHeight4Rows()*/);
252      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
253      legendShape.Row = true;
254      legendShape.Top = false;
255      legendShape.CreateLegend();
256    }
257
258    private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
259      OptionsDialog optionsdlg = new OptionsDialog(model);
260      optionsdlg.Show();
261    }
262
263    public void OnDataRowChanged(IDataRow row) {
264      RowEntry rowEntry = rowToRowEntry[row];
265
266      rowEntry.LinesShape.UpdateStyle(row);
267
268      UpdateLayout();
269
270      canvasUI.Invalidate();
271    }
272
273    #region Add-/RemoveItemEvents
274
275    protected override void AddItemEvents() {
276      base.AddItemEvents();
277
278      model.DataRowAdded += OnDataRowAdded;
279      model.DataRowRemoved += OnDataRowRemoved;
280      model.ModelChanged += OnModelChanged;
281
282      foreach (IDataRow row in model.Rows) {
283        OnDataRowAdded(row);
284      }
285    }
286
287    protected override void RemoveItemEvents() {
288      base.RemoveItemEvents();
289
290      model.DataRowAdded -= OnDataRowAdded;
291      model.DataRowRemoved -= OnDataRowRemoved;
292      model.ModelChanged -= OnModelChanged;
293    }
294
295    private void OnDataRowAdded(IDataRow row) {
296      row.ValueChanged += OnRowValueChanged;
297      row.ValuesChanged += OnRowValuesChanged;
298      row.DataRowChanged += OnDataRowChanged;
299
300      legendShape.AddLegendItem(new LegendItem(row.Label, row.Color, row.Thickness));
301      legendShape.CreateLegend();
302
303      InitLineShapes(row);
304
305      UpdateLayout();
306    }
307
308    private void OnDataRowRemoved(IDataRow row) {
309      row.ValueChanged -= OnRowValueChanged;
310      row.ValuesChanged -= OnRowValuesChanged;
311      row.DataRowChanged -= OnDataRowChanged;
312
313      rowToRowEntry.Remove(row);
314      rowEntries.RemoveAll(delegate(RowEntry rowEntry) { return rowEntry.DataRow == row; });
315
316      UpdateLayout();
317    }
318
319    #endregion
320
321    public void ZoomToFullView() {
322      SetClipX(-0.1, model.MaxDataRowValues - 0.9);
323
324      foreach (RowEntry rowEntry in rowEntries) {
325        YAxisDescriptor yAxisDescriptor = rowEntry.DataRow.YAxis;
326
327        SetClipY(rowEntry,
328                 yAxisDescriptor.MinValue - ((yAxisDescriptor.MaxValue - yAxisDescriptor.MinValue)*0.05),
329                 yAxisDescriptor.MaxValue + ((yAxisDescriptor.MaxValue - yAxisDescriptor.MinValue)*0.05));
330      }
331
332      canvasUI.Invalidate();
333    }
334
335    private void SetClipX(double x1, double x2) {
336      xAxis.ClippingArea = new RectangleD(x1,
337                                          0,
338                                          x2,
339                                          XAxisHeight);
340
341      foreach (RowEntry rowEntry in rowEntries) {
342        rowEntry.LinesShape.ClippingArea = new RectangleD(x1,
343                                                          rowEntry.LinesShape.ClippingArea.Y1,
344                                                          x2,
345                                                          rowEntry.LinesShape.ClippingArea.Y2);
346      }
347
348      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
349        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
350        info.Grid.ClippingArea = new RectangleD(x1,
351                                                info.Grid.ClippingArea.Y1,
352                                                x2,
353                                                info.Grid.ClippingArea.Y2);
354        info.YAxis.ClippingArea = new RectangleD(0,
355                                                 info.YAxis.ClippingArea.Y1,
356                                                 YAxisWidth,
357                                                 info.YAxis.ClippingArea.Y2);
358      }
359    }
360
361    private void SetClipY(RowEntry rowEntry, double y1, double y2) {
362      rowEntry.LinesShape.ClippingArea = new RectangleD(rowEntry.LinesShape.ClippingArea.X1,
363                                                        y1,
364                                                        rowEntry.LinesShape.ClippingArea.X2,
365                                                        y2);
366
367      YAxisInfo info = GetYAxisInfo(rowEntry.DataRow.YAxis);
368
369      info.Grid.ClippingArea = new RectangleD(info.Grid.ClippingArea.X1,
370                                              y1,
371                                              info.Grid.ClippingArea.X2,
372                                              y2);
373      info.YAxis.ClippingArea = new RectangleD(info.YAxis.ClippingArea.X1,
374                                               y1,
375                                               info.YAxis.ClippingArea.X2,
376                                               y2);
377    }
378
379    private void InitLineShapes(IDataRow row) {
380      RowEntry rowEntry = new RowEntry(row);
381      rowEntries.Add(rowEntry);
382      rowToRowEntry[row] = rowEntry;
383
384      if ((row.LineType == DataRowType.SingleValue)) {
385        if (row.Count > 0) {
386          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
387                                                        row.Style);
388          rowEntry.LinesShape.AddShape(lineShape);
389        }
390      } else {
391        rowEntry.showMarkers(row.ShowMarkers);
392        for (int i = 1; i < row.Count; i++) {
393          LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], row.Color, row.Thickness, row.Style);
394          rowEntry.LinesShape.AddShape(lineShape);
395          rowEntry.LinesShape.AddMarkerShape(new MarkerShape(i-1,row[i-1],8,row.Color));
396        }
397        if(row.Count>0)
398          rowEntry.LinesShape.AddMarkerShape(new MarkerShape((row.Count - 1), row[(row.Count - 1)], 8, row.Color));
399      }
400
401      ZoomToFullView();
402    }
403
404    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
405      RowEntry rowEntry = rowToRowEntry[row];
406
407      if (row.LineType == DataRowType.SingleValue) {
408        if (action == Action.Added) {
409          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
410                                                        row.Style);
411          rowEntry.LinesShape.AddShape(lineShape);
412         
413        } else {
414          LineShape lineShape = rowEntry.LinesShape.GetShape(0);
415          lineShape.Y1 = value;
416          lineShape.Y2 = value;
417        }
418      } else {
419        if (index > rowEntry.LinesShape.Count + 1) {    //MarkersShape is on position zero
420          throw new NotImplementedException();
421        }
422
423        // new value was added
424        if (index > 0 && index == rowEntry.LinesShape.Count + 1) {
425          LineShape lineShape = new LineShape(index - 1, row[index - 1], index, row[index], row.Color, row.Thickness, row.Style);
426          rowEntry.LinesShape.AddShape(lineShape);
427          rowEntry.LinesShape.AddMarkerShape(new MarkerShape(index, row[index ], 8, row.Color));
428        }
429
430        // not the first value
431        if (index > 0) {
432          rowEntry.LinesShape.GetShape(index - 1).Y2 = value;
433          ((MarkerShape)rowEntry.LinesShape.markersShape.GetShape(index - 1)).Y = value;
434        }
435
436        // not the last value
437        if (index > 0 && index < row.Count - 1) {
438          rowEntry.LinesShape.GetShape(index).Y1 = value;
439          ((MarkerShape)rowEntry.LinesShape.markersShape.GetShape(index)).Y = value;
440        }
441      }
442
443      ZoomToFullView();
444    }
445
446    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
447      foreach (double value in values) {
448        OnRowValueChanged(row, value, index++, action);
449      }
450    }
451
452    private void OnModelChanged() {
453      titleShape.Text = model.Title;
454
455      canvasUI.Invalidate();
456    }
457
458    #region Begin-/EndUpdate
459
460    private int beginUpdateCount;
461
462    public void BeginUpdate() {
463      beginUpdateCount++;
464    }
465
466    public void EndUpdate() {
467      if (beginUpdateCount == 0) {
468        throw new InvalidOperationException("Too many EndUpdates.");
469      }
470
471      beginUpdateCount--;
472
473      if (beginUpdateCount == 0) {
474        canvasUI.Invalidate();
475      }
476    }
477
478    #endregion
479
480    #region Zooming / Panning
481
482    private void Pan(Point startPoint, Point endPoint) {
483      RectangleD clippingArea = Translate.ClippingArea(startPoint, endPoint, xAxis.ClippingArea, xAxis.Viewport);
484
485      SetClipX(clippingArea.X1, clippingArea.X2);
486
487      foreach (RowEntry rowEntry in rowEntries) {
488        if (rowEntry.DataRow.YAxis.ClipChangeable) {
489          clippingArea = Translate.ClippingArea(startPoint, endPoint, rowEntry.LinesShape.ClippingArea, rowEntry.LinesShape.Viewport);
490          SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
491        }
492      }
493
494      canvasUI.Invalidate();
495    }
496
497    private void PanEnd(Point startPoint, Point endPoint) {
498      Pan(startPoint, endPoint);
499    }
500
501    private void SetClippingArea(Rectangle rectangle) {
502      RectangleD clippingArea = Transform.ToWorld(rectangle, xAxis.Viewport, xAxis.ClippingArea);
503
504      SetClipX(clippingArea.X1, clippingArea.X2);
505
506      foreach (RowEntry rowEntry in rowEntries) {
507        if (rowEntry.DataRow.YAxis.ClipChangeable) {
508          clippingArea = Transform.ToWorld(rectangle, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
509
510          SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
511        }
512      }
513
514      userInteractionShape.RemoveShape(rectangleShape);
515      canvasUI.Invalidate();
516    }
517
518    private void DrawRectangle(Rectangle rectangle) {
519      rectangleShape.Rectangle = Transform.ToWorld(rectangle, userInteractionShape.Viewport, userInteractionShape.ClippingArea);
520      canvasUI.Invalidate();
521    }
522
523    private void canvasUI1_KeyDown(object sender, KeyEventArgs e) {
524    }
525
526    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
527      Focus();
528
529      if (e.Button == MouseButtons.Right) {
530        contextMenuStrip1.Show(PointToScreen(e.Location));
531      } else if (e.Button == MouseButtons.Left) {
532        if (ModifierKeys == Keys.None) {
533          PanListener panListener = new PanListener(e.Location);
534          panListener.Pan += Pan;
535          panListener.PanEnd += PanEnd;
536
537          mouseEventListener = panListener;
538        } else if (ModifierKeys == Keys.Control) {
539          ZoomListener zoomListener = new ZoomListener(e.Location);
540          zoomListener.DrawRectangle += DrawRectangle;
541          zoomListener.SetClippingArea += SetClippingArea;
542
543          rectangleShape.Rectangle = RectangleD.Empty;
544          userInteractionShape.AddShape(rectangleShape);
545
546          mouseEventListener = zoomListener;
547        }
548      }
549    }
550
551    private void canvasUI_MouseMove(object sender, MouseEventArgs e) {
552      if (mouseEventListener != null) {
553        mouseEventListener.MouseMove(sender, e);
554      }
555    }
556
557    private void canvasUI_MouseUp(object sender, MouseEventArgs e) {
558      if (mouseEventListener != null) {
559        mouseEventListener.MouseUp(sender, e);
560      }
561
562      mouseEventListener = null;
563    }
564
565    private void canvasUI1_MouseWheel(object sender, MouseEventArgs e) {
566      if (ModifierKeys == Keys.Control) {
567        double zoomFactor = (e.Delta > 0) ? 0.7 : 1.3;
568
569        PointD world;
570
571        world = Transform.ToWorld(e.Location, xAxis.Viewport, xAxis.ClippingArea);
572
573        double x1 = world.X - (world.X - xAxis.ClippingArea.X1)*zoomFactor;
574        double x2 = world.X + (xAxis.ClippingArea.X2 - world.X)*zoomFactor;
575
576        SetClipX(x1, x2);
577
578        foreach (RowEntry rowEntry in rowEntries) {
579          world = Transform.ToWorld(e.Location, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
580
581          double y1 = world.Y - (world.Y - rowEntry.LinesShape.ClippingArea.Y1) * zoomFactor;
582          double y2 = world.Y + (rowEntry.LinesShape.ClippingArea.Y2 - world.Y)*zoomFactor;
583
584          SetClipY(rowEntry, y1, y2);
585        }
586
587        canvasUI.Invalidate();
588      }
589    }
590
591    #endregion
592
593    private class LinesShape : WorldShape {
594      public readonly CompositeShape markersShape = new CompositeShape();
595
596      public void UpdateStyle(IDataRow row) {
597        foreach (IShape shape in shapes) {
598          LineShape lineShape = shape as LineShape;
599          if (lineShape != null) {
600            lineShape.LSColor = row.Color;
601            lineShape.LSDrawingStyle = row.Style;
602            lineShape.LSThickness = row.Thickness;
603          }
604        }
605        this.markersShape.ShowChildShapes = row.ShowMarkers;
606      }
607
608      public override void Draw(Graphics graphics) {
609        GraphicsState gstate = graphics.Save();
610
611        graphics.SetClip(Viewport);
612        foreach (IShape shape in shapes) {
613          // draw child shapes using our own clipping area
614          shape.Draw(graphics);
615        }
616        markersShape.Draw(graphics);
617        graphics.Restore(gstate);
618      }
619
620      public void AddMarkerShape(IShape shape) {
621        shape.Parent = this;
622        markersShape.AddShape(shape);
623      }
624
625      public int Count {
626        get { return shapes.Count; }
627      }
628
629      public LineShape GetShape(int index) {
630        return (LineShape)shapes[index];     //shapes[0] is markersShape!!
631      }
632    }
633
634    private class RowEntry {
635      private readonly IDataRow dataRow;
636
637      private readonly LinesShape linesShape = new LinesShape();
638
639      public RowEntry(IDataRow dataRow) {
640        this.dataRow = dataRow;
641        linesShape.markersShape.Parent = linesShape;
642      }
643
644      public IDataRow DataRow {
645        get { return dataRow; }
646      }
647
648      public LinesShape LinesShape {
649        get { return linesShape; }
650      }
651
652      public void showMarkers(bool flag) {
653        linesShape.markersShape.ShowChildShapes = flag;
654      }
655    }
656
657    private class YAxisInfo {
658      private readonly Grid grid = new Grid();
659      private readonly YAxis yAxis = new YAxis();
660
661      public Grid Grid {
662        get { return grid; }
663      }
664
665      public YAxis YAxis {
666        get { return yAxis; }
667      }
668    }
669  }
670}
Note: See TracBrowser for help on using the repository browser.