using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Windows.Forms.DataVisualization.Charting; using System.IO; namespace VRPProblemAnalyzer { public class PictureGenerator { private static int TourWidth = 400; private static int TourHeight = 500; private static double MinDemandSize = 2; private static double MaxDemandSize = Math.Min(TourWidth, TourHeight) / 25.0; private static int TourBorder = (int)Math.Ceiling(MaxDemandSize); private static int ChartWidth = 300; private static int ChartHeight = TourHeight/2; private static int DemandSegments = 20; private static int DistanceSegments = 20; public static Image GeneratePicture(TSPLIBParser problemInstance, SolutionParser solution) { Image tourVisualization = GenerateTourVisualization(problemInstance, solution); Image demandDistribution = GenerateDemandDistribution(problemInstance); Image distanceDistribution = GenerateDistanceDistribution(problemInstance); SizeF titleSize; using (var font = new Font("Helvetica", 20)) { using (var b = new Bitmap(1, 1)) { using (Graphics g = Graphics.FromImage(b)) { titleSize = g.MeasureString(problemInstance.Name, font); } } Bitmap bmp = new Bitmap(TourWidth+ChartWidth, Math.Max(TourHeight, 2*ChartHeight) + (int)titleSize.Height); using (Graphics g = Graphics.FromImage(bmp)) { g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height); g.DrawString(problemInstance.Name, font, Brushes.Black, (bmp.Width-titleSize.Width)/2, 0); g.DrawImage(tourVisualization, (TourWidth-tourVisualization.Width)/2, titleSize.Height+(TourHeight-tourVisualization.Height)/2); g.DrawImage(demandDistribution, TourWidth, titleSize.Height); g.DrawImage(distanceDistribution, TourWidth, ChartHeight + titleSize.Height); } return bmp; } } private static Color[] TourColors = new[] { Color.FromArgb(100, 92, 20,237), Color.FromArgb(100,237,183, 20), Color.FromArgb(100,237, 20,219), Color.FromArgb(100, 20,237, 76), Color.FromArgb(100,237, 61, 20), Color.FromArgb(100,115, 78, 26), Color.FromArgb(100, 20,237,229), Color.FromArgb(100, 39,101, 19), Color.FromArgb(100,230,170,229), Color.FromArgb(100,142,136, 89), Color.FromArgb(100,157,217,166), Color.FromArgb(100, 31, 19,101), Color.FromArgb(100,173,237, 20), Color.FromArgb(100,230,231,161), Color.FromArgb(100,142, 89, 89), Color.FromArgb(100, 93, 89,142), Color.FromArgb(100,146,203,217), Color.FromArgb(100,101, 19, 75), Color.FromArgb(100,198, 20,237), Color.FromArgb(100,185,185,185), Color.FromArgb(100,179, 32, 32), Color.FromArgb(100, 18,119,115), Color.FromArgb(100,104,158,239), Color.Black}; private static Image GenerateTourVisualization(TSPLIBParser problemInstance, SolutionParser solution) { double[,] coordinates = problemInstance.Vertices; double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue; for (int i = 0; i < coordinates.GetLength(0); 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]; } double xRange = xMax - xMin; double yRange = yMax - yMin; double scaling = Math.Min((TourWidth-TourBorder*2)/xRange, (TourHeight-TourBorder*2)/yRange); Bitmap bitmap = new Bitmap((int)Math.Ceiling(xRange*scaling+TourBorder*2), (int)Math.Ceiling(yRange*scaling+TourBorder*2)); var pens = TourColors.Select(c => new Pen(c, 2)).ToList(); using (Graphics g = Graphics.FromImage(bitmap)) { using (var brush = new SolidBrush(Color.FromArgb(100, 0, 0, 0))) { g.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height); int currentTour = 0; foreach (List tour in solution.Routes) { Point[] tourPoints = new Point[tour.Count + 2]; int[] customerSizes = new int[tour.Count]; for (int i = -1; i <= tour.Count; i++) { int location = (i == -1 || i == tour.Count) ? 0 : tour[i]; Point locationPoint = new Point(TourBorder + ((int) ((coordinates[location, 0] - xMin)*scaling)), bitmap.Height - (TourBorder + ((int) ((coordinates[location, 1] - yMin)*scaling)))); tourPoints[i + 1] = locationPoint; if (i != -1 && i != tour.Count) { customerSizes[i] = (int) Math.Round(MinDemandSize + problemInstance.Demands[location]/problemInstance.Capacity*(MaxDemandSize - MinDemandSize)); } } g.DrawPolygon(pens[((currentTour >= pens.Count) ? (pens.Count - 1) : (currentTour))], tourPoints); for (int i = 0; i < tour.Count; i++) { int size = customerSizes[i]; g.FillEllipse(brush, tourPoints[i + 1].X - size, tourPoints[i + 1].Y - size, size*2, size*2); } g.FillRectangle(Brushes.Blue, tourPoints[0].X - 5, tourPoints[0].Y - 5, 10, 10); currentTour++; } } } foreach (var p in pens) p.Dispose(); return bitmap; } private static List> GetDemandDistribution(TSPLIBParser problemInstance) { var result = new List>(); double step = problemInstance.Capacity / (double)DemandSegments; double current = 0; while(current < problemInstance.Capacity) { int count = problemInstance.Demands.Where(d => d > current && d <= current + step).Count(); result.Add(new Tuple(current + step / 2, count)); current += step; } return result; } private static Image GenerateDemandDistribution(TSPLIBParser problemInstance) { Chart chart = new Chart(); chart.Size = new Size(ChartWidth, ChartHeight); chart.ChartAreas.Add(""); ConfigureChartAppearance(chart); chart.ChartAreas[0].AxisX.Title = "Demands"; chart.ChartAreas[0].AxisX.Minimum = 0; chart.ChartAreas[0].AxisX.Maximum = problemInstance.Capacity; chart.ChartAreas[0].AxisX.Interval = problemInstance.Capacity/DemandSegments; chart.ChartAreas[0].AxisX.LabelStyle.Interval = problemInstance.Capacity/DemandSegments*4; chart.ChartAreas[0].AxisY.Title = "Customers"; chart.ChartAreas[0].AxisY.Minimum = 0; chart.ChartAreas[0].AxisY.Maximum = problemInstance.Vertices.GetLength(0); chart.Series.Add(""); chart.Series[0].ChartType = SeriesChartType.Column; foreach (Tuple entry in GetDemandDistribution(problemInstance)) { chart.Series[0].Points.AddXY(entry.Item1, entry.Item2); } Image image; using (var chartimage = new MemoryStream()) { chart.SaveImage(chartimage, ChartImageFormat.Png); image = Image.FromStream(chartimage); } return image; } private static void ConfigureChartAppearance(Chart chart) { chart.ChartAreas[0].BackColor = Color.White; chart.ChartAreas[0].AxisX.TitleFont = new Font("Helvetica", 16); chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false; chart.ChartAreas[0].AxisY.TitleFont = new Font("Helvetica", 16); chart.ChartAreas[0].AxisY.MajorGrid.Enabled = true; chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.FromArgb(100, Color.LightBlue); } private static double GetAverageDistance(int customer, double[,] vertices) { double dist = 0; int count = 0; for (int i = 0; i < vertices.GetLength(0); i++) { if (i != customer) { dist += Utils.GetDistance(vertices, customer, i); count++; } } return dist / count; } private static List> GetDistanceDistribution(TSPLIBParser problemInstance) { var result = new List>(); double step = 1.0 / (double)DistanceSegments; double current = 0; List averageDistances = new List(); for (int i = 0; i < problemInstance.Vertices.GetLength(0); i++) { averageDistances.Add(GetAverageDistance(i, problemInstance.Vertices)); } while (current < 1.0) { int count = averageDistances.Where(d => d > current && d <= current + step).Count(); result.Add(new Tuple(current + step / 2, count)); current += step; } return result; } private static Image GenerateDistanceDistribution(TSPLIBParser problemInstance) { Chart chart = new Chart(); chart.Size = new Size(ChartWidth, ChartHeight); chart.ChartAreas.Add(""); ConfigureChartAppearance(chart); chart.ChartAreas[0].AxisX.Title = "Distances"; chart.ChartAreas[0].AxisX.Minimum = 0; chart.ChartAreas[0].AxisX.Maximum = 1; chart.ChartAreas[0].AxisX.Interval = 1.0/DistanceSegments; chart.ChartAreas[0].AxisX.LabelStyle.Interval = 1.0/DistanceSegments*4; chart.ChartAreas[0].AxisY.Title = "Customers"; chart.ChartAreas[0].AxisY.Minimum = 0; chart.ChartAreas[0].AxisY.Maximum = problemInstance.Vertices.GetLength(0); chart.Series.Add(""); chart.Series[0].ChartType = SeriesChartType.Column; foreach (Tuple entry in GetDistanceDistribution(problemInstance)) { chart.Series[0].Points.AddXY(entry.Item1, entry.Item2); } Image image; using (var chartimage = new MemoryStream()) { chart.SaveImage(chartimage, ChartImageFormat.Png); image = Image.FromStream(chartimage); } return image; } } }