#region License Information /* HeuristicLab * Copyright (C) 2002-2012 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.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; using HeuristicLab.Analysis; using HeuristicLab.Common; using HeuristicLab.Core.Views; using HeuristicLab.Data; using HeuristicLab.MainForm; using HeuristicLab.MainForm.WindowsForms; namespace HeuristicLab.Optimizer.Tools { [View("MDSView")] [Content(typeof(DoubleMatrix), IsDefaultView = false)] public partial class MDSView : ItemView { private static Color[] KellysDistinguishableColors = new Color[] { Color.FromArgb(0xFF, Color.FromArgb(0xFFB300)), Color.FromArgb(0xFF, Color.FromArgb(0x803E75)), Color.FromArgb(0xFF, Color.FromArgb(0xFF6800)), Color.FromArgb(0xFF, Color.FromArgb(0xA6BDD7)), Color.FromArgb(0xFF, Color.FromArgb(0xC10020)), Color.FromArgb(0xFF, Color.FromArgb(0xCEA262)), Color.FromArgb(0xFF, Color.FromArgb(0x817066)), Color.FromArgb(0xFF, Color.FromArgb(0x007D34)), Color.FromArgb(0xFF, Color.FromArgb(0xF6768E)), Color.FromArgb(0xFF, Color.FromArgb(0x00538A)), Color.FromArgb(0xFF, Color.FromArgb(0x53377A)), Color.FromArgb(0xFF, Color.FromArgb(0xB32851)), Color.FromArgb(0xFF, Color.FromArgb(0x7F180D)), Color.FromArgb(0xFF, Color.FromArgb(0x93AA00)), Color.FromArgb(0xFF, Color.FromArgb(0x593315)), Color.FromArgb(0xFF, Color.FromArgb(0xF13A13)), Color.FromArgb(0xFF, Color.FromArgb(0x232C16)), }; public new DoubleMatrix Content { get { return (DoubleMatrix)base.Content; } set { base.Content = value; } } private DoubleArray Characteristic { get; set; } public MDSView() { InitializeComponent(); Characteristic = new DoubleArray(); characteristicView.Content = Characteristic; } protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.RowsChanged += new EventHandler(Content_Changed); Content.ColumnsChanged += new EventHandler(Content_Changed); } protected override void DeregisterContentEvents() { Content.RowsChanged -= new EventHandler(Content_Changed); Content.ColumnsChanged -= new EventHandler(Content_Changed); base.DeregisterContentEvents(); } private void Content_Changed(object sender, EventArgs e) { SetEnabledStateOfControls(); OnRedraw(); } protected override void OnContentChanged() { base.OnContentChanged(); if (Content != null && Content.Rows != Characteristic.Length) ((IStringConvertibleArray)Characteristic).Length = Content.Rows; OnRedraw(); } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); visualizationPanel.Enabled = Content != null && Content.Rows == Content.Columns && Content.Rows > 0; } private void drawLinesCheckBox_CheckedChanged(object sender, EventArgs e) { OnRedraw(); } private void setNamesButton_Click(object sender, EventArgs e) { SetNamesDialog dialog = null; if (Content.RowNames != null && Content.RowNames.Any()) dialog = new SetNamesDialog(Content.RowNames); else dialog = new SetNamesDialog(Content.Rows); try { if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { Content.RowNames = dialog.Names; Content.ColumnNames = dialog.Names; OnRedraw(); } } finally { dialog.Dispose(); } } private void copyCoordinatesButton_Click(object sender, EventArgs e) { try { var coordinates = MultidimensionalScaling.KruskalShepard(Content); StringBuilder s = new StringBuilder(); for (int i = 0; i < coordinates.Rows; i++) { s.Append(coordinates[i, 0].ToString()); s.Append('\t'); s.Append(coordinates[i, 1].ToString()); s.Append(Environment.NewLine); } Clipboard.SetText(s.ToString()); } catch (Exception ex) { System.Windows.Forms.MessageBox.Show("Error: " + ex.ToString()); } } private void OnRedraw() { if (InvokeRequired) { Invoke((Action)OnRedraw, null); } else { if (!visualizationPanel.Visible) return; GenerateImage(); } } private void GenerateImage() { if (Content.Rows == Content.Columns && Content.Rows > 0 && mdsPictureBox.Width > 0 && mdsPictureBox.Height > 0) { Bitmap newBitmap = GenerateDistanceImage(); if (newBitmap != null) { var oldImage = mdsPictureBox.Image; mdsPictureBox.Image = newBitmap; if (oldImage != null) oldImage.Dispose(); } } } private Bitmap GenerateDistanceImage() { Bitmap newBitmap = new Bitmap(mdsPictureBox.Width, mdsPictureBox.Height); DoubleMatrix coordinates; double stress = double.NaN; try { coordinates = MultidimensionalScaling.KruskalShepard(Content); stress = MultidimensionalScaling.CalculateNormalizedStress(Content, coordinates); infoLabel.Text = "Stress: " + stress.ToString("0.00", CultureInfo.CurrentCulture.NumberFormat); } catch { WriteCenteredTextToBitmap(ref newBitmap, "Matrix is not symmetric"); infoLabel.Text = "-"; 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 (Content[i, j] > maxDistance) maxDistance = Content[i, j]; if (Content[j, i] > maxDistance) maxDistance = Content[j, i]; } } int border = 20; double xStep = xMax != xMin ? (mdsPictureBox.Width - 2 * border) / (xMax - xMin) : 1; double yStep = yMax != yMin ? (mdsPictureBox.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); if (drawLinesCheckBox.Checked) { 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(Content[i, j], Content[j, i]); float width = (float)Math.Ceiling(5.0 * d / maxDistance); if (d > 0) { graphics.DrawLine(new Pen(Color.IndianRed, width), start, end); if (Content[i, j] != Content[j, i]) caption = Content[i, j].ToString(CultureInfo.InvariantCulture.NumberFormat) + " / " + Content[j, i].ToString(CultureInfo.InvariantCulture.NumberFormat); else caption = Content[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); } } } } string[] rowNames = null; if (Content.RowNames != null && Content.RowNames.Any()) rowNames = Content.RowNames.ToArray(); double min = Characteristic.Min(), max = Characteristic.Max(); if (min == max) { var colorCodes = new Dictionary(); for (int i = 0; i < points.Length; i++) { Point p = new Point(points[i].X - 3, points[i].Y - 3); if (rowNames == null || (rowNames != null && i >= rowNames.Length)) { graphics.FillRectangle(Brushes.Black, p.X, p.Y, 8, 8); graphics.DrawString(i.ToString(), Font, Brushes.Black, p.X, p.Y + 10); } else { string pre = rowNames[i].Substring(0, 3); string post = rowNames[i].Substring(3); if (!colorCodes.ContainsKey(pre)) colorCodes[pre] = !colorCodes.Any() ? 0 : colorCodes.Values.Max() + 1; var brush = new SolidBrush(KellysDistinguishableColors[colorCodes[pre]]); var size = TextRenderer.MeasureText(post, Font); graphics.FillRectangle(brush, p.X, p.Y, 8, 8); graphics.DrawString(post, Font, brush, p.X - (size.Width / 2.0f), p.Y + 10); } } int offset = 1; foreach (var code in colorCodes) { using (var brush = new SolidBrush(KellysDistinguishableColors[code.Value])) { graphics.DrawString(code.Key, Font, brush, 5, 40 + offset * 20); } offset++; } } else { var colors = ColorGradient.Colors.ToArray(); for (int i = 0; i < points.Length; i++) { Point p = new Point(points[i].X - 3, points[i].Y - 3); double interpolation = Math.Sqrt((Characteristic[i] - min) / (max - min)); Color c = colors[(int)Math.Floor((colors.Length - 1) * interpolation)]; var brush = new SolidBrush(c); graphics.FillRectangle(brush, p.X, p.Y, 8, 8); if (rowNames == null || (rowNames != null && i >= rowNames.Length)) { graphics.DrawString(i.ToString(), Font, brush, p.X, p.Y + 10); } else { var size = TextRenderer.MeasureText(rowNames[i], Font); graphics.DrawString(rowNames[i], Font, brush, p.X - (size.Width / 2.0f), p.Y + 10); } } } } return newBitmap; } 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 > mdsPictureBox.Width) { var m = Regex.Match(text, @"\b\w+[.,]*\b*"); var builder = new StringBuilder(); while (m.Success) { builder.Append(m.Value + " "); Match next = m.NextMatch(); if (g.MeasureString(builder.ToString() + " " + next.Value, font).Width + 50 > mdsPictureBox.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)(mdsPictureBox.Width - strSize.Width) / 2.0f, (float)(mdsPictureBox.Height - strSize.Height) / 2.0f); } } } }