Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2839_HiveProjectManagement/HeuristicLab.Analysis.Views/3.3/HistogramControl.cs @ 16724

Last change on this file since 16724 was 16057, checked in by jkarder, 6 years ago

#2839:

File size: 8.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 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.Analysis.Statistics;
29
30namespace HeuristicLab.Analysis.Views {
31  public partial class HistogramControl : UserControl {
32    protected static readonly string SeriesName = "Histogram";
33    protected static readonly decimal bandwidthMin = 0.0000000000001m;
34    protected Dictionary<string, List<double>> points;
35    protected bool suppressUpdate;
36
37    public int NumberOfBins {
38      get { return (int)binsNumericUpDown.Value; }
39      set { binsNumericUpDown.Value = value; }
40    }
41
42    public int MinimumNumberOfBins {
43      get { return (int)binsNumericUpDown.Minimum; }
44      set { binsNumericUpDown.Minimum = value; }
45    }
46
47    public int MaximumNumberOfBins {
48      get { return (int)binsNumericUpDown.Maximum; }
49      set { binsNumericUpDown.Maximum = value; }
50    }
51
52    public int IncrementNumberOfBins {
53      get { return (int)binsNumericUpDown.Increment; }
54      set { binsNumericUpDown.Increment = value; }
55    }
56
57    public bool CalculateExactBins {
58      get { return exactCheckBox.Checked; }
59      set { exactCheckBox.Checked = value; }
60    }
61
62    public bool ShowExactCheckbox {
63      get { return exactCheckBox.Visible; }
64      set { exactCheckBox.Visible = value; }
65    }
66
67    public HistogramControl() {
68      InitializeComponent();
69      points = new Dictionary<string, List<double>>();
70    }
71
72    protected void InitNewRow(string name) {
73      if (!points.ContainsKey(name)) {
74        points.Add(name, new List<double>());
75      }
76    }
77
78    protected void InitSeries(string name) {
79      if (!chart.Series.Any(x => x.Name == name)) {
80        Series s = chart.Series.Add(name);
81        s.ChartType = SeriesChartType.Column;
82        s.BorderColor = Color.Black;
83        s.BorderWidth = 1;
84        s.BorderDashStyle = ChartDashStyle.Solid;
85      }
86    }
87
88    public void AddPoint(double point) {
89      InitNewRow(SeriesName);
90      InitSeries(SeriesName);
91      points[SeriesName].Add(point);
92      UpdateHistogram();
93    }
94
95    public void AddPoints(IEnumerable<double> points) {
96      InitNewRow(SeriesName);
97      InitSeries(SeriesName);
98      this.points[SeriesName].AddRange(points);
99      UpdateHistogram();
100    }
101
102    public void AddPoint(string name, double point, bool replace = false) {
103      InitNewRow(name);
104      InitSeries(name);
105      if (replace) {
106        points[name].Clear();
107      }
108      points[name].Add(point);
109      UpdateHistogram();
110    }
111
112    public void AddPoints(string name, IEnumerable<double> points, bool replace = false) {
113      InitNewRow(name);
114      InitSeries(name);
115      if (replace) {
116        this.points[name].Clear();
117      }
118      this.points[name].AddRange(points);
119      UpdateHistogram();
120    }
121
122    public void ClearPoints() {
123      points.Clear();
124      UpdateHistogram();
125    }
126
127    protected void UpdateHistogram(double bandwith = double.NaN) {
128      if (InvokeRequired) {
129        Invoke((Action<double>)UpdateHistogram, bandwith);
130        return;
131      }
132
133      double overallMin = double.MaxValue;
134      double overallMax = double.MinValue;
135      int bins = (int)binsNumericUpDown.Value;
136
137      chart.Series.Clear();
138      foreach (var point in points) {
139        if (!point.Value.Any()) continue;
140
141        double minValue = point.Value.Min();
142        double maxValue = point.Value.Max();
143        double intervalWidth = (maxValue - minValue) / bins;
144        if (intervalWidth <= 0) continue;
145
146        Series histogramSeries = new Series(point.Key);
147        chart.Series.Add(histogramSeries);
148
149        if (!exactCheckBox.Checked) {
150          intervalWidth = HumanRoundRange(intervalWidth);
151          minValue = Math.Floor(minValue / intervalWidth) * intervalWidth;
152          maxValue = Math.Ceiling(maxValue / intervalWidth) * intervalWidth;
153        }
154
155        double current = minValue, intervalCenter = intervalWidth / 2.0;
156        int count = 0;
157        foreach (double v in point.Value.OrderBy(x => x)) {
158          while (v > current + intervalWidth) {
159            histogramSeries.Points.AddXY(current + intervalCenter, count);
160            current += intervalWidth;
161            count = 0;
162          }
163          count++;
164        }
165        histogramSeries.Points.AddXY(current + intervalCenter, count);
166        histogramSeries["PointWidth"] = "1";
167
168        overallMax = Math.Max(overallMax, maxValue);
169        overallMin = Math.Min(overallMin, minValue);
170
171        chart.ApplyPaletteColors();
172        CalculateDensity(histogramSeries, point.Value, bandwith);
173      }
174
175      if (chart.Series.Any()) {
176        noDataLabel.Visible = false;
177      } else {
178        noDataLabel.Visible = true;
179      }
180
181      ChartArea chartArea = chart.ChartAreas[0];
182      // don't show grid lines for second y-axis
183      chartArea.AxisY2.MajorGrid.Enabled = false;
184      chartArea.AxisY2.MinorGrid.Enabled = false;
185      chartArea.AxisY.Title = "Frequency";
186
187      double overallIntervalWidth = (overallMax - overallMin) / bins;
188      double axisInterval = overallIntervalWidth;
189      while ((overallMax - overallMin) / axisInterval > 10.0) {
190        axisInterval *= 2.0;
191      }
192      chartArea.AxisX.Interval = axisInterval;
193    }
194
195    protected void CalculateDensity(Series series, List<double> row, double bandwidth = double.NaN) {
196      string densitySeriesName = "Density " + series.Name;
197      double stepWidth = series.Points[1].XValue - series.Points[0].XValue;
198      var rowArray = row.ToArray();
199
200      if (chart.Series.Any(x => x.Name == densitySeriesName)) {
201        var ds = chart.Series.Single(x => x.Name == densitySeriesName);
202        chart.Series.Remove(ds);
203      }
204
205      if (double.IsNaN(bandwidth)) {
206        bandwidth = KernelDensityEstimator.EstimateBandwidth(rowArray);
207        decimal bwDecimal = (decimal)bandwidth;
208        if (bwDecimal < bandwidthMin) {
209          bwDecimal = bandwidthMin;
210          bandwidth = decimal.ToDouble(bwDecimal);
211        }
212        suppressUpdate = true;
213        bandwidthNumericUpDown.Value = bwDecimal;
214      }
215      var density = KernelDensityEstimator.Density(rowArray, rowArray.Length, stepWidth, bandwidth);
216
217      Series newSeries = new Series(densitySeriesName);
218      newSeries.Color = series.Color;
219      newSeries.ChartType = SeriesChartType.FastLine;
220      newSeries.BorderWidth = 2;
221      foreach (var d in density) {
222        newSeries.Points.Add(new DataPoint(d.Item1, d.Item2));
223      }
224
225      // densities should be plotted on the second axis (different scale)
226      newSeries.YAxisType = AxisType.Secondary;
227      chart.Series.Add(newSeries);
228    }
229
230    protected double HumanRoundRange(double range) {
231      double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range)));
232      double rounding = range / base10;
233      if (rounding <= 1.5) rounding = 1;
234      else if (rounding <= 2.25) rounding = 2;
235      else if (rounding <= 3.75) rounding = 2.5;
236      else if (rounding <= 7.5) rounding = 5;
237      else rounding = 10;
238      return rounding * base10;
239    }
240
241    private void binsNumericUpDown_ValueChanged(object sender, EventArgs e) {
242      UpdateHistogram();
243    }
244
245    private void exactCheckBox_CheckedChanged(object sender, EventArgs e) {
246      UpdateHistogram();
247    }
248
249    private void bandwidthNumericUpDown_ValueChanged(object sender, EventArgs e) {
250      if (!suppressUpdate) {
251        UpdateHistogram(decimal.ToDouble(bandwidthNumericUpDown.Value));
252      }
253      suppressUpdate = false;
254    }
255  }
256}
Note: See TracBrowser for help on using the repository browser.