#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.Common; using HeuristicLab.Visualization.ChartControlsExtensions; namespace HeuristicLab.Problems.DataAnalysis.Views { public partial class GradientChart : EnhancedChart { private ModifiableDataset internalDataset; 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(); } private void UpdateChart() { // throw exceptions? if (internalDataset == null || solutionList == null || !solutionList.Any()) return; if (min.IsAlmost(max) || min > max) return; if (points == 0) return; CalculateAxisInterval(); Series.Clear(); var vla = VerticalLineAnnotation; vla.Visible = true; Annotations.Clear(); Annotations.Add(vla); 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; var ta = new TextAnnotation { Text = string.Format("X: {0:0.000}, Y: {1:0.000}", p.XValue, p.YValues[0]), Visible = true, X = p.XValue, Y = p.YValues[0], AxisX = ChartAreas[0].AxisX, AxisY = ChartAreas[0].AxisY }; ta.ClipToChartArea = "ChartArea1"; ta.Name = series.Name; Annotations.Add(ta); } AddStripLines(); 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; 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.Name = s.Name; 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.Title = Variable; var axisY = ChartAreas[0].AxisY; axisY.Title = ProblemData.TargetVariable; 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 CalculateAxisInterval() { double axisMin, axisMax, axisInterval; 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; } private void RegisterEvents() { AnnotationPositionChanging += chart_AnnotationPositionChanging; MouseMove += chart_MouseMove; FormatNumber += chart_FormatNumber; } #region events 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 a = Annotations[0]; // var x = a.X; // var s = Series[0]; // var n = s.Points.Count; // var y = GetEstimatedValue(x); // s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 }; // a.TextStyle = TextStyle.Default; // Refresh(); // } 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; // var va = VerticalLineAnnotation; // Annotations.Clear(); // Annotations.Add(va); 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 }; } foreach (var annotation in Annotations.OfType()) { var p = Series[annotation.Name].Points.Last(); annotation.Text = string.Format("X: {0:0.000}, Y: {1:0.000}", p.XValue, p.YValues[0]); annotation.X = p.XValue; annotation.Y = p.YValues[0]; } Update(); } 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; } } } #endregion 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; } } } }