如何在 C# 中缩放面板
How to scale panel in C#
我正在制作一个包含 400 多个节点的图表并尝试绘制它们。节点代表城市,所以我把它们的经度和纬度设置为 X 和 Y。问题是这些城市来自一个县,所以它们的经度和纬度彼此非常接近,所以即使我将 X 和Y 大 10 倍,它们仍然彼此靠近。唯一的办法是将“分辨率”增加到 100 倍,但随后,它们将不可见,因为值将上升到 5000px
我的问题是:我可以缩放面板以显示所有内容,即使它有 5000 像素大吗?或者也许有一种方法可以缩放经度和纬度,这样它们的间距会更大,但仍然类似于城市的现实生活布局?
你好
给定一组点,取 X 的最小值和最大值以及 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)
);
然后遍历每个点,获取 X 和 Y 的标准化点值,然后乘以相应的视口大小值(宽度和高度)。
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);
以上未考虑原始点矩形的纵横比,因此您的结果可能会被压扁。这可以使用字母装箱或柱形装箱来解决,具体取决于点矩形与视口矩形相比的 width/height。
我想这就是你想要的:
及其附带的代码。
显示的任意城市坐标为
74.2344f, 25.3134f
41.4388f, -12.158f
58.8734f, 32.3634f
22.8486f, 7.678f
83.8304f, 12.3733f
根据 Project()
方法中面板的大小映射到像素。
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 OnLoad(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;
}
}
我正在制作一个包含 400 多个节点的图表并尝试绘制它们。节点代表城市,所以我把它们的经度和纬度设置为 X 和 Y。问题是这些城市来自一个县,所以它们的经度和纬度彼此非常接近,所以即使我将 X 和Y 大 10 倍,它们仍然彼此靠近。唯一的办法是将“分辨率”增加到 100 倍,但随后,它们将不可见,因为值将上升到 5000px
我的问题是:我可以缩放面板以显示所有内容,即使它有 5000 像素大吗?或者也许有一种方法可以缩放经度和纬度,这样它们的间距会更大,但仍然类似于城市的现实生活布局? 你好
给定一组点,取 X 的最小值和最大值以及 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)
);
然后遍历每个点,获取 X 和 Y 的标准化点值,然后乘以相应的视口大小值(宽度和高度)。
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);
以上未考虑原始点矩形的纵横比,因此您的结果可能会被压扁。这可以使用字母装箱或柱形装箱来解决,具体取决于点矩形与视口矩形相比的 width/height。
我想这就是你想要的:
及其附带的代码。
显示的任意城市坐标为
74.2344f, 25.3134f
41.4388f, -12.158f
58.8734f, 32.3634f
22.8486f, 7.678f
83.8304f, 12.3733f
根据 Project()
方法中面板的大小映射到像素。
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 OnLoad(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;
}
}