Free cookie consent management tool by TermsFeed Policy Generator

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

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

#424
Did some code refactoring (created concrete MouseEventListeners)

File size: 9.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Drawing;
4using System.Windows.Forms;
5using HeuristicLab.Core;
6
7namespace HeuristicLab.Visualization {
8  public class LinesShape : WorldShape {
9    private readonly RectangleShape background = new RectangleShape(0, 0, 1, 1, Color.FromArgb(240, 240, 240));
10
11    public LinesShape(RectangleD clippingArea, RectangleD boundingBox)
12      : base(clippingArea, boundingBox) {
13      AddShape(background);
14    }
15
16    public override void Draw(Graphics graphics, Rectangle viewport, RectangleD clippingArea) {
17      UpdateLayout();
18      base.Draw(graphics, viewport, clippingArea);
19    }
20
21    private void UpdateLayout() {
22      background.Rectangle = ClippingArea;
23    }
24  }
25
26  public partial class LineChart : ViewBase {
27    private readonly IChartDataRowsModel model;
28    private int maxDataRowCount;
29    private Boolean zoomFullView;
30    private double minDataValue;
31    private double maxDataValue;
32
33    private readonly WorldShape root;
34    private readonly TextShape titleShape;
35    private readonly LinesShape linesShape;
36
37    private readonly XAxis xAxis;
38
39    /// <summary>
40    /// This constructor shouldn't be called. Only required for the designer.
41    /// </summary>
42    public LineChart() {
43      InitializeComponent();
44    }
45
46    /// <summary>
47    /// Initializes the chart.
48    /// </summary>
49    /// <param name="model">Referenz to the model, for data</param>
50    public LineChart(IChartDataRowsModel model) : this() {
51      if (model == null) {
52        throw new NullReferenceException("Model cannot be null.");
53      }
54
55      //TODO: correct Rectangle to fit
56
57      RectangleD dummy = new RectangleD(0, 0, 1, 1);
58
59      root = new WorldShape(dummy, dummy);
60
61      linesShape = new LinesShape(dummy, dummy);
62      root.AddShape(linesShape);
63
64      xAxis = new XAxis(dummy, dummy);
65      root.AddShape(xAxis);
66
67      titleShape = new TextShape(0, 0, "Title", 15);
68      root.AddShape(titleShape);
69
70      canvas.MainCanvas.WorldShape = root;
71      canvas.Resize += delegate { UpdateLayout(); };
72
73      UpdateLayout();
74
75      this.model = model;
76      Item = model;
77
78      maxDataRowCount = 0;
79      //The whole data rows are shown per default
80      zoomFullView = true;
81      minDataValue = Double.PositiveInfinity;
82      maxDataValue = Double.NegativeInfinity;
83    }
84
85    /// <summary>
86    /// Layout management - arranges the inner shapes.
87    /// </summary>
88    private void UpdateLayout() {
89      root.ClippingArea = new RectangleD(0, 0, canvas.Width, canvas.Height);
90
91      titleShape.X = 10;
92      titleShape.Y = canvas.Height - 10;
93
94      linesShape.BoundingBox = new RectangleD(0, 20, canvas.Width, canvas.Height);
95
96      xAxis.BoundingBox = new RectangleD(linesShape.BoundingBox.X1,
97                                         0,
98                                         linesShape.BoundingBox.X2,
99                                         linesShape.BoundingBox.Y1);
100    }
101
102    public void ResetView() {
103      zoomFullView = true;
104      ZoomToFullView();
105
106      canvas.Invalidate();
107    }
108
109    #region Add-/RemoveItemEvents
110
111    protected override void AddItemEvents() {
112      base.AddItemEvents();
113
114      model.DataRowAdded += OnDataRowAdded;
115      model.DataRowRemoved += OnDataRowRemoved;
116      model.ModelChanged += OnModelChanged;
117
118      foreach (IDataRow row in model.Rows) {
119        OnDataRowAdded(row);
120      }
121    }
122
123    protected override void RemoveItemEvents() {
124      base.RemoveItemEvents();
125
126      model.DataRowAdded -= OnDataRowAdded;
127      model.DataRowRemoved -= OnDataRowRemoved;
128      model.ModelChanged -= OnModelChanged;
129    }
130
131    private void OnDataRowAdded(IDataRow row) {
132      row.ValueChanged += OnRowValueChanged;
133      row.ValuesChanged += OnRowValuesChanged;
134      if (row.Count > maxDataRowCount) {
135        maxDataRowCount = row.Count;
136      }
137
138      InitLineShapes(row);
139    }
140
141    private void ZoomToFullView() {
142      if (!zoomFullView) {
143        return;
144      }
145      RectangleD newClippingArea = new RectangleD(-0.1,
146                                                  minDataValue - ((maxDataValue - minDataValue)*0.05),
147                                                  maxDataRowCount - 0.9,
148                                                  maxDataValue + ((maxDataValue - minDataValue)*0.05));
149
150      SetLineClippingArea(newClippingArea);
151    }
152
153    /// <summary>
154    /// Sets the clipping area of the data to display.
155    /// </summary>
156    /// <param name="clippingArea"></param>
157    private void SetLineClippingArea(RectangleD clippingArea) {
158      linesShape.ClippingArea = clippingArea;
159      xAxis.ClippingArea = new RectangleD(linesShape.ClippingArea.X1,
160                                          xAxis.BoundingBox.Y1,
161                                          linesShape.ClippingArea.X2,
162                                          xAxis.BoundingBox.Y2);
163    }
164
165    private void InitLineShapes(IDataRow row) {
166      List<LineShape> lineShapes = new List<LineShape>();
167      if (row.Count > 0) {
168        maxDataValue = Math.Max(row[0], maxDataValue);
169        minDataValue = Math.Min(row[0], minDataValue);
170      }
171      for (int i = 1; i < row.Count; i++) {
172        LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], 0, row.Color, row.Thickness, row.Style);
173        lineShapes.Add(lineShape);
174        // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
175        linesShape.AddShape(lineShape);
176        maxDataValue = Math.Max(row[i], maxDataValue);
177        minDataValue = Math.Min(row[i], minDataValue);
178      }
179
180      rowToLineShapes[row] = lineShapes;
181      ZoomToFullView();
182
183      canvas.Invalidate();
184    }
185
186    private void OnDataRowRemoved(IDataRow row) {
187      row.ValueChanged -= OnRowValueChanged;
188      row.ValuesChanged -= OnRowValuesChanged;
189    }
190
191    private readonly IDictionary<IDataRow, List<LineShape>> rowToLineShapes = new Dictionary<IDataRow, List<LineShape>>();
192
193    // TODO use action parameter
194    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
195      xAxis.SetLabel(index, index.ToString());
196
197      List<LineShape> lineShapes = rowToLineShapes[row];
198      maxDataValue = Math.Max(value, maxDataValue);
199      minDataValue = Math.Min(value, minDataValue);
200
201      if (index > lineShapes.Count + 1) {
202        throw new NotImplementedException();
203      }
204
205      // new value was added
206      if (index > 0 && index == lineShapes.Count + 1) {
207        if (maxDataRowCount < row.Count) {
208          maxDataRowCount = row.Count;
209        }
210        LineShape lineShape = new LineShape(index - 1, row[index - 1], index, row[index], 0, row.Color, row.Thickness, row.Style);
211        lineShapes.Add(lineShape);
212        // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
213        linesShape.AddShape(lineShape);
214      }
215
216      // not the first value
217      if (index > 0) {
218        lineShapes[index - 1].Y2 = value;
219      }
220
221      // not the last value
222      if (index > 0 && index < row.Count - 1) {
223        lineShapes[index].Y1 = value;
224      }
225      ZoomToFullView();
226
227      canvas.Invalidate();
228    }
229
230    // TODO use action parameter
231    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
232      foreach (double value in values) {
233        OnRowValueChanged(row, value, index++, action);
234      }
235    }
236
237    private void OnModelChanged() {}
238
239    #endregion
240
241    #region Begin-/EndUpdate
242
243    private int beginUpdateCount = 0;
244
245    public void BeginUpdate() {
246      beginUpdateCount++;
247    }
248
249    public void EndUpdate() {
250      if (beginUpdateCount == 0) {
251        throw new InvalidOperationException("Too many EndUpdates.");
252      }
253
254      beginUpdateCount--;
255
256      if (beginUpdateCount == 0) {
257        canvas.Invalidate();
258      }
259    }
260
261    #endregion
262
263    private RectangleShape rectangleShape;
264
265    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
266      if (ModifierKeys == Keys.Control) {
267        CreateZoomListener(e);
268      } else {
269        CreatePanListener(e);
270      }
271    }
272
273    private void CreateZoomListener(MouseEventArgs e) {
274      ZoomListener zoomListener = new ZoomListener(e.Location);
275      zoomListener.DrawRectangle += DrawRectangle;
276      zoomListener.OnMouseUp += OnZoom_MouseUp;
277
278      canvas.MouseEventListener = zoomListener;
279
280      rectangleShape = new RectangleShape(e.X, e.Y, e.X, e.Y, Color.Blue);
281      rectangleShape.Opacity = 50;
282
283      linesShape.AddShape(rectangleShape);
284    }
285
286    private void OnZoom_MouseUp(object sender, MouseEventArgs e) {
287      canvas.MouseEventListener = null;
288
289      SetLineClippingArea(rectangleShape.Rectangle);
290      linesShape.RemoveShape(rectangleShape);
291
292      zoomFullView = false; //user wants to zoom => no full view
293
294      canvas.Invalidate();
295    }
296
297    private void DrawRectangle(Rectangle rectangle) {
298      rectangleShape.Rectangle = Transform.ToWorld(rectangle, canvas.ClientRectangle, linesShape.ClippingArea);
299      canvas.Invalidate();
300    }
301
302    private void CreatePanListener(MouseEventArgs e) {
303      PanListener panListener = new PanListener(canvas.ClientRectangle, linesShape.ClippingArea, e.Location);
304
305      panListener.SetNewClippingArea += SetNewClippingArea;
306      panListener.OnMouseUp += delegate { canvas.MouseEventListener = null; };
307
308      canvas.MouseEventListener = panListener;
309    }
310
311    private void SetNewClippingArea(RectangleD newClippingArea) {
312      SetLineClippingArea(newClippingArea);
313
314      zoomFullView = false;
315      canvas.Invalidate();
316    }
317  }
318}
Note: See TracBrowser for help on using the repository browser.