1  #region License Information


2  /* HeuristicLab


3  * Copyright (C) 20022014 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.Drawing;


24  using System.Globalization;


25  using System.Text;


26  using System.Text.RegularExpressions;


27  using System.Windows.Forms;


28  using HeuristicLab.Analysis;


29  using HeuristicLab.Common;


30  using HeuristicLab.Common.Resources;


31  using HeuristicLab.Data;


32  using HeuristicLab.Encodings.PermutationEncoding;


33 


34  namespace HeuristicLab.Problems.QuadraticAssignment.Views {


35  public partial class QAPVisualizationControl : UserControl {


36  private Bitmap bitmap;


37  private Bitmap defaultBitmap;


38  private bool showingMessage;


39 


40  #region Properties


41  private DoubleMatrix distances;


42  public DoubleMatrix Distances {


43  get { return distances; }


44  set {


45  DeregisterDistancesEvents();


46  distances = value;


47  RegisterDistancesEvents();


48  OnRedraw();


49  }


50  }


51 


52  private DoubleMatrix weights;


53  public DoubleMatrix Weights {


54  get { return weights; }


55  set {


56  DeregisterWeightsEvents();


57  weights = value;


58  RegisterWeightsEvents();


59  OnRedraw();


60  }


61  }


62 


63  private Permutation assignment;


64  public Permutation Assignment {


65  get { return assignment; }


66  set {


67  DeregisterAssignmentEvents();


68  assignment = value;


69  RegisterAssignmentEvents();


70  OnRedraw();


71  }


72  }


73  #endregion


74 


75  #region Event Handling


76  private void DeregisterDistancesEvents() {


77  if (Distances != null) {


78  Distances.Reset = new EventHandler(RedrawNecessary);


79  Distances.RowsChanged = new EventHandler(RedrawNecessary);


80  Distances.ColumnsChanged = new EventHandler(RedrawNecessary);


81  Distances.ItemChanged = new EventHandler<EventArgs<int, int>>(RedrawNecessary);


82  }


83  }


84 


85  private void RegisterDistancesEvents() {


86  if (Distances != null) {


87  Distances.Reset += new EventHandler(RedrawNecessary);


88  Distances.RowsChanged += new EventHandler(RedrawNecessary);


89  Distances.ColumnsChanged += new EventHandler(RedrawNecessary);


90  Distances.ItemChanged += new EventHandler<EventArgs<int, int>>(RedrawNecessary);


91  }


92  }


93 


94  private void DeregisterWeightsEvents() {


95  if (Weights != null) {


96  Weights.Reset = new EventHandler(RedrawNecessary);


97  Weights.RowsChanged = new EventHandler(RedrawNecessary);


98  Weights.ColumnsChanged = new EventHandler(RedrawNecessary);


99  Weights.ItemChanged = new EventHandler<EventArgs<int, int>>(RedrawNecessary);


100  }


101  }


102 


103  private void RegisterWeightsEvents() {


104  if (Weights != null) {


105  Weights.Reset += new EventHandler(RedrawNecessary);


106  Weights.RowsChanged += new EventHandler(RedrawNecessary);


107  Weights.ColumnsChanged += new EventHandler(RedrawNecessary);


108  Weights.ItemChanged += new EventHandler<EventArgs<int, int>>(RedrawNecessary);


109  }


110  }


111 


112  private void DeregisterAssignmentEvents() {


113  if (Assignment != null) {


114  Assignment.Reset = new EventHandler(RedrawNecessary);


115  Assignment.ItemChanged = new EventHandler<EventArgs<int>>(RedrawNecessary);


116  }


117  }


118 


119  private void RegisterAssignmentEvents() {


120  if (Assignment != null) {


121  Assignment.Reset += new EventHandler(RedrawNecessary);


122  Assignment.ItemChanged += new EventHandler<EventArgs<int>>(RedrawNecessary);


123  }


124  }


125 


126  private void redrawButton_Click(object sender, EventArgs e) {


127  OnRedraw();


128  }


129 


130  private void radio_CheckedChanged(object sender, EventArgs e) {


131  RadioButton rb = (sender as RadioButton);


132  if (rb != null && !rb.Checked) return;


133  else OnRedraw();


134  }


135 


136  private void RedrawNecessary(object sender, EventArgs e) {


137  MarkRedrawNecessary();


138  }


139 


140  private void RedrawNecessary(object sender, EventArgs<int, int> e) {


141  MarkRedrawNecessary();


142  }


143 


144  private void pictureBox_SizeChanged(object sender, EventArgs e) {


145  SetupDefaultBitmap();


146  if (!showingMessage) MarkRedrawNecessary();


147  else OnRedraw();


148  }


149  #endregion


150 


151  public QAPVisualizationControl() {


152  InitializeComponent();


153  showingMessage = false;


154  redrawButton.Text = String.Empty;


155  redrawButton.Image = VSImageLibrary.Refresh;


156  SetupDefaultBitmap();


157  }


158 


159  private void SetupDefaultBitmap() {


160  if (defaultBitmap != null) {


161  defaultBitmap.Dispose();


162  defaultBitmap = null;


163  }


164  if (pictureBox.Width > 0 && pictureBox.Height > 0) {


165  defaultBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


166  WriteCenteredTextToBitmap(ref defaultBitmap, "No visualization available");


167  }


168  }


169 


170  private void WriteCenteredTextToBitmap(ref Bitmap bitmap, string text) {


171  if (bitmap == null) return;


172  using (Graphics g = Graphics.FromImage(bitmap)) {


173  g.Clear(Color.White);


174 


175  Font font = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular);


176  SizeF strSize = g.MeasureString(text, font);


177  if (strSize.Width + 50 > pictureBox.Width) {


178  Match m = Regex.Match(text, @"\b\w+[.,]*\b*");


179  StringBuilder builder = new StringBuilder();


180  while (m.Success) {


181  builder.Append(m.Value + " ");


182  Match next = m.NextMatch();


183  if (g.MeasureString(builder.ToString() + " " + next.Value, font).Width + 50 > pictureBox.Width)


184  builder.AppendLine();


185  m = next;


186  }


187  builder.Remove(builder.Length  1, 1);


188  text = builder.ToString();


189  strSize = g.MeasureString(text, font);


190  }


191  g.DrawString(text, font, Brushes.Black, (float)(pictureBox.Width  strSize.Width) / 2.0f, (float)(pictureBox.Height  strSize.Height) / 2.0f);


192  }


193  }


194 


195  private void OnRedraw() {


196  if (InvokeRequired) {


197  Invoke((Action)OnRedraw, null);


198  } else {


199  GenerateImage();


200  }


201  }


202 


203  private void GenerateImage() {


204  if (pictureBox.Width > 0 && pictureBox.Height > 0) {


205  Bitmap newBitmap = null;


206  stressLabel.Text = "";


207  stressLabel.ForeColor = Color.Black;


208  if (distancesRadioButton.Checked && Distances != null && Distances.Rows > 0


209  && Distances.Rows == Distances.Columns) {


210  if (Distances.Rows > 50) {


211  newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


212  WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");


213  showingMessage = true;


214  } else newBitmap = GenerateDistanceImage();


215  } else if (weightsRadioButton.Checked && Weights != null && Weights.Rows > 0


216  && Weights.Rows == Weights.Columns) {


217  if (Weights.Rows > 50) {


218  newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


219  WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");


220  showingMessage = true;


221  } else newBitmap = GenerateWeightsImage();


222  } else if (assignmentRadioButton.Checked


223  && Assignment != null && Assignment.Length > 0


224  && Weights != null && Weights.Rows > 0


225  && Distances != null && Distances.Rows > 0


226  && Weights.Rows == Weights.Columns


227  && Distances.Rows == Distances.Columns


228  && Assignment.Length == Weights.Rows


229  && Assignment.Length == Distances.Rows


230  && Assignment.Validate()) {


231  if (Assignment.Length > 50) {


232  newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


233  WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");


234  showingMessage = true;


235  } else newBitmap = GenerateAssignmentImage();


236  }


237 


238  pictureBox.Image = newBitmap != null ? newBitmap : defaultBitmap;


239  if (bitmap != null) bitmap.Dispose();


240  if (newBitmap != null) bitmap = newBitmap;


241  else {


242  bitmap = null;


243  showingMessage = true;


244  }


245  }


246  }


247 


248  private void MarkRedrawNecessary() {


249  if (pictureBox.Width > 0 && pictureBox.Height > 0) {


250  Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


251  stressLabel.Text = "";


252  stressLabel.ForeColor = Color.Black;


253  WriteCenteredTextToBitmap(ref newBitmap, "Please refresh view.");


254  showingMessage = false; // we're showing a message, but we should be showing the visualization, so this is false


255 


256  pictureBox.Image = newBitmap != null ? newBitmap : defaultBitmap;


257  if (bitmap != null) bitmap.Dispose();


258  if (newBitmap != null) bitmap = newBitmap;


259  else bitmap = null;


260  }


261  }


262 


263  #region Draw distances


264  private Bitmap GenerateDistanceImage() {


265  if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {


266  Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


267 


268  DoubleMatrix coordinates;


269  double stress = double.NaN;


270  try {


271  coordinates = MultidimensionalScaling.KruskalShepard(distances);


272  stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);


273  stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);


274  if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;


275  else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;


276  else stressLabel.ForeColor = Color.DarkRed;


277  } catch {


278  WriteCenteredTextToBitmap(ref newBitmap, "Distance matrix is not symmetric");


279  showingMessage = true;


280  stressLabel.Text = "";


281  stressLabel.ForeColor = Color.Black;


282  return newBitmap;


283  }


284  double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;


285  double maxDistance = double.MinValue;


286  for (int i = 0; i < coordinates.Rows; i++) {


287  if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];


288  if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];


289  if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];


290  if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];


291 


292  for (int j = i + 1; j < coordinates.Rows; j++) {


293  if (distances[i, j] > maxDistance) maxDistance = distances[i, j];


294  if (distances[j, i] > maxDistance) maxDistance = distances[j, i];


295  }


296  }


297 


298  int border = 20;


299  double xStep = xMax != xMin ? (pictureBox.Width  2 * border) / (xMax  xMin) : 1;


300  double yStep = yMax != yMin ? (pictureBox.Height  2 * border) / (yMax  yMin) : 1;


301 


302  Point[] points = new Point[coordinates.Rows];


303  for (int i = 0; i < coordinates.Rows; i++)


304  points[i] = new Point(border + ((int)((coordinates[i, 0]  xMin) * xStep)),


305  newBitmap.Height  (border + ((int)((coordinates[i, 1]  yMin) * yStep))));


306 


307  Random rand = new Random();


308  using (Graphics graphics = Graphics.FromImage(newBitmap)) {


309  graphics.Clear(Color.White);


310  graphics.DrawString("Showing locations layed out according to their distances", Font, Brushes.Black, 5, 2);


311 


312  for (int i = 0; i < coordinates.Rows  1; i++) {


313  for (int j = i + 1; j < coordinates.Rows; j++) {


314  Point start = points[i], end = points[j];


315  string caption = String.Empty;


316  double d = Math.Max(distances[i, j], distances[j, i]);


317  float width = (float)Math.Ceiling(5.0 * d / maxDistance);


318  if (d > 0) {


319  graphics.DrawLine(new Pen(Color.IndianRed, width), start, end);


320  if (distances[i, j] != distances[j, i])


321  caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat)


322  + " / " + distances[j, i].ToString(CultureInfo.InvariantCulture.NumberFormat);


323  else


324  caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat);


325  }


326  if (!String.IsNullOrEmpty(caption)) {


327  double r = rand.NextDouble();


328  while (r < 0.2  r > 0.8) r = rand.NextDouble();


329  float x = (float)(start.X + (end.X  start.X) * r + 5);


330  float y = (float)(start.Y + (end.Y  start.Y) * r + 5);


331  graphics.DrawString(caption, Font, Brushes.Black, x, y);


332  }


333  }


334  }


335 


336  for (int i = 0; i < points.Length; i++) {


337  Point p = new Point(points[i].X  3, points[i].Y  3);


338  graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);


339  graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);


340  }


341  }


342  showingMessage = false;


343  return newBitmap;


344  }


345  return null;


346  }


347  #endregion


348 


349  #region Draw weights


350  private Bitmap GenerateWeightsImage() {


351  if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {


352  Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


353 


354  double maxWeight = double.MinValue;


355  for (int i = 0; i < weights.Rows; i++)


356  for (int j = i + 1; j < weights.Rows; j++) {


357  if (weights[i, j] > maxWeight) maxWeight = weights[i, j];


358  if (weights[j, i] > maxWeight) maxWeight = weights[j, i];


359  }


360 


361  DoubleMatrix distances = new DoubleMatrix(weights.Rows, weights.Columns);


362  for (int i = 0; i < distances.Rows; i++)


363  for (int j = 0; j < distances.Columns; j++) {


364  if (weights[i, j] == 0) distances[i, j] = double.NaN;


365  else distances[i, j] = maxWeight / weights[i, j];


366  }


367 


368  DoubleMatrix coordinates;


369  double stress = double.NaN;


370  try {


371  coordinates = MultidimensionalScaling.KruskalShepard(distances);


372  stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);


373  stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);


374  if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;


375  else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;


376  else stressLabel.ForeColor = Color.DarkRed;


377  } catch {


378  WriteCenteredTextToBitmap(ref newBitmap, "Weights matrix is not symmetric");


379  showingMessage = true;


380  stressLabel.Text = "";


381  stressLabel.ForeColor = Color.Black;


382  return newBitmap;


383  }


384  double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;


385  for (int i = 0; i < coordinates.Rows; i++) {


386  if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];


387  if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];


388  if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];


389  if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];


390  }


391 


392  int border = 20;


393  double xStep = xMax != xMin ? (pictureBox.Width  2 * border) / (xMax  xMin) : 1;


394  double yStep = yMax != yMin ? (pictureBox.Height  2 * border) / (yMax  yMin) : 1;


395 


396  Point[] points = new Point[coordinates.Rows];


397  for (int i = 0; i < coordinates.Rows; i++)


398  points[i] = new Point(border + ((int)((coordinates[i, 0]  xMin) * xStep)),


399  newBitmap.Height  (border + ((int)((coordinates[i, 1]  yMin) * yStep))));


400 


401  Random rand = new Random();


402  using (Graphics graphics = Graphics.FromImage(newBitmap)) {


403  graphics.Clear(Color.White);


404  graphics.DrawString("Showing facilities layed out according to their weights", Font, Brushes.Black, 5, 2);


405 


406  for (int i = 0; i < coordinates.Rows  1; i++) {


407  for (int j = i + 1; j < coordinates.Rows; j++) {


408  Point start = points[i], end = points[j];


409  string caption = String.Empty;


410  double d = Math.Max(distances[i, j], distances[j, i]);


411  double w = weights[i, j];


412  if (w > 0) {


413  float width = (float)Math.Ceiling(3.0 * w / maxWeight);


414  graphics.DrawLine(new Pen(Color.MediumBlue, width), start, end);


415  caption = w.ToString(CultureInfo.InvariantCulture.NumberFormat);


416  }


417  if (!String.IsNullOrEmpty(caption)) {


418  double r = rand.NextDouble();


419  while (r < 0.2  r > 0.8) r = rand.NextDouble();


420  float x = (float)(start.X + (end.X  start.X) * r + 5);


421  float y = (float)(start.Y + (end.Y  start.Y) * r + 5);


422  graphics.DrawString(caption, Font, Brushes.Black, x, y);


423  }


424  }


425  }


426  for (int i = 0; i < points.Length; i++) {


427  Point p = new Point(points[i].X  3, points[i].Y  3);


428  graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);


429  graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);


430  }


431  }


432  showingMessage = false;


433  return newBitmap;


434  }


435  return null;


436  }


437  #endregion


438 


439  #region Draw assignment


440  private Bitmap GenerateAssignmentImage() {


441  if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {


442  Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);


443 


444  for (int i = 0; i < distances.Rows; i++) {


445  for (int j = i + 1; j < distances.Rows; j++) {


446  if (distances[i, j] != distances[j, i]) {


447  WriteCenteredTextToBitmap(ref newBitmap, "Distance matrix is not symmetric");


448  stressLabel.Text = "";


449  showingMessage = true;


450  return newBitmap;


451  }


452  if (weights[i, j] != weights[j, i]) {


453  WriteCenteredTextToBitmap(ref newBitmap, "Weights matrix is not symmetric");


454  stressLabel.Text = "";


455  showingMessage = true;


456  return newBitmap;


457  }


458  }


459  }


460 


461  DoubleMatrix coordinates = null;


462  double stress = double.NaN;


463  try {


464  coordinates = MultidimensionalScaling.KruskalShepard(distances);


465  stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);


466  stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);


467  if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;


468  else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;


469  else stressLabel.ForeColor = Color.DarkRed;


470  } catch {


471  WriteCenteredTextToBitmap(ref newBitmap, "Unknown error");


472  showingMessage = true;


473  stressLabel.Text = "";


474  stressLabel.ForeColor = Color.Black;


475  return newBitmap;


476  }


477 


478  double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;


479  double maxWeight = double.MinValue;


480  for (int i = 0; i < coordinates.Rows; i++) {


481  if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];


482  if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];


483  if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];


484  if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];


485 


486  for (int j = i + 1; j < coordinates.Rows; j++) {


487  if (weights[i, j] > maxWeight) maxWeight = weights[i, j];


488  }


489  }


490 


491  int border = 20;


492  double xStep = xMax != xMin ? (pictureBox.Width  2 * border) / (xMax  xMin) : 1;


493  double yStep = yMax != yMin ? (pictureBox.Height  2 * border) / (yMax  yMin) : 1;


494 


495  Point[] points = new Point[coordinates.Rows];


496  for (int i = 0; i < coordinates.Rows; i++)


497  points[i] = new Point(border + ((int)((coordinates[i, 0]  xMin) * xStep)),


498  newBitmap.Height  (border + ((int)((coordinates[i, 1]  yMin) * yStep))));


499 


500  Random rand = new Random();


501  using (Graphics graphics = Graphics.FromImage(newBitmap)) {


502  graphics.Clear(Color.White);


503  for (int i = 0; i < assignment.Length  1; i++) {


504  for (int j = i + 1; j < assignment.Length; j++) {


505  Point start, end;


506  string caption = String.Empty;


507  double d = distances[i, j];


508  start = points[assignment[i]];


509  end = points[assignment[j]];


510  double w = weights[i, j];


511  if (w > 0) {


512  float width = (float)Math.Ceiling(4.0 * w / maxWeight);


513  graphics.DrawLine(new Pen(Color.MediumBlue, width), start, end);


514  caption = w.ToString(CultureInfo.InvariantCulture.NumberFormat);


515  }


516  if (!String.IsNullOrEmpty(caption)) {


517  double r = rand.NextDouble();


518  while (r < 0.2  r > 0.8) r = rand.NextDouble();


519  float x = (float)(start.X + (end.X  start.X) * r + 5);


520  float y = (float)(start.Y + (end.Y  start.Y) * r + 5);


521  graphics.DrawString(caption, Font, Brushes.Black, x, y);


522  }


523  }


524  }


525 


526  for (int i = 0; i < points.Length; i++) {


527  Point p = new Point(points[i].X  3, points[i].Y  3);


528  graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);


529  graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);


530  }


531  }


532  showingMessage = false;


533  return newBitmap;


534  }


535  return null;


536  }


537  #endregion


538 


539  private void CustomDispose(bool disposing) {


540  DeregisterDistancesEvents();


541  DeregisterWeightsEvents();


542  DeregisterAssignmentEvents();


543  if (bitmap != null) bitmap.Dispose();


544  bitmap = null;


545  if (defaultBitmap != null) {


546  defaultBitmap.Dispose();


547  defaultBitmap = null;


548  }


549  }


550  }


551  }

