source: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/ConfidenceRegressionSolutionLineChartView.cs @ 14185

Last change on this file since 14185 was 14185, checked in by swagner, 5 years ago

#2526: Updated year of copyrights in license headers

File size: 16.0 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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;
28using HeuristicLab.Problems.DataAnalysis;
29using HeuristicLab.Problems.DataAnalysis.Views;
30
31namespace HeuristicLab.Algorithms.DataAnalysis.Views {
32  [View("Line Chart (95% confidence interval)")]
33  [Content(typeof(IConfidenceRegressionSolution))]
34  public partial class ConfidenceRegressionSolutionLineChartView : DataAnalysisSolutionEvaluationView {
35    private const string TARGETVARIABLE_SERIES_NAME = "Target Variable";
36    private const string ESTIMATEDVALUES_TRAINING_SERIES_NAME = "Estimated Values (training)";
37    private const string ESTIMATEDVALUES_TEST_SERIES_NAME = "Estimated Values (test)";
38    private const string ESTIMATEDVALUES_ALL_SERIES_NAME = "Estimated Values (all samples)";
39
40    public new IConfidenceRegressionSolution Content {
41      get { return (IConfidenceRegressionSolution)base.Content; }
42      set { base.Content = value; }
43    }
44
45    public ConfidenceRegressionSolutionLineChartView()
46      : base() {
47      InitializeComponent();
48      //configure axis
49      this.chart.CustomizeAllChartAreas();
50      this.chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
51      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
52      this.chart.ChartAreas[0].AxisX.IsStartedFromZero = true;
53      this.chart.ChartAreas[0].CursorX.Interval = 1;
54
55      this.chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
56      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
57      this.chart.ChartAreas[0].CursorY.Interval = 0;
58    }
59
60    private void RedrawChart() {
61      this.chart.Series.Clear();
62      if (Content != null) {
63        this.chart.ChartAreas[0].AxisX.Minimum = 0;
64        this.chart.ChartAreas[0].AxisX.Maximum = Content.ProblemData.Dataset.Rows - 1;
65
66        // training series
67        this.chart.Series.Add(ESTIMATEDVALUES_TRAINING_SERIES_NAME);
68        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].LegendText = ESTIMATEDVALUES_TRAINING_SERIES_NAME;
69        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].ChartType = SeriesChartType.Range;
70        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].EmptyPointStyle.Color = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Color;
71        var mean = Content.EstimatedTrainingValues.ToArray();
72        var s2 = Content.EstimatedTrainingVariances.ToArray();
73        var lower = mean.Zip(s2, GetLowerConfBound).ToArray();
74        var upper = mean.Zip(s2, GetUpperConfBound).ToArray();
75        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.DataBindXY(Content.ProblemData.TrainingIndices.ToArray(), lower, upper);
76        this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME]);
77        this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Tag = Content;
78
79        // test series
80        this.chart.Series.Add(ESTIMATEDVALUES_TEST_SERIES_NAME);
81        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].LegendText = ESTIMATEDVALUES_TEST_SERIES_NAME;
82        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].ChartType = SeriesChartType.Range;
83
84        mean = Content.EstimatedTestValues.ToArray();
85        s2 = Content.EstimatedTestVariances.ToArray();
86        lower = mean.Zip(s2, GetLowerConfBound).ToArray();
87        upper = mean.Zip(s2, GetUpperConfBound).ToArray();
88        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Points.DataBindXY(Content.ProblemData.TestIndices.ToArray(), lower, upper);
89        this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME]);
90        this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Tag = Content;
91
92        // series of remaining points
93        int[] allIndices = Enumerable.Range(0, Content.ProblemData.Dataset.Rows).Except(Content.ProblemData.TrainingIndices).Except(Content.ProblemData.TestIndices).ToArray();
94        mean = Content.EstimatedValues.ToArray();
95        s2 = Content.EstimatedVariances.ToArray();
96        lower = mean.Zip(s2, GetLowerConfBound).ToArray();
97        upper = mean.Zip(s2, GetUpperConfBound).ToArray();
98        List<double> allLower = allIndices.Select(index => lower[index]).ToList();
99        List<double> allUpper = allIndices.Select(index => upper[index]).ToList();
100        this.chart.Series.Add(ESTIMATEDVALUES_ALL_SERIES_NAME);
101        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].LegendText = ESTIMATEDVALUES_ALL_SERIES_NAME;
102        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].ChartType = SeriesChartType.Range;
103        if (allIndices.Count() > 0) {
104          this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Points.DataBindXY(allIndices, allLower, allUpper);
105          this.InsertEmptyPoints(this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME]);
106        }
107        this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Tag = Content;
108
109        // target
110        this.chart.Series.Add(TARGETVARIABLE_SERIES_NAME);
111        this.chart.Series[TARGETVARIABLE_SERIES_NAME].LegendText = Content.ProblemData.TargetVariable;
112        this.chart.Series[TARGETVARIABLE_SERIES_NAME].ChartType = SeriesChartType.FastLine;
113        this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.DataBindXY(Enumerable.Range(0, Content.ProblemData.Dataset.Rows).ToArray(),
114          Content.ProblemData.Dataset.GetDoubleValues(Content.ProblemData.TargetVariable).ToArray());
115
116        this.ToggleSeriesData(this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME]);
117
118
119        // the series have been added in different order than in the normal line chart
120        // --> adapt coloring;
121        chart.ApplyPaletteColors();
122        this.chart.Palette = ChartColorPalette.None;
123        var s0Color = chart.Series[0].Color;
124        var s1Color = chart.Series[1].Color;
125        var s2Color = chart.Series[2].Color;
126        var s3Color = chart.Series[3].Color;
127        this.chart.PaletteCustomColors = new Color[] { s1Color, s2Color, s3Color, s0Color };
128
129        UpdateCursorInterval();
130        this.UpdateStripLines();
131      }
132    }
133
134    private void InsertEmptyPoints(Series series) {
135      int i = 0;
136      while (i < series.Points.Count - 1) {
137        if (series.Points[i].IsEmpty) {
138          ++i;
139          continue;
140        }
141
142        var p1 = series.Points[i];
143        var p2 = series.Points[i + 1];
144        // check for consecutive indices
145        if ((int)p2.XValue - (int)p1.XValue != 1) {
146          // insert an empty point between p1 and p2 so that the line will be invisible (transparent)
147          var p = new DataPoint((int)((p1.XValue + p2.XValue) / 2), new double[] { 0.0, 0.0 }) { IsEmpty = true };
148          // insert
149          series.Points.Insert(i + 1, p);
150        }
151        ++i;
152      }
153    }
154
155    private void UpdateCursorInterval() {
156      var estimatedValues = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0);
157      var targetValues = this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0);
158      double estimatedValuesRange = estimatedValues.Max() - estimatedValues.Min();
159      double targetValuesRange = targetValues.Max() - targetValues.Min();
160      double interestingValuesRange = Math.Min(Math.Max(targetValuesRange, 1.0), Math.Max(estimatedValuesRange, 1.0));
161      double digits = (int)Math.Log10(interestingValuesRange) - 3;
162      double yZoomInterval = Math.Max(Math.Pow(10, digits), 10E-5);
163      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
164    }
165
166    #region events
167    protected override void RegisterContentEvents() {
168      base.RegisterContentEvents();
169      Content.ModelChanged += new EventHandler(Content_ModelChanged);
170      Content.ProblemDataChanged += new EventHandler(Content_ProblemDataChanged);
171    }
172    protected override void DeregisterContentEvents() {
173      base.DeregisterContentEvents();
174      Content.ModelChanged -= new EventHandler(Content_ModelChanged);
175      Content.ProblemDataChanged -= new EventHandler(Content_ProblemDataChanged);
176    }
177
178    protected override void OnContentChanged() {
179      base.OnContentChanged();
180      RedrawChart();
181    }
182    private void Content_ProblemDataChanged(object sender, EventArgs e) {
183      RedrawChart();
184    }
185    private void Content_ModelChanged(object sender, EventArgs e) {
186      RedrawChart();
187    }
188
189
190
191    private void Chart_MouseDoubleClick(object sender, MouseEventArgs e) {
192      HitTestResult result = chart.HitTest(e.X, e.Y);
193      if (result.ChartArea != null && (result.ChartElementType == ChartElementType.PlottingArea ||
194                                       result.ChartElementType == ChartElementType.Gridlines) ||
195                                       result.ChartElementType == ChartElementType.StripLines) {
196        foreach (var axis in result.ChartArea.Axes)
197          axis.ScaleView.ZoomReset(int.MaxValue);
198      }
199    }
200    #endregion
201
202    private void UpdateStripLines() {
203      this.chart.ChartAreas[0].AxisX.StripLines.Clear();
204
205      int[] attr = new int[Content.ProblemData.Dataset.Rows + 1]; // add a virtual last row that is again empty to simplify loop further down
206      foreach (var row in Content.ProblemData.TrainingIndices) {
207        attr[row] += 1;
208      }
209      foreach (var row in Content.ProblemData.TestIndices) {
210        attr[row] += 2;
211      }
212      int start = 0;
213      int curAttr = attr[start];
214      for (int row = 0; row < attr.Length; row++) {
215        if (attr[row] != curAttr) {
216          switch (curAttr) {
217            case 0: break;
218            case 1:
219              this.CreateAndAddStripLine("Training", start, row, Color.FromArgb(40, Color.Green), Color.Transparent);
220              break;
221            case 2:
222              this.CreateAndAddStripLine("Test", start, row, Color.FromArgb(40, Color.Red), Color.Transparent);
223              break;
224            case 3:
225              this.CreateAndAddStripLine("Training and Test", start, row, Color.FromArgb(40, Color.Green), Color.FromArgb(40, Color.Red), ChartHatchStyle.WideUpwardDiagonal);
226              break;
227            default:
228              // should not happen
229              break;
230          }
231          curAttr = attr[row];
232          start = row;
233        }
234      }
235    }
236
237    private void CreateAndAddStripLine(string title, int start, int end, Color color, Color secondColor, ChartHatchStyle hatchStyle = ChartHatchStyle.None) {
238      StripLine stripLine = new StripLine();
239      stripLine.BackColor = color;
240      stripLine.BackSecondaryColor = secondColor;
241      stripLine.BackHatchStyle = hatchStyle;
242      stripLine.Text = title;
243      stripLine.Font = new Font("Times New Roman", 12, FontStyle.Bold);
244      // strip range is [start .. end] inclusive, but we evaluate [start..end[ (end is exclusive)
245      // the strip should be by one longer (starting at start - 0.5 and ending at end + 0.5)
246      stripLine.StripWidth = end - start;
247      stripLine.IntervalOffset = start - 0.5; // start slightly to the left of the first point to clearly indicate the first point in the partition
248      this.chart.ChartAreas[0].AxisX.StripLines.Add(stripLine);
249    }
250
251    private void ToggleSeriesData(Series series) {
252      if (series.Points.Count > 0) {  //checks if series is shown
253        if (this.chart.Series.Any(s => s != series && s.Points.Count > 0)) {
254          ClearPointsQuick(series.Points);
255        }
256      } else if (Content != null) {
257
258        IEnumerable<int> indices = null;
259        IEnumerable<double> mean = null;
260        IEnumerable<double> s2 = null;
261        double[] lower = null;
262        double[] upper = null;
263        switch (series.Name) {
264          case ESTIMATEDVALUES_ALL_SERIES_NAME:
265            indices = Enumerable.Range(0, Content.ProblemData.Dataset.Rows).Except(Content.ProblemData.TrainingIndices).Except(Content.ProblemData.TestIndices).ToArray();
266            mean = Content.EstimatedValues.ToArray();
267            s2 = Content.EstimatedVariances.ToArray();
268            lower = mean.Zip(s2, GetLowerConfBound).ToArray();
269            upper = mean.Zip(s2, GetUpperConfBound).ToArray();
270            lower = indices.Select(index => lower[index]).ToArray();
271            upper = indices.Select(index => upper[index]).ToArray();
272            break;
273          case ESTIMATEDVALUES_TRAINING_SERIES_NAME:
274            indices = Content.ProblemData.TrainingIndices.ToArray();
275            mean = Content.EstimatedTrainingValues.ToArray();
276            s2 = Content.EstimatedTrainingVariances.ToArray();
277            lower = mean.Zip(s2, GetLowerConfBound).ToArray();
278            upper = mean.Zip(s2, GetUpperConfBound).ToArray();
279            break;
280          case ESTIMATEDVALUES_TEST_SERIES_NAME:
281            indices = Content.ProblemData.TestIndices.ToArray();
282            mean = Content.EstimatedTestValues.ToArray();
283            s2 = Content.EstimatedTestVariances.ToArray();
284            lower = mean.Zip(s2, GetLowerConfBound).ToArray();
285            upper = mean.Zip(s2, GetUpperConfBound).ToArray();
286            break;
287        }
288        if (indices.Count() > 0) {
289          series.Points.DataBindXY(indices, lower, upper);
290          this.InsertEmptyPoints(series);
291          chart.Legends[series.Legend].ForeColor = Color.Black;
292          UpdateCursorInterval();
293          chart.Refresh();
294        }
295      }
296    }
297
298    private double GetLowerConfBound(double m, double s) {
299      return m - 1.96 * Math.Sqrt(s);
300    }
301
302
303    private double GetUpperConfBound(double m, double s) {
304      return m + 1.96 * Math.Sqrt(s);
305    }
306
307    // workaround as per http://stackoverflow.com/questions/5744930/datapointcollection-clear-performance
308    private static void ClearPointsQuick(DataPointCollection points) {
309      points.SuspendUpdates();
310      while (points.Count > 0)
311        points.RemoveAt(points.Count - 1);
312      points.ResumeUpdates();
313    }
314
315    private void chart_MouseMove(object sender, MouseEventArgs e) {
316      HitTestResult result = chart.HitTest(e.X, e.Y);
317      if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME)
318        Cursor = Cursors.Hand;
319      else
320        Cursor = Cursors.Default;
321    }
322    private void chart_MouseDown(object sender, MouseEventArgs e) {
323      HitTestResult result = chart.HitTest(e.X, e.Y);
324      if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME) {
325        ToggleSeriesData(result.Series);
326      }
327    }
328
329    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
330      if (chart.Series.Count != 4) return;
331      e.LegendItems[0].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_TRAINING_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
332      e.LegendItems[1].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
333      e.LegendItems[2].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
334      e.LegendItems[3].Cells[1].ForeColor = this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
335    }
336  }
337}
Note: See TracBrowser for help on using the repository browser.