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

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

#2597: Fix text annotations.

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