source: branches/symbreg-factors-2650/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/RegressionSolutionTargetResponseGradientView.cs @ 14277

Last change on this file since 14277 was 14277, checked in by gkronber, 5 years ago

#2650: merged r14245:14273 from trunk to branch (fixing conflicts in RegressionSolutionTargetResponseGradientView)

File size: 22.4 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;
24using System.Collections.Generic;
25using System.Drawing;
26using System.Globalization;
27using System.Linq;
28using System.Threading.Tasks;
29using System.Windows.Forms;
30using HeuristicLab.Common;
31using HeuristicLab.MainForm;
32using HeuristicLab.Visualization.ChartControlsExtensions;
33
34namespace HeuristicLab.Problems.DataAnalysis.Views {
35  [View("Target Response Gradients")]
36  [Content(typeof(IRegressionSolution))]
37  public partial class RegressionSolutionTargetResponseGradientView : DataAnalysisSolutionEvaluationView {
38    private readonly Dictionary<string, IGradientChart> gradientCharts;
39    private readonly Dictionary<string, DensityChart> densityCharts;
40    private readonly Dictionary<string, Panel> groupingPanels;
41    private ModifiableDataset sharedFixedVariables;
42
43    private const int Points = 200;
44    private int MaxColumns = 4;
45
46    private IEnumerable<string> VisibleVariables {
47      get {
48        foreach (ListViewItem item in variableListView.CheckedItems)
49          yield return item.Text;
50      }
51    }
52    private IEnumerable<IGradientChart> VisibleGradientCharts {
53      get { return VisibleVariables.Select(v => gradientCharts[v]); }
54    }
55    private IEnumerable<DensityChart> VisibleDensityCharts {
56      get { return VisibleVariables.Select(v => densityCharts[v]); }
57    }
58    private IEnumerable<Panel> VisibleChartsPanels {
59      get { return VisibleVariables.Select(v => groupingPanels[v]); }
60    }
61
62    public RegressionSolutionTargetResponseGradientView() {
63      InitializeComponent();
64      gradientCharts = new Dictionary<string, IGradientChart>();
65      densityCharts = new Dictionary<string, DensityChart>();
66      groupingPanels = new Dictionary<string, Panel>();
67
68      limitView.Content = new DoubleLimit(0, 1);
69      limitView.Content.ValueChanged += limit_ValueChanged;
70
71      densityComboBox.SelectedIndex = 1; // select Training
72
73      // Avoid additional horizontal scrollbar
74      var vertScrollWidth = SystemInformation.VerticalScrollBarWidth;
75      scrollPanel.Padding = new Padding(0, 0, vertScrollWidth, 0);
76      scrollPanel.AutoScroll = true;
77    }
78
79    public new IRegressionSolution Content {
80      get { return (IRegressionSolution)base.Content; }
81      set { base.Content = value; }
82    }
83
84    protected override void RegisterContentEvents() {
85      base.RegisterContentEvents();
86      Content.ModelChanged += solution_ModelChanged;
87    }
88
89    protected override void DeregisterContentEvents() {
90      Content.ModelChanged -= solution_ModelChanged;
91      base.DeregisterContentEvents();
92    }
93
94    protected override void OnContentChanged() {
95      base.OnContentChanged();
96      if (Content == null) return;
97      var problemData = Content.ProblemData;
98
99      // Init Y-axis range
100      double min = double.MaxValue, max = double.MinValue;
101      var trainingTarget = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
102      foreach (var t in trainingTarget) {
103        if (t < min) min = t;
104        if (t > max) max = t;
105      }
106      double range = max - min;
107      const double scale = 1.0 / 3.0;
108      double axisMin, axisMax, axisInterval;
109      ChartUtil.CalculateAxisInterval(min - scale * range, max + scale * range, 5, out axisMin, out axisMax, out axisInterval);
110      automaticYAxisCheckBox.Checked = false;
111      limitView.ReadOnly = false;
112      limitView.Content.Lower = axisMin;
113      limitView.Content.Upper = axisMax;
114
115      // create dataset
116      var allowedInputVariables = Content.ProblemData.AllowedInputVariables;
117      var doubleVariables = allowedInputVariables.Where(problemData.Dataset.VariableHasType<double>);
118      var doubleVariableValues = (IEnumerable<IList>)doubleVariables.Select(x => new List<double> { problemData.Dataset.GetDoubleValues(x, problemData.TrainingIndices).Median() });
119
120      var factorVariables = allowedInputVariables.Where(problemData.Dataset.VariableHasType<string>);
121      var factorVariableValues = (IEnumerable<IList>)factorVariables.Select(x => new List<string> {
122        problemData.Dataset.GetStringValues(x, problemData.TrainingIndices)
123        .GroupBy(val => val).OrderByDescending(g => g.Count()).First().Key // most frequent value
124      });
125
126      if (sharedFixedVariables != null)
127        sharedFixedVariables.ItemChanged += SharedFixedVariables_ItemChanged;
128
129      sharedFixedVariables = new ModifiableDataset(doubleVariables.Concat(factorVariables), doubleVariableValues.Concat(factorVariableValues));
130
131
132      // create controls
133      gradientCharts.Clear();
134      densityCharts.Clear();
135      groupingPanels.Clear();
136      foreach (var variableName in doubleVariables) {
137        var gradientChart = CreateGradientChart(variableName, sharedFixedVariables);
138        gradientCharts.Add(variableName, gradientChart);
139
140        var densityChart = new DensityChart() {
141          Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right,
142          Margin = Padding.Empty,
143          Height = 12,
144          Visible = false,
145          Top = (int)(gradientChart.Height * 0.1),
146        };
147        densityCharts.Add(variableName, densityChart);
148
149        gradientChart.ZoomChanged += (o, e) => {
150          var gradient = (GradientChart)o;
151          var density = densityCharts[gradient.FreeVariable];
152          density.Visible = densityComboBox.SelectedIndex != 0 && !gradient.IsZoomed;
153          if (density.Visible)
154            UpdateDensityChart(density, gradient.FreeVariable);
155        };
156        gradientChart.SizeChanged += (o, e) => {
157          var gradient = (GradientChart)o;
158          var density = densityCharts[gradient.FreeVariable];
159          density.Top = (int)(gradient.Height * 0.1);
160        };
161
162        // Initially, the inner plot areas are not initialized for hidden charts (scollpanel, ...)
163        // This event handler listens for the paint event once (where everything is already initialized) to do some manual layouting.
164        gradientChart.ChartPostPaint += OnGradientChartPostPaint;
165
166        var panel = new Panel() {
167          Dock = DockStyle.Fill,
168          Margin = Padding.Empty,
169          BackColor = Color.White
170        };
171
172        panel.Controls.Add(densityChart);
173        panel.Controls.Add(gradientChart);
174        groupingPanels.Add(variableName, panel);
175      }
176      foreach (var variableName in factorVariables) {
177        var gradientChart = CreateFactorGradientChart(variableName, sharedFixedVariables);
178        gradientCharts.Add(variableName, gradientChart);
179
180        var densityChart = new DensityChart() {
181          Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right,
182          Margin = Padding.Empty,
183          Height = 12,
184          Visible = false,
185          Top = (int)(gradientChart.Height * 0.1),
186        };
187        densityCharts.Add(variableName, densityChart);
188        gradientChart.ZoomChanged += (o, e) => {
189          var gradient = (FactorGradientChart)o;
190          var density = densityCharts[gradient.FreeVariable];
191          density.Visible = densityComboBox.SelectedIndex != 0 && !gradient.IsZoomed;
192          if (density.Visible)
193            UpdateDensityChart(density, gradient.FreeVariable);
194        };
195        gradientChart.SizeChanged += (o, e) => {
196          var gradient = (FactorGradientChart)o;
197          var density = densityCharts[gradient.FreeVariable];
198          density.Top = (int)(gradient.Height * 0.1);
199        };
200
201        // Initially, the inner plot areas are not initialized for hidden charts (scollpanel, ...)
202        // This event handler listens for the paint event once (where everything is already initialized) to do some manual layouting.
203        gradientChart.ChartPostPaint += OnFactorGradientChartPostPaint;
204
205        var panel = new Panel() {
206          Dock = DockStyle.Fill,
207          Margin = Padding.Empty,
208          BackColor = Color.White
209        };
210
211        panel.Controls.Add(densityChart);
212        panel.Controls.Add(gradientChart);
213        groupingPanels.Add(variableName, panel);
214      }
215      // update variable list
216      variableListView.ItemChecked -= variableListView_ItemChecked;
217      variableListView.Items.Clear();
218      foreach (var variable in allowedInputVariables)
219        variableListView.Items.Add(key: variable, text: variable, imageIndex: 0);
220
221      foreach (var variable in Content.Model.VariablesUsedForPrediction)
222        variableListView.Items[variable].Checked = true;
223      variableListView.ItemChecked += variableListView_ItemChecked;
224
225      sharedFixedVariables.ItemChanged += SharedFixedVariables_ItemChanged;
226
227      RecalculateAndRelayoutCharts();
228    }
229
230    private void SharedFixedVariables_ItemChanged(object sender, EventArgs<int, int> e) {
231      double yValue = Content.Model.GetEstimatedValues(sharedFixedVariables, new[] { 0 }).Single();
232      string title = Content.ProblemData.TargetVariable + ": " + yValue.ToString("G5", CultureInfo.CurrentCulture);
233      foreach (var chart in gradientCharts.Values) {
234        if (!string.IsNullOrEmpty(chart.YAxisTitle)) { // only show title for first column in grid
235          chart.YAxisTitle = title;
236        }
237      }
238    }
239
240
241    private void OnGradientChartPostPaint(object o, EventArgs e) {
242      var gradient = (GradientChart)o;
243      var density = densityCharts[gradient.FreeVariable];
244
245      density.Width = gradient.Width;
246
247      var gcPlotPosition = gradient.InnerPlotPosition;
248      density.Left = (int)(gcPlotPosition.X / 100.0 * gradient.Width);
249      density.Width = (int)(gcPlotPosition.Width / 100.0 * gradient.Width);
250      gradient.UpdateTitlePosition();
251
252      // removed after succesful layouting due to performance reasons
253      if (gcPlotPosition.Width != 0)
254        gradient.ChartPostPaint -= OnGradientChartPostPaint;
255    }
256
257    private void OnFactorGradientChartPostPaint(object o, EventArgs e) {
258      var gradient = (FactorGradientChart)o;
259      var density = densityCharts[gradient.FreeVariable];
260
261      density.Width = gradient.Width;
262
263      var gcPlotPosition = gradient.InnerPlotPosition;
264      density.Left = (int)(gcPlotPosition.X / 100.0 * gradient.Width);
265      density.Width = (int)(gcPlotPosition.Width / 100.0 * gradient.Width);
266      gradient.UpdateTitlePosition();
267
268      // removed after succesful layouting due to performance reasons
269      if (gcPlotPosition.Width != 0)
270        gradient.ChartPostPaint -= OnFactorGradientChartPostPaint;
271    }
272
273    private async void RecalculateAndRelayoutCharts() {
274      foreach (var variable in VisibleVariables) {
275        var gradientChart = gradientCharts[variable];
276        await gradientChart.RecalculateAsync(false, false);
277      }
278      gradientChartTableLayout.SuspendLayout();
279      SetupYAxis();
280      ReOrderControls();
281      SetStyles();
282      gradientChartTableLayout.ResumeLayout();
283      gradientChartTableLayout.Refresh();
284      foreach (var variable in VisibleVariables) {
285        DensityChart densityChart;
286        if (densityCharts.TryGetValue(variable, out densityChart)) {
287          UpdateDensityChart(densityChart, variable);
288        }
289      }
290    }
291    private GradientChart CreateGradientChart(string variableName, ModifiableDataset sharedFixedVariables) {
292      var gradientChart = new GradientChart {
293        Dock = DockStyle.Fill,
294        Margin = Padding.Empty,
295        ShowLegend = false,
296        ShowCursor = true,
297        ShowConfigButton = false,
298        YAxisTicks = 5,
299      };
300      gradientChart.VariableValueChanged += async (o, e) => {
301        var recalculations = VisibleGradientCharts
302          .Except(new[] { (IGradientChart)o })
303          .Select(async chart => {
304            await chart.RecalculateAsync(updateOnFinish: false, resetYAxis: false);
305          }).ToList();
306        await Task.WhenAll(recalculations);
307
308        if (recalculations.All(t => t.IsCompleted))
309          SetupYAxis();
310      };
311      gradientChart.Configure(new[] { Content }, sharedFixedVariables, variableName, Points);
312      gradientChart.SolutionAdded += gradientChart_SolutionAdded;
313      gradientChart.SolutionRemoved += gradientChart_SolutionRemoved;
314      return gradientChart;
315    }
316    private FactorGradientChart CreateFactorGradientChart(string variableName, ModifiableDataset sharedFixedVariables) {
317      var gradientChart = new FactorGradientChart {
318        Dock = DockStyle.Fill,
319        Margin = Padding.Empty,
320        ShowLegend = false,
321        ShowCursor = true,
322        YAxisTicks = 5,
323      };
324      gradientChart.VariableValueChanged += async (o, e) => {
325        var recalculations = VisibleGradientCharts
326          .Except(new[] { (FactorGradientChart)o })
327          .Select(async chart => {
328            await chart.RecalculateAsync(updateOnFinish: false, resetYAxis: false);
329          }).ToList();
330        await Task.WhenAll(recalculations);
331
332        if (recalculations.All(t => t.IsCompleted))
333          SetupYAxis();
334      };
335      var variableValues = Content.ProblemData.Dataset.GetStringValues(variableName).Distinct().OrderBy(n => n).ToList();
336      gradientChart.Configure(new[] { Content }, sharedFixedVariables, variableName, variableValues);
337      gradientChart.SolutionAdded += gradientChart_SolutionAdded;
338      gradientChart.SolutionRemoved += gradientChart_SolutionRemoved;
339      return gradientChart;
340    }
341    private void SetupYAxis() {
342      double axisMin, axisMax;
343      if (automaticYAxisCheckBox.Checked) {
344        double min = double.MaxValue, max = double.MinValue;
345        foreach (var chart in VisibleGradientCharts) {
346          if (chart.YMin < min) min = chart.YMin;
347          if (chart.YMax > max) max = chart.YMax;
348        }
349
350        double axisInterval;
351        ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval);
352      } else {
353        axisMin = limitView.Content.Lower;
354        axisMax = limitView.Content.Upper;
355      }
356
357      foreach (var chart in VisibleGradientCharts) {
358        chart.FixedYAxisMin = axisMin;
359        chart.FixedYAxisMax = axisMax;
360      }
361    }
362
363    // reorder chart controls so that they always appear in the same order as in the list view
364    // the table layout containing the controls should be suspended before calling this method
365    private void ReOrderControls() {
366      var tl = gradientChartTableLayout;
367      tl.Controls.Clear();
368      int row = 0, column = 0;
369      double yValue = Content.Model.GetEstimatedValues(sharedFixedVariables, new[] { 0 }).Single();
370      string title = Content.ProblemData.TargetVariable + ": " + yValue.ToString("G5", CultureInfo.CurrentCulture);
371
372      foreach (var v in VisibleVariables) {
373        var chartsPanel = groupingPanels[v];
374        tl.Controls.Add(chartsPanel, column, row);
375
376        var chart = gradientCharts[v];
377        chart.YAxisTitle = column == 0 ? title : string.Empty;
378        column++;
379
380        if (column == MaxColumns) {
381          row++;
382          column = 0;
383        }
384      }
385    }
386
387    private void SetStyles() {
388      var tl = gradientChartTableLayout;
389      tl.RowStyles.Clear();
390      tl.ColumnStyles.Clear();
391      int numVariables = VisibleVariables.Count();
392      if (numVariables == 0)
393        return;
394
395      // set column styles
396      tl.ColumnCount = Math.Min(numVariables, MaxColumns);
397      for (int c = 0; c < tl.ColumnCount; c++)
398        tl.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100.0f / tl.ColumnCount));
399
400      // set row styles
401      tl.RowCount = (int)Math.Ceiling((double)numVariables / tl.ColumnCount);
402      var columnWidth = tl.Width / tl.ColumnCount; // assume all columns have the same width
403      var rowHeight = (int)(0.8 * columnWidth);
404      for (int r = 0; r < tl.RowCount; r++)
405        tl.RowStyles.Add(new RowStyle(SizeType.Absolute, rowHeight));
406    }
407
408    private async void gradientChart_SolutionAdded(object sender, EventArgs<IRegressionSolution> e) {
409      var solution = e.Value;
410      foreach (var chart in gradientCharts.Values) {
411        if (sender == chart) continue;
412        await chart.AddSolutionAsync(solution);
413      }
414    }
415
416    private async void gradientChart_SolutionRemoved(object sender, EventArgs<IRegressionSolution> e) {
417      var solution = e.Value;
418      foreach (var chart in gradientCharts.Values) {
419        if (sender == chart) continue;
420        await chart.RemoveSolutionAsync(solution);
421      }
422    }
423
424    private async void variableListView_ItemChecked(object sender, ItemCheckedEventArgs e) {
425      var item = e.Item;
426      var variable = item.Text;
427      var gradientChart = gradientCharts[variable];
428      var chartsPanel = groupingPanels[variable];
429      var tl = gradientChartTableLayout;
430
431      tl.SuspendLayout();
432      if (item.Checked) {
433        tl.Controls.Add(chartsPanel);
434        await gradientChart.RecalculateAsync(false, false);
435      } else {
436        tl.Controls.Remove(chartsPanel);
437      }
438
439      if (tl.Controls.Count > 0) {
440        SetupYAxis();
441        ReOrderControls();
442        SetStyles();
443      }
444      tl.ResumeLayout();
445      tl.Refresh();
446      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty);
447    }
448
449    private void automaticYAxisCheckBox_CheckedChanged(object sender, EventArgs e) {
450      limitView.ReadOnly = automaticYAxisCheckBox.Checked;
451      SetupYAxis();
452      gradientChartTableLayout.Refresh();
453      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty); // necessary to realign the density plots
454    }
455
456    private void limit_ValueChanged(object sender, EventArgs e) {
457      if (automaticYAxisCheckBox.Checked)
458        return;
459      SetupYAxis();
460      gradientChartTableLayout.Refresh();
461      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty); // necessary to realign the density plots
462    }
463
464    private void densityComboBox_SelectedIndexChanged(object sender, EventArgs e) {
465      if (Content == null)
466        return;
467
468      int si = densityComboBox.SelectedIndex;
469      if (si == 0) {
470        foreach (var densityChart in densityCharts.Values)
471          densityChart.Visible = false;
472      } else {
473        var indices = GetDensityIndices(si).ToList();
474
475        foreach (var entry in densityCharts) {
476          var variableName = entry.Key;
477          var densityChart = entry.Value;
478          if (!VisibleVariables.Contains(variableName) || gradientCharts[variableName].IsZoomed)
479            continue;
480
481          UpdateDensityChart(densityChart, variableName, indices);
482        }
483      }
484    }
485    private IEnumerable<int> GetDensityIndices(int selectedIndex) {
486      var problemData = Content.ProblemData;
487      return
488        selectedIndex == 1 ? problemData.TrainingIndices :
489        selectedIndex == 2 ? problemData.TestIndices :
490        problemData.AllIndices;
491    }
492    private void UpdateDensityChart(DensityChart densityChart, string variable, IList<int> indices = null) {
493      if (densityComboBox.SelectedIndex == 0)
494        return;
495      if (indices == null) {
496        indices = GetDensityIndices(densityComboBox.SelectedIndex).ToList();
497      }
498      if (Content.ProblemData.Dataset.VariableHasType<double>(variable)) {
499        var data = Content.ProblemData.Dataset.GetDoubleValues(variable, indices).ToList();
500        var gradientChart = gradientCharts[variable] as GradientChart;
501        if (gradientChart != null) {
502          var min = gradientChart.FixedXAxisMin;
503          var max = gradientChart.FixedXAxisMax;
504          var buckets = gradientChart.DrawingSteps;
505          if (min.HasValue && max.HasValue) {
506            densityChart.UpdateChart(data, min.Value, max.Value, buckets);
507            densityChart.Width = gradientChart.Width;
508
509            var gcPlotPosition = gradientChart.InnerPlotPosition;
510            densityChart.Left = (int)(gcPlotPosition.X / 100.0 * gradientChart.Width);
511            densityChart.Width = (int)(gcPlotPosition.Width / 100.0 * gradientChart.Width);
512
513            densityChart.Visible = true;
514          }
515          gradientChart.UpdateTitlePosition();
516        }
517      } else if (Content.ProblemData.Dataset.VariableHasType<string>(variable)) {
518        var data = Content.ProblemData.Dataset.GetStringValues(variable).ToList();
519        var gradientChart = gradientCharts[variable] as FactorGradientChart;
520        if (gradientChart != null) {
521          densityChart.UpdateChart(data);
522          densityChart.Width = gradientChart.Width;
523
524          var gcPlotPosition = gradientChart.InnerPlotPosition;
525          densityChart.Left = (int)(gcPlotPosition.X / 100.0 * gradientChart.Width);
526          densityChart.Width = (int)(gcPlotPosition.Width / 100.0 * gradientChart.Width);
527
528          densityChart.Visible = true;
529
530          gradientChart.UpdateTitlePosition();
531        }
532      }
533    }
534
535    private void columnsNumericUpDown_ValueChanged(object sender, EventArgs e) {
536      MaxColumns = (int)columnsNumericUpDown.Value;
537      int columns = Math.Min(VisibleVariables.Count(), MaxColumns);
538      if (columns > 0) {
539        var tl = gradientChartTableLayout;
540        MaxColumns = columns;
541        tl.SuspendLayout();
542        ReOrderControls();
543        SetStyles();
544        tl.ResumeLayout();
545        tl.Refresh();
546        densityComboBox_SelectedIndexChanged(this, EventArgs.Empty);
547      }
548    }
549
550    private async void solution_ModelChanged(object sender, EventArgs e) {
551      foreach (var variable in VisibleVariables) {
552        var gradientChart = gradientCharts[variable];
553        var densityChart = densityCharts[variable];
554        // recalculate and refresh
555        await gradientChart.RecalculateAsync(false, false);
556        gradientChart.Refresh();
557        UpdateDensityChart(densityChart, variable);
558      }
559    }
560  }
561}
Note: See TracBrowser for help on using the repository browser.