Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2597: Removed unused Configure method.

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