Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 1342 was 1342, checked in by shofstad, 15 years ago

Legend implementation updated with position setting (#407)

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