Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2597: Fixed small bug when the values are not correctly rendered in the chart initially. removed unused variable limits from view.

File size: 15.3 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 || points == 0)
201        return;
202      Series.Clear();
203      var vla = VerticalLineAnnotation;
204      vla.Visible = true;
205      Annotations.Clear();
206      Annotations.Add(vla);
207      double axisMin, axisMax, axisInterval;
208      // calculate X-axis interval
209      ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval);
210      var axis = ChartAreas[0].AxisX;
211      axis.Minimum = axisMin;
212      axis.Maximum = axisMax;
213      axis.Interval = axisInterval;
214
215      for (int i = 0; i < solutionList.Count; ++i) {
216        var solution = solutionList[i];
217        var series = PlotSeries(solution);
218        series.Name = Target + " " + i;
219        Series.Add(series);
220        var p = series.Points.Last();
221        vla.X = p.XValue;
222      }
223      // calculate Y-axis interval
224      double ymin = 0, ymax = 0;
225      foreach (var v in Series[0].Points.Select(x => x.YValues[0])) {
226        if (ymin > v) ymin = v;
227        if (ymax < v) ymax = v;
228      }
229      ChartUtil.CalculateAxisInterval(ymin, ymax, 5, out axisMin, out axisMax, out axisInterval);
230      axis = ChartAreas[0].AxisY;
231      axis.Minimum = axisMin;
232      axis.Maximum = axisMax;
233      axis.Interval = axisInterval;
234      ChartAreas[0].AxisX.Title = Variable + " : " + vla.X.ToString("N3", CultureInfo.CurrentCulture); // set axis titlet
235      AddStripLines(); // add strip lines
236      if (ShowLegend)
237        AddLegends();
238    }
239
240    private void UpdateInternalDataset() {
241      var variables = ProblemData.Dataset.DoubleVariables.ToList();
242      var variableValues = new List<double>[variables.Count];
243
244      if (UseMedianValues) {
245        for (int i = 0; i < variables.Count; ++i) {
246          var median = ProblemData.Dataset.GetDoubleValues(variables[i], ProblemData.TrainingIndices).Median();
247          variableValues[i] = new List<double> { median };
248        }
249      } else {
250        for (int i = 0; i < variables.Count; ++i) {
251          var variableValue = ProblemData.Dataset.GetDoubleValue(variables[i], Row);
252          variableValues[i] = new List<double> { variableValue };
253        }
254      }
255      internalDataset = new ModifiableDataset(variables, variableValues);
256    }
257
258    private double GetEstimatedValue(IRegressionSolution solution, double x) {
259      var v = internalDataset.GetDoubleValue(Variable, 0);
260      internalDataset.SetVariableValue(x, Variable, 0);
261      var y = solution.Model.GetEstimatedValues(internalDataset, new[] { 0 }).Single();
262      internalDataset.SetVariableValue(v, Variable, 0);
263      return y;
264    }
265
266    private Series PlotSeries(IRegressionSolution solution) {
267      var v = internalDataset.GetDoubleValue(variable, 0);
268      var series = new Series { ChartType = SeriesChartType.Point };
269
270      var step = (max - min) / points;
271      var axisX = ChartAreas[0].AxisX;
272      axisX.Title = Variable + " : " + v.ToString("N3", CultureInfo.CurrentCulture);
273      var axisY = ChartAreas[0].AxisY;
274      axisY.Title = Target;
275      double y;
276      // lefthand section outside of the training range
277      for (double x = axisX.Minimum; x < min; x += step) {
278        y = GetEstimatedValue(solution, x);
279        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
280      }
281      // points in the trainig range
282      for (double x = min; x < max; x += step) {
283        y = GetEstimatedValue(solution, x);
284        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2 });
285      }
286      // righthand section outside of the training range
287      for (double x = max; x < axisX.Maximum; x += step) {
288        y = GetEstimatedValue(solution, x);
289        series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
290      }
291
292      y = GetEstimatedValue(solution, v);
293      series.Points.Add(new DataPoint(v, y) { MarkerSize = 5, MarkerColor = Color.Red });
294      series.IsVisibleInLegend = true;
295
296      return series;
297    }
298
299    private void AddLegends() {
300      Legends.Clear();
301      var legend = new Legend();
302      legend.Alignment = StringAlignment.Center;
303      legend.LegendStyle = LegendStyle.Row;
304      legend.Docking = Docking.Top;
305      Legends.Add(legend);
306      foreach (var s in Series) {
307        s.Legend = legend.Name;
308      }
309    }
310
311    private void AddStripLines() {
312      var axisX = ChartAreas[0].AxisX;
313      axisX.StripLines.Clear();
314      axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = axisX.Minimum, StripWidth = min - axisX.Minimum });
315      axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = max, StripWidth = axisX.Maximum - max });
316    }
317
318    private void RegisterEvents() {
319      AnnotationPositionChanging += chart_AnnotationPositionChanging;
320      MouseMove += chart_MouseMove;
321      FormatNumber += chart_FormatNumber;
322    }
323
324    #region events
325    public event EventHandler VariableValueChanged;
326    public void OnVariableValueChanged(object sender, EventArgs args) {
327      var changed = VariableValueChanged;
328      if (changed == null) return;
329      changed(sender, args);
330    }
331
332    public event EventHandler ChartPropertyChanged;
333    public void OnChartPropertyChanged(object sender, EventArgs args) {
334      var changed = ChartPropertyChanged;
335      if (changed == null) return;
336      changed(sender, args);
337    }
338
339    private void chart_AnnotationPositionChanged(object sender, EventArgs e) {
340      var annotation = VerticalLineAnnotation;
341      var x = annotation.X;
342      internalDataset.SetVariableValue(x, Variable, 0);
343      for (int i = 0; i < solutionList.Count; ++i) {
344        var y = GetEstimatedValue(solutionList[i], x);
345        var s = Series[i];
346        var n = s.Points.Count;
347        s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 };
348      }
349      ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
350      Update();
351      OnVariableValueChanged(this, EventArgs.Empty);
352    }
353
354    private void chart_AnnotationPositionChanging(object sender, AnnotationPositionChangingEventArgs e) {
355      var step = (max - min) / points;
356      e.NewLocationX = step * (long)Math.Round(e.NewLocationX / step);
357      var axisX = ChartAreas[0].AxisX;
358      if (e.NewLocationX > axisX.Maximum)
359        e.NewLocationX = axisX.Maximum;
360      if (e.NewLocationX < axisX.Minimum)
361        e.NewLocationX = axisX.Minimum;
362      //      var x = e.NewLocationX;
363      //      internalDataset.SetVariableValue(x, Variable, 0);
364      //      for (int i = 0; i < solutionList.Count; ++i) {
365      //        var y = GetEstimatedValue(solutionList[i], x);
366      //        var s = Series[i];
367      //        var n = s.Points.Count;
368      //        s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 };
369      //      }
370      //      ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
371      //      Update();
372      //      OnVariableValueChanged(this, EventArgs.Empty);
373    }
374
375    private void chart_MouseMove(object sender, MouseEventArgs e) {
376      this.Cursor = HitTest(e.X, e.Y).ChartElementType == ChartElementType.Annotation ? Cursors.VSplit : Cursors.Default;
377    }
378
379    private void chart_FormatNumber(object sender, FormatNumberEventArgs e) {
380      if (e.ElementType == ChartElementType.AxisLabels) {
381        switch (e.Format) {
382          case "CustomAxisXFormat":
383            break;
384          case "CustomAxisYFormat":
385            var v = e.Value;
386            e.LocalizedValue = string.Format("{0,5}", v);
387            break;
388          default:
389            break;
390        }
391      }
392    }
393
394    private void GradientChart_DragDrop(object sender, DragEventArgs e) {
395      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
396      if (data != null) {
397        var solution = data as IRegressionSolution;
398        if (!Solutions.Contains(solution))
399          AddSolution(solution);
400      }
401    }
402
403    private void GradientChart_DragEnter(object sender, DragEventArgs e) {
404      if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return;
405      e.Effect = DragDropEffects.None;
406
407      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
408      var regressionSolution = data as IRegressionSolution;
409      if (regressionSolution != null) {
410        e.Effect = DragDropEffects.Copy;
411      }
412    }
413    #endregion
414  }
415}
Note: See TracBrowser for help on using the repository browser.