Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/RegressionSolutionLineChartView.cs @ 8485

Last change on this file since 8485 was 8485, checked in by gkronber, 12 years ago

#1890 fixed a bug in the regression solution line chart that leads to an exception when the chart is shown which is however lost in the message queue. The bug has the effect that the empty point manipulation does not work correctly for instance in solutions produced through cross-validation

File size: 13.8 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21using System;
22using System.Collections.Generic;
23using System.Drawing;
24using System.Linq;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
27using HeuristicLab.MainForm;
28
29namespace HeuristicLab.Problems.DataAnalysis.Views {
30  [View("Line Chart")]
31  [Content(typeof(IRegressionSolution))]
32  public partial class RegressionSolutionLineChartView : DataAnalysisSolutionEvaluationView {
33    private const string TARGETVARIABLE_SERIES_NAME = "Target Variable";
34    private const string ESTIMATEDVALUES_TRAINING_SERIES_NAME = "Estimated Values (training)";
35    private const string ESTIMATEDVALUES_TEST_SERIES_NAME = "Estimated Values (test)";
36    private const string ESTIMATEDVALUES_ALL_SERIES_NAME = "Estimated Values (all samples)";
37
38    public new IRegressionSolution Content {
39      get { return (IRegressionSolution)base.Content; }
40      set { base.Content = value; }
41    }
42
43    public RegressionSolutionLineChartView()
44      : base() {
45      InitializeComponent();
46      //configure axis
47      this.chart.CustomizeAllChartAreas();
48      this.chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
49      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
50      this.chart.ChartAreas[0].AxisX.IsStartedFromZero = true;
51      this.chart.ChartAreas[0].CursorX.Interval = 1;
52
53      this.chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
54      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
55      this.chart.ChartAreas[0].CursorY.Interval = 0;
56    }
57
58    private void RedrawChart() {
59      this.chart.Series.Clear();
60      if (Content != null) {
61        this.chart.ChartAreas[0].AxisX.Minimum = 0;
62        this.chart.ChartAreas[0].AxisX.Maximum = Content.ProblemData.Dataset.Rows - 1;
63
64        this.chart.Series.Add(TARGETVARIABLE_SERIES_NAME);
65        this.chart.Series[TARGETVARIABLE_SERIES_NAME].LegendText = Content.ProblemData.TargetVariable;
66        this.chart.Series[TARGETVARIABLE_SERIES_NAME].ChartType = SeriesChartType.FastLine;
67        this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.DataBindXY(Enumerable.Range(0, Content.ProblemData.Dataset.Rows).ToArray(),
68          Content.ProblemData.Dataset.GetDoubleValues(Content.ProblemData.TargetVariable).ToArray());
69        // training series
70        this.chart.Series.Add(ESTIMATEDVALUES_TRAINING_SERIES_NAME);
71        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].LegendText = ESTIMATEDVALUES_TRAINING_SERIES_NAME;
72        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].ChartType = SeriesChartType.FastLine;
73        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].EmptyPointStyle.Color = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Color;
74        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.DataBindXY(Content.ProblemData.TrainingIndices.ToArray(), Content.EstimatedTrainingValues.ToArray());
75        this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME]);
76        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Tag = Content;
77        // test series
78        this.chart.Series.Add(ESTIMATEDVALUES_TEST_SERIES_NAME);
79        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].LegendText = ESTIMATEDVALUES_TEST_SERIES_NAME;
80        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].ChartType = SeriesChartType.FastLine;
81        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Points.DataBindXY(Content.ProblemData.TestIndices.ToArray(), Content.EstimatedTestValues.ToArray());
82        this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME]);
83        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Tag = Content;
84        // series of remaining points
85        int[] allIndices = Enumerable.Range(0, Content.ProblemData.Dataset.Rows).Except(Content.ProblemData.TrainingIndices).Except(Content.ProblemData.TestIndices).ToArray();
86        var estimatedValues = Content.EstimatedValues.ToArray();
87        List<double> allEstimatedValues = allIndices.Select(index => estimatedValues[index]).ToList();
88        this.chart.Series.Add(ESTIMATEDVALUES_ALL_SERIES_NAME);
89        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].LegendText = ESTIMATEDVALUES_ALL_SERIES_NAME;
90        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].ChartType = SeriesChartType.FastLine;
91        if (allEstimatedValues.Count > 0) {
92          this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Points.DataBindXY(allIndices, allEstimatedValues);
93          this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME]);
94        }
95        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Tag = Content;
96        this.ToggleSeriesData(this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME]);
97
98        UpdateCursorInterval();
99        this.UpdateStripLines();
100      }
101    }
102
103    private void InsertEmptyPoints(Series series) {
104      int i = 0;
105      while (i < series.Points.Count - 1) {
106        if (series.Points[i].IsEmpty) {
107          ++i;
108          continue;
109        }
110
111        var p1 = series.Points[i];
112        var p2 = series.Points[i + 1];
113        // check for consecutive indices
114        if ((int)p2.XValue - (int)p1.XValue != 1) {
115          // insert an empty point between p1 and p2 so that the line will be invisible (transparent)
116          var p = new DataPoint((int)((p1.XValue + p2.XValue) / 2), 0.0) { IsEmpty = true };
117          series.Points.Insert(i + 1, p);
118        }
119        ++i;
120      }
121    }
122
123    private void UpdateCursorInterval() {
124      var estimatedValues = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0);
125      var targetValues = this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0);
126      double estimatedValuesRange = estimatedValues.Max() - estimatedValues.Min();
127      double targetValuesRange = targetValues.Max() - targetValues.Min();
128      double interestingValuesRange = Math.Min(Math.Max(targetValuesRange, 1.0), Math.Max(estimatedValuesRange, 1.0));
129      double digits = (int)Math.Log10(interestingValuesRange) - 3;
130      double yZoomInterval = Math.Max(Math.Pow(10, digits), 10E-5);
131      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
132    }
133
134    #region events
135    protected override void RegisterContentEvents() {
136      base.RegisterContentEvents();
137      Content.ModelChanged += new EventHandler(Content_ModelChanged);
138      Content.ProblemDataChanged += new EventHandler(Content_ProblemDataChanged);
139    }
140    protected override void DeregisterContentEvents() {
141      base.DeregisterContentEvents();
142      Content.ModelChanged -= new EventHandler(Content_ModelChanged);
143      Content.ProblemDataChanged -= new EventHandler(Content_ProblemDataChanged);
144    }
145
146    protected override void OnContentChanged() {
147      base.OnContentChanged();
148      RedrawChart();
149    }
150    private void Content_ProblemDataChanged(object sender, EventArgs e) {
151      RedrawChart();
152    }
153    private void Content_ModelChanged(object sender, EventArgs e) {
154      RedrawChart();
155    }
156
157
158
159    private void Chart_MouseDoubleClick(object sender, MouseEventArgs e) {
160      HitTestResult result = chart.HitTest(e.X, e.Y);
161      if (result.ChartArea != null && (result.ChartElementType == ChartElementType.PlottingArea ||
162                                       result.ChartElementType == ChartElementType.Gridlines) ||
163                                       result.ChartElementType == ChartElementType.StripLines) {
164        foreach (var axis in result.ChartArea.Axes)
165          axis.ScaleView.ZoomReset(int.MaxValue);
166      }
167    }
168    #endregion
169
170    private void UpdateStripLines() {
171      this.chart.ChartAreas[0].AxisX.StripLines.Clear();
172
173      int[] attr = new int[Content.ProblemData.Dataset.Rows + 1]; // add a virtual last row that is again empty to simplify loop further down
174      foreach (var row in Content.ProblemData.TrainingIndices) {
175        attr[row] += 1;
176      }
177      foreach (var row in Content.ProblemData.TestIndices) {
178        attr[row] += 2;
179      }
180      int start = 0;
181      int curAttr = attr[start];
182      for (int row = 0; row < attr.Length; row++) {
183        if (attr[row] != curAttr) {
184          switch (curAttr) {
185            case 0: break;
186            case 1:
187              this.CreateAndAddStripLine("Training", start, row, Color.FromArgb(40, Color.Green), Color.Transparent);
188              break;
189            case 2:
190              this.CreateAndAddStripLine("Test", start, row, Color.FromArgb(40, Color.Red), Color.Transparent);
191              break;
192            case 3:
193              this.CreateAndAddStripLine("Training and Test", start, row, Color.FromArgb(40, Color.Green), Color.FromArgb(40, Color.Red), ChartHatchStyle.WideUpwardDiagonal);
194              break;
195            default:
196              // should not happen
197              break;
198          }
199          curAttr = attr[row];
200          start = row;
201        }
202      }
203    }
204
205    private void CreateAndAddStripLine(string title, int start, int end, Color color, Color secondColor, ChartHatchStyle hatchStyle = ChartHatchStyle.None) {
206      StripLine stripLine = new StripLine();
207      stripLine.BackColor = color;
208      stripLine.BackSecondaryColor = secondColor;
209      stripLine.BackHatchStyle = hatchStyle;
210      stripLine.Text = title;
211      stripLine.Font = new Font("Times New Roman", 12, FontStyle.Bold);
212      // strip range is [start .. end] inclusive, but we evaluate [start..end[ (end is exclusive)
213      // the strip should be by one longer (starting at start - 0.5 and ending at end + 0.5)
214      stripLine.StripWidth = end - start;
215      stripLine.IntervalOffset = start - 0.5; // start slightly to the left of the first point to clearly indicate the first point in the partition
216      this.chart.ChartAreas[0].AxisX.StripLines.Add(stripLine);
217    }
218
219    private void ToggleSeriesData(Series series) {
220      if (series.Points.Count > 0) {  //checks if series is shown
221        if (this.chart.Series.Any(s => s != series && s.Points.Count > 0)) {
222          ClearPointsQuick(series.Points);
223        }
224      } else if (Content != null) {
225        string targetVariableName = Content.ProblemData.TargetVariable;
226
227        IEnumerable<int> indices = null;
228        double[] predictedValues = null;
229        switch (series.Name) {
230          case ESTIMATEDVALUES_ALL_SERIES_NAME:
231            indices = Enumerable.Range(0, Content.ProblemData.Dataset.Rows).Except(Content.ProblemData.TrainingIndices).Except(Content.ProblemData.TestIndices).ToArray();
232            var estimatedValues = Content.EstimatedValues.ToArray();
233            predictedValues = indices.Select(index => estimatedValues[index]).ToArray();
234            break;
235          case ESTIMATEDVALUES_TRAINING_SERIES_NAME:
236            indices = Content.ProblemData.TrainingIndices.ToArray();
237            predictedValues = Content.EstimatedTrainingValues.ToArray();
238            break;
239          case ESTIMATEDVALUES_TEST_SERIES_NAME:
240            indices = Content.ProblemData.TestIndices.ToArray();
241            predictedValues = Content.EstimatedTestValues.ToArray();
242            break;
243        }
244        if (predictedValues.Length > 0) {
245          series.Points.DataBindXY(indices, predictedValues);
246          this.InsertEmptyPoints(series);
247        }
248        chart.Legends[series.Legend].ForeColor = Color.Black;
249        UpdateCursorInterval();
250        chart.Refresh();
251      }
252    }
253
254    // workaround as per http://stackoverflow.com/questions/5744930/datapointcollection-clear-performance
255    private static void ClearPointsQuick(DataPointCollection points) {
256      points.SuspendUpdates();
257      while (points.Count > 0)
258        points.RemoveAt(points.Count - 1);
259      points.ResumeUpdates();
260    }
261
262    private void chart_MouseMove(object sender, MouseEventArgs e) {
263      HitTestResult result = chart.HitTest(e.X, e.Y);
264      if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME)
265        Cursor = Cursors.Hand;
266      else
267        Cursor = Cursors.Default;
268    }
269    private void chart_MouseDown(object sender, MouseEventArgs e) {
270      HitTestResult result = chart.HitTest(e.X, e.Y);
271      if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME) {
272        ToggleSeriesData(result.Series);
273      }
274    }
275
276    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
277      if (chart.Series.Count != 4) return;
278      e.LegendItems[0].Cells[1].ForeColor = this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
279      e.LegendItems[1].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
280      e.LegendItems[2].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
281      e.LegendItems[3].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
282    }
283  }
284}
Note: See TracBrowser for help on using the repository browser.