Free cookie consent management tool by TermsFeed Policy Generator

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

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

#520 Started to implement font changes in options dialog

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