Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2597:

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