如何在 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;
    }
}