Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.RegressionSolutionGradientView/HeuristicLab.Problems.DataAnalysis.Views/3.4/GradientChart.cs @ 13825

Last change on this file since 13825 was 13825, checked in by bburlacu, 8 years ago

#2597: Fix small bug showing the vertical annotation.

File size: 14.9 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
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Globalization;
26using System.Linq;
27using System.Windows.Forms;
28using System.Windows.Forms.DataVisualization.Charting;
29using HeuristicLab.Common;
30using HeuristicLab.Visualization.ChartControlsExtensions;
31
32namespace HeuristicLab.Problems.DataAnalysis.Views {
33  public partial class GradientChart : EnhancedChart {
34    private ModifiableDataset dataset;
35
36    public bool ShowLegend { get; set; }
37    public bool ShowXAxisLabel { get; set; }
38    public bool ShowYAxisLabel { get; set; }
39    public bool ShowCursor { get; set; }
40
41    private bool useMedianValues;
42    public bool UseMedianValues {
43      get { return useMedianValues; }
44      set {
45        if (value == useMedianValues) return;
46        useMedianValues = value;
47        OnChartPropertyChanged(this, EventArgs.Empty);
48        UpdateChart();
49      }
50    }
51
52    private int row;
53    public int Row {
54      get { return row; }
55      set {
56        if (row == value) return;
57        row = value;
58        OnChartPropertyChanged(this, EventArgs.Empty);
59        UpdateChart();
60      }
61    }
62
63    private double min;
64    public double Min {
65      get { return min; }
66      set {
67        if (value.IsAlmost(min)) return;
68        min = value;
69        OnChartPropertyChanged(this, EventArgs.Empty);
70        UpdateChart();
71      }
72    }
73
74    private double max;
75    public double Max {
76      get { return max; }
77      set {
78        if (value.IsAlmost(max)) return;
79        max = value;
80        OnChartPropertyChanged(this, EventArgs.Empty);
81        UpdateChart();
82      }
83    }
84
85    private int points;
86    public int Points {
87      get { return points; }
88      set {
89        if (value == points) return;
90        points = value;
91        OnChartPropertyChanged(this, EventArgs.Empty);
92        UpdateChart();
93      }
94    }
95
96    private IRegressionProblemData problemData;
97    public IRegressionProblemData ProblemData {
98      get { return problemData; }
99      set {
100        if (!SolutionsCompatibleWithProblemData(value, solutionList))
101          throw new ArgumentException("The problem data provided does not contain all the variables required by the solutions.");
102        problemData = value;
103        UpdateDataset();
104        UpdateChart();
105      }
106    }
107
108    public string Target {
109      get { return Solutions.First().ProblemData.TargetVariable; }
110    }
111
112    private string variable;
113    public string Variable {
114      get { return variable; }
115      set {
116        if (variable == value) return;
117        if (!ProblemData.Dataset.DoubleVariables.Contains(value))
118          throw new ArgumentException("The variable must be present in the problem dataset.");
119        OnChartPropertyChanged(this, EventArgs.Empty);
120        variable = value;
121        var values = ProblemData.Dataset.GetReadOnlyDoubleValues(variable);
122        min = values.Min();
123        max = values.Max();
124        UpdateChart();
125      }
126    }
127
128    private List<IRegressionSolution> solutionList;
129    public IEnumerable<IRegressionSolution> Solutions {
130      get { return solutionList; }
131      set {
132        if (!value.Any())
133          throw new ArgumentException("At least one solution must be provided.");
134        if (SolutionsCompatibleWithProblemData(problemData, value))
135          solutionList = new List<IRegressionSolution>(value);
136        else
137          throw new ArgumentException("The provided solution collection is not compatible with the existing problem data.");
138        UpdateChart();
139      }
140    }
141
142    public VerticalLineAnnotation VerticalLineAnnotation {
143      get { return (VerticalLineAnnotation)Annotations.SingleOrDefault(x => x is VerticalLineAnnotation); }
144    }
145
146    public GradientChart() {
147      InitializeComponent();
148      RegisterEvents();
149    }
150
151    public void AddSolution(IRegressionSolution solution) {
152      if (!SolutionsCompatibleWithProblemData(problemData, new[] { solution })) {
153        throw new ArgumentException("The solution is not compatible with the problem data.");
154      }
155      solutionList.Add(solution);
156      UpdateChart();
157    }
158
159    public void RemoveSolution(IRegressionSolution solution) {
160      var removed = solutionList.RemoveAll(x => x == solution);
161      if (removed > 0)
162        UpdateChart();
163    }
164
165    private static bool SolutionsCompatibleWithProblemData(IRegressionProblemData pd, IEnumerable<IRegressionSolution> solutions) {
166      if (pd == null || !solutions.Any()) return true;
167      if (solutions.Any(x => x.ProblemData.TargetVariable != pd.TargetVariable)) return false;
168      var variables = new HashSet<string>(pd.Dataset.DoubleVariables);
169      return solutions.SelectMany(x => x.ProblemData.Dataset.DoubleVariables).All(variables.Contains);
170    }
171
172
173    public void Configure(IEnumerable<IRegressionSolution> solutions, IRegressionProblemData pd, double min, double max, int points) {
174      if (!SolutionsCompatibleWithProblemData(pd, solutions))
175        throw new ArgumentException("Solutions are not compatible with the problem data.");
176      this.solutionList = new List<IRegressionSolution>(solutions);
177      this.problemData = pd;
178      this.variable = pd.Dataset.DoubleVariables.First();
179      this.min = min;
180      this.max = max;
181      this.points = points;
182
183      UpdateDataset();
184      UpdateChart();
185    }
186
187    public void Configure(IEnumerable<IRegressionSolution> solutions, IRegressionProblemData pd, ModifiableDataset dataset, string variable, double min, double max, int points) {
188      if (!SolutionsCompatibleWithProblemData(pd, solutions))
189        throw new ArgumentException("Solutions are not compatible with the problem data.");
190      this.solutionList = new List<IRegressionSolution>(solutions);
191      this.problemData = pd;
192      this.variable = variable;
193      this.dataset = dataset;
194      this.min = min;
195      this.max = max;
196      this.points = points;
197    }
198
199    public void UpdateChart() {
200      // throw exceptions?
201      if (dataset == null || solutionList == null || !solutionList.Any())
202        return;
203      if (min.IsAlmost(max) || min > max || points == 0)
204        return;
205      Series.Clear();
206
207      var defaultValue = dataset.GetDoubleValue(variable, 0);
208      var vla = VerticalLineAnnotation;
209      vla.X = defaultValue;
210      vla.Visible = ShowCursor;
211      Annotations.Clear();
212      Annotations.Add(vla);
213
214      double axisMin, axisMax, axisInterval;
215      // calculate X-axis interval
216      ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval);
217      var axis = ChartAreas[0].AxisX;
218      axis.Minimum = axisMin;
219      axis.Maximum = axisMax;
220      axis.Interval = axisInterval;
221
222      for (int i = 0; i < solutionList.Count; ++i) {
223        var solution = solutionList[i];
224        var series = PlotSeries(solution);
225        series.Name = Target + " " + i;
226        Series.Add(series);
227      }
228      // calculate Y-axis interval
229      double ymin = 0, ymax = 0;
230      foreach (var v in Series[0].Points.Select(x => x.YValues[0])) {
231        if (ymin > v) ymin = v;
232        if (ymax < v) ymax = v;
233      }
234      ChartUtil.CalculateAxisInterval(ymin, ymax, 5, out axisMin, out axisMax, out axisInterval);
235      axis = ChartAreas[0].AxisY;
236      axis.Minimum = axisMin;
237      axis.Maximum = axisMax;
238      axis.Interval = axisInterval;
239
240      if (ShowXAxisLabel) {
241        ChartAreas[0].AxisX.Title = Variable + " : " + defaultValue.ToString("N3", CultureInfo.CurrentCulture); // set axis title
242      }
243
244      AddStripLines(); // add strip lines
245      if (ShowLegend)
246        AddLegends();
247    }
248
249    private void UpdateDataset() {
250      var variables = ProblemData.Dataset.DoubleVariables.ToList();
251      var variableValues = new List<double>[variables.Count];
252
253      if (UseMedianValues) {
254        for (int i = 0; i < variables.Count; ++i) {
255          var median = ProblemData.Dataset.GetDoubleValues(variables[i], ProblemData.TrainingIndices).Median();
256          variableValues[i] = new List<double> { median };
257        }
258      } else {
259        for (int i = 0; i < variables.Count; ++i) {
260          var variableValue = ProblemData.Dataset.GetDoubleValue(variables[i], Row);
261          variableValues[i] = new List<double> { variableValue };
262        }
263      }
264      dataset = new ModifiableDataset(variables, variableValues);
265    }
266
267    private double GetEstimatedValue(IRegressionSolution solution, double x) {
268      var v = dataset.GetDoubleValue(Variable, 0);
269      dataset.SetVariableValue(x, Variable, 0);
270      var y = solution.Model.GetEstimatedValues(dataset, new[] { 0 }).Single();
271      dataset.SetVariableValue(v, Variable, 0);
272      return y;
273    }
274
275    private Series PlotSeries(IRegressionSolution solution) {
276      var v = dataset.GetDoubleValue(variable, 0);
277      var series = new Series { ChartType = SeriesChartType.Point };
278
279      var step = (max - min) / points;
280      var axisX = ChartAreas[0].AxisX;
281      if (ShowXAxisLabel) {
282        axisX.Title = Variable + " : " + v.ToString("N3", CultureInfo.CurrentCulture);
283      }
284      var axisY = ChartAreas[0].AxisY;
285      if (ShowYAxisLabel) { axisY.Title = Target; }
286      double y;
287      // lefthand section outside of the training range
288      for (double x = axisX.Minimum; x < min; x += step) {
289        y = GetEstimatedValue(solution, x);
290        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
291      }
292      // points in the trainig range
293      for (double x = min; x < max; x += step) {
294        y = GetEstimatedValue(solution, x);
295        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2 });
296      }
297      // righthand section outside of the training range
298      for (double x = max; x < axisX.Maximum; x += step) {
299        y = GetEstimatedValue(solution, x);
300        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
301      }
302
303      if (ShowCursor) {
304        y = GetEstimatedValue(solution, v);
305        series.Points.Add(new DataPoint(v, y) { MarkerSize = 5, MarkerColor = Color.Red });
306      }
307      if (ShowLegend) {
308        series.IsVisibleInLegend = true;
309      }
310
311      return series;
312    }
313
314    private void AddLegends() {
315      Legends.Clear();
316      var legend = new Legend();
317      legend.Alignment = StringAlignment.Center;
318      legend.LegendStyle = LegendStyle.Row;
319      legend.Docking = Docking.Top;
320      Legends.Add(legend);
321      foreach (var s in Series) {
322        s.Legend = legend.Name;
323      }
324    }
325
326    private void AddStripLines() {
327      var axisX = ChartAreas[0].AxisX;
328      axisX.StripLines.Clear();
329      axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = axisX.Minimum, StripWidth = min - axisX.Minimum });
330      axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = max, StripWidth = axisX.Maximum - max });
331    }
332
333    private void RegisterEvents() {
334      AnnotationPositionChanging += chart_AnnotationPositionChanging;
335      MouseMove += chart_MouseMove;
336      FormatNumber += chart_FormatNumber;
337    }
338
339    #region events
340    public event EventHandler VariableValueChanged;
341    public void OnVariableValueChanged(object sender, EventArgs args) {
342      var changed = VariableValueChanged;
343      if (changed == null) return;
344      changed(sender, args);
345    }
346
347    public event EventHandler ChartPropertyChanged;
348    public void OnChartPropertyChanged(object sender, EventArgs args) {
349      var changed = ChartPropertyChanged;
350      if (changed == null) return;
351      changed(sender, args);
352    }
353
354    private void chart_AnnotationPositionChanged(object sender, EventArgs e) {
355      var annotation = VerticalLineAnnotation;
356      var x = annotation.X;
357      dataset.SetVariableValue(x, Variable, 0);
358      for (int i = 0; i < solutionList.Count; ++i) {
359        var y = GetEstimatedValue(solutionList[i], x);
360        var s = Series[i];
361        var n = s.Points.Count;
362        s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 };
363      }
364      if (ShowXAxisLabel) {
365        ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
366      }
367      Update();
368      OnVariableValueChanged(this, EventArgs.Empty);
369    }
370
371    private void chart_AnnotationPositionChanging(object sender, AnnotationPositionChangingEventArgs e) {
372      var step = (max - min) / points;
373      e.NewLocationX = step * (long)Math.Round(e.NewLocationX / step);
374      var axisX = ChartAreas[0].AxisX;
375      if (e.NewLocationX > axisX.Maximum)
376        e.NewLocationX = axisX.Maximum;
377      if (e.NewLocationX < axisX.Minimum)
378        e.NewLocationX = axisX.Minimum;
379    }
380
381    private void chart_MouseMove(object sender, MouseEventArgs e) {
382      this.Cursor = HitTest(e.X, e.Y).ChartElementType == ChartElementType.Annotation ? Cursors.VSplit : Cursors.Default;
383    }
384
385    private void chart_FormatNumber(object sender, FormatNumberEventArgs e) {
386      if (e.ElementType == ChartElementType.AxisLabels) {
387        switch (e.Format) {
388          case "CustomAxisXFormat":
389            break;
390          case "CustomAxisYFormat":
391            var v = e.Value;
392            e.LocalizedValue = string.Format("{0,5}", v);
393            break;
394          default:
395            break;
396        }
397      }
398    }
399
400    private void GradientChart_DragDrop(object sender, DragEventArgs e) {
401      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
402      if (data != null) {
403        var solution = data as IRegressionSolution;
404        if (!Solutions.Contains(solution))
405          AddSolution(solution);
406      }
407    }
408
409    private void GradientChart_DragEnter(object sender, DragEventArgs e) {
410      if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return;
411      e.Effect = DragDropEffects.None;
412
413      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
414      var regressionSolution = data as IRegressionSolution;
415      if (regressionSolution != null) {
416        e.Effect = DragDropEffects.Copy;
417      }
418    }
419    #endregion
420  }
421}
Note: See TracBrowser for help on using the repository browser.