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
RevLine 
[683]1using System;
[861]2using System.Collections.Generic;
[928]3using System.Drawing;
4using System.Windows.Forms;
[697]5using HeuristicLab.Core;
[1233]6using HeuristicLab.Visualization.Legend;
[1195]7using HeuristicLab.Visualization.Options;
[683]8
[861]9namespace HeuristicLab.Visualization {
[1187]10  public partial class LineChart : ViewBase {
[697]11    private readonly IChartDataRowsModel model;
[1240]12    private readonly Canvas canvas;
13
[1285]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>();
[684]18
[1285]19    private readonly Dictionary<IDataRow, RowEntry> rowToRowEntry = new Dictionary<IDataRow, RowEntry>();
[1038]20
[1341]21    private readonly ViewSettings viewSettings;
22
[1285]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;
[983]27
[1285]28    private const int YAxisWidth = 100;
29    private const int XAxisHeight = 20;
30
[1249]31    private bool zoomToFullView;
[1240]32
[1337]33
[697]34    /// <summary>
35    /// This constructor shouldn't be called. Only required for the designer.
36    /// </summary>
[861]37    public LineChart() {
[684]38      InitializeComponent();
39    }
40
[697]41    /// <summary>
42    /// Initializes the chart.
43    /// </summary>
[754]44    /// <param name="model">Referenz to the model, for data</param>
[861]45    public LineChart(IChartDataRowsModel model) : this() {
[1045]46      if (model == null) {
[697]47        throw new NullReferenceException("Model cannot be null.");
[1045]48      }
[684]49
[1240]50      canvas = canvasUI.Canvas;
[983]51
[1285]52      this.model = model;
[1341]53      viewSettings = model.ViewSettings;
54      viewSettings.OnUpdateSettings += UpdateViewProperties;
[1038]55
[983]56      Item = model;
[1038]57
[1240]58      UpdateLayout();
59      canvasUI.Resize += delegate { UpdateLayout(); };
[1187]60
[1285]61      ZoomToFullView();
[697]62    }
[684]63
[1337]64    private void UpdateViewProperties() {
[1341]65      titleShape.Font = viewSettings.TitleFont;
66      titleShape.Color = viewSettings.TitleColor;
[1337]67
[1341]68      legendShape.Font = viewSettings.LegendFont;
69      legendShape.Color = viewSettings.LegendColor;
[1337]70
[1341]71      xAxis.Font = viewSettings.XAxisFont;
72      xAxis.Color = viewSettings.XAxisColor;
[1337]73
74      canvasUI.Invalidate();
75    }
76
[1038]77    /// <summary>
78    /// Layout management - arranges the inner shapes.
79    /// </summary>
80    private void UpdateLayout() {
[1285]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
[1038]102      titleShape.X = 10;
[1240]103      titleShape.Y = canvasUI.Height - 10;
[1038]104
[1285]105      int yAxesWidth = 0;
[1038]106
[1285]107      foreach (RowEntry rowEntry in rowEntries) {
108        if (rowEntry.YAxis.Visible) {
109          yAxesWidth += YAxisWidth;
110        }
111      }
[1187]112
[1285]113      RectangleD linesAreaBoundingBox = new RectangleD(yAxesWidth,
114                                                       XAxisHeight,
115                                                       canvasUI.Width,
116                                                       canvasUI.Height);
[1240]117
[1285]118      foreach (RowEntry rowEntry in rowEntries) {
119        rowEntry.LinesShape.BoundingBox = linesAreaBoundingBox;
120        rowEntry.Grid.BoundingBox = linesAreaBoundingBox;
121      }
[1182]122
[1285]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      }
[1187]131
[1285]132      userInteractionShape.BoundingBox = linesAreaBoundingBox;
133      userInteractionShape.ClippingArea = new RectangleD(0, 0, userInteractionShape.BoundingBox.Width, userInteractionShape.BoundingBox.Height);
[1182]134
[1285]135      xAxis.BoundingBox = new RectangleD(linesAreaBoundingBox.X1,
[1038]136                                         0,
[1285]137                                         linesAreaBoundingBox.X2,
138                                         linesAreaBoundingBox.Y1);
[1049]139
[1240]140      legendShape.BoundingBox = new RectangleD(10, 10, 110, canvasUI.Height - 50);
[1195]141      legendShape.ClippingArea = new RectangleD(0, 0, legendShape.BoundingBox.Width,
142                                                legendShape.BoundingBox.Height);
[1285]143
144      canvasUI.Invalidate();
[1038]145    }
146
[1242]147    private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
[1341]148      OptionsDialog optionsdlg = new OptionsDialog(model);
[1242]149      optionsdlg.ShowDialog(this);
150    }
151
152    public void OnDataRowChanged(IDataRow row) {
[1285]153      RowEntry rowEntry = rowToRowEntry[row];
154
155      rowEntry.LinesShape.UpdateStyle(row);
156
[1242]157      canvasUI.Invalidate();
158    }
159
[861]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;
[869]168
[1045]169      foreach (IDataRow row in model.Rows) {
[869]170        OnDataRowAdded(row);
[1045]171      }
[683]172    }
[684]173
[861]174    protected override void RemoveItemEvents() {
175      base.RemoveItemEvents();
176
177      model.DataRowAdded -= OnDataRowAdded;
178      model.DataRowRemoved -= OnDataRowRemoved;
179      model.ModelChanged -= OnModelChanged;
[697]180    }
181
[861]182    private void OnDataRowAdded(IDataRow row) {
183      row.ValueChanged += OnRowValueChanged;
184      row.ValuesChanged += OnRowValuesChanged;
[1237]185      row.DataRowChanged += OnDataRowChanged;
186
[1049]187      legendShape.AddLegendItem(new LegendItem(row.Label, row.Color, row.Thickness));
188      legendShape.CreateLegend();
[1285]189
[987]190      InitLineShapes(row);
[1285]191
192      UpdateLayout();
[684]193    }
[697]194
[1240]195    private void OnDataRowRemoved(IDataRow row) {
196      row.ValueChanged -= OnRowValueChanged;
197      row.ValuesChanged -= OnRowValuesChanged;
198      row.DataRowChanged -= OnDataRowChanged;
[1285]199
200      rowToRowEntry.Remove(row);
201      rowEntries.RemoveAll(delegate(RowEntry rowEntry) { return rowEntry.DataRow == row; });
202
203      UpdateLayout();
[1240]204    }
205
206    #endregion
207
[1249]208    public void ZoomToFullView() {
[1285]209      SetClipX(-0.1, model.MaxDataRowValues - 0.9);
[987]210
[1285]211      foreach (RowEntry rowEntry in rowEntries) {
212        IDataRow row = rowEntry.DataRow;
[1249]213
[1285]214        SetClipY(rowEntry,
215                 row.MinValue - ((row.MaxValue - row.MinValue)*0.05),
216                 row.MaxValue + ((row.MaxValue - row.MinValue)*0.05));
217      }
218
[1249]219      zoomToFullView = true;
[1285]220
221      canvasUI.Invalidate();
[987]222    }
223
[1285]224    private void SetClipX(double x1, double x2) {
225      xAxis.ClippingArea = new RectangleD(x1,
226                                          0,
227                                          x2,
228                                          XAxisHeight);
[1249]229
[1285]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);
[1249]243      }
[1285]244    }
[1249]245
[1285]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);
[981]259    }
260
[987]261    private void InitLineShapes(IDataRow row) {
[1285]262      RowEntry rowEntry = new RowEntry(row);
263      rowEntries.Add(rowEntry);
264      rowToRowEntry[row] = rowEntry;
265
[1242]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);
[1285]270          rowEntry.LinesShape.AddShape(lineShape);
[1242]271        }
[1249]272      } else {
[1242]273        for (int i = 1; i < row.Count; i++) {
[1283]274          LineShape lineShape = new LineShape(i - 1, row[i - 1], i, row[i], row.Color, row.Thickness, row.Style);
[1285]275          rowEntry.LinesShape.AddShape(lineShape);
[1242]276        }
277      }
[1249]278
[981]279      ZoomToFullView();
[697]280    }
281
[869]282    private void OnRowValueChanged(IDataRow row, double value, int index, Action action) {
[1285]283      RowEntry rowEntry = rowToRowEntry[row];
284
[1242]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);
[1285]289          rowEntry.LinesShape.AddShape(lineShape);
[1249]290        } else {
[1285]291          LineShape lineShape = rowEntry.LinesShape.GetShape(0);
292          lineShape.Y1 = value;
293          lineShape.Y2 = value;
[1242]294        }
[1249]295      } else {
[1285]296        if (index > rowEntry.LinesShape.Count + 1) {
[1242]297          throw new NotImplementedException();
298        }
[861]299
[1242]300        // new value was added
[1285]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);
[1045]304        }
[861]305
[1242]306        // not the first value
307        if (index > 0) {
[1285]308          rowEntry.LinesShape.GetShape(index - 1).Y2 = value;
[1242]309        }
310
311        // not the last value
312        if (index > 0 && index < row.Count - 1) {
[1285]313          rowEntry.LinesShape.GetShape(index).Y1 = value;
[1242]314        }
[1045]315      }
[861]316
[981]317      ZoomToFullView();
[697]318    }
319
[869]320    private void OnRowValuesChanged(IDataRow row, double[] values, int index, Action action) {
[1045]321      foreach (double value in values) {
[869]322        OnRowValueChanged(row, value, index++, action);
[1045]323      }
[861]324    }
[761]325
[1182]326    private void OnModelChanged() {
327      titleShape.Text = model.Title;
[697]328
[1240]329      canvasUI.Invalidate();
[1182]330    }
331
[697]332    #region Begin-/EndUpdate
333
[1242]334    private int beginUpdateCount;
[697]335
[861]336    public void BeginUpdate() {
[697]337      beginUpdateCount++;
338    }
339
[861]340    public void EndUpdate() {
[1045]341      if (beginUpdateCount == 0) {
[697]342        throw new InvalidOperationException("Too many EndUpdates.");
[1045]343      }
[697]344
345      beginUpdateCount--;
346
[1045]347      if (beginUpdateCount == 0) {
[1240]348        canvasUI.Invalidate();
[1045]349      }
[697]350    }
351
352    #endregion
[928]353
[1059]354    #region Zooming / Panning
355
[1249]356    private void Pan(Point startPoint, Point endPoint) {
[1285]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();
[1249]367    }
[928]368
[1249]369    private void PanEnd(Point startPoint, Point endPoint) {
[1285]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();
[1249]380    }
381
[1285]382    private static RectangleD CalcPanClippingArea(Point startPoint, Point endPoint, LinesShape linesShape) {
[1249]383      return Translate.ClippingArea(startPoint, endPoint, linesShape.ClippingArea, linesShape.Viewport);
384    }
385
386    private void SetClippingArea(Rectangle rectangle) {
[1285]387      foreach (RowEntry rowEntry in rowEntries) {
388        RectangleD clippingArea = Transform.ToWorld(rectangle, rowEntry.LinesShape.Viewport, rowEntry.LinesShape.ClippingArea);
[1249]389
[1285]390        SetClipX(clippingArea.X1, clippingArea.X1);
391        SetClipY(rowEntry, clippingArea.Y1, clippingArea.Y2);
392      }
393
[1249]394      userInteractionShape.RemoveShape(rectangleShape);
[1285]395      canvasUI.Invalidate();
[1249]396    }
397
398    private void DrawRectangle(Rectangle rectangle) {
399      rectangleShape.Rectangle = Transform.ToWorld(rectangle, userInteractionShape.Viewport, userInteractionShape.ClippingArea);
400      canvasUI.Invalidate();
401    }
402
[1059]403    private void canvasUI1_KeyDown(object sender, KeyEventArgs e) {
[1285]404//      if (e.KeyCode == Keys.Back && clippingAreaHistory.Count > 1) {
405//        clippingAreaHistory.Pop();
406//
407//        RectangleD clippingArea = clippingAreaHistory.Peek();
408//
409//        SetLineClippingArea(clippingArea, false);
410//      }
[1059]411    }
412
[928]413    private void canvasUI1_MouseDown(object sender, MouseEventArgs e) {
[1058]414      Focus();
[1249]415
[1237]416      if (e.Button == MouseButtons.Right) {
[1242]417        contextMenuStrip1.Show(PointToScreen(e.Location));
[1249]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;
[1187]434        }
435      }
[928]436    }
437
[1249]438    private void canvasUI_MouseMove(object sender, MouseEventArgs e) {
439      if (mouseEventListener != null) {
440        mouseEventListener.MouseMove(sender, e);
441      }
442    }
[1244]443
[1249]444    private void canvasUI_MouseUp(object sender, MouseEventArgs e) {
445      if (mouseEventListener != null) {
446        mouseEventListener.MouseUp(sender, e);
447      }
[1240]448
[1249]449      mouseEventListener = null;
[1240]450    }
451
[1058]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
[1285]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        }
[1058]462      }
463    }
464
[1249]465    #endregion
[928]466
[1285]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      }
[928]478
[1285]479      public int Count {
480        get { return shapes.Count; }
481      }
[928]482
[1285]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    }
[684]515  }
[1249]516}
Note: See TracBrowser for help on using the repository browser.