1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Linq;
|
---|
4 | using System.Text;
|
---|
5 | using System.Drawing;
|
---|
6 | using System.Windows.Forms.DataVisualization.Charting;
|
---|
7 | using System.IO;
|
---|
8 |
|
---|
9 | namespace VRPProblemAnalyzer {
|
---|
10 | public class PictureGenerator {
|
---|
11 | private static int TourSize = 1000;
|
---|
12 | private static double MinDemandSize = 2;
|
---|
13 | private static double MaxDemandSize = TourSize / 25.0;
|
---|
14 |
|
---|
15 | private static int TourBorder = (int)Math.Ceiling(MaxDemandSize);
|
---|
16 |
|
---|
17 | private static int ChartWidth = TourSize / 2;
|
---|
18 | private static int ChartHeight = 250;
|
---|
19 |
|
---|
20 | private static int DemandSegments = 10;
|
---|
21 |
|
---|
22 | private static int DistanceSegments = 10;
|
---|
23 |
|
---|
24 | public static Image GeneratePicture(TSPLIBParser problemInstance, SolutionParser solution) {
|
---|
25 | Image tourVisualization = GenerateTourVisualization(problemInstance, solution);
|
---|
26 | Image demandDistribution = GenerateDemandDistribution(problemInstance);
|
---|
27 | Image distanceDistribution = GenerateDistanceDistribution(problemInstance);
|
---|
28 |
|
---|
29 | Bitmap bmp = new Bitmap(Math.Max(TourSize, ChartWidth * 2), tourVisualization.Height + ChartHeight);
|
---|
30 | using (Graphics g = Graphics.FromImage(bmp)) {
|
---|
31 | g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height);
|
---|
32 | g.DrawImage(tourVisualization, 0, 0);
|
---|
33 | g.DrawImage(demandDistribution, 0, tourVisualization.Height);
|
---|
34 | g.DrawImage(distanceDistribution, ChartWidth, tourVisualization.Height);
|
---|
35 | }
|
---|
36 | return bmp;
|
---|
37 | }
|
---|
38 |
|
---|
39 | private static Image GenerateTourVisualization(TSPLIBParser problemInstance, SolutionParser solution) {
|
---|
40 | double[,] coordinates = problemInstance.Vertices;
|
---|
41 |
|
---|
42 | double xMin = double.MaxValue, yMin = double.MaxValue, xMax = double.MinValue, yMax = double.MinValue;
|
---|
43 | for (int i = 0; i < coordinates.GetLength(0); i++) {
|
---|
44 | if (xMin > coordinates[i, 0]) xMin = coordinates[i, 0];
|
---|
45 | if (yMin > coordinates[i, 1]) yMin = coordinates[i, 1];
|
---|
46 | if (xMax < coordinates[i, 0]) xMax = coordinates[i, 0];
|
---|
47 | if (yMax < coordinates[i, 1]) yMax = coordinates[i, 1];
|
---|
48 | }
|
---|
49 | double xWidth = xMax - xMin;
|
---|
50 | double yHeight = yMax - yMin;
|
---|
51 |
|
---|
52 | int width, height;
|
---|
53 | if (xWidth > yHeight) {
|
---|
54 | width = TourSize;
|
---|
55 | height = (int)Math.Round(TourSize * (yHeight / xWidth));
|
---|
56 | } else {
|
---|
57 | width = (int)Math.Round(TourSize * (xWidth / yHeight));
|
---|
58 | height = TourSize;
|
---|
59 | }
|
---|
60 | Bitmap bitmap = new Bitmap(width, height);
|
---|
61 |
|
---|
62 | double xStep = (width - 2 * TourBorder) / xWidth;
|
---|
63 | double yStep = (height - 2 * TourBorder) / yHeight;
|
---|
64 |
|
---|
65 | Pen[] pens = {new Pen(Color.FromArgb(100, 92,20,237)), new Pen(Color.FromArgb(100, 237,183,20)), new Pen(Color.FromArgb(100, 237,20,219)), new Pen(Color.FromArgb(100, 20,237,76)),
|
---|
66 | new Pen(Color.FromArgb(100, 237,61,20)), new Pen(Color.FromArgb(100, 115,78,26)), new Pen(Color.FromArgb(100, 20,237,229)), new Pen(Color.FromArgb(100, 39,101,19)),
|
---|
67 | new Pen(Color.FromArgb(100, 230,170,229)), new Pen(Color.FromArgb(100, 142,136,89)), new Pen(Color.FromArgb(100, 157,217,166)), new Pen(Color.FromArgb(100, 31,19,101)),
|
---|
68 | new Pen(Color.FromArgb(100, 173,237,20)), new Pen(Color.FromArgb(100, 230,231,161)), new Pen(Color.FromArgb(100, 142,89,89)), new Pen(Color.FromArgb(100, 93,89,142)),
|
---|
69 | new Pen(Color.FromArgb(100, 146,203,217)), new Pen(Color.FromArgb(100, 101,19,75)), new Pen(Color.FromArgb(100, 198,20,237)), new Pen(Color.FromArgb(100, 185,185,185)),
|
---|
70 | new Pen(Color.FromArgb(100, 179,32,32)), new Pen(Color.FromArgb(100, 18,119,115)), new Pen(Color.FromArgb(100, 104,158,239)), new Pen(Color.Black)};
|
---|
71 |
|
---|
72 | foreach (Pen pen in pens) {
|
---|
73 | pen.Width = 2;
|
---|
74 | }
|
---|
75 |
|
---|
76 | using (Graphics graphics = Graphics.FromImage(bitmap)) {
|
---|
77 | graphics.FillRectangle(Brushes.White, 0, 0, width, height);
|
---|
78 |
|
---|
79 | int currentTour = 0;
|
---|
80 | foreach (List<int> tour in solution.Routes) {
|
---|
81 | Point[] tourPoints = new Point[tour.Count + 2];
|
---|
82 | int[] customerSizes = new int[tour.Count];
|
---|
83 | int lastCustomer = 0;
|
---|
84 |
|
---|
85 | for (int i = -1; i <= tour.Count; i++) {
|
---|
86 | int location = 0;
|
---|
87 |
|
---|
88 | if (i == -1 || i == tour.Count)
|
---|
89 | location = 0; //depot
|
---|
90 | else
|
---|
91 | location = tour[i];
|
---|
92 |
|
---|
93 | Point locationPoint = new Point(TourBorder + ((int)((coordinates[location, 0] - xMin) * xStep)),
|
---|
94 | bitmap.Height - (TourBorder + ((int)((coordinates[location, 1] - yMin) * yStep))));
|
---|
95 | tourPoints[i + 1] = locationPoint;
|
---|
96 |
|
---|
97 | if (i != -1 && i != tour.Count) {
|
---|
98 | customerSizes[i] = (int)Math.Round(MinDemandSize + problemInstance.Demands[location] / problemInstance.Capacity * (MaxDemandSize - MinDemandSize));
|
---|
99 | }
|
---|
100 | lastCustomer = location;
|
---|
101 | }
|
---|
102 |
|
---|
103 | graphics.DrawPolygon(pens[((currentTour >= pens.Length) ? (pens.Length - 1) : (currentTour))], tourPoints);
|
---|
104 |
|
---|
105 | using(Brush brush = new SolidBrush(Color.FromArgb(100, 0, 0, 0))) {
|
---|
106 | for (int i = 0; i < tour.Count; i++) {
|
---|
107 | int size = customerSizes[i];
|
---|
108 | graphics.FillEllipse(brush, tourPoints[i + 1].X - size, tourPoints[i + 1].Y - size, size * 2, size * 2);
|
---|
109 | }
|
---|
110 | }
|
---|
111 |
|
---|
112 | graphics.FillRectangle(Brushes.Blue, tourPoints[0].X - 5, tourPoints[0].Y - 5, 10, 10);
|
---|
113 |
|
---|
114 | currentTour++;
|
---|
115 | }
|
---|
116 | }
|
---|
117 |
|
---|
118 | for (int i = 0; i < pens.Length; i++)
|
---|
119 | pens[i].Dispose();
|
---|
120 |
|
---|
121 | return bitmap;
|
---|
122 | }
|
---|
123 |
|
---|
124 | private static List<Tuple<double, int>> GetDemandDistribution(TSPLIBParser problemInstance) {
|
---|
125 | var result = new List<Tuple<double, int>>();
|
---|
126 |
|
---|
127 | double step = problemInstance.Capacity / (double)DemandSegments;
|
---|
128 | double current = 0;
|
---|
129 |
|
---|
130 | while(current < problemInstance.Capacity) {
|
---|
131 | int count = problemInstance.Demands.Where(d => d > current && d <= current + step).Count();
|
---|
132 | result.Add(new Tuple<double, int>(current + step / 2, count));
|
---|
133 | current += step;
|
---|
134 | }
|
---|
135 |
|
---|
136 | return result;
|
---|
137 | }
|
---|
138 |
|
---|
139 | private static Image GenerateDemandDistribution(TSPLIBParser problemInstance) {
|
---|
140 | Chart chart = new Chart();
|
---|
141 | chart.Size = new Size(ChartWidth, ChartHeight);
|
---|
142 |
|
---|
143 | chart.Titles.Add("Demand Distribution");
|
---|
144 | chart.ChartAreas.Add("");
|
---|
145 |
|
---|
146 | chart.ChartAreas[0].BackColor = Color.White;
|
---|
147 |
|
---|
148 | chart.ChartAreas[0].AxisX.Title = "Demand";
|
---|
149 | chart.ChartAreas[0].AxisX.Minimum = 0;
|
---|
150 | chart.ChartAreas[0].AxisX.Maximum = problemInstance.Capacity;
|
---|
151 | chart.ChartAreas[0].AxisX.Interval = problemInstance.Capacity / (double)DemandSegments;
|
---|
152 | chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
|
---|
153 |
|
---|
154 | chart.ChartAreas[0].AxisY.Title = "Customers";
|
---|
155 | chart.ChartAreas[0].AxisY.Minimum = 0;
|
---|
156 | chart.ChartAreas[0].AxisY.Maximum = problemInstance.Vertices.GetLength(0);
|
---|
157 | chart.ChartAreas[0].AxisY.MajorGrid.Enabled = true;
|
---|
158 | chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.FromArgb(100, Color.Blue);
|
---|
159 |
|
---|
160 | chart.Series.Add("");
|
---|
161 | chart.Series[0].ChartType = SeriesChartType.Column;
|
---|
162 |
|
---|
163 | foreach (Tuple<double, int> entry in GetDemandDistribution(problemInstance)) {
|
---|
164 | chart.Series[0].Points.AddXY(entry.Item1, entry.Item2);
|
---|
165 | }
|
---|
166 |
|
---|
167 | Image image;
|
---|
168 | using (var chartimage = new MemoryStream()) {
|
---|
169 | chart.SaveImage(chartimage, ChartImageFormat.Png);
|
---|
170 | image = Image.FromStream(chartimage);
|
---|
171 | }
|
---|
172 | return image;
|
---|
173 | }
|
---|
174 |
|
---|
175 | private static double GetAverageDistance(int customer, double[,] vertices) {
|
---|
176 | double dist = 0;
|
---|
177 | int count = 0;
|
---|
178 |
|
---|
179 | for (int i = 0; i < vertices.GetLength(0); i++) {
|
---|
180 | if (i != customer) {
|
---|
181 | dist += Utils.GetDistance(vertices, customer, i);
|
---|
182 | count++;
|
---|
183 | }
|
---|
184 | }
|
---|
185 |
|
---|
186 | return dist / count;
|
---|
187 | }
|
---|
188 |
|
---|
189 | private static List<Tuple<double, int>> GetDistanceDistribution(TSPLIBParser problemInstance) {
|
---|
190 | var result = new List<Tuple<double, int>>();
|
---|
191 |
|
---|
192 | double step = 1.0 / (double)DistanceSegments;
|
---|
193 | double current = 0;
|
---|
194 |
|
---|
195 | List<double> averageDistances = new List<double>();
|
---|
196 | for (int i = 0; i < problemInstance.Vertices.GetLength(0); i++) {
|
---|
197 | averageDistances.Add(GetAverageDistance(i, problemInstance.Vertices));
|
---|
198 | }
|
---|
199 |
|
---|
200 | while (current < 1.0) {
|
---|
201 | int count = averageDistances.Where(d => d > current && d <= current + step).Count();
|
---|
202 | result.Add(new Tuple<double, int>(current + step / 2, count));
|
---|
203 | current += step;
|
---|
204 | }
|
---|
205 |
|
---|
206 | return result;
|
---|
207 | }
|
---|
208 |
|
---|
209 | private static Image GenerateDistanceDistribution(TSPLIBParser problemInstance) {
|
---|
210 | Chart chart = new Chart();
|
---|
211 | chart.Size = new Size(ChartWidth, ChartHeight);
|
---|
212 |
|
---|
213 | chart.Titles.Add("Distance Distribution");
|
---|
214 | chart.ChartAreas.Add("");
|
---|
215 |
|
---|
216 | chart.ChartAreas[0].BackColor = Color.White;
|
---|
217 |
|
---|
218 | chart.ChartAreas[0].AxisX.Title = "Distance";
|
---|
219 | chart.ChartAreas[0].AxisX.Minimum = 0;
|
---|
220 | chart.ChartAreas[0].AxisX.Maximum = 1;
|
---|
221 | chart.ChartAreas[0].AxisX.Interval = 1.0 / (double)DistanceSegments;
|
---|
222 | chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
|
---|
223 |
|
---|
224 | chart.ChartAreas[0].AxisY.Title = "Customers";
|
---|
225 | chart.ChartAreas[0].AxisY.Minimum = 0;
|
---|
226 | chart.ChartAreas[0].AxisY.Maximum = problemInstance.Vertices.GetLength(0);
|
---|
227 | chart.ChartAreas[0].AxisY.MajorGrid.Enabled = true;
|
---|
228 | chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.FromArgb(100, Color.Blue);
|
---|
229 |
|
---|
230 | chart.Series.Add("");
|
---|
231 | chart.Series[0].ChartType = SeriesChartType.Column;
|
---|
232 |
|
---|
233 | foreach (Tuple<double, int> entry in GetDistanceDistribution(problemInstance)) {
|
---|
234 | chart.Series[0].Points.AddXY(entry.Item1, entry.Item2);
|
---|
235 | }
|
---|
236 |
|
---|
237 | Image image;
|
---|
238 | using (var chartimage = new MemoryStream()) {
|
---|
239 | chart.SaveImage(chartimage, ChartImageFormat.Png);
|
---|
240 | image = Image.FromStream(chartimage);
|
---|
241 | }
|
---|
242 | return image;
|
---|
243 | }
|
---|
244 | }
|
---|
245 | }
|
---|