Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 983 was 983, checked in by mstoeger, 16 years ago

Added XAxis shape to project (#433)
Some small refactorings in LineChart (#345)

File size: 7.2 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
195    private void CreateMouseEventListeners() {
196      panListener = new MouseEventListener();
197      panListener.OnMouseMove += Pan_OnMouseMove;
198      panListener.OnMouseUp += Pan_OnMouseUp;
199    }
200
201
202    private RectangleD startClippingArea;
203
204    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
205      panListener.StartPoint = e.Location;
206      canvasUI1.MouseEventListener = panListener;
207
208      startClippingArea = root.ClippingArea;
209    }
210
211    private void Pan_OnMouseUp(Point startPoint, Point actualPoint) {
212       canvasUI1.MouseEventListener = null;
213    }
214
215    private void Pan_OnMouseMove(Point startPoint, Point actualPoint) {
216      Rectangle viewPort = canvasUI1.ClientRectangle;
217
218      PointD worldStartPoint = Transform.ToWorld(startPoint, viewPort, startClippingArea);
219      PointD worldActualPoint = Transform.ToWorld(actualPoint, viewPort, startClippingArea);
220
221      double xDiff = worldActualPoint.X - worldStartPoint.X;
222      double yDiff = worldActualPoint.Y - worldStartPoint.Y;
223
224      RectangleD newClippingArea = new RectangleD();
225      newClippingArea.X1 = startClippingArea.X1 - xDiff;
226      newClippingArea.X2 = startClippingArea.X2 - xDiff;
227      newClippingArea.Y1 = startClippingArea.Y1 - yDiff;
228      newClippingArea.Y2 = startClippingArea.Y2 - yDiff;
229
230      root.ClippingArea = newClippingArea;
231      panListener.StartPoint = startPoint;
232
233      this.zoomFullView = false; //user wants to pan => no full view
234
235      canvasUI1.Invalidate();
236    }
237  }
238}
Note: See TracBrowser for help on using the repository browser.