Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 1343 was 1343, checked in by mstoeger, 15 years ago

Display of Y-Axes can be individually switched on and off in the options dialog. (#433)

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