Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2597: Added RegressionSolutionTargetResponseGradientView and updated GradientChart.

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