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

Last change on this file since 13780 was 13780, checked in by bburlacu, 5 years ago

#2597: Initial commit implementing gradient view and chart.

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