#region License Information
/* HeuristicLab
* Copyright (C) 2002-2019 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.Drawing;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using HeuristicLab.Analysis;
using HeuristicLab.Common;
using HeuristicLab.Common.Resources;
using HeuristicLab.Data;
using HeuristicLab.Encodings.PermutationEncoding;
namespace HeuristicLab.Problems.QuadraticAssignment.Views {
public partial class QAPVisualizationControl : UserControl {
private Bitmap bitmap;
private Bitmap defaultBitmap;
private bool showingMessage;
#region Properties
private DoubleMatrix distances;
public DoubleMatrix Distances {
get { return distances; }
set {
DeregisterDistancesEvents();
distances = value;
RegisterDistancesEvents();
OnRedraw();
}
}
private DoubleMatrix weights;
public DoubleMatrix Weights {
get { return weights; }
set {
DeregisterWeightsEvents();
weights = value;
RegisterWeightsEvents();
OnRedraw();
}
}
private Permutation assignment;
public Permutation Assignment {
get { return assignment; }
set {
DeregisterAssignmentEvents();
assignment = value;
RegisterAssignmentEvents();
OnRedraw();
}
}
#endregion
#region Event Handling
private void DeregisterDistancesEvents() {
if (Distances != null) {
Distances.Reset -= new EventHandler(RedrawNecessary);
Distances.RowsChanged -= new EventHandler(RedrawNecessary);
Distances.ColumnsChanged -= new EventHandler(RedrawNecessary);
Distances.ItemChanged -= new EventHandler>(RedrawNecessary);
}
}
private void RegisterDistancesEvents() {
if (Distances != null) {
Distances.Reset += new EventHandler(RedrawNecessary);
Distances.RowsChanged += new EventHandler(RedrawNecessary);
Distances.ColumnsChanged += new EventHandler(RedrawNecessary);
Distances.ItemChanged += new EventHandler>(RedrawNecessary);
}
}
private void DeregisterWeightsEvents() {
if (Weights != null) {
Weights.Reset -= new EventHandler(RedrawNecessary);
Weights.RowsChanged -= new EventHandler(RedrawNecessary);
Weights.ColumnsChanged -= new EventHandler(RedrawNecessary);
Weights.ItemChanged -= new EventHandler>(RedrawNecessary);
}
}
private void RegisterWeightsEvents() {
if (Weights != null) {
Weights.Reset += new EventHandler(RedrawNecessary);
Weights.RowsChanged += new EventHandler(RedrawNecessary);
Weights.ColumnsChanged += new EventHandler(RedrawNecessary);
Weights.ItemChanged += new EventHandler>(RedrawNecessary);
}
}
private void DeregisterAssignmentEvents() {
if (Assignment != null) {
Assignment.Reset -= new EventHandler(RedrawNecessary);
Assignment.ItemChanged -= new EventHandler>(RedrawNecessary);
}
}
private void RegisterAssignmentEvents() {
if (Assignment != null) {
Assignment.Reset += new EventHandler(RedrawNecessary);
Assignment.ItemChanged += new EventHandler>(RedrawNecessary);
}
}
private void redrawButton_Click(object sender, EventArgs e) {
OnRedraw();
}
private void radio_CheckedChanged(object sender, EventArgs e) {
RadioButton rb = (sender as RadioButton);
if (rb != null && !rb.Checked) return;
else OnRedraw();
}
private void RedrawNecessary(object sender, EventArgs e) {
MarkRedrawNecessary();
}
private void RedrawNecessary(object sender, EventArgs e) {
MarkRedrawNecessary();
}
private void pictureBox_SizeChanged(object sender, EventArgs e) {
SetupDefaultBitmap();
if (!showingMessage) MarkRedrawNecessary();
else OnRedraw();
}
#endregion
public QAPVisualizationControl() {
InitializeComponent();
showingMessage = false;
redrawButton.Text = String.Empty;
redrawButton.Image = VSImageLibrary.Refresh;
SetupDefaultBitmap();
}
private void SetupDefaultBitmap() {
if (defaultBitmap != null) {
defaultBitmap.Dispose();
defaultBitmap = null;
}
if (pictureBox.Width > 0 && pictureBox.Height > 0) {
defaultBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
WriteCenteredTextToBitmap(ref defaultBitmap, "No visualization available");
}
}
private void WriteCenteredTextToBitmap(ref Bitmap bitmap, string text) {
if (bitmap == null) return;
using (Graphics g = Graphics.FromImage(bitmap)) {
g.Clear(Color.White);
Font font = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular);
SizeF strSize = g.MeasureString(text, font);
if (strSize.Width + 50 > pictureBox.Width) {
Match m = Regex.Match(text, @"\b\w+[.,]*\b*");
StringBuilder builder = new StringBuilder();
while (m.Success) {
builder.Append(m.Value + " ");
Match next = m.NextMatch();
if (g.MeasureString(builder.ToString() + " " + next.Value, font).Width + 50 > pictureBox.Width)
builder.AppendLine();
m = next;
}
builder.Remove(builder.Length - 1, 1);
text = builder.ToString();
strSize = g.MeasureString(text, font);
}
g.DrawString(text, font, Brushes.Black, (float)(pictureBox.Width - strSize.Width) / 2.0f, (float)(pictureBox.Height - strSize.Height) / 2.0f);
}
}
private void OnRedraw() {
if (InvokeRequired) {
Invoke((Action)OnRedraw, null);
} else {
GenerateImage();
}
}
private void GenerateImage() {
if (pictureBox.Width > 0 && pictureBox.Height > 0) {
Bitmap newBitmap = null;
stressLabel.Text = "-";
stressLabel.ForeColor = Color.Black;
if (distancesRadioButton.Checked && Distances != null && Distances.Rows > 0
&& Distances.Rows == Distances.Columns) {
if (Distances.Rows > 50) {
newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");
showingMessage = true;
} else newBitmap = GenerateDistanceImage();
} else if (weightsRadioButton.Checked && Weights != null && Weights.Rows > 0
&& Weights.Rows == Weights.Columns) {
if (Weights.Rows > 50) {
newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");
showingMessage = true;
} else newBitmap = GenerateWeightsImage();
} else if (assignmentRadioButton.Checked
&& Assignment != null && Assignment.Length > 0
&& Weights != null && Weights.Rows > 0
&& Distances != null && Distances.Rows > 0
&& Weights.Rows == Weights.Columns
&& Distances.Rows == Distances.Columns
&& Assignment.Length == Weights.Rows
&& Assignment.Length == Distances.Rows
&& Assignment.Validate()) {
if (Assignment.Length > 50) {
newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
WriteCenteredTextToBitmap(ref newBitmap, "Problem dimension is too large for visualization.");
showingMessage = true;
} else newBitmap = GenerateAssignmentImage();
}
pictureBox.Image = newBitmap != null ? newBitmap : defaultBitmap;
if (bitmap != null) bitmap.Dispose();
if (newBitmap != null) bitmap = newBitmap;
else {
bitmap = null;
showingMessage = true;
}
}
}
private void MarkRedrawNecessary() {
if (pictureBox.Width > 0 && pictureBox.Height > 0) {
Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
stressLabel.Text = "-";
stressLabel.ForeColor = Color.Black;
WriteCenteredTextToBitmap(ref newBitmap, "Please refresh view.");
showingMessage = false; // we're showing a message, but we should be showing the visualization, so this is false
pictureBox.Image = newBitmap != null ? newBitmap : defaultBitmap;
if (bitmap != null) bitmap.Dispose();
if (newBitmap != null) bitmap = newBitmap;
else bitmap = null;
}
}
#region Draw distances
private Bitmap GenerateDistanceImage() {
if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {
Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
DoubleMatrix coordinates;
double stress = double.NaN;
try {
coordinates = MultidimensionalScaling.KruskalShepard(distances);
stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);
stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);
if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;
else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;
else stressLabel.ForeColor = Color.DarkRed;
} catch {
WriteCenteredTextToBitmap(ref newBitmap, "Distance matrix is not symmetric");
showingMessage = true;
stressLabel.Text = "-";
stressLabel.ForeColor = Color.Black;
return newBitmap;
}
double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;
double maxDistance = double.MinValue;
for (int i = 0; i < coordinates.Rows; i++) {
if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];
if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];
if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];
if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];
for (int j = i + 1; j < coordinates.Rows; j++) {
if (distances[i, j] > maxDistance) maxDistance = distances[i, j];
if (distances[j, i] > maxDistance) maxDistance = distances[j, i];
}
}
int border = 20;
double xStep = xMax != xMin ? (pictureBox.Width - 2 * border) / (xMax - xMin) : 1;
double yStep = yMax != yMin ? (pictureBox.Height - 2 * border) / (yMax - yMin) : 1;
Point[] points = new Point[coordinates.Rows];
for (int i = 0; i < coordinates.Rows; i++)
points[i] = new Point(border + ((int)((coordinates[i, 0] - xMin) * xStep)),
newBitmap.Height - (border + ((int)((coordinates[i, 1] - yMin) * yStep))));
Random rand = new Random();
using (Graphics graphics = Graphics.FromImage(newBitmap)) {
graphics.Clear(Color.White);
graphics.DrawString("Showing locations layed out according to their distances", Font, Brushes.Black, 5, 2);
for (int i = 0; i < coordinates.Rows - 1; i++) {
for (int j = i + 1; j < coordinates.Rows; j++) {
Point start = points[i], end = points[j];
string caption = String.Empty;
double d = Math.Max(distances[i, j], distances[j, i]);
float width = (float)Math.Ceiling(5.0 * d / maxDistance);
if (d > 0) {
graphics.DrawLine(new Pen(Color.IndianRed, width), start, end);
if (distances[i, j] != distances[j, i])
caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat)
+ " / " + distances[j, i].ToString(CultureInfo.InvariantCulture.NumberFormat);
else
caption = distances[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat);
}
if (!String.IsNullOrEmpty(caption)) {
double r = rand.NextDouble();
while (r < 0.2 || r > 0.8) r = rand.NextDouble();
float x = (float)(start.X + (end.X - start.X) * r + 5);
float y = (float)(start.Y + (end.Y - start.Y) * r + 5);
graphics.DrawString(caption, Font, Brushes.Black, x, y);
}
}
}
for (int i = 0; i < points.Length; i++) {
Point p = new Point(points[i].X - 3, points[i].Y - 3);
graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);
graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);
}
}
showingMessage = false;
return newBitmap;
}
return null;
}
#endregion
#region Draw weights
private Bitmap GenerateWeightsImage() {
if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {
Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
double maxWeight = double.MinValue;
for (int i = 0; i < weights.Rows; i++)
for (int j = i + 1; j < weights.Rows; j++) {
if (weights[i, j] > maxWeight) maxWeight = weights[i, j];
if (weights[j, i] > maxWeight) maxWeight = weights[j, i];
}
DoubleMatrix distances = new DoubleMatrix(weights.Rows, weights.Columns);
for (int i = 0; i < distances.Rows; i++)
for (int j = 0; j < distances.Columns; j++) {
if (weights[i, j] == 0) distances[i, j] = double.NaN;
else distances[i, j] = maxWeight / weights[i, j];
}
DoubleMatrix coordinates;
double stress = double.NaN;
try {
coordinates = MultidimensionalScaling.KruskalShepard(distances);
stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);
stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);
if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;
else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;
else stressLabel.ForeColor = Color.DarkRed;
} catch {
WriteCenteredTextToBitmap(ref newBitmap, "Weights matrix is not symmetric");
showingMessage = true;
stressLabel.Text = "-";
stressLabel.ForeColor = Color.Black;
return newBitmap;
}
double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;
for (int i = 0; i < coordinates.Rows; i++) {
if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];
if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];
if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];
if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];
}
int border = 20;
double xStep = xMax != xMin ? (pictureBox.Width - 2 * border) / (xMax - xMin) : 1;
double yStep = yMax != yMin ? (pictureBox.Height - 2 * border) / (yMax - yMin) : 1;
Point[] points = new Point[coordinates.Rows];
for (int i = 0; i < coordinates.Rows; i++)
points[i] = new Point(border + ((int)((coordinates[i, 0] - xMin) * xStep)),
newBitmap.Height - (border + ((int)((coordinates[i, 1] - yMin) * yStep))));
Random rand = new Random();
using (Graphics graphics = Graphics.FromImage(newBitmap)) {
graphics.Clear(Color.White);
graphics.DrawString("Showing facilities layed out according to their weights", Font, Brushes.Black, 5, 2);
for (int i = 0; i < coordinates.Rows - 1; i++) {
for (int j = i + 1; j < coordinates.Rows; j++) {
Point start = points[i], end = points[j];
string caption = String.Empty;
double d = Math.Max(distances[i, j], distances[j, i]);
double w = weights[i, j];
if (w > 0) {
float width = (float)Math.Ceiling(3.0 * w / maxWeight);
graphics.DrawLine(new Pen(Color.MediumBlue, width), start, end);
caption = w.ToString(CultureInfo.InvariantCulture.NumberFormat);
}
if (!String.IsNullOrEmpty(caption)) {
double r = rand.NextDouble();
while (r < 0.2 || r > 0.8) r = rand.NextDouble();
float x = (float)(start.X + (end.X - start.X) * r + 5);
float y = (float)(start.Y + (end.Y - start.Y) * r + 5);
graphics.DrawString(caption, Font, Brushes.Black, x, y);
}
}
}
for (int i = 0; i < points.Length; i++) {
Point p = new Point(points[i].X - 3, points[i].Y - 3);
graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);
graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);
}
}
showingMessage = false;
return newBitmap;
}
return null;
}
#endregion
#region Draw assignment
private Bitmap GenerateAssignmentImage() {
if ((pictureBox.Width > 0) && (pictureBox.Height > 0)) {
Bitmap newBitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
for (int i = 0; i < distances.Rows; i++) {
for (int j = i + 1; j < distances.Rows; j++) {
if (distances[i, j] != distances[j, i]) {
WriteCenteredTextToBitmap(ref newBitmap, "Distance matrix is not symmetric");
stressLabel.Text = "-";
showingMessage = true;
return newBitmap;
}
if (weights[i, j] != weights[j, i]) {
WriteCenteredTextToBitmap(ref newBitmap, "Weights matrix is not symmetric");
stressLabel.Text = "-";
showingMessage = true;
return newBitmap;
}
}
}
DoubleMatrix coordinates = null;
double stress = double.NaN;
try {
coordinates = MultidimensionalScaling.KruskalShepard(distances);
stress = MultidimensionalScaling.CalculateNormalizedStress(distances, coordinates);
stressLabel.Text = stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat);
if (stress < 0.1) stressLabel.ForeColor = Color.DarkGreen;
else if (stress < 0.2) stressLabel.ForeColor = Color.DarkOrange;
else stressLabel.ForeColor = Color.DarkRed;
} catch {
WriteCenteredTextToBitmap(ref newBitmap, "Unknown error");
showingMessage = true;
stressLabel.Text = "-";
stressLabel.ForeColor = Color.Black;
return newBitmap;
}
double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;
double maxWeight = double.MinValue;
for (int i = 0; i < coordinates.Rows; i++) {
if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];
if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];
if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];
if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];
for (int j = i + 1; j < coordinates.Rows; j++) {
if (weights[i, j] > maxWeight) maxWeight = weights[i, j];
}
}
int border = 20;
double xStep = xMax != xMin ? (pictureBox.Width - 2 * border) / (xMax - xMin) : 1;
double yStep = yMax != yMin ? (pictureBox.Height - 2 * border) / (yMax - yMin) : 1;
Point[] points = new Point[coordinates.Rows];
for (int i = 0; i < coordinates.Rows; i++)
points[i] = new Point(border + ((int)((coordinates[i, 0] - xMin) * xStep)),
newBitmap.Height - (border + ((int)((coordinates[i, 1] - yMin) * yStep))));
Random rand = new Random();
using (Graphics graphics = Graphics.FromImage(newBitmap)) {
graphics.Clear(Color.White);
for (int i = 0; i < assignment.Length - 1; i++) {
for (int j = i + 1; j < assignment.Length; j++) {
Point start, end;
string caption = String.Empty;
double d = distances[i, j];
start = points[assignment[i]];
end = points[assignment[j]];
double w = weights[i, j];
if (w > 0) {
float width = (float)Math.Ceiling(4.0 * w / maxWeight);
graphics.DrawLine(new Pen(Color.MediumBlue, width), start, end);
caption = w.ToString(CultureInfo.InvariantCulture.NumberFormat);
}
if (!String.IsNullOrEmpty(caption)) {
double r = rand.NextDouble();
while (r < 0.2 || r > 0.8) r = rand.NextDouble();
float x = (float)(start.X + (end.X - start.X) * r + 5);
float y = (float)(start.Y + (end.Y - start.Y) * r + 5);
graphics.DrawString(caption, Font, Brushes.Black, x, y);
}
}
}
for (int i = 0; i < points.Length; i++) {
Point p = new Point(points[i].X - 3, points[i].Y - 3);
graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8);
graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10);
}
}
showingMessage = false;
return newBitmap;
}
return null;
}
#endregion
private void CustomDispose(bool disposing) {
DeregisterDistancesEvents();
DeregisterWeightsEvents();
DeregisterAssignmentEvents();
if (bitmap != null) bitmap.Dispose();
bitmap = null;
if (defaultBitmap != null) {
defaultBitmap.Dispose();
defaultBitmap = null;
}
}
}
}