Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 1245 was 1244, checked in by mstoeger, 16 years ago

Fixed MouseMove event bug with some mouse drivers (#503)

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