Free cookie consent management tool by TermsFeed Policy Generator

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

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

Display of X- and Y-Axis-Labels (#556)

File size: 21.1 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;
8using HeuristicLab.Visualization.Test;
9
10namespace HeuristicLab.Visualization {
11  public partial class LineChart : ViewBase {
12    private readonly IChartDataRowsModel model;
13    private readonly Canvas canvas;
14
15    private readonly TextShape titleShape = new TextShape("Title");
16    private readonly LegendShape legendShape = new LegendShape();
17    private readonly XAxis xAxis = new XAxis();
18    private readonly List<RowEntry> rowEntries = new List<RowEntry>();
19
20    private readonly Dictionary<IDataRow, RowEntry> rowToRowEntry = new Dictionary<IDataRow, RowEntry>();
21
22    private readonly ViewSettings viewSettings;
23
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 = 40;
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    /// <summary>
39    /// Initializes the chart.
40    /// </summary>
41    /// <param name="model">Referenz to the model, for data</param>
42    public LineChart(IChartDataRowsModel model) : this() {
43      if (model == null) {
44        throw new NullReferenceException("Model cannot be null.");
45      }
46
47      canvas = canvasUI.Canvas;
48
49      this.model = model;
50      viewSettings = model.ViewSettings;
51      viewSettings.OnUpdateSettings += UpdateViewSettings;
52
53      Item = model;
54
55      UpdateLayout();
56      canvasUI.Resize += delegate { UpdateLayout(); };
57
58      ZoomToFullView();
59    }
60
61    /// <summary>
62    /// updates the view settings
63    /// </summary>
64    private void UpdateViewSettings() {
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      SetLegendPosition();
75
76      canvasUI.Invalidate();
77    }
78
79    /// <summary>
80    /// Layout management - arranges the inner shapes.
81    /// </summary>
82    private void UpdateLayout() {
83      canvas.ClearShapes();
84
85      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
86        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
87        if (yAxisDescriptor.ShowGrid) {
88          info.Grid.Color = yAxisDescriptor.GridColor;
89          canvas.AddShape(info.Grid);
90        }
91      }
92
93      foreach (RowEntry rowEntry in rowEntries) {
94        canvas.AddShape(rowEntry.LinesShape);
95      }
96
97      xAxis.ShowLabel = model.ShowXAxisLabel;
98      xAxis.Label = model.XAxisLabel;
99
100      canvas.AddShape(xAxis);
101
102      int yAxesWidthLeft = 0;
103      int yAxesWidthRight = 0;
104
105      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
106        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
107        if (yAxisDescriptor.ShowYAxis) {
108          canvas.AddShape(info.YAxis);
109          info.YAxis.ShowLabel = yAxisDescriptor.ShowYAxisLabel;
110          info.YAxis.Label = yAxisDescriptor.Label;
111          info.YAxis.Position = yAxisDescriptor.Position;
112          switch (yAxisDescriptor.Position) {
113            case AxisPosition.Left:
114              yAxesWidthLeft += YAxisWidth;
115              break;
116            case AxisPosition.Right:
117              yAxesWidthRight += YAxisWidth;
118              break;
119            default:
120              throw new NotImplementedException();
121          }
122        }
123      }
124
125      canvas.AddShape(titleShape);
126      canvas.AddShape(legendShape);
127
128      canvas.AddShape(userInteractionShape);
129
130      titleShape.X = 10;
131      titleShape.Y = canvasUI.Height - 10;
132
133      RectangleD linesAreaBoundingBox = new RectangleD(yAxesWidthLeft,
134                                                       XAxisHeight,
135                                                       canvasUI.Width - yAxesWidthRight,
136                                                       canvasUI.Height);
137
138      foreach (RowEntry rowEntry in rowEntries) {
139        rowEntry.LinesShape.BoundingBox = linesAreaBoundingBox;
140      }
141
142      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
143        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
144        info.Grid.BoundingBox = linesAreaBoundingBox;
145      }
146
147      int yAxisLeft = 0;
148      int yAxisRight = (int)linesAreaBoundingBox.X2;
149
150      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
151        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
152        if (yAxisDescriptor.ShowYAxis) {
153          switch (yAxisDescriptor.Position) {
154            case AxisPosition.Left:
155              info.YAxis.BoundingBox = new RectangleD(yAxisLeft,
156                                                      linesAreaBoundingBox.Y1,
157                                                      yAxisLeft + YAxisWidth,
158                                                      linesAreaBoundingBox.Y2);
159              yAxisLeft += YAxisWidth;
160              break;
161            case AxisPosition.Right:
162              info.YAxis.BoundingBox = new RectangleD(yAxisRight,
163                                                      linesAreaBoundingBox.Y1,
164                                                      yAxisRight + YAxisWidth,
165                                                      linesAreaBoundingBox.Y2);
166              yAxisRight += YAxisWidth;
167              break;
168            default:
169              throw new NotImplementedException();
170          }
171        }
172      }
173
174      userInteractionShape.BoundingBox = linesAreaBoundingBox;
175      userInteractionShape.ClippingArea = new RectangleD(0, 0, userInteractionShape.BoundingBox.Width, userInteractionShape.BoundingBox.Height);
176
177      xAxis.BoundingBox = new RectangleD(linesAreaBoundingBox.X1,
178                                         0,
179                                         linesAreaBoundingBox.X2,
180                                         linesAreaBoundingBox.Y1);
181
182      SetLegendPosition();
183    }
184
185    private readonly Dictionary<YAxisDescriptor, YAxisInfo> yAxisInfos = new Dictionary<YAxisDescriptor, YAxisInfo>();
186
187    private YAxisInfo GetYAxisInfo(YAxisDescriptor yAxisDescriptor) {
188      YAxisInfo info;
189
190      if (!yAxisInfos.TryGetValue(yAxisDescriptor, out info)) {
191        info = new YAxisInfo();
192        yAxisInfos[yAxisDescriptor] = info;
193      }
194
195      return info;
196    }
197
198    /// <summary>
199    /// sets the legend position
200    /// </summary>
201    private void SetLegendPosition() {
202      switch (viewSettings.LegendPosition) {
203        case LegendPosition.Bottom:
204          setLegendBottom();
205          break;
206
207        case LegendPosition.Top:
208          setLegendTop();
209          break;
210
211        case LegendPosition.Left:
212          setLegendLeft();
213          break;
214
215        case LegendPosition.Right:
216          setLegendRight();
217          break;
218      }
219    }
220
221    public void setLegendRight() {
222      // legend right
223      legendShape.BoundingBox = new RectangleD(canvasUI.Width - legendShape.GetMaxLabelLength(), 10, canvasUI.Width, canvasUI.Height - 50);
224      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
225      legendShape.Row = false;
226      legendShape.CreateLegend();
227    }
228
229    public void setLegendLeft() {
230      // legend left
231      legendShape.BoundingBox = new RectangleD(10, 10, canvasUI.Width, canvasUI.Height - 50);
232      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
233      legendShape.Row = false;
234      legendShape.CreateLegend();
235
236      canvasUI.Invalidate();
237    }
238
239    public void setLegendTop() {
240      // legend top
241      legendShape.BoundingBox = new RectangleD(100, canvasUI.Height - canvasUI.Height, canvasUI.Width, canvasUI.Height - 10);
242      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
243      legendShape.Row = true;
244      legendShape.Top = true;
245      legendShape.CreateLegend();
246    }
247
248    public void setLegendBottom() {
249      // legend bottom
250      legendShape.BoundingBox = new RectangleD(100, 10, canvasUI.Width, canvasUI.Height/*legendShape.GetHeight4Rows()*/);
251      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width, legendShape.BoundingBox.Height);
252      legendShape.Row = true;
253      legendShape.Top = false;
254      legendShape.CreateLegend();
255    }
256
257    private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
258      OptionsDialog optionsdlg = new OptionsDialog(model);
259      optionsdlg.Show();
260    }
261
262    public void OnDataRowChanged(IDataRow row) {
263      RowEntry rowEntry = rowToRowEntry[row];
264
265      rowEntry.LinesShape.UpdateStyle(row);
266
267      UpdateLayout();
268
269      canvasUI.Invalidate();
270    }
271
272    #region Add-/RemoveItemEvents
273
274    protected override void AddItemEvents() {
275      base.AddItemEvents();
276
277      model.DataRowAdded += OnDataRowAdded;
278      model.DataRowRemoved += OnDataRowRemoved;
279      model.ModelChanged += OnModelChanged;
280
281      foreach (IDataRow row in model.Rows) {
282        OnDataRowAdded(row);
283      }
284    }
285
286    protected override void RemoveItemEvents() {
287      base.RemoveItemEvents();
288
289      model.DataRowAdded -= OnDataRowAdded;
290      model.DataRowRemoved -= OnDataRowRemoved;
291      model.ModelChanged -= OnModelChanged;
292    }
293
294    private void OnDataRowAdded(IDataRow row) {
295      row.ValueChanged += OnRowValueChanged;
296      row.ValuesChanged += OnRowValuesChanged;
297      row.DataRowChanged += OnDataRowChanged;
298
299      legendShape.AddLegendItem(new LegendItem(row.Label, row.Color, row.Thickness));
300      legendShape.CreateLegend();
301
302      InitLineShapes(row);
303
304      UpdateLayout();
305    }
306
307    private void OnDataRowRemoved(IDataRow row) {
308      row.ValueChanged -= OnRowValueChanged;
309      row.ValuesChanged -= OnRowValuesChanged;
310      row.DataRowChanged -= OnDataRowChanged;
311
312      rowToRowEntry.Remove(row);
313      rowEntries.RemoveAll(delegate(RowEntry rowEntry) { return rowEntry.DataRow == row; });
314
315      UpdateLayout();
316    }
317
318    #endregion
319
320    public void ZoomToFullView() {
321      SetClipX(-0.1, model.MaxDataRowValues - 0.9);
322
323      foreach (RowEntry rowEntry in rowEntries) {
324        YAxisDescriptor yAxisDescriptor = rowEntry.DataRow.YAxis;
325
326        SetClipY(rowEntry,
327                 yAxisDescriptor.MinValue - ((yAxisDescriptor.MaxValue - yAxisDescriptor.MinValue)*0.05),
328                 yAxisDescriptor.MaxValue + ((yAxisDescriptor.MaxValue - yAxisDescriptor.MinValue)*0.05));
329      }
330
331      canvasUI.Invalidate();
332    }
333
334    private void SetClipX(double x1, double x2) {
335      xAxis.ClippingArea = new RectangleD(x1,
336                                          0,
337                                          x2,
338                                          XAxisHeight);
339
340      foreach (RowEntry rowEntry in rowEntries) {
341        rowEntry.LinesShape.ClippingArea = new RectangleD(x1,
342                                                          rowEntry.LinesShape.ClippingArea.Y1,
343                                                          x2,
344                                                          rowEntry.LinesShape.ClippingArea.Y2);
345      }
346
347      foreach (YAxisDescriptor yAxisDescriptor in model.YAxes) {
348        YAxisInfo info = GetYAxisInfo(yAxisDescriptor);
349        info.Grid.ClippingArea = new RectangleD(x1,
350                                                info.Grid.ClippingArea.Y1,
351                                                x2,
352                                                info.Grid.ClippingArea.Y2);
353        info.YAxis.ClippingArea = new RectangleD(0,
354                                                 info.YAxis.ClippingArea.Y1,
355                                                 YAxisWidth,
356                                                 info.YAxis.ClippingArea.Y2);
357      }
358    }
359
360    private void SetClipY(RowEntry rowEntry, double y1, double y2) {
361      rowEntry.LinesShape.ClippingArea = new RectangleD(rowEntry.LinesShape.ClippingArea.X1,
362                                                        y1,
363                                                        rowEntry.LinesShape.ClippingArea.X2,
364                                                        y2);
365
366      YAxisInfo info = GetYAxisInfo(rowEntry.DataRow.YAxis);
367
368      info.Grid.ClippingArea = new RectangleD(info.Grid.ClippingArea.X1,
369                                              y1,
370                                              info.Grid.ClippingArea.X2,
371                                              y2);
372      info.YAxis.ClippingArea = new RectangleD(info.YAxis.ClippingArea.X1,
373                                               y1,
374                                               info.YAxis.ClippingArea.X2,
375                                               y2);
376    }
377
378    private void InitLineShapes(IDataRow row) {
379      RowEntry rowEntry = new RowEntry(row);
380      rowEntries.Add(rowEntry);
381      rowToRowEntry[row] = rowEntry;
382
383      if ((row.LineType == DataRowType.SingleValue)) {
384        if (row.Count > 0) {
385          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
386                                                        row.Style);
387          rowEntry.LinesShape.AddShape(lineShape);
388        }
389      } else {
390        for (int i = 1; i < row.Count; i++) {
391          LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], row.Color, row.Thickness, row.Style);
392          rowEntry.LinesShape.AddShape(lineShape);
393        }
394      }
395
396      ZoomToFullView();
397    }
398
399    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
400      RowEntry rowEntry = rowToRowEntry[row];
401
402      if (row.LineType == DataRowType.SingleValue) {
403        if (action == Action.Added) {
404          LineShape lineShape = new HorizontalLineShape(0, row[0], double.MaxValue, row[0], row.Color, row.Thickness,
405                                                        row.Style);
406          rowEntry.LinesShape.AddShape(lineShape);
407        } else {
408          LineShape lineShape = rowEntry.LinesShape.GetShape(0);
409          lineShape.Y1 = value;
410          lineShape.Y2 = value;
411        }
412      } else {
413        if (index > rowEntry.LinesShape.Count + 1) {
414          throw new NotImplementedException();
415        }
416
417        // new value was added
418        if (index > 0 && index == rowEntry.LinesShape.Count + 1) {
419          LineShape lineShape = new LineShape(index - 1, row[index - 1], index, row[index], row.Color, row.Thickness, row.Style);
420          rowEntry.LinesShape.AddShape(lineShape);
421        }
422
423        // not the first value
424        if (index > 0) {
425          rowEntry.LinesShape.GetShape(index - 1).Y2 = value;
426        }
427
428        // not the last value
429        if (index > 0 && index < row.Count - 1) {
430          rowEntry.LinesShape.GetShape(index).Y1 = value;
431        }
432      }
433
434      ZoomToFullView();
435    }
436
437    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
438      foreach (double value in values) {
439        OnRowValueChanged(row, value, index++, action);
440      }
441    }
442
443    private void OnModelChanged() {
444      titleShape.Text = model.Title;
445
446      canvasUI.Invalidate();
447    }
448
449    #region Begin-/EndUpdate
450
451    private int beginUpdateCount;
452
453    public void BeginUpdate() {
454      beginUpdateCount++;
455    }
456
457    public void EndUpdate() {
458      if (beginUpdateCount == 0) {
459        throw new InvalidOperationException("Too many EndUpdates.");
460      }
461
462      beginUpdateCount--;
463
464      if (beginUpdateCount == 0) {
465        canvasUI.Invalidate();
466      }
467    }
468
469    #endregion
470
471    #region Zooming / Panning
472
473    private void Pan(Point startPoint, Point endPoint) {
474      RectangleD clippingArea = Translate.ClippingArea(startPoint, endPoint, xAxis.ClippingArea, xAxis.Viewport);
475
476      SetClipX(clippingArea.X1, clippingArea.X2);
477
478      foreach (RowEntry rowEntry in rowEntries) {
479        if (rowEntry.DataRow.YAxis.ClipChangeable) {
480          clippingArea = Translate.ClippingArea(startPoint, endPoint, rowEntry.LinesShape.ClippingArea, rowEntry.LinesShape.Viewport);
481          SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
482        }
483      }
484
485      canvasUI.Invalidate();
486    }
487
488    private void PanEnd(Point startPoint, Point endPoint) {
489      Pan(startPoint, endPoint);
490    }
491
492    private void SetClippingArea(Rectangle rectangle) {
493      RectangleD clippingArea = Transform.ToWorld(rectangle, xAxis.Viewport, xAxis.ClippingArea);
494
495      SetClipX(clippingArea.X1, clippingArea.X2);
496
497      foreach (RowEntry rowEntry in rowEntries) {
498        if (rowEntry.DataRow.YAxis.ClipChangeable) {
499          clippingArea = Transform.ToWorld(rectangle, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
500
501          SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
502        }
503      }
504
505      userInteractionShape.RemoveShape(rectangleShape);
506      canvasUI.Invalidate();
507    }
508
509    private void DrawRectangle(Rectangle rectangle) {
510      rectangleShape.Rectangle = Transform.ToWorld(rectangle, userInteractionShape.Viewport, userInteractionShape.ClippingArea);
511      canvasUI.Invalidate();
512    }
513
514    private void canvasUI1_KeyDown(object sender, KeyEventArgs e) {
515    }
516
517    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
518      Focus();
519
520      if (e.Button == MouseButtons.Right) {
521        contextMenuStrip1.Show(PointToScreen(e.Location));
522      } else if (e.Button == MouseButtons.Left) {
523        if (ModifierKeys == Keys.None) {
524          PanListener panListener = new PanListener(e.Location);
525          panListener.Pan += Pan;
526          panListener.PanEnd += PanEnd;
527
528          mouseEventListener = panListener;
529        } else if (ModifierKeys == Keys.Control) {
530          ZoomListener zoomListener = new ZoomListener(e.Location);
531          zoomListener.DrawRectangle += DrawRectangle;
532          zoomListener.SetClippingArea += SetClippingArea;
533
534          rectangleShape.Rectangle = RectangleD.Empty;
535          userInteractionShape.AddShape(rectangleShape);
536
537          mouseEventListener = zoomListener;
538        }
539      }
540    }
541
542    private void canvasUI_MouseMove(object sender, MouseEventArgs e) {
543      if (mouseEventListener != null) {
544        mouseEventListener.MouseMove(sender, e);
545      }
546    }
547
548    private void canvasUI_MouseUp(object sender, MouseEventArgs e) {
549      if (mouseEventListener != null) {
550        mouseEventListener.MouseUp(sender, e);
551      }
552
553      mouseEventListener = null;
554    }
555
556    private void canvasUI1_MouseWheel(object sender, MouseEventArgs e) {
557      if (ModifierKeys == Keys.Control) {
558        double zoomFactor = (e.Delta > 0) ? 0.7 : 1.3;
559
560        PointD world;
561
562        world = Transform.ToWorld(e.Location, xAxis.Viewport, xAxis.ClippingArea);
563
564        double x1 = world.X - (world.X - xAxis.ClippingArea.X1)*zoomFactor;
565        double x2 = world.X + (xAxis.ClippingArea.X2 - world.X)*zoomFactor;
566
567        SetClipX(x1, x2);
568
569        foreach (RowEntry rowEntry in rowEntries) {
570          world = Transform.ToWorld(e.Location, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
571
572          double y1 = world.Y - (world.Y - rowEntry.LinesShape.ClippingArea.Y1) * zoomFactor;
573          double y2 = world.Y + (rowEntry.LinesShape.ClippingArea.Y2 - world.Y)*zoomFactor;
574
575          SetClipY(rowEntry, y1, y2);
576        }
577
578        canvasUI.Invalidate();
579      }
580    }
581
582    #endregion
583
584    private class LinesShape : WorldShape {
585      public void UpdateStyle(IDataRow row) {
586        foreach (IShape shape in shapes) {
587          LineShape lineShape = shape as LineShape;
588          if (lineShape != null) {
589            lineShape.LSColor = row.Color;
590            lineShape.LSDrawingStyle = row.Style;
591            lineShape.LSThickness = row.Thickness;
592          }
593        }
594      }
595
596      public int Count {
597        get { return shapes.Count; }
598      }
599
600      public LineShape GetShape(int index) {
601        return (LineShape)shapes[index];
602      }
603    }
604
605    private class RowEntry {
606      private readonly IDataRow dataRow;
607
608      private readonly LinesShape linesShape = new LinesShape();
609
610      public RowEntry(IDataRow dataRow) {
611        this.dataRow = dataRow;
612      }
613
614      public IDataRow DataRow {
615        get { return dataRow; }
616      }
617
618      public LinesShape LinesShape {
619        get { return linesShape; }
620      }
621    }
622
623    private class YAxisInfo {
624      private readonly Grid grid = new Grid();
625      private readonly YAxis yAxis = new YAxis();
626
627      public Grid Grid {
628        get { return grid; }
629      }
630
631      public YAxis YAxis {
632        get { return yAxis; }
633      }
634    }
635  }
636}
Note: See TracBrowser for help on using the repository browser.