#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.Globalization; using System.Linq; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using HeuristicLab.Common; using HeuristicLab.Visualization.ChartControlsExtensions; namespace HeuristicLab.Problems.DataAnalysis.Views { public partial class GradientChart : EnhancedChart { private ModifiableDataset internalDataset; public bool ShowLegend { get; set; } private bool useMedianValues; public bool UseMedianValues { get { return useMedianValues; } set { if (value == useMedianValues) return; useMedianValues = value; OnChartPropertyChanged(this, EventArgs.Empty); UpdateChart(); } } private int row; public int Row { get { return row; } set { if (row == value) return; row = value; OnChartPropertyChanged(this, EventArgs.Empty); UpdateChart(); } } private double min; public double Min { get { return min; } set { if (value.IsAlmost(min)) return; min = value; OnChartPropertyChanged(this, EventArgs.Empty); UpdateChart(); } } private double max; public double Max { get { return max; } set { if (value.IsAlmost(max)) return; max = value; OnChartPropertyChanged(this, EventArgs.Empty); UpdateChart(); } } private int points; public int Points { get { return points; } set { if (value == points) return; points = value; OnChartPropertyChanged(this, EventArgs.Empty); UpdateChart(); } } private IRegressionProblemData problemData; public IRegressionProblemData ProblemData { get { return problemData; } set { if (!SolutionsCompatibleWithProblemData(value, solutionList)) throw new ArgumentException("The problem data provided does not contain all the variables required by the solutions."); problemData = value; UpdateInternalDataset(); UpdateChart(); } } public string Target { get { return Solutions.First().ProblemData.TargetVariable; } } private string variable; public string Variable { get { return variable; } set { if (variable == value) return; if (!ProblemData.Dataset.DoubleVariables.Contains(value)) throw new ArgumentException("The variable must be present in the problem dataset."); OnChartPropertyChanged(this, EventArgs.Empty); variable = value; var values = ProblemData.Dataset.GetReadOnlyDoubleValues(variable); min = values.Min(); max = values.Max(); UpdateChart(); } } private List solutionList; public IEnumerable Solutions { get { return solutionList; } set { if (!value.Any()) throw new ArgumentException("At least one solution must be provided."); if (SolutionsCompatibleWithProblemData(problemData, value)) solutionList = new List(value); else throw new ArgumentException("The provided solution collection is not compatible with the existing problem data."); UpdateChart(); } } public VerticalLineAnnotation VerticalLineAnnotation { get { return (VerticalLineAnnotation)Annotations.SingleOrDefault(x => x is VerticalLineAnnotation); } } public GradientChart() { InitializeComponent(); RegisterEvents(); } public void AddSolution(IRegressionSolution solution) { if (!SolutionsCompatibleWithProblemData(problemData, new[] { solution })) { throw new ArgumentException("The solution is not compatible with the problem data."); } solutionList.Add(solution); UpdateChart(); } public void RemoveSolution(IRegressionSolution solution) { var removed = solutionList.RemoveAll(x => x == solution); if (removed > 0) UpdateChart(); } private static bool SolutionsCompatibleWithProblemData(IRegressionProblemData pd, IEnumerable solutions) { if (pd == null || !solutions.Any()) return true; if (solutions.Any(x => x.ProblemData.TargetVariable != pd.TargetVariable)) return false; var variables = new HashSet(pd.Dataset.DoubleVariables); return solutions.SelectMany(x => x.ProblemData.Dataset.DoubleVariables).All(variables.Contains); } public void Configure(IEnumerable solutions, IRegressionProblemData pd, double min, double max, int points) { if (!SolutionsCompatibleWithProblemData(pd, solutions)) throw new ArgumentException("Solutions are not compatible with the problem data."); this.solutionList = new List(solutions); this.problemData = pd; this.variable = pd.Dataset.DoubleVariables.First(); this.min = min; this.max = max; this.points = points; UpdateInternalDataset(); UpdateChart(); } public void Configure(IEnumerable solutions, IRegressionProblemData pd, ModifiableDataset dataset, string variable, double min, double max, int points) { if (!SolutionsCompatibleWithProblemData(pd, solutions)) throw new ArgumentException("Solutions are not compatible with the problem data."); this.solutionList = new List(solutions); this.problemData = pd; this.variable = variable; this.internalDataset = dataset; this.min = min; this.max = max; this.points = points; } public void UpdateChart() { // throw exceptions? if (internalDataset == null || solutionList == null || !solutionList.Any()) return; if (min.IsAlmost(max) || min > max || points == 0) return; Series.Clear(); var vla = VerticalLineAnnotation; vla.Visible = true; Annotations.Clear(); Annotations.Add(vla); double axisMin, axisMax, axisInterval; // calculate X-axis interval ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval); var axis = ChartAreas[0].AxisX; axis.Minimum = axisMin; axis.Maximum = axisMax; axis.Interval = axisInterval; for (int i = 0; i < solutionList.Count; ++i) { var solution = solutionList[i]; var series = PlotSeries(solution); series.Name = Target + " " + i; Series.Add(series); var p = series.Points.Last(); vla.X = p.XValue; } // calculate Y-axis interval double ymin = 0, ymax = 0; foreach (var v in Series[0].Points.Select(x => x.YValues[0])) { if (ymin > v) ymin = v; if (ymax < v) ymax = v; } ChartUtil.CalculateAxisInterval(ymin, ymax, 5, out axisMin, out axisMax, out axisInterval); axis = ChartAreas[0].AxisY; axis.Minimum = axisMin; axis.Maximum = axisMax; axis.Interval = axisInterval; ChartAreas[0].AxisX.Title = Variable + " : " + vla.X.ToString("N3", CultureInfo.CurrentCulture); // set axis titlet AddStripLines(); // add strip lines if (ShowLegend) AddLegends(); } private void UpdateInternalDataset() { var variables = ProblemData.Dataset.DoubleVariables.ToList(); var variableValues = new List[variables.Count]; if (UseMedianValues) { for (int i = 0; i < variables.Count; ++i) { var median = ProblemData.Dataset.GetDoubleValues(variables[i], ProblemData.TrainingIndices).Median(); variableValues[i] = new List { median }; } } else { for (int i = 0; i < variables.Count; ++i) { var variableValue = ProblemData.Dataset.GetDoubleValue(variables[i], Row); variableValues[i] = new List { variableValue }; } } internalDataset = new ModifiableDataset(variables, variableValues); } private double GetEstimatedValue(IRegressionSolution solution, double x) { var v = internalDataset.GetDoubleValue(Variable, 0); internalDataset.SetVariableValue(x, Variable, 0); var y = solution.Model.GetEstimatedValues(internalDataset, new[] { 0 }).Single(); internalDataset.SetVariableValue(v, Variable, 0); return y; } private Series PlotSeries(IRegressionSolution solution) { var v = internalDataset.GetDoubleValue(variable, 0); var series = new Series { ChartType = SeriesChartType.Point }; var step = (max - min) / points; var axisX = ChartAreas[0].AxisX; axisX.Title = Variable + " : " + v.ToString("N3", CultureInfo.CurrentCulture); var axisY = ChartAreas[0].AxisY; axisY.Title = Target; double y; // lefthand section outside of the training range for (double x = axisX.Minimum; x < min; x += step) { y = GetEstimatedValue(solution, x); series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange }); } // points in the trainig range for (double x = min; x < max; x += step) { y = GetEstimatedValue(solution, x); series.Points.Add(new DataPoint(x, y) { MarkerSize = 2 }); } // righthand section outside of the training range for (double x = max; x < axisX.Maximum; x += step) { y = GetEstimatedValue(solution, x); series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange }); } y = GetEstimatedValue(solution, v); series.Points.Add(new DataPoint(v, y) { MarkerSize = 5, MarkerColor = Color.Red }); series.IsVisibleInLegend = true; return series; } private void AddLegends() { Legends.Clear(); var legend = new Legend(); legend.Alignment = StringAlignment.Center; legend.LegendStyle = LegendStyle.Row; legend.Docking = Docking.Top; Legends.Add(legend); foreach (var s in Series) { s.Legend = legend.Name; } } private void AddStripLines() { var axisX = ChartAreas[0].AxisX; axisX.StripLines.Clear(); axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = axisX.Minimum, StripWidth = min - axisX.Minimum }); axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = max, StripWidth = axisX.Maximum - max }); } private void RegisterEvents() { AnnotationPositionChanging += chart_AnnotationPositionChanging; MouseMove += chart_MouseMove; FormatNumber += chart_FormatNumber; } #region events public event EventHandler VariableValueChanged; public void OnVariableValueChanged(object sender, EventArgs args) { var changed = VariableValueChanged; if (changed == null) return; changed(sender, args); } public event EventHandler ChartPropertyChanged; public void OnChartPropertyChanged(object sender, EventArgs args) { var changed = ChartPropertyChanged; if (changed == null) return; changed(sender, args); } private void chart_AnnotationPositionChanged(object sender, EventArgs e) { var annotation = VerticalLineAnnotation; var x = annotation.X; internalDataset.SetVariableValue(x, Variable, 0); for (int i = 0; i < solutionList.Count; ++i) { var y = GetEstimatedValue(solutionList[i], x); var s = Series[i]; var n = s.Points.Count; s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 }; } ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture); Update(); OnVariableValueChanged(this, EventArgs.Empty); } private void chart_AnnotationPositionChanging(object sender, AnnotationPositionChangingEventArgs e) { var step = (max - min) / points; e.NewLocationX = step * (long)Math.Round(e.NewLocationX / step); var axisX = ChartAreas[0].AxisX; if (e.NewLocationX > axisX.Maximum) e.NewLocationX = axisX.Maximum; if (e.NewLocationX < axisX.Minimum) e.NewLocationX = axisX.Minimum; // var x = e.NewLocationX; // internalDataset.SetVariableValue(x, Variable, 0); // for (int i = 0; i < solutionList.Count; ++i) { // var y = GetEstimatedValue(solutionList[i], x); // var s = Series[i]; // var n = s.Points.Count; // s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 }; // } // ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture); // Update(); // OnVariableValueChanged(this, EventArgs.Empty); } private void chart_MouseMove(object sender, MouseEventArgs e) { this.Cursor = HitTest(e.X, e.Y).ChartElementType == ChartElementType.Annotation ? Cursors.VSplit : Cursors.Default; } private void chart_FormatNumber(object sender, FormatNumberEventArgs e) { if (e.ElementType == ChartElementType.AxisLabels) { switch (e.Format) { case "CustomAxisXFormat": break; case "CustomAxisYFormat": var v = e.Value; e.LocalizedValue = string.Format("{0,5}", v); break; default: break; } } } private void GradientChart_DragDrop(object sender, DragEventArgs e) { var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat); if (data != null) { var solution = data as IRegressionSolution; if (!Solutions.Contains(solution)) AddSolution(solution); } } private void GradientChart_DragEnter(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return; e.Effect = DragDropEffects.None; var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat); var regressionSolution = data as IRegressionSolution; if (regressionSolution != null) { e.Effect = DragDropEffects.Copy; } } #endregion } }