Free cookie consent management tool by TermsFeed Policy Generator

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

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

Use DataRowChanged event instead of ApplyChangesToRow Method.
Moved OptionsDialog files to Options subfolder. (#497)

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