#region License Information
/* HeuristicLab
* Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using HeuristicLab.Collections;
using HeuristicLab.Data;
using HeuristicLab.MainForm;
using HeuristicLab.MainForm.WindowsForms;
using HeuristicLab.Problems.DataAnalysis;
using HeuristicLab.Problems.DataAnalysis.Views;
using HeuristicLab.Visualization.ChartControlsExtensions;
namespace HeuristicLab.Algorithms.DataAnalysis.Views {
[View("Gradient View")]
[Content(typeof(IConfidenceBoundRegressionSolution))]
public partial class RegressionSolutionGradientView
: DataAnalysisSolutionEvaluationView {
private const int DrawingSteps = 1000;
private const string EstimatedMeanSeriesName = "Estimated Mean";
private const string EstimatedVarianceSeriesName = "95% Conficence Interval";
private readonly List dimensionNames;
private readonly ObservableList dimensionTrackbars;
private int ActiveDimension {
get { return dimensionTrackbars.FindIndex(tb => tb.Checked); }
}
public new IConfidenceBoundRegressionSolution Content {
get { return (IConfidenceBoundRegressionSolution)base.Content; }
set { base.Content = value; }
}
public RegressionSolutionGradientView()
: base() {
dimensionNames = new List();
dimensionTrackbars = new ObservableList();
dimensionTrackbars.ItemsAdded += (sender, args) => {
ForEach(args.Items.Select(i => i.Value), RegisterEvents);
};
dimensionTrackbars.ItemsRemoved += (sender, args) => {
ForEach(args.Items.Select(i => i.Value), DeregisterEvents);
};
dimensionTrackbars.CollectionReset += (sender, args) => {
ForEach(args.OldItems.Select(i => i.Value), DeregisterEvents);
ForEach(args.Items.Select(i => i.Value), RegisterEvents);
};
InitializeComponent();
// Avoid additional horizontal scrollbar
var vertScrollWidth = SystemInformation.VerticalScrollBarWidth;
tableLayoutPanel.Padding = new Padding(0, 0, vertScrollWidth, 0);
// Configure axis
chart.CustomizeAllChartAreas();
chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
chart.ChartAreas[0].CursorX.Interval = 1;
chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
chart.ChartAreas[0].CursorY.Interval = 0;
}
private void RedrawChart() {
chart.Series.Clear();
chart.ChartAreas[0].AxisX.StripLines.Clear();
if (Content == null || ActiveDimension < 0) return;
double minX = dimensionTrackbars[ActiveDimension].Limits.Lower;
double maxX = dimensionTrackbars[ActiveDimension].Limits.Upper;
decimal stepSize = (decimal)((maxX - minX) / DrawingSteps);
// Build dataset
var activeXs = Enumerable.Range(0, DrawingSteps).Select(i => (decimal)minX + i * stepSize).ToList();
var fixedXs = dimensionTrackbars.Select(tb => tb.Value).ToList();
var values = new double[DrawingSteps, dimensionNames.Count];
for (int r = 0; r < DrawingSteps; r++) {
for (int c = 0; c < dimensionNames.Count; c++) {
values[r, c] = (double)(c == ActiveDimension ? activeXs[r] : fixedXs[c]);
}
}
var dataset = new Dataset(dimensionNames, values);
// Estimations
var model = Content.Model;
var means = model.GetEstimatedValues(dataset, Enumerable.Range(0, DrawingSteps)).ToList();
var variances = model.GetEstimatedVariances(dataset, Enumerable.Range(0, DrawingSteps)).ToList();
// Charting config
chart.ChartAreas[0].AxisX.Minimum = minX;
chart.ChartAreas[0].AxisX.Maximum = maxX;
chart.ChartAreas[0].AxisX.Interval = (maxX - minX) / 10;
// ToDo only databind and put config in codebehind
chart.Series.Add(EstimatedVarianceSeriesName);
chart.Series[EstimatedVarianceSeriesName].LegendText = EstimatedVarianceSeriesName;
chart.Series[EstimatedVarianceSeriesName].ChartType = SeriesChartType.Range;
chart.Series[EstimatedVarianceSeriesName].EmptyPointStyle.Color = chart.Series[EstimatedVarianceSeriesName].Color;
chart.Series.Add(EstimatedMeanSeriesName);
chart.Series[EstimatedMeanSeriesName].LegendText = EstimatedMeanSeriesName;
chart.Series[EstimatedMeanSeriesName].ChartType = SeriesChartType.FastLine;
// Charting databind
var lower = means.Zip(variances, GetLowerConfBound).ToList();
var upper = means.Zip(variances, GetUpperConfBound).ToList();
chart.Series[EstimatedVarianceSeriesName].Points.DataBindXY(activeXs, lower, upper);
chart.Series[EstimatedMeanSeriesName].Points.DataBindXY(activeXs, means);
// Update StripLines
var trainingValues = Content.ProblemData.Dataset.GetDoubleValues(dimensionNames[ActiveDimension],
Content.ProblemData.TrainingIndices);
var trainingRange = new DoubleRange(trainingValues.Min(), trainingValues.Max());
if (minX < trainingRange.Start)
CreateAndAddStripLine(minX, trainingRange.Start, Color.FromArgb(40, 223, 58, 2), Color.Transparent);
if (maxX > trainingRange.End)
CreateAndAddStripLine(trainingRange.End, maxX, Color.FromArgb(40, 223, 58, 2), Color.Transparent);
// Update axis description
chart.ChartAreas[0].AxisX.Title = dimensionNames[ActiveDimension];
}
private void CreateAndAddStripLine(double start, double end, Color color, Color secondColor) {
StripLine stripLine = new StripLine {
BackColor = color,
BackSecondaryColor = secondColor,
StripWidth = end - start,
IntervalOffset = start
};
chart.ChartAreas[0].AxisX.StripLines.Add(stripLine);
}
private void UpdateConfigurationControls() {
tableLayoutPanel.SuspendRepaint();
tableLayoutPanel.SuspendLayout();
tableLayoutPanel.RowCount = 0;
tableLayoutPanel.Controls.Clear();
dimensionNames.Clear();
dimensionTrackbars.Clear();
if (Content == null) {
tableLayoutPanel.ResumeLayout(true);
tableLayoutPanel.ResumeRepaint(true);
return;
}
dimensionNames.AddRange(Content.ProblemData.AllowedInputVariables);
var ranges = new List();
for (int i = 0; i < dimensionNames.Count; i++) {
var values = Content.ProblemData.Dataset.GetDoubleValues(dimensionNames[i], Content.ProblemData.AllIndices);
double min, max, interval;
ChartUtil.CalculateAxisInterval(values.Min(), values.Max(), 10, out min, out max, out interval);
ranges.Add(new DoubleLimit(min, max));
}
tableLayoutPanel.RowCount = dimensionNames.Count;
while (tableLayoutPanel.RowStyles.Count < dimensionNames.Count)
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
for (int i = 0; i < dimensionNames.Count; i++) {
var name = dimensionNames[i];
var trainingData =
Content.ProblemData.Dataset.GetDoubleValues(name, Content.ProblemData.TrainingIndices).ToList();
var dimensionTrackbar = new DensityTrackbar(name, ranges[i], trainingData);
// events registered automatically
dimensionTrackbars.Add(dimensionTrackbar);
dimensionTrackbar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
tableLayoutPanel.Controls.Add(dimensionTrackbar, 0, i);
}
if (dimensionTrackbars.Any())
dimensionTrackbars.First().Checked = true;
tableLayoutPanel.ResumeLayout(true);
tableLayoutPanel.ResumeRepaint(true);
}
private void RegisterEvents(DensityTrackbar trackbar) {
trackbar.CheckedChanged += DimensionTrackbar_CheckedChanged;
trackbar.ValueChanged += DimensionTrackbar_ValueChanged;
trackbar.LimitsChanged += DimensionTrackbar_LimitsChanged;
}
private void DeregisterEvents(DensityTrackbar trackbar) {
trackbar.CheckedChanged -= DimensionTrackbar_CheckedChanged;
trackbar.ValueChanged -= DimensionTrackbar_ValueChanged;
trackbar.LimitsChanged -= DimensionTrackbar_LimitsChanged;
}
private void DimensionTrackbar_CheckedChanged(object sender, EventArgs e) {
var trackBarSender = sender as DensityTrackbar;
if (trackBarSender == null || !trackBarSender.Checked) return;
// Uncheck all others
foreach (var tb in dimensionTrackbars.Except(new[] { trackBarSender }))
tb.Checked = false;
RedrawChart();
}
private void DimensionTrackbar_LimitsChanged(object sender, EventArgs e) {
RedrawChart();
}
private void DimensionTrackbar_ValueChanged(object sender, EventArgs e) {
RedrawChart();
}
#region Events
protected override void RegisterContentEvents() {
base.RegisterContentEvents();
Content.ModelChanged += Content_ModelChanged;
Content.ProblemDataChanged += Content_ProblemDataChanged;
}
protected override void DeregisterContentEvents() {
base.DeregisterContentEvents();
Content.ModelChanged -= Content_ModelChanged;
Content.ProblemDataChanged -= Content_ProblemDataChanged;
}
protected override void OnContentChanged() {
base.OnContentChanged();
UpdateConfigurationControls();
RedrawChart();
}
private void Content_ModelChanged(object sender, EventArgs e) {
RedrawChart();
}
private void Content_ProblemDataChanged(object sender, EventArgs e) {
UpdateConfigurationControls();
RedrawChart();
}
private void Chart_MouseDoubleClick(object sender, MouseEventArgs e) {
var result = chart.HitTest(e.X, e.Y);
if (true || result.ChartArea != null && (result.ChartElementType == ChartElementType.PlottingArea ||
result.ChartElementType == ChartElementType.Gridlines) ||
result.ChartElementType == ChartElementType.StripLines) {
foreach (var axis in result.ChartArea.Axes)
axis.ScaleView.ZoomReset(int.MaxValue);
}
}
private void chart_MouseMove(object sender, MouseEventArgs e) {
//HitTestResult result = chart.HitTest(e.X, e.Y);
//if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME)
// Cursor = Cursors.Hand;
//else
// Cursor = Cursors.Default;
}
private void chart_MouseDown(object sender, MouseEventArgs e) {
//HitTestResult result = chart.HitTest(e.X, e.Y);
//if (result.ChartElementType == ChartElementType.LegendItem && result.Series.Name != TARGETVARIABLE_SERIES_NAME) {
// ToggleSeriesData(result.Series);
//}
}
private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
//if (chart.Series.Count != 4) return;
//e.LegendItems[0].Cells[1].ForeColor = this.chart.Series[EstimatedMeanSeriesName].Points.Count == 0 ? Color.Gray : Color.Black;
//e.LegendItems[1].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_TEST_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
//e.LegendItems[2].Cells[1].ForeColor = this.chart.Series[ESTIMATEDVALUES_ALL_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
//e.LegendItems[3].Cells[1].ForeColor = this.chart.Series[TARGETVARIABLE_SERIES_NAME].Points.Count == 0 ? Color.Gray : Color.Black;
}
#endregion
#region Helper
private double GetLowerConfBound(double m, double s2) {
return m - 1.96 * Math.Sqrt(s2);
}
private double GetUpperConfBound(double m, double s2) {
return m + 1.96 * Math.Sqrt(s2);
}
public static void ForEach(IEnumerable source, Action action) {
foreach (T item in source)
action(item);
}
#endregion
}
}