[13780] | 1 | #region License Information
|
---|
| 2 | /* HeuristicLab
|
---|
| 3 | * Copyright (C) 2002-2016 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;
|
---|
[13817] | 25 | using System.Globalization;
|
---|
[13780] | 26 | using System.Linq;
|
---|
| 27 | using System.Windows.Forms;
|
---|
| 28 | using System.Windows.Forms.DataVisualization.Charting;
|
---|
| 29 | using HeuristicLab.Common;
|
---|
| 30 | using HeuristicLab.Visualization.ChartControlsExtensions;
|
---|
| 31 |
|
---|
| 32 | namespace HeuristicLab.Problems.DataAnalysis.Views {
|
---|
| 33 | public partial class GradientChart : EnhancedChart {
|
---|
| 34 | private ModifiableDataset internalDataset;
|
---|
| 35 |
|
---|
[13817] | 36 | public bool ShowLegend { get; set; }
|
---|
| 37 |
|
---|
[13780] | 38 | private bool useMedianValues;
|
---|
| 39 | public bool UseMedianValues {
|
---|
| 40 | get { return useMedianValues; }
|
---|
| 41 | set {
|
---|
| 42 | if (value == useMedianValues) return;
|
---|
| 43 | useMedianValues = value;
|
---|
| 44 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 45 | UpdateChart();
|
---|
| 46 | }
|
---|
| 47 | }
|
---|
| 48 |
|
---|
| 49 | private int row;
|
---|
| 50 | public int Row {
|
---|
| 51 | get { return row; }
|
---|
| 52 | set {
|
---|
| 53 | if (row == value) return;
|
---|
| 54 | row = value;
|
---|
| 55 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 56 | UpdateChart();
|
---|
| 57 | }
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 | private double min;
|
---|
| 61 | public double Min {
|
---|
| 62 | get { return min; }
|
---|
| 63 | set {
|
---|
| 64 | if (value.IsAlmost(min)) return;
|
---|
| 65 | min = value;
|
---|
| 66 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 67 | UpdateChart();
|
---|
| 68 | }
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | private double max;
|
---|
| 72 | public double Max {
|
---|
| 73 | get { return max; }
|
---|
| 74 | set {
|
---|
| 75 | if (value.IsAlmost(max)) return;
|
---|
| 76 | max = value;
|
---|
| 77 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 78 | UpdateChart();
|
---|
| 79 | }
|
---|
| 80 | }
|
---|
| 81 |
|
---|
| 82 | private int points;
|
---|
| 83 | public int Points {
|
---|
| 84 | get { return points; }
|
---|
| 85 | set {
|
---|
| 86 | if (value == points) return;
|
---|
| 87 | points = value;
|
---|
| 88 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 89 | UpdateChart();
|
---|
| 90 | }
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 | private IRegressionProblemData problemData;
|
---|
| 94 | public IRegressionProblemData ProblemData {
|
---|
| 95 | get { return problemData; }
|
---|
| 96 | set {
|
---|
| 97 | if (!SolutionsCompatibleWithProblemData(value, solutionList))
|
---|
| 98 | throw new ArgumentException("The problem data provided does not contain all the variables required by the solutions.");
|
---|
| 99 | problemData = value;
|
---|
| 100 | UpdateInternalDataset();
|
---|
| 101 | UpdateChart();
|
---|
| 102 | }
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | public string Target {
|
---|
| 106 | get { return Solutions.First().ProblemData.TargetVariable; }
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | private string variable;
|
---|
| 110 | public string Variable {
|
---|
| 111 | get { return variable; }
|
---|
| 112 | set {
|
---|
| 113 | if (variable == value) return;
|
---|
| 114 | if (!ProblemData.Dataset.DoubleVariables.Contains(value))
|
---|
| 115 | throw new ArgumentException("The variable must be present in the problem dataset.");
|
---|
| 116 | OnChartPropertyChanged(this, EventArgs.Empty);
|
---|
| 117 | variable = value;
|
---|
| 118 | var values = ProblemData.Dataset.GetReadOnlyDoubleValues(variable);
|
---|
| 119 | min = values.Min();
|
---|
| 120 | max = values.Max();
|
---|
| 121 | UpdateChart();
|
---|
| 122 | }
|
---|
| 123 | }
|
---|
| 124 |
|
---|
| 125 | private List<IRegressionSolution> solutionList;
|
---|
| 126 | public IEnumerable<IRegressionSolution> Solutions {
|
---|
| 127 | get { return solutionList; }
|
---|
| 128 | set {
|
---|
| 129 | if (!value.Any())
|
---|
| 130 | throw new ArgumentException("At least one solution must be provided.");
|
---|
| 131 | if (SolutionsCompatibleWithProblemData(problemData, value))
|
---|
| 132 | solutionList = new List<IRegressionSolution>(value);
|
---|
| 133 | else
|
---|
| 134 | throw new ArgumentException("The provided solution collection is not compatible with the existing problem data.");
|
---|
| 135 | UpdateChart();
|
---|
| 136 | }
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | public VerticalLineAnnotation VerticalLineAnnotation {
|
---|
| 140 | get { return (VerticalLineAnnotation)Annotations.SingleOrDefault(x => x is VerticalLineAnnotation); }
|
---|
| 141 | }
|
---|
| 142 |
|
---|
| 143 | public GradientChart() {
|
---|
| 144 | InitializeComponent();
|
---|
| 145 | RegisterEvents();
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | public void AddSolution(IRegressionSolution solution) {
|
---|
| 149 | if (!SolutionsCompatibleWithProblemData(problemData, new[] { solution })) {
|
---|
| 150 | throw new ArgumentException("The solution is not compatible with the problem data.");
|
---|
| 151 | }
|
---|
| 152 | solutionList.Add(solution);
|
---|
| 153 | UpdateChart();
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | public void RemoveSolution(IRegressionSolution solution) {
|
---|
| 157 | var removed = solutionList.RemoveAll(x => x == solution);
|
---|
| 158 | if (removed > 0)
|
---|
| 159 | UpdateChart();
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | private static bool SolutionsCompatibleWithProblemData(IRegressionProblemData pd, IEnumerable<IRegressionSolution> solutions) {
|
---|
| 163 | if (pd == null || !solutions.Any()) return true;
|
---|
| 164 | if (solutions.Any(x => x.ProblemData.TargetVariable != pd.TargetVariable)) return false;
|
---|
| 165 | var variables = new HashSet<string>(pd.Dataset.DoubleVariables);
|
---|
| 166 | return solutions.SelectMany(x => x.ProblemData.Dataset.DoubleVariables).All(variables.Contains);
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 |
|
---|
| 170 | public void Configure(IEnumerable<IRegressionSolution> solutions, IRegressionProblemData pd, double min, double max, int points) {
|
---|
| 171 | if (!SolutionsCompatibleWithProblemData(pd, solutions))
|
---|
| 172 | throw new ArgumentException("Solutions are not compatible with the problem data.");
|
---|
| 173 | this.solutionList = new List<IRegressionSolution>(solutions);
|
---|
| 174 | this.problemData = pd;
|
---|
| 175 | this.variable = pd.Dataset.DoubleVariables.First();
|
---|
| 176 | this.min = min;
|
---|
| 177 | this.max = max;
|
---|
| 178 | this.points = points;
|
---|
| 179 |
|
---|
| 180 | UpdateInternalDataset();
|
---|
| 181 | UpdateChart();
|
---|
| 182 | }
|
---|
| 183 |
|
---|
[13808] | 184 | public void Configure(IEnumerable<IRegressionSolution> solutions, IRegressionProblemData pd, ModifiableDataset dataset, string variable, double min, double max, int points) {
|
---|
| 185 | if (!SolutionsCompatibleWithProblemData(pd, solutions))
|
---|
| 186 | throw new ArgumentException("Solutions are not compatible with the problem data.");
|
---|
| 187 | this.solutionList = new List<IRegressionSolution>(solutions);
|
---|
| 188 | this.problemData = pd;
|
---|
| 189 | this.variable = variable;
|
---|
| 190 | this.internalDataset = dataset;
|
---|
| 191 | this.min = min;
|
---|
| 192 | this.max = max;
|
---|
| 193 | this.points = points;
|
---|
| 194 | }
|
---|
| 195 |
|
---|
| 196 | public void UpdateChart() {
|
---|
[13780] | 197 | // throw exceptions?
|
---|
| 198 | if (internalDataset == null || solutionList == null || !solutionList.Any())
|
---|
| 199 | return;
|
---|
[13818] | 200 | if (min.IsAlmost(max) || min > max || points == 0)
|
---|
[13780] | 201 | return;
|
---|
| 202 | Series.Clear();
|
---|
| 203 | var vla = VerticalLineAnnotation;
|
---|
| 204 | vla.Visible = true;
|
---|
| 205 | Annotations.Clear();
|
---|
| 206 | Annotations.Add(vla);
|
---|
[13818] | 207 | double axisMin, axisMax, axisInterval;
|
---|
| 208 | // calculate X-axis interval
|
---|
| 209 | ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval);
|
---|
| 210 | var axis = ChartAreas[0].AxisX;
|
---|
| 211 | axis.Minimum = axisMin;
|
---|
| 212 | axis.Maximum = axisMax;
|
---|
| 213 | axis.Interval = axisInterval;
|
---|
| 214 |
|
---|
[13780] | 215 | for (int i = 0; i < solutionList.Count; ++i) {
|
---|
| 216 | var solution = solutionList[i];
|
---|
| 217 | var series = PlotSeries(solution);
|
---|
| 218 | series.Name = Target + " " + i;
|
---|
| 219 | Series.Add(series);
|
---|
| 220 | var p = series.Points.Last();
|
---|
| 221 | vla.X = p.XValue;
|
---|
| 222 | }
|
---|
[13818] | 223 | // calculate Y-axis interval
|
---|
| 224 | double ymin = 0, ymax = 0;
|
---|
| 225 | foreach (var v in Series[0].Points.Select(x => x.YValues[0])) {
|
---|
| 226 | if (ymin > v) ymin = v;
|
---|
| 227 | if (ymax < v) ymax = v;
|
---|
| 228 | }
|
---|
| 229 | ChartUtil.CalculateAxisInterval(ymin, ymax, 5, out axisMin, out axisMax, out axisInterval);
|
---|
| 230 | axis = ChartAreas[0].AxisY;
|
---|
| 231 | axis.Minimum = axisMin;
|
---|
| 232 | axis.Maximum = axisMax;
|
---|
| 233 | axis.Interval = axisInterval;
|
---|
| 234 | ChartAreas[0].AxisX.Title = Variable + " : " + vla.X.ToString("N3", CultureInfo.CurrentCulture); // set axis titlet
|
---|
| 235 | AddStripLines(); // add strip lines
|
---|
[13817] | 236 | if (ShowLegend)
|
---|
| 237 | AddLegends();
|
---|
[13780] | 238 | }
|
---|
| 239 |
|
---|
| 240 | private void UpdateInternalDataset() {
|
---|
| 241 | var variables = ProblemData.Dataset.DoubleVariables.ToList();
|
---|
| 242 | var variableValues = new List<double>[variables.Count];
|
---|
| 243 |
|
---|
| 244 | if (UseMedianValues) {
|
---|
| 245 | for (int i = 0; i < variables.Count; ++i) {
|
---|
| 246 | var median = ProblemData.Dataset.GetDoubleValues(variables[i], ProblemData.TrainingIndices).Median();
|
---|
| 247 | variableValues[i] = new List<double> { median };
|
---|
| 248 | }
|
---|
| 249 | } else {
|
---|
| 250 | for (int i = 0; i < variables.Count; ++i) {
|
---|
| 251 | var variableValue = ProblemData.Dataset.GetDoubleValue(variables[i], Row);
|
---|
| 252 | variableValues[i] = new List<double> { variableValue };
|
---|
| 253 | }
|
---|
| 254 | }
|
---|
| 255 | internalDataset = new ModifiableDataset(variables, variableValues);
|
---|
| 256 | }
|
---|
| 257 |
|
---|
| 258 | private double GetEstimatedValue(IRegressionSolution solution, double x) {
|
---|
| 259 | var v = internalDataset.GetDoubleValue(Variable, 0);
|
---|
| 260 | internalDataset.SetVariableValue(x, Variable, 0);
|
---|
| 261 | var y = solution.Model.GetEstimatedValues(internalDataset, new[] { 0 }).Single();
|
---|
| 262 | internalDataset.SetVariableValue(v, Variable, 0);
|
---|
| 263 | return y;
|
---|
| 264 | }
|
---|
| 265 |
|
---|
| 266 | private Series PlotSeries(IRegressionSolution solution) {
|
---|
| 267 | var v = internalDataset.GetDoubleValue(variable, 0);
|
---|
| 268 | var series = new Series { ChartType = SeriesChartType.Point };
|
---|
| 269 |
|
---|
| 270 | var step = (max - min) / points;
|
---|
| 271 | var axisX = ChartAreas[0].AxisX;
|
---|
[13817] | 272 | axisX.Title = Variable + " : " + v.ToString("N3", CultureInfo.CurrentCulture);
|
---|
[13780] | 273 | var axisY = ChartAreas[0].AxisY;
|
---|
| 274 | axisY.Title = Target;
|
---|
| 275 | double y;
|
---|
| 276 | // lefthand section outside of the training range
|
---|
| 277 | for (double x = axisX.Minimum; x < min; x += step) {
|
---|
| 278 | y = GetEstimatedValue(solution, x);
|
---|
| 279 | series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
|
---|
| 280 | }
|
---|
| 281 | // points in the trainig range
|
---|
| 282 | for (double x = min; x < max; x += step) {
|
---|
| 283 | y = GetEstimatedValue(solution, x);
|
---|
| 284 | series.Points.Add(new DataPoint(x, y) { MarkerSize = 2 });
|
---|
| 285 | }
|
---|
| 286 | // righthand section outside of the training range
|
---|
| 287 | for (double x = max; x < axisX.Maximum; x += step) {
|
---|
| 288 | y = GetEstimatedValue(solution, x);
|
---|
| 289 | series.Points.Add(new DataPoint(x, y) { MarkerSize = 2, MarkerColor = Color.Orange });
|
---|
| 290 | }
|
---|
| 291 |
|
---|
| 292 | y = GetEstimatedValue(solution, v);
|
---|
| 293 | series.Points.Add(new DataPoint(v, y) { MarkerSize = 5, MarkerColor = Color.Red });
|
---|
| 294 | series.IsVisibleInLegend = true;
|
---|
| 295 |
|
---|
| 296 | return series;
|
---|
| 297 | }
|
---|
| 298 |
|
---|
| 299 | private void AddLegends() {
|
---|
| 300 | Legends.Clear();
|
---|
| 301 | var legend = new Legend();
|
---|
| 302 | legend.Alignment = StringAlignment.Center;
|
---|
| 303 | legend.LegendStyle = LegendStyle.Row;
|
---|
| 304 | legend.Docking = Docking.Top;
|
---|
| 305 | Legends.Add(legend);
|
---|
| 306 | foreach (var s in Series) {
|
---|
| 307 | s.Legend = legend.Name;
|
---|
| 308 | }
|
---|
| 309 | }
|
---|
| 310 |
|
---|
| 311 | private void AddStripLines() {
|
---|
| 312 | var axisX = ChartAreas[0].AxisX;
|
---|
| 313 | axisX.StripLines.Clear();
|
---|
| 314 | axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = axisX.Minimum, StripWidth = min - axisX.Minimum });
|
---|
| 315 | axisX.StripLines.Add(new StripLine { BackColor = Color.FromArgb(30, Color.Green), IntervalOffset = max, StripWidth = axisX.Maximum - max });
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | private void RegisterEvents() {
|
---|
| 319 | AnnotationPositionChanging += chart_AnnotationPositionChanging;
|
---|
| 320 | MouseMove += chart_MouseMove;
|
---|
| 321 | FormatNumber += chart_FormatNumber;
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | #region events
|
---|
[13817] | 325 | public event EventHandler VariableValueChanged;
|
---|
| 326 | public void OnVariableValueChanged(object sender, EventArgs args) {
|
---|
| 327 | var changed = VariableValueChanged;
|
---|
| 328 | if (changed == null) return;
|
---|
| 329 | changed(sender, args);
|
---|
| 330 | }
|
---|
| 331 |
|
---|
[13780] | 332 | public event EventHandler ChartPropertyChanged;
|
---|
| 333 | public void OnChartPropertyChanged(object sender, EventArgs args) {
|
---|
| 334 | var changed = ChartPropertyChanged;
|
---|
| 335 | if (changed == null) return;
|
---|
| 336 | changed(sender, args);
|
---|
| 337 | }
|
---|
| 338 |
|
---|
[13818] | 339 | private void chart_AnnotationPositionChanged(object sender, EventArgs e) {
|
---|
| 340 | var annotation = VerticalLineAnnotation;
|
---|
| 341 | var x = annotation.X;
|
---|
[13817] | 342 | internalDataset.SetVariableValue(x, Variable, 0);
|
---|
[13780] | 343 | for (int i = 0; i < solutionList.Count; ++i) {
|
---|
| 344 | var y = GetEstimatedValue(solutionList[i], x);
|
---|
| 345 | var s = Series[i];
|
---|
| 346 | var n = s.Points.Count;
|
---|
| 347 | s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 };
|
---|
| 348 | }
|
---|
[13817] | 349 | ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
|
---|
[13780] | 350 | Update();
|
---|
[13817] | 351 | OnVariableValueChanged(this, EventArgs.Empty);
|
---|
[13780] | 352 | }
|
---|
| 353 |
|
---|
[13818] | 354 | private void chart_AnnotationPositionChanging(object sender, AnnotationPositionChangingEventArgs e) {
|
---|
| 355 | var step = (max - min) / points;
|
---|
| 356 | e.NewLocationX = step * (long)Math.Round(e.NewLocationX / step);
|
---|
| 357 | var axisX = ChartAreas[0].AxisX;
|
---|
| 358 | if (e.NewLocationX > axisX.Maximum)
|
---|
| 359 | e.NewLocationX = axisX.Maximum;
|
---|
| 360 | if (e.NewLocationX < axisX.Minimum)
|
---|
| 361 | e.NewLocationX = axisX.Minimum;
|
---|
| 362 | // var x = e.NewLocationX;
|
---|
| 363 | // internalDataset.SetVariableValue(x, Variable, 0);
|
---|
| 364 | // for (int i = 0; i < solutionList.Count; ++i) {
|
---|
| 365 | // var y = GetEstimatedValue(solutionList[i], x);
|
---|
| 366 | // var s = Series[i];
|
---|
| 367 | // var n = s.Points.Count;
|
---|
| 368 | // s.Points[n - 1] = new DataPoint(x, y) { MarkerColor = Color.Red, MarkerSize = 5 };
|
---|
| 369 | // }
|
---|
| 370 | // ChartAreas[0].AxisX.Title = Variable + " : " + x.ToString("N3", CultureInfo.CurrentCulture);
|
---|
| 371 | // Update();
|
---|
| 372 | // OnVariableValueChanged(this, EventArgs.Empty);
|
---|
| 373 | }
|
---|
| 374 |
|
---|
[13780] | 375 | private void chart_MouseMove(object sender, MouseEventArgs e) {
|
---|
| 376 | this.Cursor = HitTest(e.X, e.Y).ChartElementType == ChartElementType.Annotation ? Cursors.VSplit : Cursors.Default;
|
---|
| 377 | }
|
---|
| 378 |
|
---|
| 379 | private void chart_FormatNumber(object sender, FormatNumberEventArgs e) {
|
---|
| 380 | if (e.ElementType == ChartElementType.AxisLabels) {
|
---|
| 381 | switch (e.Format) {
|
---|
| 382 | case "CustomAxisXFormat":
|
---|
| 383 | break;
|
---|
| 384 | case "CustomAxisYFormat":
|
---|
| 385 | var v = e.Value;
|
---|
| 386 | e.LocalizedValue = string.Format("{0,5}", v);
|
---|
| 387 | break;
|
---|
| 388 | default:
|
---|
| 389 | break;
|
---|
| 390 | }
|
---|
| 391 | }
|
---|
| 392 | }
|
---|
| 393 |
|
---|
| 394 | private void GradientChart_DragDrop(object sender, DragEventArgs e) {
|
---|
| 395 | var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
|
---|
| 396 | if (data != null) {
|
---|
| 397 | var solution = data as IRegressionSolution;
|
---|
| 398 | if (!Solutions.Contains(solution))
|
---|
| 399 | AddSolution(solution);
|
---|
| 400 | }
|
---|
| 401 | }
|
---|
| 402 |
|
---|
| 403 | private void GradientChart_DragEnter(object sender, DragEventArgs e) {
|
---|
| 404 | if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return;
|
---|
| 405 | e.Effect = DragDropEffects.None;
|
---|
| 406 |
|
---|
| 407 | var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
|
---|
| 408 | var regressionSolution = data as IRegressionSolution;
|
---|
| 409 | if (regressionSolution != null) {
|
---|
| 410 | e.Effect = DragDropEffects.Copy;
|
---|
| 411 | }
|
---|
| 412 | }
|
---|
[13817] | 413 | #endregion
|
---|
[13780] | 414 | }
|
---|
| 415 | }
|
---|