Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 13831 was 13831, checked in by pfleck, 8 years ago

#2597:

  • merged recent trunk changes for GetUsedVariablesForPrediction method.
  • Merged chart from RegressionSolutionGradientView and existing GradientChart.
  • Used the GradientChart in the RegressionSolutionGradientView.
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.Globalization;
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 : UserControl {
33    private ModifiableDataset sharedFixedVariables; // used for syncronising variable values between charts
34    private ModifiableDataset internalDataset; // used to cache values and speed up calculations
35
36    public bool ShowLegend {
37      get { return chart.Legends[0].Enabled; }
38      set { chart.Legends[0].Enabled = value; }
39    }
40    public bool ShowXAxisLabel {
41      get { return chart.ChartAreas[0].AxisX.Enabled == AxisEnabled.True; }
42      set { chart.ChartAreas[0].AxisX.Enabled = value ? AxisEnabled.True : AxisEnabled.False; }
43    }
44    public bool ShowYAxisLabel {
45      get { return chart.ChartAreas[0].AxisY.Enabled == AxisEnabled.True; }
46      set { chart.ChartAreas[0].AxisY.Enabled = value ? AxisEnabled.True : AxisEnabled.False; }
47    }
48    public bool ShowCursor {
49      get { return chart.Annotations[0].Visible; }
50      set { chart.Annotations[0].Visible = value; }
51    }
52
53    private int xAxisTicks = 5;
54    public int XAxisTicks {
55      get { return xAxisTicks; }
56      set { if (xAxisTicks != value) { xAxisTicks = value; UpdateChart(); } }
57    }
58    private int yAxisTicks = 5;
59    public int YXAxisTicks {
60      get { return yAxisTicks; }
61      set { if (yAxisTicks != value) { yAxisTicks = value; UpdateChart(); } }
62    }
63
64    private double trainingMin = double.MinValue;
65    public double TrainingMin {
66      get { return trainingMin; }
67      set { if (!value.IsAlmost(trainingMin)) { trainingMin = value; UpdateChart(); } }
68    }
69    private double trainingMax = double.MaxValue;
70    public double TrainingMax {
71      get { return trainingMax; }
72      set { if (!value.IsAlmost(trainingMax)) { trainingMax = value; UpdateChart(); } }
73    }
74
75    private int drawingSteps = 1000;
76    public int DrawingSteps {
77      get { return drawingSteps; }
78      set { if (value != drawingSteps) { drawingSteps = value; UpdateChart(); } }
79    }
80
81    private string freeVariable;
82    public string FreeVariable {
83      get { return freeVariable; }
84      set {
85        if (value == freeVariable) return;
86        if (solutions.Any(s => !s.ProblemData.Dataset.DoubleVariables.Contains(value))) {
87          throw new ArgumentException("Variable does not exist in the ProblemData of the Solutions.");
88        }
89        freeVariable = value;
90        RecalculateInternalDataset();
91        UpdateChart();
92      }
93    }
94
95    private bool updateChartAutomatically = false;
96    public bool UpdateChartAutomatically {
97      get { return updateChartAutomatically; }
98      set { updateChartAutomatically = value; if (updateChartAutomatically) UpdateChart(); }
99    }
100
101    private readonly List<IRegressionSolution> solutions = new List<IRegressionSolution>();
102    public IEnumerable<IRegressionSolution> Solutions {
103      get { return solutions; }
104    }
105
106    private VerticalLineAnnotation VerticalLineAnnotation {
107      get { return (VerticalLineAnnotation)chart.Annotations.SingleOrDefault(x => x is VerticalLineAnnotation); }
108    }
109
110    public GradientChart() {
111      InitializeComponent();
112    }
113
114    public void Configure(IEnumerable<IRegressionSolution> solutions, ModifiableDataset sharedFixedVariables, string freeVariable, int drawingSteps) {
115      if (!SolutionsCompatible(solutions))
116        throw new ArgumentException("Solutions are not compatible with the problem data.");
117      this.solutions.Clear();
118      this.solutions.AddRange(solutions);
119      this.freeVariable = freeVariable;
120      this.drawingSteps = drawingSteps;
121
122      // add an event such that whenever a value is changed in the shared dataset,
123      // this change is reflected in the internal dataset (where the value becomes a whole column)
124      if (this.sharedFixedVariables != null)
125        this.sharedFixedVariables.ItemChanged -= sharedFixedVariables_ItemChanged;
126      this.sharedFixedVariables = sharedFixedVariables;
127      this.sharedFixedVariables.ItemChanged += sharedFixedVariables_ItemChanged;
128
129      trainingMin = solutions.Select(s => s.ProblemData.Dataset.GetDoubleValues(freeVariable, s.ProblemData.TrainingIndices).Min()).Max();
130      trainingMax = solutions.Select(s => s.ProblemData.Dataset.GetDoubleValues(freeVariable, s.ProblemData.TrainingIndices).Max()).Min();
131
132      RecalculateInternalDataset();
133    }
134
135    private void sharedFixedVariables_ItemChanged(object o, EventArgs<int, int> e) {
136      var sender = (ModifiableDataset)o;
137      var variables = sharedFixedVariables.DoubleVariables.ToList();
138      var rowIndex = e.Value;
139      var columnIndex = e.Value2;
140
141      var variableName = variables[columnIndex];
142      if (variableName == FreeVariable) return;
143      var v = sender.GetDoubleValue(variableName, rowIndex);
144      var values = new List<double>(Enumerable.Repeat(v, DrawingSteps));
145      internalDataset.ReplaceVariable(variableName, values);
146
147      if (UpdateChartAutomatically)
148        UpdateChart();
149    }
150
151    private void RecalculateInternalDataset() {
152      // we expand the range in order to get nice tick intervals on the x axis
153      double xmin, xmax, xinterval;
154      ChartUtil.CalculateAxisInterval(trainingMin, trainingMax, XAxisTicks, out xmin, out xmax, out xinterval);
155      double step = (xmax - xmin) / drawingSteps;
156
157      var xvalues = new List<double>();
158      for (int i = 0; i < drawingSteps; i++)
159        xvalues.Add(xmin + i * step);
160
161      var variables = sharedFixedVariables.DoubleVariables.ToList();
162      internalDataset = new ModifiableDataset(variables,
163        variables.Select(x => x == FreeVariable
164          ? xvalues
165          : Enumerable.Repeat(sharedFixedVariables.GetDoubleValue(x, 0), xvalues.Count).ToList()
166        )
167      );
168    }
169
170    public void UpdateChart() {
171      // throw exceptions?
172      if (sharedFixedVariables == null || solutions == null || !solutions.Any())
173        return;
174      if (trainingMin.IsAlmost(trainingMax) || trainingMin > trainingMax || drawingSteps == 0)
175        return;
176
177      // Set cursor
178      var defaultValue = sharedFixedVariables.GetDoubleValue(freeVariable, 0);
179      VerticalLineAnnotation.X = defaultValue;
180
181      // Calculate X-axis interval
182      double axisMin, axisMax, axisInterval;
183      ChartUtil.CalculateAxisInterval(trainingMin, trainingMax, XAxisTicks, out axisMin, out axisMax, out axisInterval);
184      var axis = chart.ChartAreas[0].AxisX;
185      axis.Minimum = axisMin;
186      axis.Maximum = axisMax;
187      axis.Interval = axisInterval;
188
189      // Create series
190      chart.Series.Clear();
191      for (int i = 0; i < solutions.Count; ++i) {
192        var solution = solutions[i];
193        Series confidenceIntervalPlotSeries;
194        var series = CreateSeries(solution, out confidenceIntervalPlotSeries);
195        series.Name = Solutions.First().ProblemData.TargetVariable + " " + i;
196        if (confidenceIntervalPlotSeries != null)
197          chart.Series.Add(confidenceIntervalPlotSeries);
198        chart.Series.Add(series);
199      }
200      //// calculate Y-axis interval
201      //double ymin = 0, ymax = 0;
202      //foreach (var v in chart.Series[0].Points.Select(x => x.YValues[0])) {
203      //  if (ymin > v) ymin = v;
204      //  if (ymax < v) ymax = v;
205      //}
206      //ChartUtil.CalculateAxisInterval(ymin, ymax, YXAxisTicks, out axisMin, out axisMax, out axisInterval);
207      //axis = chart.ChartAreas[0].AxisY;
208      //axis.Minimum = axisMin;
209      //axis.Maximum = axisMax;
210      //axis.Interval = axisInterval;
211
212      // set axis title
213      chart.ChartAreas[0].AxisX.Title = FreeVariable + " : " + defaultValue.ToString("N3", CultureInfo.CurrentCulture);
214
215      UpdateStripLines();
216    }
217
218    private Series CreateSeries(IRegressionSolution solution, out Series confidenceIntervalPlotSeries) {
219      var series = new Series {
220        ChartType = SeriesChartType.Line
221      };
222
223      var xvalues = internalDataset.GetDoubleValues(FreeVariable).ToList();
224      var yvalues = solution.Model.GetEstimatedValues(internalDataset, Enumerable.Range(0, internalDataset.Rows)).ToList();
225      series.Points.DataBindXY(xvalues, yvalues);
226
227      var confidenceBoundSolution = solution as IConfidenceBoundRegressionSolution;
228      if (confidenceBoundSolution != null) {
229        var variances = confidenceBoundSolution.Model.GetEstimatedVariances(internalDataset, Enumerable.Range(0, internalDataset.Rows)).ToList();
230
231        var lower = yvalues.Zip(variances, (m, s2) => m - 1.96 * Math.Sqrt(s2)).ToList();
232        var upper = yvalues.Zip(variances, (m, s2) => m + 1.96 * Math.Sqrt(s2)).ToList();
233
234        confidenceIntervalPlotSeries = new Series {
235          ChartType = SeriesChartType.Range,
236          YValuesPerPoint = 2
237        };
238        confidenceIntervalPlotSeries.Points.DataBindXY(xvalues, lower, upper);
239      } else {
240        confidenceIntervalPlotSeries = null;
241      }
242
243      return series;
244    }
245
246    public void AddSolution(IRegressionSolution solution) {
247      if (!SolutionsCompatible(solutions.Concat(new[] { solution })))
248        throw new ArgumentException("The solution is not compatible with the problem data.");
249      if (solutions.Contains(solution)) return;
250      solutions.Add(solution);
251      UpdateChart();
252    }
253    public void RemoveSolution(IRegressionSolution solution) {
254      bool removed = solutions.Remove(solution);
255      if (removed)
256        UpdateChart();
257    }
258
259    private static bool SolutionsCompatible(IEnumerable<IRegressionSolution> solutions) {
260      foreach (var solution1 in solutions) {
261        var variables1 = solution1.ProblemData.Dataset.DoubleVariables;
262        foreach (var solution2 in solutions) {
263          if (solution1 == solution2)
264            continue;
265          var variables2 = solution2.ProblemData.Dataset.DoubleVariables;
266          if (!variables1.All(variables2.Contains))
267            return false;
268        }
269      }
270      return true;
271    }
272
273    private void UpdateStripLines() {
274      var axisX = chart.ChartAreas[0].AxisX;
275      var lowerStripLine = axisX.StripLines[0];
276      var upperStripLine = axisX.StripLines[1];
277
278      lowerStripLine.IntervalOffset = axisX.Minimum;
279      lowerStripLine.StripWidth = trainingMin - axisX.Minimum;
280
281      upperStripLine.IntervalOffset = trainingMax;
282      upperStripLine.StripWidth = axisX.Maximum - trainingMax;
283    }
284
285    #region events
286    public event EventHandler VariableValueChanged;
287    public void OnVariableValueChanged(object sender, EventArgs args) {
288      var changed = VariableValueChanged;
289      if (changed == null) return;
290      changed(sender, args);
291    }
292
293    private void chart_AnnotationPositionChanged(object sender, EventArgs e) {
294      var annotation = VerticalLineAnnotation;
295      var x = annotation.X;
296      sharedFixedVariables.SetVariableValue(x, FreeVariable, 0);
297
298      chart.ChartAreas[0].AxisX.Title = FreeVariable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
299      chart.Update();
300
301      OnVariableValueChanged(this, EventArgs.Empty);
302    }
303
304    private void chart_AnnotationPositionChanging(object sender, AnnotationPositionChangingEventArgs e) {
305      //var step = (trainingMax - trainingMin) / drawingSteps;
306      //e.NewLocationX = step * (long)Math.Round(e.NewLocationX / step);
307      //var axisX = chart.ChartAreas[0].AxisX;
308      //if (e.NewLocationX > axisX.Maximum)
309      //  e.NewLocationX = axisX.Maximum;
310      //if (e.NewLocationX < axisX.Minimum)
311      //  e.NewLocationX = axisX.Minimum;
312
313      var annotation = VerticalLineAnnotation;
314      var x = annotation.X;
315      sharedFixedVariables.SetVariableValue(x, FreeVariable, 0);
316
317      chart.ChartAreas[0].AxisX.Title = FreeVariable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
318      chart.Update();
319
320      OnVariableValueChanged(this, EventArgs.Empty);
321    }
322
323    private void chart_MouseMove(object sender, MouseEventArgs e) {
324      chart.Cursor = chart.HitTest(e.X, e.Y).ChartElementType == ChartElementType.Annotation ? Cursors.VSplit : Cursors.Default;
325    }
326
327    private void chart_FormatNumber(object sender, FormatNumberEventArgs e) {
328      if (e.ElementType == ChartElementType.AxisLabels) {
329        switch (e.Format) {
330          case "CustomAxisXFormat":
331            break;
332          case "CustomAxisYFormat":
333            var v = e.Value;
334            e.LocalizedValue = string.Format("{0,5}", v);
335            break;
336          default:
337            break;
338        }
339      }
340    }
341
342    private void GradientChart_DragDrop(object sender, DragEventArgs e) {
343      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
344      if (data != null) {
345        var solution = data as IRegressionSolution;
346        if (!Solutions.Contains(solution))
347          AddSolution(solution);
348      }
349    }
350    private void GradientChart_DragEnter(object sender, DragEventArgs e) {
351      if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return;
352      e.Effect = DragDropEffects.None;
353
354      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
355      var regressionSolution = data as IRegressionSolution;
356      if (regressionSolution != null) {
357        e.Effect = DragDropEffects.Copy;
358      }
359    }
360    #endregion
361  }
362}
Note: See TracBrowser for help on using the repository browser.