I'm making a graph with over 400 nodes and trying to draw them. The nodes are representation of cities so I took their longitude and latitude and set them as X and Y. The problem is that those cities are from one county so their longitude and latitude are pretty close to each other so even if I make the X and Y 10 times bigger they are still close to each other. The only thing is to increase the "resolution" to 100 times bigger but then, they won't be visible because the values are going up to 5000px
My question is: can I scale the panel so it will show everything, even if it's 5000px big? Or maybe there is a method scale the longitude and latitude so they'll be more spaced but still resemble the real life layout of the cities? Thak you
CodePudding user response:
Given a set of points, take the min and max for X and the min and max for Y
var minMaxX = new PointF(
points.Min(point => point.X),
points.Max(point => point.X)
);
var minMaxY = new PointF(
points.Min(point => point.Y),
points.Max(point => point.Y)
);
Then loop over each point, get the normalized point values for X and Y, then multiply by the corresponding viewport size values (width and height).
var normalizedX = (point.X - minMaxX.X) / (minMaxX.Y - minMaxX.X);
var normalizedY = (point.Y - minMaxY.X) / (minMaxY.Y - minMaxY.X);
var posX = (int)(normalizedX * viewportWidth);
var posY = (int)(normalizedY * viewportHeight);
The above does not take into account the aspect ratio of the original points' rectangle, so your results will likely be squished. This can be solved using letter boxing or pillar boxing, depending on the width/height of the points rectangle compared to the viewport rectangle.
CodePudding user response:
I think this is what you want:
and the code that goes with it.
The arbitrary city coordinates shown are
74.2344f, 25.3134f
41.4388f, -12.158f
58.8734f, 32.3634f
22.8486f, 7.678f
83.8304f, 12.3733f
which get mapped into pixels based on the size of the panel in the Project() method.
public readonly struct City
{
public City(float longitude, float latitude, float size) : this()
{
Longitude=longitude;
Latitude=latitude;
Size=size;
}
public float Longitude { get; }
public float Latitude { get; }
public float Size { get; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Cities = new List<City>();
}
protected override void onl oad(EventArgs e)
{
base.OnLoad(e);
pictureBox1.Paint = (s, ev) =>
{
ev.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
using (var fill = new SolidBrush(Color.Black))
{
// Draw cities as dots of various sizes.
var cities = Cities.ToArray();
var points = Project(cities, pictureBox1, out var scale);
for (int i = 0; i < points.Length; i )
{
float r = Math.Max(1, scale * cities[i].Size);
ev.Graphics.FillEllipse(fill, points[i].X-r, points[i].Y-r, 2*r, 2*r);
ev.Graphics.DrawString($"City {i 1}", SystemFonts.CaptionFont, Brushes.Blue, points[i].X-2*r, points[i].Y 2*r);
}
// Draw a bounding box to check math is correct.
using (var pen = new Pen(Color.LightGray,0))
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
var bounds = GetBounds(cities);
var box = Project(
new City[] {
new City(bounds.Left, bounds.Top,0),
new City(bounds.Right, bounds.Top,0),
new City(bounds.Right, bounds.Bottom,0),
new City(bounds.Left, bounds.Bottom,0) }, pictureBox1, out scale);
ev.Graphics.DrawPolygon(pen, box);
}
}
};
pictureBox1.Resize = (s, ev) =>
{
pictureBox1.Invalidate();
};
Cities.Add(new City(74.2344f, 25.3134f, 0.2f));
Cities.Add(new City(41.4388f, -12.158f, 0.25f));
Cities.Add(new City(58.8734f, 32.3634f, 0.12f));
Cities.Add(new City(22.8486f, 7.678f, 0.25f));
Cities.Add(new City(83.8304f, 12.3733f, 0.07f));
}
public List<City> Cities { get; }
public RectangleF GetBounds(City[] map)
{
RectangleF bounds = new RectangleF(map[0].Longitude, map[0].Latitude, 0, 0);
for (int i = 1; i < map.Length; i )
{
bounds.X = Math.Min(bounds.X, map[i].Longitude);
bounds.Width = Math.Max(bounds.Width, map[i].Longitude-bounds.X);
bounds.Y = Math.Min(bounds.Y, map[i].Latitude);
bounds.Height = Math.Max(bounds.Height, map[i].Latitude-bounds.Y);
}
return bounds;
}
public PointF[] Project(City[] map, Control target, out float scale)
{
// Find the bounds of the map
var bounds = GetBounds(map);
// Find the scaling between pixels and map distances
int margin = 32;
int wt = target.ClientSize.Width, ht = target.ClientSize.Height;
scale = Math.Min(
(wt-2*margin)/bounds.Width,
(ht-2*margin)/bounds.Height);
// Minimum 1 pixel per unit (to avoid negative scales etc)
scale = Math.Max(1, scale);
// Convert map to pixels starting from the center
// of the target control.
PointF[] pixels = new PointF[map.Length];
for (int i = 0; i < pixels.Length; i )
{
pixels[i] = new PointF(
wt/2 scale * (map[i].Longitude - bounds.X - bounds.Width/2),
ht/2 - scale * (map[i].Latitude - bounds.Y - bounds.Height/2));
}
return pixels;
}
}

