source: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/RegressionSolutionScatterPlotView.cs @ 14008

Last change on this file since 14008 was 14008, checked in by bburlacu, 6 years ago

#2594: Added chart util method to calculate the "optimal" axis interval (reducing the number of fractional digits to make it look nice). Modified the regression solution line chart and scatter plot to use the new scaling method.

File size: 12.8 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 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
21using System;
22using System.Drawing;
23using System.Linq;
24using System.Windows.Forms;
25using System.Windows.Forms.DataVisualization.Charting;
26using HeuristicLab.MainForm;
27using HeuristicLab.MainForm.WindowsForms;
28using HeuristicLab.Visualization.ChartControlsExtensions;
29
30namespace HeuristicLab.Problems.DataAnalysis.Views {
31  [View("Scatter Plot")]
32  [Content(typeof(IRegressionSolution))]
33  public partial class RegressionSolutionScatterPlotView : DataAnalysisSolutionEvaluationView {
34    private const string ALL_SERIES = "All samples";
35    private const string TRAINING_SERIES = "Training samples";
36    private const string TEST_SERIES = "Test samples";
37
38    private const int OPACITY_LEVEL = 150;
39
40    public new IRegressionSolution Content {
41      get { return (IRegressionSolution)base.Content; }
42      set { base.Content = value; }
43    }
44
45    public RegressionSolutionScatterPlotView()
46      : base() {
47      InitializeComponent();
48
49      this.chart.Series.Add(ALL_SERIES);
50      this.chart.Series[ALL_SERIES].LegendText = ALL_SERIES;
51      this.chart.Series[ALL_SERIES].ChartType = SeriesChartType.FastPoint;
52
53      this.chart.Series.Add(TRAINING_SERIES);
54      this.chart.Series[TRAINING_SERIES].LegendText = TRAINING_SERIES;
55      this.chart.Series[TRAINING_SERIES].ChartType = SeriesChartType.FastPoint;
56      this.chart.Series[TRAINING_SERIES].Points.Add(1.0);
57
58      this.chart.Series.Add(TEST_SERIES);
59      this.chart.Series[TEST_SERIES].LegendText = TEST_SERIES;
60      this.chart.Series[TEST_SERIES].ChartType = SeriesChartType.FastPoint;
61
62      this.chart.TextAntiAliasingQuality = TextAntiAliasingQuality.High;
63      this.chart.AxisViewChanged += new EventHandler<System.Windows.Forms.DataVisualization.Charting.ViewEventArgs>(chart_AxisViewChanged);
64
65      //make series colors semi transparent
66      this.chart.ApplyPaletteColors();
67      this.chart.Series[ALL_SERIES].Color = Color.FromArgb(OPACITY_LEVEL, this.chart.Series[ALL_SERIES].Color);
68      this.chart.Series[TRAINING_SERIES].Color = Color.FromArgb(OPACITY_LEVEL, this.chart.Series[TRAINING_SERIES].Color);
69      this.chart.Series[TEST_SERIES].Color = Color.FromArgb(OPACITY_LEVEL, this.chart.Series[TEST_SERIES].Color);
70
71      //change all markers to circles
72      this.chart.Series[ALL_SERIES].MarkerStyle = MarkerStyle.Circle;
73      this.chart.Series[TRAINING_SERIES].MarkerStyle = MarkerStyle.Circle;
74      this.chart.Series[TEST_SERIES].MarkerStyle = MarkerStyle.Circle;
75
76      //configure axis
77      this.chart.CustomizeAllChartAreas();
78      this.chart.ChartAreas[0].AxisX.Title = "Estimated Values";
79      this.chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
80      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
81      this.chart.ChartAreas[0].CursorX.Interval = 1;
82      this.chart.ChartAreas[0].CursorY.Interval = 1;
83
84      this.chart.ChartAreas[0].AxisY.Title = "Target Values";
85      this.chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
86      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
87      this.chart.ChartAreas[0].AxisY.IsStartedFromZero = true;
88    }
89
90    protected override void RegisterContentEvents() {
91      base.RegisterContentEvents();
92      Content.ModelChanged += new EventHandler(Content_ModelChanged);
93      Content.ProblemDataChanged += new EventHandler(Content_ProblemDataChanged);
94    }
95    protected override void DeregisterContentEvents() {
96      base.DeregisterContentEvents();
97      Content.ModelChanged -= new EventHandler(Content_ModelChanged);
98      Content.ProblemDataChanged -= new EventHandler(Content_ProblemDataChanged);
99    }
100
101
102    private void Content_ProblemDataChanged(object sender, EventArgs e) {
103      UpdateChart();
104    }
105    private void Content_ModelChanged(object sender, EventArgs e) {
106      UpdateSeries();
107    }
108
109    protected override void OnContentChanged() {
110      base.OnContentChanged();
111      UpdateChart();
112    }
113
114    private void UpdateChart() {
115      if (InvokeRequired) Invoke((Action)UpdateChart);
116      else {
117        if (Content != null) {
118          this.UpdateSeries();
119          if (!this.chart.Series.Any(s => s.Points.Count > 0))
120            this.ClearChart();
121        }
122      }
123    }
124
125    private void UpdateCursorInterval() {
126      var estimatedValues = this.chart.Series[ALL_SERIES].Points.Select(x => x.XValue).DefaultIfEmpty(1.0);
127      var targetValues = this.chart.Series[ALL_SERIES].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0);
128      double estimatedValuesRange = estimatedValues.Max() - estimatedValues.Min();
129      double targetValuesRange = targetValues.Max() - targetValues.Min();
130      double interestingValuesRange = Math.Min(Math.Max(targetValuesRange, 1.0), Math.Max(estimatedValuesRange, 1.0));
131      double digits = (int)Math.Log10(interestingValuesRange) - 3;
132      double zoomInterval = Math.Max(Math.Pow(10, digits), 10E-5);
133      this.chart.ChartAreas[0].CursorX.Interval = zoomInterval;
134      this.chart.ChartAreas[0].CursorY.Interval = zoomInterval;
135
136      this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = zoomInterval;
137      this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = zoomInterval;
138
139      this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSizeType = DateTimeIntervalType.Number;
140      this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSize = zoomInterval;
141      this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSizeType = DateTimeIntervalType.Number;
142      this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSize = zoomInterval;
143
144      if (digits < 0) {
145        this.chart.ChartAreas[0].AxisX.LabelStyle.Format = "F" + (int)Math.Abs(digits);
146        this.chart.ChartAreas[0].AxisY.LabelStyle.Format = "F" + (int)Math.Abs(digits);
147      } else {
148        this.chart.ChartAreas[0].AxisX.LabelStyle.Format = "F0";
149        this.chart.ChartAreas[0].AxisY.LabelStyle.Format = "F0";
150      }
151    }
152
153
154    private void UpdateSeries() {
155      if (InvokeRequired) Invoke((Action)UpdateSeries);
156      else {
157        string targetVariableName = Content.ProblemData.TargetVariable;
158        var dataset = Content.ProblemData.Dataset;
159        if (this.chart.Series[ALL_SERIES].Points.Count > 0)
160          this.chart.Series[ALL_SERIES].Points.DataBindXY(Content.EstimatedValues.ToArray(), "",
161            dataset.GetDoubleValues(targetVariableName).ToArray(), "");
162        if (this.chart.Series[TRAINING_SERIES].Points.Count > 0)
163          this.chart.Series[TRAINING_SERIES].Points.DataBindXY(Content.EstimatedTrainingValues.ToArray(), "",
164            dataset.GetDoubleValues(targetVariableName, Content.ProblemData.TrainingIndices).ToArray(), "");
165        if (this.chart.Series[TEST_SERIES].Points.Count > 0)
166          this.chart.Series[TEST_SERIES].Points.DataBindXY(Content.EstimatedTestValues.ToArray(), "",
167           dataset.GetDoubleValues(targetVariableName, Content.ProblemData.TestIndices).ToArray(), "");
168
169        double max = Content.EstimatedTrainingValues.Concat(Content.EstimatedTestValues.Concat(Content.EstimatedValues.Concat(dataset.GetDoubleValues(targetVariableName)))).Max();
170        double min = Content.EstimatedTrainingValues.Concat(Content.EstimatedTestValues.Concat(Content.EstimatedValues.Concat(dataset.GetDoubleValues(targetVariableName)))).Min();
171
172        double axisMin, axisMax, axisInterval;
173        ChartUtil.CalculateOptimalAxisInterval(min, max, out axisMin, out axisMax, out axisInterval);
174        this.chart.ChartAreas[0].AxisX.Maximum = axisMax;
175        this.chart.ChartAreas[0].AxisX.Minimum = axisMin;
176        this.chart.ChartAreas[0].AxisX.Interval = axisInterval;
177        this.chart.ChartAreas[0].AxisY.Maximum = axisMax;
178        this.chart.ChartAreas[0].AxisY.Minimum = axisMin;
179        this.chart.ChartAreas[0].AxisY.Interval = axisInterval;
180
181        UpdateCursorInterval();
182      }
183    }
184
185    private void ClearChart() {
186      this.chart.Series[ALL_SERIES].Points.Clear();
187      this.chart.Series[TRAINING_SERIES].Points.Clear();
188      this.chart.Series[TEST_SERIES].Points.Clear();
189    }
190
191    private void ToggleSeriesData(Series series) {
192      if (series.Points.Count > 0) {  //checks if series is shown
193        if (this.chart.Series.Any(s => s != series && s.Points.Count > 0)) {
194          series.Points.Clear();
195        }
196      } else if (Content != null) {
197        string targetVariableName = Content.ProblemData.TargetVariable;
198
199        double[] predictedValues = null;
200        double[] targetValues = null;
201        switch (series.Name) {
202          case ALL_SERIES:
203            predictedValues = Content.EstimatedValues.ToArray();
204            targetValues = Content.ProblemData.Dataset.GetDoubleValues(targetVariableName).ToArray();
205            break;
206          case TRAINING_SERIES:
207            predictedValues = Content.EstimatedTrainingValues.ToArray();
208            targetValues = Content.ProblemData.Dataset.GetDoubleValues(targetVariableName, Content.ProblemData.TrainingIndices).ToArray();
209            break;
210          case TEST_SERIES:
211            predictedValues = Content.EstimatedTestValues.ToArray();
212            targetValues = Content.ProblemData.Dataset.GetDoubleValues(targetVariableName, Content.ProblemData.TestIndices).ToArray();
213            break;
214        }
215        if (predictedValues.Length == targetValues.Length)
216          series.Points.DataBindXY(predictedValues, "", targetValues, "");
217        this.chart.Legends[series.Legend].ForeColor = Color.Black;
218        UpdateCursorInterval();
219      }
220    }
221
222    private void chart_MouseDown(object sender, MouseEventArgs e) {
223      HitTestResult result = chart.HitTest(e.X, e.Y);
224      if (result.ChartElementType == ChartElementType.LegendItem) {
225        this.ToggleSeriesData(result.Series);
226      }
227    }
228
229    private void chart_MouseMove(object sender, MouseEventArgs e) {
230      HitTestResult result = chart.HitTest(e.X, e.Y);
231      if (result.ChartElementType == ChartElementType.LegendItem)
232        this.Cursor = Cursors.Hand;
233      else
234        this.Cursor = Cursors.Default;
235    }
236
237    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
238      this.chart.ChartAreas[0].AxisX.ScaleView.Size = e.NewSize;
239      this.chart.ChartAreas[0].AxisY.ScaleView.Size = e.NewSize;
240    }
241
242    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
243      e.LegendItems[0].Cells[1].ForeColor = this.chart.Series[ALL_SERIES].Points.Count == 0 ? Color.Gray : Color.Black;
244      e.LegendItems[1].Cells[1].ForeColor = this.chart.Series[TRAINING_SERIES].Points.Count == 0 ? Color.Gray : Color.Black;
245      e.LegendItems[2].Cells[1].ForeColor = this.chart.Series[TEST_SERIES].Points.Count == 0 ? Color.Gray : Color.Black;
246    }
247
248    private void chart_PostPaint(object sender, ChartPaintEventArgs e) {
249      var chartArea = e.ChartElement as ChartArea;
250      if (chartArea != null) {
251        ChartGraphics chartGraphics = e.ChartGraphics;
252        using (Pen p = new Pen(Color.DarkGray)) {
253          double xmin = chartArea.AxisX.ScaleView.ViewMinimum;
254          double xmax = chartArea.AxisX.ScaleView.ViewMaximum;
255          double ymin = chartArea.AxisY.ScaleView.ViewMinimum;
256          double ymax = chartArea.AxisY.ScaleView.ViewMaximum;
257
258          if (xmin > ymax || ymin > xmax) return;
259
260          PointF start = PointF.Empty;
261          start.X = (float)chartGraphics.GetPositionFromAxis(chartArea.Name, chartArea.AxisX.AxisName, Math.Max(xmin, ymin));
262          start.Y = (float)chartGraphics.GetPositionFromAxis(chartArea.Name, chartArea.AxisY.AxisName, Math.Max(xmin, ymin));
263          PointF end = PointF.Empty;
264          end.X = (float)chartGraphics.GetPositionFromAxis(chartArea.Name, chartArea.AxisX.AxisName, Math.Min(xmax, ymax));
265          end.Y = (float)chartGraphics.GetPositionFromAxis(chartArea.Name, chartArea.AxisY.AxisName, Math.Min(xmax, ymax));
266
267          chartGraphics.Graphics.DrawLine(p, chartGraphics.GetAbsolutePoint(start), chartGraphics.GetAbsolutePoint(end));
268        }
269      }
270    }
271  }
272}
Note: See TracBrowser for help on using the repository browser.