Free cookie consent management tool by TermsFeed Policy Generator

source: branches/Hive_Management_Console_Refactoring_Ticket508/HeuristicLab.Visualization/LineChart.cs @ 1332

Last change on this file since 1332 was 1249, checked in by bspisic, 15 years ago

reimplemented panning and zooming (#424)

File size: 15.2 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 int maxDataRowCount;
15    private double minDataValue;
16    private double maxDataValue;
17
18    private readonly TextShape titleShape;
19    private readonly LinesShape linesShape;
20    private readonly LegendShape legendShape;
21
22    private readonly XAxis xAxis;
23    private readonly YAxis yAxis;
24    private readonly Grid grid;
25
26    private readonly Stack<RectangleD> clippingAreaHistory = new Stack<RectangleD>();
27    private readonly WorldShape userInteractionShape;
28    private readonly RectangleShape rectangleShape;
29    private IMouseEventListener mouseEventListener;
30    private bool zoomToFullView;
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      grid = new Grid();
51      canvas.AddShape(grid);
52
53      linesShape = new LinesShape();
54      canvas.AddShape(linesShape);
55
56      xAxis = new XAxis();
57      canvas.AddShape(xAxis);
58
59      yAxis = new YAxis();
60      canvas.AddShape(yAxis);
61
62      titleShape = new TextShape(0, 0, model.Title, 15);
63      canvas.AddShape(titleShape);
64
65      //  horizontalLineShape = new HorizontalLineShape(this.maxDataValue, Color.Yellow, 4, DrawingStyle.Solid);
66      //  root.AddShape(horizontalLineShape);
67
68      legendShape = new LegendShape();
69      canvas.AddShape(legendShape);
70
71      userInteractionShape = new WorldShape();
72      canvas.AddShape(userInteractionShape);
73
74      rectangleShape = new RectangleShape(0, 0, 0, 0, Color.Blue);
75      rectangleShape.Opacity = 50;
76
77      maxDataRowCount = 0;
78      this.model = model;
79      Item = model;
80
81      UpdateLayout();
82      canvasUI.Resize += delegate { UpdateLayout(); };
83
84      //The whole data rows are shown per default
85      ZoomToFullView();
86    }
87
88    /// <summary>
89    /// Layout management - arranges the inner shapes.
90    /// </summary>
91    private void UpdateLayout() {
92      titleShape.X = 10;
93      titleShape.Y = canvasUI.Height - 10;
94
95      const int yAxisWidth = 100;
96      const int xAxisHeight = 20;
97
98      linesShape.BoundingBox = new RectangleD(yAxisWidth,
99                                              xAxisHeight,
100                                              canvasUI.Width,
101                                              canvasUI.Height);
102
103      userInteractionShape.BoundingBox = linesShape.BoundingBox;
104      userInteractionShape.ClippingArea = new RectangleD(0, 0, userInteractionShape.BoundingBox.Width, userInteractionShape.BoundingBox.Height);
105
106      grid.BoundingBox = linesShape.BoundingBox;
107
108
109      yAxis.BoundingBox = new RectangleD(0,
110                                         linesShape.BoundingBox.Y1,
111                                         linesShape.BoundingBox.X1,
112                                         linesShape.BoundingBox.Y2);
113
114      xAxis.BoundingBox = new RectangleD(linesShape.BoundingBox.X1,
115                                         0,
116                                         linesShape.BoundingBox.X2,
117                                         linesShape.BoundingBox.Y1);
118
119      legendShape.BoundingBox = new RectangleD(10, 10, 110, canvasUI.Height - 50);
120      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width,
121                                                legendShape.BoundingBox.Height);
122    }
123
124    private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
125      var optionsdlg = new OptionsDialog(this.model);
126      optionsdlg.ShowDialog(this);
127    }
128
129    public void OnDataRowChanged(IDataRow row) {
130      foreach (LineShape ls in rowToLineShapes[row]) {
131        ls.LSColor = row.Color;
132        ls.LSThickness = row.Thickness;
133        ls.LSDrawingStyle = row.Style;
134      }
135      canvasUI.Invalidate();
136    }
137
138    #region Add-/RemoveItemEvents
139
140    private readonly IDictionary<IDataRow, List<LineShape>> rowToLineShapes =
141      new Dictionary<IDataRow, List<LineShape>>();
142
143    protected override void AddItemEvents() {
144      base.AddItemEvents();
145
146      model.DataRowAdded += OnDataRowAdded;
147      model.DataRowRemoved += OnDataRowRemoved;
148      model.ModelChanged += OnModelChanged;
149
150      foreach (IDataRow row in model.Rows) {
151        OnDataRowAdded(row);
152      }
153    }
154
155    protected override void RemoveItemEvents() {
156      base.RemoveItemEvents();
157
158      model.DataRowAdded -= OnDataRowAdded;
159      model.DataRowRemoved -= OnDataRowRemoved;
160      model.ModelChanged -= OnModelChanged;
161    }
162
163    private void OnDataRowAdded(IDataRow row) {
164      row.ValueChanged += OnRowValueChanged;
165      row.ValuesChanged += OnRowValuesChanged;
166      row.DataRowChanged += OnDataRowChanged;
167
168      if (row.Count > maxDataRowCount) {
169        maxDataRowCount = row.Count;
170        //   UpdateSingleValueRows();
171      }
172
173      legendShape.AddLegendItem(new LegendItem(row.Label, row.Color, row.Thickness));
174      legendShape.CreateLegend();
175      InitLineShapes(row);
176    }
177
178    private void OnDataRowRemoved(IDataRow row) {
179      row.ValueChanged -= OnRowValueChanged;
180      row.ValuesChanged -= OnRowValuesChanged;
181      row.DataRowChanged -= OnDataRowChanged;
182    }
183
184    #endregion
185
186    public void ZoomToFullView() {
187      RectangleD newClippingArea = new RectangleD(-0.1,
188                                                  minDataValue - ((maxDataValue - minDataValue)*0.05),
189                                                  maxDataRowCount - 0.9,
190                                                  maxDataValue + ((maxDataValue - minDataValue)*0.05));
191
192      SetLineClippingArea(newClippingArea, true);
193
194      zoomToFullView = true;
195    }
196
197    /// <summary>
198    /// Sets the clipping area of the data to display.
199    /// </summary>
200    /// <param name="clippingArea"></param>
201    /// <param name="pushToHistoryStack"></param>
202    private void SetLineClippingArea(RectangleD clippingArea, bool pushToHistoryStack) {
203      zoomToFullView = false;
204
205      if (pushToHistoryStack) {
206        int count = clippingAreaHistory.Count;
207
208        if (count > 40) {
209          RectangleD[] clippingAreas = clippingAreaHistory.ToArray();
210          clippingAreaHistory.Clear();
211
212          for (int i = count - 20; i < count; i++) {
213            clippingAreaHistory.Push(clippingAreas[i]);
214          }
215        }
216
217        clippingAreaHistory.Push(clippingArea);
218      }
219
220      linesShape.ClippingArea = clippingArea;
221
222      grid.ClippingArea = linesShape.ClippingArea;
223
224      // horizontalLineShape.ClippingArea = linesShape.ClippingArea;
225
226
227      xAxis.ClippingArea = new RectangleD(linesShape.ClippingArea.X1,
228                                          xAxis.BoundingBox.Y1,
229                                          linesShape.ClippingArea.X2,
230                                          xAxis.BoundingBox.Y2);
231
232      yAxis.ClippingArea = new RectangleD(yAxis.BoundingBox.X1,
233                                          linesShape.ClippingArea.Y1,
234                                          yAxis.BoundingBox.X2,
235                                          linesShape.ClippingArea.Y2);
236
237      canvasUI.Invalidate();
238    }
239
240    private void InitLineShapes(IDataRow row) {
241      var lineShapes = new List<LineShape>();
242      if (rowToLineShapes.Count == 0) {
243        minDataValue = Double.PositiveInfinity;
244        maxDataValue = Double.NegativeInfinity;
245      }
246      if ((row.Count > 0)) {
247        maxDataValue = Math.Max(row[0], maxDataValue);
248        minDataValue = Math.Min(row[0], minDataValue);
249      }
250      if ((row.LineType == DataRowType.SingleValue)) {
251        if (row.Count > 0) {
252          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
253                                                        row.Style);
254          lineShapes.Add(lineShape);
255          // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
256          linesShape.AddShape(lineShape);
257        }
258      } else {
259        for (int i = 1; i < row.Count; i++) {
260          var lineShape = new LineShape(i - 1, row[i - 1], i, row[i], row.Color, row.Thickness, row.Style);
261          lineShapes.Add(lineShape);
262          // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
263          linesShape.AddShape(lineShape);
264          maxDataValue = Math.Max(row[i], maxDataValue);
265          minDataValue = Math.Min(row[i], minDataValue);
266        }
267      }
268      //horizontalLineShape.YVal = maxDataValue;
269      rowToLineShapes[row] = lineShapes;
270
271      ZoomToFullView();
272    }
273
274    // TODO use action parameter
275    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
276      List<LineShape> lineShapes = rowToLineShapes[row];
277      maxDataValue = Math.Max(value, maxDataValue);
278      minDataValue = Math.Min(value, minDataValue);
279      if (row.LineType == DataRowType.SingleValue) {
280        if (action == Action.Added) {
281          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
282                                                        row.Style);
283          lineShapes.Add(lineShape);
284          // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
285          linesShape.AddShape(lineShape);
286        } else {
287          // lineShapes[0].X2 = maxDataRowCount;
288          lineShapes[0].Y1 = value;
289          lineShapes[0].Y2 = value;
290        }
291      } else {
292        //  horizontalLineShape.YVal = maxDataValue;
293        if (index > lineShapes.Count + 1) {
294          throw new NotImplementedException();
295        }
296
297        // new value was added
298        if (index > 0 && index == lineShapes.Count + 1) {
299          if (maxDataRowCount < row.Count) {
300            maxDataRowCount = row.Count;
301            //  UpdateSingleValueRows();
302          }
303          var lineShape = new LineShape(index - 1, row[index - 1], index, row[index], row.Color, row.Thickness,
304                                        row.Style);
305          lineShapes.Add(lineShape);
306          // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
307          linesShape.AddShape(lineShape);
308        }
309
310        // not the first value
311        if (index > 0) {
312          lineShapes[index - 1].Y2 = value;
313        }
314
315        // not the last value
316        if (index > 0 && index < row.Count - 1) {
317          lineShapes[index].Y1 = value;
318        }
319      }
320
321      ZoomToFullView();
322    }
323
324    // TODO remove (see ticket #501)
325    public IList<IDataRow> GetRows() {
326      return model.Rows;
327    }
328
329
330    // TODO use action parameter
331    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
332      foreach (double value in values) {
333        OnRowValueChanged(row, value, index++, action);
334      }
335    }
336
337    private void OnModelChanged() {
338      titleShape.Text = model.Title;
339
340      canvasUI.Invalidate();
341    }
342
343    #region Begin-/EndUpdate
344
345    private int beginUpdateCount;
346
347    public void BeginUpdate() {
348      beginUpdateCount++;
349    }
350
351    public void EndUpdate() {
352      if (beginUpdateCount == 0) {
353        throw new InvalidOperationException("Too many EndUpdates.");
354      }
355
356      beginUpdateCount--;
357
358      if (beginUpdateCount == 0) {
359        canvasUI.Invalidate();
360      }
361    }
362
363    #endregion
364
365    #region Zooming / Panning
366
367    private void Pan(Point startPoint, Point endPoint) {
368      RectangleD clippingArea = CalcPanClippingArea(startPoint, endPoint);
369      SetLineClippingArea(clippingArea, false);
370    }
371
372    private void PanEnd(Point startPoint, Point endPoint) {
373      RectangleD clippingArea = CalcPanClippingArea(startPoint, endPoint);
374      SetLineClippingArea(clippingArea, true);
375    }
376
377    private RectangleD CalcPanClippingArea(Point startPoint, Point endPoint) {
378      return Translate.ClippingArea(startPoint, endPoint, linesShape.ClippingArea, linesShape.Viewport);
379    }
380
381    private void SetClippingArea(Rectangle rectangle) {
382      RectangleD clippingArea = Transform.ToWorld(rectangle, linesShape.Viewport, linesShape.ClippingArea);
383
384      SetLineClippingArea(clippingArea, true);
385      userInteractionShape.RemoveShape(rectangleShape);
386    }
387
388    private void DrawRectangle(Rectangle rectangle) {
389      rectangleShape.Rectangle = Transform.ToWorld(rectangle, userInteractionShape.Viewport, userInteractionShape.ClippingArea);
390      canvasUI.Invalidate();
391    }
392
393    private void canvasUI1_KeyDown(object sender, KeyEventArgs e) {
394      if (e.KeyCode == Keys.Back && clippingAreaHistory.Count > 1) {
395        clippingAreaHistory.Pop();
396
397        RectangleD clippingArea = clippingAreaHistory.Peek();
398
399        SetLineClippingArea(clippingArea, false);
400      }
401    }
402
403    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
404      Focus();
405
406      if (e.Button == MouseButtons.Right) {
407        contextMenuStrip1.Show(PointToScreen(e.Location));
408      } else if (e.Button == MouseButtons.Left) {
409        if (ModifierKeys == Keys.None) {
410          PanListener panListener = new PanListener(e.Location);
411          panListener.Pan += Pan;
412          panListener.PanEnd += PanEnd;
413
414          mouseEventListener = panListener;
415        } else if (ModifierKeys == Keys.Control) {
416          ZoomListener zoomListener = new ZoomListener(e.Location);
417          zoomListener.DrawRectangle += DrawRectangle;
418          zoomListener.SetClippingArea += SetClippingArea;
419
420          rectangleShape.Rectangle = RectangleD.Empty;
421          userInteractionShape.AddShape(rectangleShape);
422
423          mouseEventListener = zoomListener;
424        }
425      }
426    }
427
428    private void canvasUI_MouseMove(object sender, MouseEventArgs e) {
429      if (mouseEventListener != null) {
430        mouseEventListener.MouseMove(sender, e);
431      }
432    }
433
434    private void canvasUI_MouseUp(object sender, MouseEventArgs e) {
435      if (mouseEventListener != null) {
436        mouseEventListener.MouseUp(sender, e);
437      }
438
439      mouseEventListener = null;
440    }
441
442    private void canvasUI1_MouseWheel(object sender, MouseEventArgs e) {
443      if (ModifierKeys == Keys.Control) {
444        double zoomFactor = (e.Delta > 0) ? 0.9 : 1.1;
445
446        RectangleD clippingArea = ZoomListener.ZoomClippingArea(linesShape.ClippingArea, zoomFactor);
447
448        SetLineClippingArea(clippingArea, true);
449      }
450    }
451
452    #endregion
453
454    #region Nested type: LinesShape
455
456    internal class LinesShape : WorldShape {}
457
458    #endregion
459  }
460}
Note: See TracBrowser for help on using the repository browser.