Free cookie consent management tool by TermsFeed Policy Generator

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

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