source: trunk/sources/HeuristicLab.Visualization/LineChart.cs @ 1346

Last change on this file since 1346 was 1346, checked in by shofstad, 12 years ago

added code comments (#406)

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