Free cookie consent management tool by TermsFeed Policy Generator

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

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

#520 Renamed ViewPropertiesModel to ViewSettings and moved from LineChart to ChartDataRowsModel

File size: 17.0 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Drawing;
4using System.Windows.Forms;
5using HeuristicLab.Core;
6using HeuristicLab.Visualization.Legend;
7using HeuristicLab.Visualization.Options;
8
9namespace HeuristicLab.Visualization {
10  public partial class LineChart : ViewBase {
11    private readonly IChartDataRowsModel model;
12    private readonly Canvas canvas;
13
14    private readonly TextShape titleShape = new TextShape("Title");
15    private readonly LegendShape legendShape = new LegendShape();
16    private readonly XAxis xAxis = new XAxis();
17    private readonly List<RowEntry> rowEntries = new List<RowEntry>();
18
19    private readonly Dictionary<IDataRow, RowEntry> rowToRowEntry = new Dictionary<IDataRow, RowEntry>();
20
21    private readonly ViewSettings viewSettings;
22
23//    private readonly Stack<RectangleD> clippingAreaHistory = new Stack<RectangleD>();
24    private readonly WorldShape userInteractionShape = new WorldShape();
25    private readonly RectangleShape rectangleShape = new RectangleShape(0, 0, 0, 0, Color.FromArgb(50, 0, 0, 255));
26    private IMouseEventListener mouseEventListener;
27
28    private const int YAxisWidth = 100;
29    private const int XAxisHeight = 20;
30
31    private bool zoomToFullView;
32
33
34    /// <summary>
35    /// This constructor shouldn't be called. Only required for the designer.
36    /// </summary>
37    public LineChart() {
38      InitializeComponent();
39    }
40
41    /// <summary>
42    /// Initializes the chart.
43    /// </summary>
44    /// <param name="model">Referenz to the model, for data</param>
45    public LineChart(IChartDataRowsModel model) : this() {
46      if (model == null) {
47        throw new NullReferenceException("Model cannot be null.");
48      }
49
50      canvas = canvasUI.Canvas;
51
52      this.model = model;
53      viewSettings = model.ViewSettings;
54      viewSettings.OnUpdateSettings += UpdateViewProperties;
55
56      Item = model;
57
58      UpdateLayout();
59      canvasUI.Resize += delegate { UpdateLayout(); };
60
61      ZoomToFullView();
62    }
63
64    private void UpdateViewProperties() {
65      titleShape.Font = viewSettings.TitleFont;
66      titleShape.Color = viewSettings.TitleColor;
67
68      legendShape.Font = viewSettings.LegendFont;
69      legendShape.Color = viewSettings.LegendColor;
70
71      xAxis.Font = viewSettings.XAxisFont;
72      xAxis.Color = viewSettings.XAxisColor;
73
74      canvasUI.Invalidate();
75    }
76
77    /// <summary>
78    /// Layout management - arranges the inner shapes.
79    /// </summary>
80    private void UpdateLayout() {
81      canvas.ClearShapes();
82
83      foreach (RowEntry rowEntry in rowEntries) {
84        canvas.AddShape(rowEntry.Grid);
85      }
86
87      foreach (RowEntry rowEntry in rowEntries) {
88        canvas.AddShape(rowEntry.LinesShape);
89      }
90
91      canvas.AddShape(xAxis);
92
93      foreach (RowEntry rowEntry in rowEntries) {
94        canvas.AddShape(rowEntry.YAxis);
95      }
96
97      canvas.AddShape(titleShape);
98      canvas.AddShape(legendShape);
99
100      canvas.AddShape(userInteractionShape);
101
102      titleShape.X = 10;
103      titleShape.Y = canvasUI.Height - 10;
104
105      int yAxesWidth = 0;
106
107      foreach (RowEntry rowEntry in rowEntries) {
108        if (rowEntry.YAxis.Visible) {
109          yAxesWidth += YAxisWidth;
110        }
111      }
112
113      RectangleD linesAreaBoundingBox = new RectangleD(yAxesWidth,
114                                                       XAxisHeight,
115                                                       canvasUI.Width,
116                                                       canvasUI.Height);
117
118      foreach (RowEntry rowEntry in rowEntries) {
119        rowEntry.LinesShape.BoundingBox = linesAreaBoundingBox;
120        rowEntry.Grid.BoundingBox = linesAreaBoundingBox;
121      }
122
123      int yAxisLeft = 0;
124      foreach (RowEntry rowEntry in rowEntries) {
125        rowEntry.YAxis.BoundingBox = new RectangleD(yAxisLeft,
126                                                    linesAreaBoundingBox.Y1,
127                                                    yAxisLeft + YAxisWidth,
128                                                    linesAreaBoundingBox.Y2);
129        yAxisLeft += YAxisWidth;
130      }
131
132      userInteractionShape.BoundingBox = linesAreaBoundingBox;
133      userInteractionShape.ClippingArea = new RectangleD(0, 0, userInteractionShape.BoundingBox.Width, userInteractionShape.BoundingBox.Height);
134
135      xAxis.BoundingBox = new RectangleD(linesAreaBoundingBox.X1,
136                                         0,
137                                         linesAreaBoundingBox.X2,
138                                         linesAreaBoundingBox.Y1);
139
140      legendShape.BoundingBox = new RectangleD(10, 10, 110, canvasUI.Height - 50);
141      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width,
142                                                legendShape.BoundingBox.Height);
143
144      canvasUI.Invalidate();
145    }
146
147    private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
148      OptionsDialog optionsdlg = new OptionsDialog(model);
149      optionsdlg.ShowDialog(this);
150    }
151
152    public void OnDataRowChanged(IDataRow row) {
153      RowEntry rowEntry = rowToRowEntry[row];
154
155      rowEntry.LinesShape.UpdateStyle(row);
156
157      canvasUI.Invalidate();
158    }
159
160    #region Add-/RemoveItemEvents
161
162    protected override void AddItemEvents() {
163      base.AddItemEvents();
164
165      model.DataRowAdded += OnDataRowAdded;
166      model.DataRowRemoved += OnDataRowRemoved;
167      model.ModelChanged += OnModelChanged;
168
169      foreach (IDataRow row in model.Rows) {
170        OnDataRowAdded(row);
171      }
172    }
173
174    protected override void RemoveItemEvents() {
175      base.RemoveItemEvents();
176
177      model.DataRowAdded -= OnDataRowAdded;
178      model.DataRowRemoved -= OnDataRowRemoved;
179      model.ModelChanged -= OnModelChanged;
180    }
181
182    private void OnDataRowAdded(IDataRow row) {
183      row.ValueChanged += OnRowValueChanged;
184      row.ValuesChanged += OnRowValuesChanged;
185      row.DataRowChanged += OnDataRowChanged;
186
187      legendShape.AddLegendItem(new LegendItem(row.Label, row.Color, row.Thickness));
188      legendShape.CreateLegend();
189
190      InitLineShapes(row);
191
192      UpdateLayout();
193    }
194
195    private void OnDataRowRemoved(IDataRow row) {
196      row.ValueChanged -= OnRowValueChanged;
197      row.ValuesChanged -= OnRowValuesChanged;
198      row.DataRowChanged -= OnDataRowChanged;
199
200      rowToRowEntry.Remove(row);
201      rowEntries.RemoveAll(delegate(RowEntry rowEntry) { return rowEntry.DataRow == row; });
202
203      UpdateLayout();
204    }
205
206    #endregion
207
208    public void ZoomToFullView() {
209      SetClipX(-0.1, model.MaxDataRowValues - 0.9);
210
211      foreach (RowEntry rowEntry in rowEntries) {
212        IDataRow row = rowEntry.DataRow;
213
214        SetClipY(rowEntry,
215                 row.MinValue - ((row.MaxValue - row.MinValue)*0.05),
216                 row.MaxValue + ((row.MaxValue - row.MinValue)*0.05));
217      }
218
219      zoomToFullView = true;
220
221      canvasUI.Invalidate();
222    }
223
224    private void SetClipX(double x1, double x2) {
225      xAxis.ClippingArea = new RectangleD(x1,
226                                          0,
227                                          x2,
228                                          XAxisHeight);
229
230      foreach (RowEntry rowEntry in rowEntries) {
231        rowEntry.LinesShape.ClippingArea = new RectangleD(x1,
232                                                          rowEntry.LinesShape.ClippingArea.Y1,
233                                                          x2,
234                                                          rowEntry.LinesShape.ClippingArea.Y2);
235        rowEntry.Grid.ClippingArea = new RectangleD(x1,
236                                                    rowEntry.Grid.ClippingArea.Y1,
237                                                    x2,
238                                                    rowEntry.Grid.ClippingArea.Y2);
239        rowEntry.YAxis.ClippingArea = new RectangleD(0,
240                                                     rowEntry.YAxis.ClippingArea.Y1,
241                                                     YAxisWidth,
242                                                     rowEntry.YAxis.ClippingArea.Y2);
243      }
244    }
245
246    private static void SetClipY(RowEntry rowEntry, double y1, double y2) {
247      rowEntry.LinesShape.ClippingArea = new RectangleD(rowEntry.LinesShape.ClippingArea.X1,
248                                                        y1,
249                                                        rowEntry.LinesShape.ClippingArea.X2,
250                                                        y2);
251      rowEntry.Grid.ClippingArea = new RectangleD(rowEntry.Grid.ClippingArea.X1,
252                                                  y1,
253                                                  rowEntry.Grid.ClippingArea.X2,
254                                                  y2);
255      rowEntry.YAxis.ClippingArea = new RectangleD(rowEntry.YAxis.ClippingArea.X1,
256                                                   y1,
257                                                   rowEntry.YAxis.ClippingArea.X2,
258                                                   y2);
259    }
260
261    private void InitLineShapes(IDataRow row) {
262      RowEntry rowEntry = new RowEntry(row);
263      rowEntries.Add(rowEntry);
264      rowToRowEntry[row] = rowEntry;
265
266      if ((row.LineType == DataRowType.SingleValue)) {
267        if (row.Count > 0) {
268          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
269                                                        row.Style);
270          rowEntry.LinesShape.AddShape(lineShape);
271        }
272      } else {
273        for (int i = 1; i < row.Count; i++) {
274          LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], row.Color, row.Thickness, row.Style);
275          rowEntry.LinesShape.AddShape(lineShape);
276        }
277      }
278
279      ZoomToFullView();
280    }
281
282    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
283      RowEntry rowEntry = rowToRowEntry[row];
284
285      if (row.LineType == DataRowType.SingleValue) {
286        if (action == Action.Added) {
287          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
288                                                        row.Style);
289          rowEntry.LinesShape.AddShape(lineShape);
290        } else {
291          LineShape lineShape = rowEntry.LinesShape.GetShape(0);
292          lineShape.Y1 = value;
293          lineShape.Y2 = value;
294        }
295      } else {
296        if (index > rowEntry.LinesShape.Count + 1) {
297          throw new NotImplementedException();
298        }
299
300        // new value was added
301        if (index > 0 && index == rowEntry.LinesShape.Count + 1) {
302          LineShape lineShape = new LineShape(index - 1, row[index - 1], index, row[index], row.Color, row.Thickness, row.Style);
303          rowEntry.LinesShape.AddShape(lineShape);
304        }
305
306        // not the first value
307        if (index > 0) {
308          rowEntry.LinesShape.GetShape(index - 1).Y2 = value;
309        }
310
311        // not the last value
312        if (index > 0 && index < row.Count - 1) {
313          rowEntry.LinesShape.GetShape(index).Y1 = value;
314        }
315      }
316
317      ZoomToFullView();
318    }
319
320    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
321      foreach (double value in values) {
322        OnRowValueChanged(row, value, index++, action);
323      }
324    }
325
326    private void OnModelChanged() {
327      titleShape.Text = model.Title;
328
329      canvasUI.Invalidate();
330    }
331
332    #region Begin-/EndUpdate
333
334    private int beginUpdateCount;
335
336    public void BeginUpdate() {
337      beginUpdateCount++;
338    }
339
340    public void EndUpdate() {
341      if (beginUpdateCount == 0) {
342        throw new InvalidOperationException("Too many EndUpdates.");
343      }
344
345      beginUpdateCount--;
346
347      if (beginUpdateCount == 0) {
348        canvasUI.Invalidate();
349      }
350    }
351
352    #endregion
353
354    #region Zooming / Panning
355
356    private void Pan(Point startPoint, Point endPoint) {
357      zoomToFullView = false;
358
359      foreach (RowEntry rowEntry in rowEntries) {
360        RectangleD clippingArea = CalcPanClippingArea(startPoint, endPoint, rowEntry.LinesShape);
361
362        SetClipX(clippingArea.X1, clippingArea.X1);
363        SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
364      }
365
366      canvasUI.Invalidate();
367    }
368
369    private void PanEnd(Point startPoint, Point endPoint) {
370      zoomToFullView = false;
371
372      foreach (RowEntry rowEntry in rowEntries) {
373        RectangleD clippingArea = CalcPanClippingArea(startPoint, endPoint, rowEntry.LinesShape);
374
375        SetClipX(clippingArea.X1, clippingArea.X1);
376        SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
377      }
378
379      canvasUI.Invalidate();
380    }
381
382    private static RectangleD CalcPanClippingArea(Point startPoint, Point endPoint, LinesShape linesShape) {
383      return Translate.ClippingArea(startPoint, endPoint, linesShape.ClippingArea, linesShape.Viewport);
384    }
385
386    private void SetClippingArea(Rectangle rectangle) {
387      foreach (RowEntry rowEntry in rowEntries) {
388        RectangleD clippingArea = Transform.ToWorld(rectangle, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
389
390        SetClipX(clippingArea.X1, clippingArea.X1);
391        SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
392      }
393
394      userInteractionShape.RemoveShape(rectangleShape);
395      canvasUI.Invalidate();
396    }
397
398    private void DrawRectangle(Rectangle rectangle) {
399      rectangleShape.Rectangle = Transform.ToWorld(rectangle, userInteractionShape.Viewport, userInteractionShape.ClippingArea);
400      canvasUI.Invalidate();
401    }
402
403    private void canvasUI1_KeyDown(object sender, KeyEventArgs e) {
404//      if (e.KeyCode == Keys.Back && clippingAreaHistory.Count > 1) {
405//        clippingAreaHistory.Pop();
406//
407//        RectangleD clippingArea = clippingAreaHistory.Peek();
408//
409//        SetLineClippingArea(clippingArea, false);
410//      }
411    }
412
413    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
414      Focus();
415
416      if (e.Button == MouseButtons.Right) {
417        contextMenuStrip1.Show(PointToScreen(e.Location));
418      } else if (e.Button == MouseButtons.Left) {
419        if (ModifierKeys == Keys.None) {
420          PanListener panListener = new PanListener(e.Location);
421          panListener.Pan += Pan;
422          panListener.PanEnd += PanEnd;
423
424          mouseEventListener = panListener;
425        } else if (ModifierKeys == Keys.Control) {
426          ZoomListener zoomListener = new ZoomListener(e.Location);
427          zoomListener.DrawRectangle += DrawRectangle;
428          zoomListener.SetClippingArea += SetClippingArea;
429
430          rectangleShape.Rectangle = RectangleD.Empty;
431          userInteractionShape.AddShape(rectangleShape);
432
433          mouseEventListener = zoomListener;
434        }
435      }
436    }
437
438    private void canvasUI_MouseMove(object sender, MouseEventArgs e) {
439      if (mouseEventListener != null) {
440        mouseEventListener.MouseMove(sender, e);
441      }
442    }
443
444    private void canvasUI_MouseUp(object sender, MouseEventArgs e) {
445      if (mouseEventListener != null) {
446        mouseEventListener.MouseUp(sender, e);
447      }
448
449      mouseEventListener = null;
450    }
451
452    private void canvasUI1_MouseWheel(object sender, MouseEventArgs e) {
453      if (ModifierKeys == Keys.Control) {
454        double zoomFactor = (e.Delta > 0) ? 0.9 : 1.1;
455
456        foreach (RowEntry rowEntry in rowEntries) {
457          RectangleD clippingArea = ZoomListener.ZoomClippingArea(rowEntry.LinesShape.ClippingArea, zoomFactor);
458         
459          SetClipX(clippingArea.X1, clippingArea.X1);
460          SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
461        }
462      }
463    }
464
465    #endregion
466
467    private class LinesShape : WorldShape {
468      public void UpdateStyle(IDataRow row) {
469        foreach (IShape shape in shapes) {
470          LineShape lineShape = shape as LineShape;
471          if (lineShape != null) {
472            lineShape.LSColor = row.Color;
473            lineShape.LSDrawingStyle = row.Style;
474            lineShape.LSThickness = row.Thickness;
475          }
476        }
477      }
478
479      public int Count {
480        get { return shapes.Count; }
481      }
482
483      public LineShape GetShape(int index) {
484        return (LineShape)shapes[index];
485      }
486    }
487
488    private class RowEntry {
489      private readonly IDataRow dataRow;
490
491      private readonly Grid grid = new Grid();
492      private readonly YAxis yAxis = new YAxis();
493      private readonly LinesShape linesShape = new LinesShape();
494
495      public RowEntry(IDataRow dataRow) {
496        this.dataRow = dataRow;
497      }
498
499      public IDataRow DataRow {
500        get { return dataRow; }
501      }
502
503      public Grid Grid {
504        get { return grid; }
505      }
506
507      public YAxis YAxis {
508        get { return yAxis; }
509      }
510
511      public LinesShape LinesShape {
512        get { return linesShape; }
513      }
514    }
515  }
516}
Note: See TracBrowser for help on using the repository browser.