Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2597: Added flags for controlling the display of legend, axis labels, or vertical cursor on the chart.

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