Free cookie consent management tool by TermsFeed Policy Generator

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

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

#424
Implemented history stack for clipping areas - by pressing the "back" button the antecedent clipping area will be set

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