Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 984 was 984, checked in by bspisic, 16 years ago

#424
Implemented zooming

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