1  #region License Information


2  /* HeuristicLab


3  * Copyright (C) 20022015 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 


22  using System;


23  using System.Collections.Generic;


24  using System.Drawing;


25  using System.Linq;


26  using System.Windows.Forms;


27  using System.Windows.Forms.DataVisualization.Charting;


28  using HeuristicLab.Analysis.Statistics;


29 


30  namespace 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  Series histogramSeries = new Series(point.Key);


142  chart.Series.Add(histogramSeries);


143  double minValue = point.Value.Min();


144  double maxValue = point.Value.Max();


145  double intervalWidth = (maxValue  minValue) / bins;


146  if (intervalWidth <= 0) continue;


147 


148  if (!exactCheckBox.Checked) {


149  intervalWidth = HumanRoundRange(intervalWidth);


150  minValue = Math.Floor(minValue / intervalWidth) * intervalWidth;


151  maxValue = Math.Ceiling(maxValue / intervalWidth) * intervalWidth;


152  }


153 


154  double current = minValue, intervalCenter = intervalWidth / 2.0;


155  int count = 0;


156  foreach (double v in point.Value.OrderBy(x => x)) {


157  while (v > current + intervalWidth) {


158  histogramSeries.Points.AddXY(current + intervalCenter, count);


159  current += intervalWidth;


160  count = 0;


161  }


162  count++;


163  }


164  histogramSeries.Points.AddXY(current + intervalCenter, count);


165  histogramSeries["PointWidth"] = "1";


166 


167  overallMax = Math.Max(overallMax, maxValue);


168  overallMin = Math.Min(overallMin, minValue);


169  }


170 


171  chart.ApplyPaletteColors();


172 


173  int i = 0;


174  foreach (var point in points) {


175  if (!point.Value.Any()) continue;


176 


177  var histogramSeries = chart.Series[i];


178  CalculateDensity(histogramSeries, point.Value, bandwith);


179 


180  i++;


181  }


182 


183 


184  ChartArea chartArea = chart.ChartAreas[0];


185  // don't show grid lines for second yaxis


186  chartArea.AxisY2.MajorGrid.Enabled = false;


187  chartArea.AxisY2.MinorGrid.Enabled = false;


188  chartArea.AxisY.Title = "Frequency";


189 


190  double overallIntervalWidth = (overallMax  overallMin) / bins;


191  double axisInterval = overallIntervalWidth;


192  while ((overallMax  overallMin) / axisInterval > 10.0) {


193  axisInterval *= 2.0;


194  }


195  chartArea.AxisX.Interval = axisInterval;


196  }


197 


198  protected void CalculateDensity(Series series, List<double> row, double bandwidth = double.NaN) {


199  string densitySeriesName = "Density " + series.Name;


200  double stepWidth = series.Points[1].XValue  series.Points[0].XValue;


201  var rowArray = row.ToArray();


202 


203  if (chart.Series.Any(x => x.Name == densitySeriesName)) {


204  var ds = chart.Series.Single(x => x.Name == densitySeriesName);


205  chart.Series.Remove(ds);


206  }


207 


208  if (double.IsNaN(bandwidth)) {


209  bandwidth = KernelDensityEstimator.EstimateBandwidth(rowArray);


210  decimal bwDecimal = (decimal)bandwidth;


211  if (bwDecimal < bandwidthMin) {


212  bwDecimal = bandwidthMin;


213  bandwidth = decimal.ToDouble(bwDecimal);


214  }


215  suppressUpdate = true;


216  bandwidthNumericUpDown.Value = bwDecimal;


217  }


218  var density = KernelDensityEstimator.Density(rowArray, rowArray.Length, stepWidth, bandwidth);


219 


220  Series newSeries = new Series(densitySeriesName);


221  newSeries.Color = series.Color;


222  newSeries.ChartType = SeriesChartType.FastLine;


223  newSeries.BorderWidth = 2;


224  foreach (var d in density) {


225  newSeries.Points.Add(new DataPoint(d.Item1, d.Item2));


226  }


227 


228  // densities should be plotted on the second axis (different scale)


229  newSeries.YAxisType = AxisType.Secondary;


230  chart.Series.Add(newSeries);


231  }


232 


233  protected double HumanRoundRange(double range) {


234  double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range)));


235  double rounding = range / base10;


236  if (rounding <= 1.5) rounding = 1;


237  else if (rounding <= 2.25) rounding = 2;


238  else if (rounding <= 3.75) rounding = 2.5;


239  else if (rounding <= 7.5) rounding = 5;


240  else rounding = 10;


241  return rounding * base10;


242  }


243 


244  private void binsNumericUpDown_ValueChanged(object sender, EventArgs e) {


245  UpdateHistogram();


246  }


247 


248  private void exactCheckBox_CheckedChanged(object sender, EventArgs e) {


249  UpdateHistogram();


250  }


251 


252  private void bandwidthNumericUpDown_ValueChanged(object sender, EventArgs e) {


253  if (!suppressUpdate) {


254  UpdateHistogram(decimal.ToDouble(bandwidthNumericUpDown.Value));


255  }


256  suppressUpdate = false;


257  }


258  }


259  }

