Changeset 15203 for trunk/sources/HeuristicLab.Problems.TestFunctions.Views/3.3/ParetoFrontScatterPlotView.cs
- Timestamp:
- 07/11/17 19:36:03 (7 years ago)
- File:
-
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/sources/HeuristicLab.Problems.TestFunctions.Views/3.3/ParetoFrontScatterPlotView.cs
r15202 r15203 19 19 */ 20 20 #endregion 21 21 22 using System; 22 using System.Drawing;23 23 using System.Linq; 24 using System.Text; 25 using System.Windows.Forms; 26 using System.Windows.Forms.DataVisualization.Charting; 24 using HeuristicLab.Analysis; 25 using HeuristicLab.Common; 27 26 using HeuristicLab.Core.Views; 28 27 using HeuristicLab.MainForm; … … 31 30 namespace HeuristicLab.Problems.TestFunctions.Views { 32 31 [View("Scatter Plot")] 33 [Content(typeof(ScatterPlotContent))] 34 public partial class MultiObjectiveTestFunctionParetoFrontScatterPlotView : ItemView { 35 private const string QUALITIES = "Qualities"; 36 private const string PARETO_FRONT = "Best Known Pareto Front"; 37 private Series qualitySeries; 38 private Series paretoSeries; 39 private int xDim = 0; 40 private int yDim = 1; 41 int objectives = -1; 32 [Content(typeof(ParetoFrontScatterPlot))] 33 public partial class ParetoFrontScatterPlotView : ItemView { 42 34 43 public new ScatterPlotContent Content { 44 get { return (ScatterPlotContent)base.Content; } 35 private readonly ScatterPlot scatterPlot; 36 private readonly ScatterPlotDataRow qualitiesRow; 37 private readonly ScatterPlotDataRow paretoFrontRow; 38 39 private int oldObjectives = -1; 40 private int oldProblemSize = -1; 41 42 private bool suppressEvents; 43 44 public new ParetoFrontScatterPlot Content { 45 get { return (ParetoFrontScatterPlot)base.Content; } 45 46 set { base.Content = value; } 46 47 } 47 48 48 public MultiObjectiveTestFunctionParetoFrontScatterPlotView() 49 : base() { 49 public ParetoFrontScatterPlotView() { 50 50 InitializeComponent(); 51 51 52 BuildEmptySeries();52 scatterPlot = new ScatterPlot(); 53 53 54 //start with qualities toggled ON 55 qualitySeries.Points.AddXY(0, 0); 54 qualitiesRow = new ScatterPlotDataRow("Qualities", string.Empty, Enumerable.Empty<Point2D<double>>()) { 55 VisualProperties = { 56 PointSize = 8 , 57 PointStyle = ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Circle 58 } 59 }; 60 scatterPlot.Rows.Add(qualitiesRow); 56 61 57 this.chart.TextAntiAliasingQuality = TextAntiAliasingQuality.High; 58 this.chart.AxisViewChanged += new EventHandler<System.Windows.Forms.DataVisualization.Charting.ViewEventArgs>(chart_AxisViewChanged); 59 this.chart.GetToolTipText += new System.EventHandler<ToolTipEventArgs>(this.Chart_GetToolTipText); 60 61 //configure axis 62 this.chart.CustomizeAllChartAreas(); 63 this.chart.ChartAreas[0].AxisX.Title = "Objective " + xDim; 64 this.chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true; 65 this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true; 66 this.chart.ChartAreas[0].CursorX.Interval = 1; 67 this.chart.ChartAreas[0].CursorY.Interval = 1; 68 69 this.chart.ChartAreas[0].AxisY.Title = "Objective " + yDim; 70 this.chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true; 71 this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true; 72 this.chart.ChartAreas[0].AxisY.IsStartedFromZero = true; 73 } 74 75 76 private void Chart_GetToolTipText(object sender, ToolTipEventArgs e) { 77 if (e.HitTestResult.ChartElementType == ChartElementType.LegendItem) { 78 if (e.HitTestResult.Series == paretoSeries && (Content.ParetoFront == null || Content.ParetoFront.Length == 0)) { 79 e.Text = "No optimal pareto front is available for this problem with this number of objectives"; 80 } 81 if (e.HitTestResult.Series == paretoSeries && (xDim >= Content.Objectives || yDim >= Content.Objectives)) { 82 e.Text = "The optimal pareto front can only be displayed in Objective Space"; 83 } 84 } 85 86 // Check selected chart element and set tooltip text 87 if (e.HitTestResult.ChartElementType == ChartElementType.DataPoint) { 88 int i = e.HitTestResult.PointIndex; 89 StringBuilder toolTippText = new StringBuilder(); 90 DataPoint qp = e.HitTestResult.Series.Points[i]; 91 toolTippText.Append("Objective " + xDim + " = " + qp.XValue + "\n"); 92 toolTippText.Append("Objective " + yDim + " = " + qp.YValues[0]); 93 94 Series s = e.HitTestResult.Series; 95 if (s.Equals(this.chart.Series[QUALITIES])) { 96 double[] dp = Content.Solutions[i]; 97 toolTippText.Append("\nSolution: {"); 98 for (int j = 0; j < dp.Length; j++) { 99 toolTippText.Append(dp[j]); 100 toolTippText.Append(";"); 62 paretoFrontRow = new ScatterPlotDataRow("Best Known Pareto Front", string.Empty, Enumerable.Empty<Point2D<double>>()) { 63 VisualProperties = { 64 PointSize = 4, 65 PointStyle = ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Square 101 66 } 102 toolTippText.Remove(toolTippText.Length - 1, 1); 103 toolTippText.Append("}"); 104 e.Text = toolTippText.ToString(); 105 } 106 107 108 } 67 }; 68 scatterPlot.Rows.Add(paretoFrontRow); 109 69 } 110 70 111 71 protected override void OnContentChanged() { 112 72 base.OnContentChanged(); 113 if (Content == null) return; 114 if (objectives != Content.Objectives) { 115 AddMenuItems(); 116 objectives = Content.Objectives; 73 74 if (Content == null) { 75 scatterPlotView.Content = null; 76 xAxisComboBox.Items.Clear(); 77 xAxisComboBox.SelectedIndex = -1; 78 yAxisComboBox.Items.Clear(); 79 yAxisComboBox.SelectedIndex = -1; 80 return; 117 81 } 118 if (Content.ParetoFront == null && chart.Series.Contains(paretoSeries)) {119 Series s = this.chart.Series[PARETO_FRONT];120 paretoSeries = null;121 this.chart.Series.Remove(s);122 82 123 } else if (Content.ParetoFront != null && !chart.Series.Contains(paretoSeries)) { 124 this.chart.Series.Add(PARETO_FRONT); 125 paretoSeries = this.chart.Series[PARETO_FRONT]; 126 this.chart.Series[PARETO_FRONT].LegendText = PARETO_FRONT; 127 this.chart.Series[PARETO_FRONT].ChartType = SeriesChartType.FastPoint; 128 } 129 UpdateChart(); 83 scatterPlotView.Content = scatterPlot; 84 85 if (oldObjectives != Content.Objectives || oldProblemSize != Content.ProblemSize) 86 UpdateAxisComboBoxes(); 87 88 UpdateChartData(); 89 90 oldObjectives = Content.Objectives; 91 oldProblemSize = Content.ProblemSize; 130 92 } 131 93 132 private void UpdateChart() { 133 if (InvokeRequired) Invoke((Action)UpdateChart); 134 else { 135 if (Content != null) { 136 this.UpdateSeries(); 137 if (!this.chart.Series.Any(s => s.Points.Count > 0)) 138 this.ClearChart(); 94 private void UpdateChartData() { 95 if (InvokeRequired) { 96 Invoke((Action)UpdateChartData); 97 return; 98 } 99 100 int xDimGlobal = xAxisComboBox.SelectedIndex; 101 int yDimGlobal = yAxisComboBox.SelectedIndex; 102 103 qualitiesRow.Points.Replace(CreatePoints(Content.Qualities, Content.Solutions, xDimGlobal, yDimGlobal)); 104 105 paretoFrontRow.Points.Replace(CreatePoints(Content.ParetoFront, null, xDimGlobal, yDimGlobal)); 106 paretoFrontRow.VisualProperties.IsVisibleInLegend = paretoFrontRow.Points.Count > 0; // hide if empty 107 } 108 109 private void UpdateAxisComboBoxes() { 110 try { 111 suppressEvents = true; 112 113 string prevSelectedX = (string)xAxisComboBox.SelectedItem; 114 string prevSelectedY = (string)yAxisComboBox.SelectedItem; 115 116 xAxisComboBox.Items.Clear(); 117 yAxisComboBox.Items.Clear(); 118 119 // Add Objectives first 120 for (int i = 0; i < Content.Objectives; i++) { 121 xAxisComboBox.Items.Add("Objective " + i); 122 yAxisComboBox.Items.Add("Objective " + i); 139 123 } 124 125 // Add Problem Dimension 126 for (int i = 0; i < Content.ProblemSize; i++) { 127 xAxisComboBox.Items.Add("Problem Dimension " + i); 128 yAxisComboBox.Items.Add("Problem Dimension " + i); 129 } 130 131 // Selection 132 int count = xAxisComboBox.Items.Count; 133 if (count > 0) { 134 if (prevSelectedX != null && xAxisComboBox.Items.Contains(prevSelectedX)) 135 xAxisComboBox.SelectedItem = prevSelectedX; 136 else xAxisComboBox.SelectedIndex = 0; 137 138 if (prevSelectedY != null && yAxisComboBox.Items.Contains(prevSelectedY)) 139 yAxisComboBox.SelectedItem = prevSelectedY; 140 else yAxisComboBox.SelectedIndex = Math.Min(1, count - 1); 141 } else { 142 xAxisComboBox.SelectedIndex = -1; 143 yAxisComboBox.SelectedIndex = -1; 144 } 145 146 UpdateAxisDescription(); 147 } finally { 148 suppressEvents = false; 140 149 } 141 150 } 142 151 143 private void UpdateCursorInterval() { 144 var estimatedValues = this.chart.Series[QUALITIES].Points.Select(x => x.XValue).DefaultIfEmpty(1.0); 145 var targetValues = this.chart.Series[QUALITIES].Points.Select(x => x.YValues[0]).DefaultIfEmpty(1.0); 146 double estimatedValuesRange = estimatedValues.Max() - estimatedValues.Min(); 147 double targetValuesRange = targetValues.Max() - targetValues.Min(); 148 double interestingValuesRange = Math.Min(Math.Max(targetValuesRange, 1.0), Math.Max(estimatedValuesRange, 1.0)); 149 double digits = (int)Math.Log10(interestingValuesRange) - 3; 150 double zoomInterval = Math.Max(Math.Pow(10, digits), 10E-5); 151 this.chart.ChartAreas[0].CursorX.Interval = zoomInterval; 152 this.chart.ChartAreas[0].CursorY.Interval = zoomInterval; 153 154 this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = zoomInterval; 155 this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = zoomInterval; 156 157 this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSizeType = DateTimeIntervalType.Number; 158 this.chart.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSize = zoomInterval; 159 this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSizeType = DateTimeIntervalType.Number; 160 this.chart.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSize = zoomInterval; 161 162 if (digits < 0) { 163 this.chart.ChartAreas[0].AxisX.LabelStyle.Format = "F" + (int)Math.Abs(digits); 164 this.chart.ChartAreas[0].AxisY.LabelStyle.Format = "F" + (int)Math.Abs(digits); 165 } else { 166 this.chart.ChartAreas[0].AxisX.LabelStyle.Format = "F0"; 167 this.chart.ChartAreas[0].AxisY.LabelStyle.Format = "F0"; 168 } 152 private void UpdateAxisDescription() { 153 scatterPlot.VisualProperties.XAxisTitle = (string)xAxisComboBox.SelectedItem; 154 scatterPlot.VisualProperties.YAxisTitle = (string)yAxisComboBox.SelectedItem; 169 155 } 170 156 171 private void UpdateSeries() { 172 if (InvokeRequired) Invoke((Action)UpdateSeries); 173 else { 157 private static Point2D<double>[] CreatePoints(double[][] qualities, double[][] solutions, int xDimGlobal, int yDimGlobal) { 158 if (qualities == null || qualities.Length == 0) return new Point2D<double>[0]; 174 159 175 if (this.chart.Series.Contains(qualitySeries) && qualitySeries.Points.Count() != 0) { 176 FillSeries(Content.Qualities, Content.Solutions, qualitySeries); 177 } 178 if (this.chart.Series.Contains(paretoSeries) && paretoSeries.Points.Count() != 0) { 179 FillSeries(Content.ParetoFront, null, paretoSeries); 180 } 160 int objectives = qualities[0].Length; 181 161 162 // "Global" dimension index describes the index as if the qualities and solutions would be in a single array 163 // If the global dimension index is too long for the qualities, use solutions 164 var xDimArray = xDimGlobal < objectives ? qualities : solutions; 165 var yDimArray = yDimGlobal < objectives ? qualities : solutions; 166 var xDimIndex = xDimGlobal < objectives ? xDimGlobal : xDimGlobal - objectives; 167 var yDimIndex = yDimGlobal < objectives ? yDimGlobal : yDimGlobal - objectives; 182 168 183 double minX = Double.MaxValue; 184 double maxX = Double.MinValue; 185 double minY = Double.MaxValue; 186 double maxY = Double.MinValue; 187 foreach (Series s in this.chart.Series) { 188 if (s.Points.Count == 0) continue; 189 minX = Math.Min(minX, s.Points.Select(p => p.XValue).Min()); 190 maxX = Math.Max(maxX, s.Points.Select(p => p.XValue).Max()); 191 minY = Math.Min(minY, s.Points.Select(p => p.YValues.Min()).Min()); 192 maxY = Math.Max(maxY, s.Points.Select(p => p.YValues.Max()).Max()); 193 } 169 if (xDimArray == null || yDimArray == null) 170 return new Point2D<double>[0]; 194 171 195 maxX = maxX + 0.2 * Math.Abs(maxX); 196 minX = minX - 0.2 * Math.Abs(minX); 197 maxY = maxY + 0.2 * Math.Abs(maxY); 198 minY = minY - 0.2 * Math.Abs(minY); 199 200 double interestingValuesRangeX = maxX - minX; 201 double interestingValuesRangeY = maxY - minY; 202 203 int digitsX = Math.Max(0, 3 - (int)Math.Log10(interestingValuesRangeX)); 204 int digitsY = Math.Max(0, 3 - (int)Math.Log10(interestingValuesRangeY)); 205 206 207 maxX = Math.Round(maxX, digitsX); 208 minX = Math.Round(minX, digitsX); 209 maxY = Math.Round(maxY, digitsY); 210 minY = Math.Round(minY, digitsY); 211 if (minX > maxX) { 212 minX = 0; 213 maxX = 1; 214 } 215 if (minY > maxY) { 216 minY = 0; 217 maxY = 1; 218 } 219 220 221 this.chart.ChartAreas[0].AxisX.Maximum = maxX; 222 this.chart.ChartAreas[0].AxisX.Minimum = minX; 223 this.chart.ChartAreas[0].AxisY.Maximum = maxY; 224 this.chart.ChartAreas[0].AxisY.Minimum = minY; 225 UpdateCursorInterval(); 172 var points = new Point2D<double>[xDimArray.Length]; 173 for (int i = 0; i < xDimArray.Length; i++) { 174 points[i] = new Point2D<double>(xDimArray[i][xDimIndex], yDimArray[i][yDimIndex]); 226 175 } 176 return points; 227 177 } 228 178 229 private void ClearChart() { 230 if (chart.Series.Contains(qualitySeries)) chart.Series.Remove(qualitySeries); 231 if (chart.Series.Contains(paretoSeries)) chart.Series.Remove(paretoSeries); 232 BuildEmptySeries(); 179 #region Event Handler 180 private void axisComboBox_SelectedIndexChanged(object sender, EventArgs e) { 181 if (suppressEvents) return; 182 UpdateAxisDescription(); 183 UpdateChartData(); 233 184 } 234 235 private void ToggleSeriesData(Series series) { 236 if (series.Points.Count > 0) { //checks if series is shown 237 series.Points.Clear(); 238 } else if (Content != null) { 239 switch (series.Name) { 240 case PARETO_FRONT: 241 FillSeries(Content.ParetoFront, null, this.chart.Series[PARETO_FRONT]); 242 break; 243 case QUALITIES: 244 FillSeries(Content.Qualities, Content.Solutions, this.chart.Series[QUALITIES]); 245 break; 246 } 247 } 248 } 249 250 private void chart_MouseDown(object sender, MouseEventArgs e) { 251 HitTestResult result = chart.HitTest(e.X, e.Y); 252 if (result.ChartElementType == ChartElementType.LegendItem) { 253 this.ToggleSeriesData(result.Series); 254 } 255 256 } 257 258 private void chart_MouseMove(object sender, MouseEventArgs e) { 259 HitTestResult result = chart.HitTest(e.X, e.Y); 260 if (result.ChartElementType == ChartElementType.LegendItem) 261 this.Cursor = Cursors.Hand; 262 else 263 this.Cursor = Cursors.Default; 264 } 265 266 private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) { 267 this.chart.ChartAreas[0].AxisX.ScaleView.Size = e.NewSize; 268 this.chart.ChartAreas[0].AxisY.ScaleView.Size = e.NewSize; 269 } 270 271 private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) { 272 if (this.chart.Series.Contains(qualitySeries)) e.LegendItems[0].Cells[1].ForeColor = this.chart.Series[QUALITIES].Points.Count == 0 ? Color.Gray : Color.Black; 273 if (this.chart.Series.Contains(paretoSeries)) e.LegendItems[1].Cells[1].ForeColor = this.chart.Series[PARETO_FRONT].Points.Count == 0 ? Color.Gray : Color.Black; 274 } 275 276 private void AddMenuItems() { 277 chooseDimensionToolStripMenuItem.DropDownItems.Clear(); 278 chooseYDimensionToolStripMenuItem.DropDownItems.Clear(); 279 if (Content == null) { return; } 280 int i = 0; 281 for (; i < Content.Objectives; i++) { 282 //add Menu Points 283 ToolStripMenuItem xItem = MakeMenuItem("X", "Objective " + i, i); 284 ToolStripMenuItem yItem = MakeMenuItem("Y", "Objective " + i, i); 285 xItem.Click += new System.EventHandler(this.XMenu_Click); 286 yItem.Click += new System.EventHandler(this.YMenu_Click); 287 chooseDimensionToolStripMenuItem.DropDownItems.Add(xItem); 288 chooseYDimensionToolStripMenuItem.DropDownItems.Add(yItem); 289 } 290 291 for (; i < Content.Solutions[0].Length + Content.Objectives; i++) { 292 ToolStripMenuItem xItem = MakeMenuItem("X", "ProblemDimension " + (i - Content.Objectives), i); 293 ToolStripMenuItem yItem = MakeMenuItem("Y", "ProblemDimension " + (i - Content.Objectives), i); ; 294 xItem.Click += new System.EventHandler(this.XMenu_Click); 295 yItem.Click += new System.EventHandler(this.YMenu_Click); 296 chooseDimensionToolStripMenuItem.DropDownItems.Add(xItem); 297 chooseYDimensionToolStripMenuItem.DropDownItems.Add(yItem); 298 } 299 } 300 301 private ToolStripMenuItem MakeMenuItem(String axis, String label, int i) { 302 ToolStripMenuItem xItem = new ToolStripMenuItem(); 303 xItem.Name = "obj" + i; 304 xItem.Size = new System.Drawing.Size(269, 38); 305 xItem.Text = label; 306 return xItem; 307 } 308 309 private void YMenu_Click(object sender, EventArgs e) { 310 ToolStripMenuItem item = (ToolStripMenuItem)sender; 311 yDim = Int32.Parse(item.Name.Remove(0, 3)); 312 String label = item.Text; 313 this.chooseYDimensionToolStripMenuItem.Text = label; 314 this.chart.ChartAreas[0].AxisY.Title = label; 315 UpdateChart(); 316 } 317 318 private void XMenu_Click(object sender, EventArgs e) { 319 ToolStripMenuItem item = (ToolStripMenuItem)sender; 320 xDim = Int32.Parse(item.Name.Remove(0, 3)); 321 String label = item.Text; 322 this.chooseDimensionToolStripMenuItem.Text = label; 323 this.chart.ChartAreas[0].AxisX.Title = label; 324 UpdateChart(); 325 } 326 327 private void FillSeries(double[][] qualities, double[][] solutions, Series series) { 328 series.Points.Clear(); 329 if (qualities == null || qualities.Length == 0) return; 330 int jx = xDim - qualities[0].Length; 331 int jy = yDim - qualities[0].Length; 332 if ((jx >= 0 || jy >= 0) && solutions == null) { 333 return; 334 } 335 for (int i = 0; i < qualities.Length; i++) { //Assumtion: Columnwise 336 double[] d = qualities[i]; 337 double[] q = null; 338 if (jx >= 0 || jy >= 0) { q = solutions[i]; } 339 series.Points.AddXY(jx < 0 ? d[xDim] : q[jx], jy < 0 ? d[yDim] : q[jy]); 340 } 341 } 342 343 private void BuildEmptySeries() { 344 345 this.chart.Series.Add(QUALITIES); 346 qualitySeries = this.chart.Series[QUALITIES]; 347 348 this.chart.Series[QUALITIES].LegendText = QUALITIES; 349 this.chart.Series[QUALITIES].ChartType = SeriesChartType.FastPoint; 350 351 this.chart.Series.Add(PARETO_FRONT); 352 paretoSeries = this.chart.Series[PARETO_FRONT]; 353 paretoSeries.Color = Color.FromArgb(100, Color.Orange); 354 this.chart.Series[PARETO_FRONT].LegendText = PARETO_FRONT; 355 this.chart.Series[PARETO_FRONT].ChartType = SeriesChartType.FastPoint; 356 } 185 #endregion 357 186 } 358 187 } 359
Note: See TracChangeset
for help on using the changeset viewer.