Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 1242 was 1242, checked in by dwagner, 15 years ago

Changed Behaviour of Single value lines; They are now shown without bounds.
OptionsDialog now modifies the model and not the view; (#501 and #502)

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