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
RevLine 
[683]1using System;
[861]2using System.Collections.Generic;
[928]3using System.Drawing;
4using System.Windows.Forms;
[697]5using HeuristicLab.Core;
[683]6
[861]7namespace HeuristicLab.Visualization {
8  public partial class LineChart : ViewBase {
[697]9    private readonly IChartDataRowsModel model;
[981]10    private int maxDataRowCount;
11    private Boolean zoomFullView;
12    private double minDataValue;
13    private double maxDataValue;
[684]14
[983]15    private readonly WorldShape root;
16    private readonly XAxis xAxis;
17
[697]18    /// <summary>
19    /// This constructor shouldn't be called. Only required for the designer.
20    /// </summary>
[861]21    public LineChart() {
[684]22      InitializeComponent();
23    }
24
[697]25    /// <summary>
26    /// Initializes the chart.
27    /// </summary>
[754]28    /// <param name="model">Referenz to the model, for data</param>
[861]29    public LineChart(IChartDataRowsModel model) : this() {
[928]30      if (model == null) {
[697]31        throw new NullReferenceException("Model cannot be null.");
[928]32      }
[684]33
[754]34      //TODO: correct Rectangle to fit
[981]35      RectangleD clientRectangle = new RectangleD(-1, -1, 1, 1);
[983]36
37      root = new WorldShape(clientRectangle, clientRectangle);
38
39      xAxis = new XAxis();
40      root.AddShape(xAxis);
41
42      canvasUI1.MainCanvas.WorldShape = root;
[981]43         
[928]44      CreateMouseEventListeners();
[981]45         
[869]46      this.model = model;
[983]47      Item = model;
[981]48      maxDataRowCount = 0;
49      //The whole data rows are shown per default
50      zoomFullView = true;
51      minDataValue = Double.PositiveInfinity;
52      maxDataValue = Double.NegativeInfinity;
53
[697]54    }
[684]55
[861]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;
[869]64
65      foreach (IDataRow row in model.Rows) {
66        OnDataRowAdded(row);
67      }
[683]68    }
[684]69
[861]70    protected override void RemoveItemEvents() {
71      base.RemoveItemEvents();
72
73      model.DataRowAdded -= OnDataRowAdded;
74      model.DataRowRemoved -= OnDataRowRemoved;
75      model.ModelChanged -= OnModelChanged;
[697]76    }
77
[861]78    private void OnDataRowAdded(IDataRow row) {
79      row.ValueChanged += OnRowValueChanged;
80      row.ValuesChanged += OnRowValuesChanged;
[981]81      if (row.Count > maxDataRowCount)
82        maxDataRowCount = row.Count;
83     
[861]84      InitShapes(row);
[684]85    }
[697]86
[981]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));
[983]94      root.ClippingArea = newClippingArea;
[981]95    }
96
[861]97    private void InitShapes(IDataRow row) {
[981]98     
99       
[861]100      List<LineShape> lineShapes = new List<LineShape>();
[981]101      if (row.Count > 0) {
[983]102        maxDataValue = Math.Max(row[0], this.maxDataValue);
103        minDataValue = Math.Min(row[0], minDataValue);
[981]104      }
[861]105      for (int i = 1; i < row.Count; i++) {
[980]106        LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], 0, row.Color, row.Thickness, row.Style);
[861]107        lineShapes.Add(lineShape);
[870]108        // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
[983]109        root.AddShape(lineShape);
110        maxDataValue = Math.Max(row[i], maxDataValue);
111        minDataValue = Math.Min(row[i], minDataValue);
[861]112      }
113
114      rowToLineShapes[row] = lineShapes;
[981]115      ZoomToFullView();
[861]116      canvasUI1.Invalidate();
[697]117    }
118
[861]119    private void OnDataRowRemoved(IDataRow row) {
120      row.ValueChanged -= OnRowValueChanged;
121      row.ValuesChanged -= OnRowValuesChanged;
[697]122    }
123
[861]124    private readonly IDictionary<IDataRow, List<LineShape>> rowToLineShapes = new Dictionary<IDataRow, List<LineShape>>();
[697]125
[870]126    // TODO use action parameter
[869]127    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
[861]128      List<LineShape> lineShapes = rowToLineShapes[row];
[983]129      maxDataValue = Math.Max(value, maxDataValue);
130      minDataValue = Math.Min(value, minDataValue);
[861]131
[928]132      if (index > lineShapes.Count + 1) {
[861]133        throw new NotImplementedException();
[928]134      }
[861]135
136      // new value was added
[928]137      if (index > 0 && index == lineShapes.Count + 1) {
[981]138       
139        if (maxDataRowCount < row.Count)
140          maxDataRowCount = row.Count;
[980]141        LineShape lineShape = new LineShape(index - 1, row[index - 1], index, row[index], 0, row.Color, row.Thickness, row.Style);
[861]142        lineShapes.Add(lineShape);
[870]143        // TODO each DataRow needs its own WorldShape so Y Axes can be zoomed independently.
[861]144        canvasUI1.MainCanvas.WorldShape.AddShape(lineShape);
145      }
146
147      // not the first value
[928]148      if (index > 0) {
149        lineShapes[index - 1].Y2 = value;
150      }
[861]151
152      // not the last value
[928]153      if (index > 0 && index < row.Count - 1) {
[861]154        lineShapes[index].Y1 = value;
[928]155      }
[981]156      ZoomToFullView();
[861]157      canvasUI1.Invalidate();
[697]158    }
159
[870]160    // TODO use action parameter
[869]161    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
[861]162      foreach (double value in values) {
[869]163        OnRowValueChanged(row, value, index++, action);
[861]164      }
165    }
[761]166
[928]167    private void OnModelChanged() {}
[697]168
169    #endregion
170
171    #region Begin-/EndUpdate
172
173    private int beginUpdateCount = 0;
174
[861]175    public void BeginUpdate() {
[697]176      beginUpdateCount++;
177    }
178
[861]179    public void EndUpdate() {
[928]180      if (beginUpdateCount == 0) {
[697]181        throw new InvalidOperationException("Too many EndUpdates.");
[928]182      }
[697]183
184      beginUpdateCount--;
185
[928]186      if (beginUpdateCount == 0) {
[697]187        Invalidate();
[928]188      }
[697]189    }
190
191    #endregion
[928]192
193    private MouseEventListener panListener;
[984]194    private MouseEventListener zoomListener;
[928]195
196    private void CreateMouseEventListeners() {
197      panListener = new MouseEventListener();
198      panListener.OnMouseMove += Pan_OnMouseMove;
199      panListener.OnMouseUp += Pan_OnMouseUp;
[984]200
201      zoomListener = new MouseEventListener();
202      zoomListener.OnMouseMove += Zoom_OnMouseMove;
203      zoomListener.OnMouseUp += Zoom_OnMouseUp;
[928]204    }
205
206    private RectangleD startClippingArea;
207
208    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
[984]209      if (ModifierKeys == Keys.Control) {
210        zoomListener.StartPoint = e.Location;
211        canvasUI1.MouseEventListener = zoomListener;
[928]212
[984]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      }
[928]223    }
224
225    private void Pan_OnMouseUp(Point startPoint, Point actualPoint) {
[984]226      canvasUI1.MouseEventListener = null;
[928]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
[983]244      root.ClippingArea = newClippingArea;
[928]245      panListener.StartPoint = startPoint;
246
[984]247      zoomFullView = false; //user wants to pan => no full view
[981]248
[928]249      canvasUI1.Invalidate();
250    }
[984]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    }
[684]289  }
[984]290}
Note: See TracBrowser for help on using the repository browser.