source: stable/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/ConfidenceRegressionSolutionLineChartView.cs @ 16435

Last change on this file since 16435 was 15584, checked in by swagner, 23 months ago

#2640: Updated year of copyrights in license headers on stable

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