using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace HeuristicLab.Analysis.FitnessLandscape.FDC { public partial class HeatMapControl : Control { public const int N_BINS = 50; public HeatMapControl() { InitializeComponent(); } private List points = new List(); public void AddPoints(IEnumerable points) { this.points.AddRange(points); ForceUpdate(); } public void Clear() { points.Clear(); ForceUpdate(); } private Bitmap bitmap = null; protected override void OnPaint(PaintEventArgs pe) { if (bitmap != null) pe.Graphics.DrawImage(bitmap, new Rectangle(0, 0, Width, Height)); base.OnPaint(pe); } public static RectangleF CreateBounds(IEnumerable points, BackgroundWorker worker, DoWorkEventArgs a) { if (points.Count() == 0) return new RectangleF(0, 0, 1, 1); double minX = points.Select(p => p.X).Min(); if (worker.CancellationPending) { a.Cancel = true; return new RectangleF(0, 0, 1, 1); } double maxX = points.Select(p => p.X).Max(); if (worker.CancellationPending) { a.Cancel = true; return new RectangleF(0, 0, 1, 1); } double minY = points.Select(p => p.Y).Min(); if (worker.CancellationPending) { a.Cancel = true; return new RectangleF(0, 0, 1, 1); } double maxY = points.Select(p => p.Y).Max(); if (worker.CancellationPending) { a.Cancel = true; return new RectangleF(0, 0, 1, 1); } return new RectangleF((float)minX, (float)minY, (float)(maxX - minX), (float)(maxY - minY)); } public static double[,] CreateHeapMap(IEnumerable points, int hBins, int vBins, BackgroundWorker worker, DoWorkEventArgs a) { RectangleF bounds = CreateBounds(points, worker, a); MatrixBuffer countMap = new MatrixBuffer(hBins, vBins); foreach (PointF p in points) { if (worker.CancellationPending) { a.Cancel = true; return null; } int x = (int)Math.Round((hBins - 1) * (p.X - bounds.X) / bounds.Width); int y = (int)Math.Round((vBins - 1) * (p.Y - bounds.Y) / bounds.Height); countMap[x, y] += 1; } int maxCount = countMap.Max(); double[,] heatMap = new double[hBins, vBins]; if (maxCount > 0) { for (int x = 0; x < hBins; x++) { for (int y = 0; y < vBins; y++) { if (worker.CancellationPending) { a.Cancel = true; return null; } heatMap[x, y] = 1.0 * countMap[x, y] / maxCount; } } } return heatMap; } private static Bitmap CreateBitmap(IEnumerable points, int width, int height, BackgroundWorker worker, DoWorkEventArgs a) { ColorGradient gradient = new ColorGradient(); gradient[0.0] = Color.Blue; gradient[0.5] = Color.Green; gradient[1.0] = Color.Red; var heatMap = CreateHeapMap(points, N_BINS, N_BINS, worker, a); double binWidth = 1.0 * width / N_BINS; double binHeight = 1.0 * height / N_BINS; if (worker.CancellationPending) { a.Cancel = true; return new Bitmap(1, 1); } Bitmap bitmap = new Bitmap(width, height); using (Graphics g = Graphics.FromImage(bitmap)) { for (int x = 0; x < heatMap.GetLength(0); x++) { for (int y = 0; y < heatMap.GetLength(1); y++) { if (worker.CancellationPending) { a.Cancel = true; break; } using (Brush b = new SolidBrush(gradient[heatMap[x, y]])) { g.FillRectangle(b, (float)(x * binWidth), (float)(y * binHeight), (float)binWidth, (float)binHeight); } } } g.Flush(); } return bitmap; } private void HeatMapControl_Resize(object sender, EventArgs e) { if (bitmap == null || bitmap.Width < Width || bitmap.Height < bitmap.Height) ForceUpdate(); } BackgroundWorker worker; object workerLock = new object(); public void ForceUpdate() { lock (workerLock) { if (worker != null) worker.CancelAsync(); worker = new BackgroundWorker() { WorkerSupportsCancellation = true }; Bitmap bitmap = null; IEnumerable points = this.points.ToList(); int width, height; width = height = Math.Min(Width, Height); worker.DoWork += (s, a) => { bitmap = CreateBitmap(points, width, height, worker, a); }; worker.RunWorkerCompleted += (s, a) => { if (!a.Cancelled) { SetBitmap(bitmap); lock (workerLock) { this.worker = null; } } }; worker.RunWorkerAsync(); } } private void SetBitmap(Bitmap bitmap) { if (InvokeRequired) { Invoke(new Action(SetBitmap)); } else { this.bitmap = bitmap; Invalidate(); } } } public class MatrixBuffer : IEnumerable { private T[,] matrix; public MatrixBuffer(int x, int y) { matrix = new T[x, y]; } public T this[int x, int y] { get { if (IsInside(x, y)) return matrix[x, y]; else return default(T); } set { if (IsInside(x, y)) matrix[x, y] = value; } } public bool IsInside(int x, int y) { return x >= 0 && x < matrix.GetLength(0) && y >= 0 && y < matrix.GetLength(1); } #region IEnumerable Members public IEnumerator GetEnumerator() { foreach (T e in matrix) yield return e; } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return matrix.GetEnumerator(); } #endregion } }