在WindowsForms DataVisualization Chart中限制宽高比

使用System.Windows.Forms.DataVisualization.Charting.Chart的图表控件,我正在制作一个散点图。

如何约束它以使X轴的刻度与Y轴的刻度相同?

简单地将控件本身设置为方形是不够的,因为它具有用于绘制和标记不相等的轴的内部边距。

我可以选择一个特定的大小并将其调整为方形,但它需要是正方形和可resize。

我在文档和属性浏览器中搜索了高低,但在resize事件中我找不到任何东西或想到任何方法。

这是一个很好的问题,但不幸的是没有像锁定两个Axes或设置一个值的简单解决方案。

让我们先看一下相关的玩家:

  • Chart控件有一个名为ClientSize的内部Size ,即Chart.Size减去边框。 两种尺寸均以像素为单位。

  • 里面可能有一个或多个ChartAreas 。 每个都有一个ElementPosition类型的ElementPosition

  • 每个ChartArea内部都是一个用于实际绘制点数的区域; 它被称为InnerPlotPosition

InnerPlotPosition属性定义图表区域元素中用于绘制数据的矩形; 它不包括刻度线,轴标签等。

用于此属性的坐标(0,0到100,100)与ChartArea对象相关,而不是与整个Chart相关。

InnerPlotPosition属性可用于对齐多个图表区域。 但是,如果一个图表区域有刻度线和轴标签而另一个没有,则它们的轴线不能对齐。

  • ChartArea.PositionChartArea.InnerPlotPosition都不仅包含位置包含区域的大小 ; 所有值都以外部区域的百分比表示,即ChartArea.InnerPlotPosition相对于ChartArea.PositionChartArea.Position相对于Chart.ClientSize 。 所有百分比均为0-100

因此ChartArea包括LabelsLegends以及AxesTickMarks ..

我们想要的是找到一种方法来使InnerPlotArea正方形,即具有相同的宽度和高度( 以像素为单位) 。 百分比不会做!

让我们从几个简单的计算开始; 如果这些是我们的数据..:

  // we'll work with one ChartArea only..: ChartArea ca = chart1.ChartAreas[0]; ElementPosition cap = ca.Position; ElementPosition ipp = ca.InnerPlotPosition; 

..那么这些是两个区域的像素大小:

  // chartarea pixel size: Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f), (int)( cap.Height * chart1.ClientSize.Height / 100f)); // InnerPlotArea pixel size: Size IppSize = new Size((int)(ipp.Width * CaSize.Width / 100f), (int)(ipp.Height * CaSize.Height / 100f)); 

理想情况下,我们希望InnerPlotArea是正方形; 因为不能很好地让较小的一面增长(否则图表会过度绘制),我们需要缩小较大的一面。 所以InnerPlotArea的新像素大小是

 int ippNewSide = Math.Min(IppSize.Width, IppSize.Height); 

接下来是什么? 由于Chart.Size刚刚设置好,我们不想搞砸它。 我们也不应该乱用ChartArea :它仍然需要空间来容纳Legend等。

所以我们改变了InnerPlotArea的大小..:

首先创建一个类级变量来存储InnerPlotPosition的原始值:

  ElementPosition ipp0 = null; 

我们需要它保持原始百分比,即边距,以便在计算新的百分比时使用它们。 当我们调整图表时,当前的图表已经被改变/扭曲了..

然后我们创建一个函数来生成InnerPlotArea方块,它将所有内容包装起来:

 void makeSquare(Chart chart) { ChartArea ca = chart.ChartAreas[0]; // store the original value: if (ipp0 == null) ipp0 = ca.InnerPlotPosition; // get the current chart area : ElementPosition cap = ca.Position; // get both area sizes in pixels: Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f), (int)( cap.Height * chart1.ClientSize.Height / 100f)); Size IppSize = new Size((int)(ipp0.Width * CaSize.Width / 100f), (int)(ipp0.Height * CaSize.Height / 100f)); // we need to use the smaller side: int ippNewSide = Math.Min(IppSize.Width, IppSize.Height); // calculate the scaling factors float px = ipp0.Width / IppSize.Width * ippNewSide; float py = ipp0.Height / IppSize.Height * ippNewSide; // use one or the other: if (IppSize.Width < IppSize.Height) ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, ipp0.Width, py); else ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, px, ipp0.Height); } 

您可以在resize之后或期间调用该函数。

 private void chart1_Resize(object sender, EventArgs e) { makeSquare(chart1); } 

这个function在起作用:

原始尺寸: 原版的

挤了一下: 缩放

并再次成为正方形: 广场

请注意绿色ChartArea如何为LabelsLegend保留足够的空间,以及轴的自动缩放如何仍然有效..但X轴标签现在不适合一行。 另请注意ChartArea.BackColor 实际上只是InnerPlotArea的颜色!

请注意,在修改ChartArea布局(如放大或移动或移除Legends或更改Labels的大小或角度等)之后,您可能必须刷新变量ipp0以反映更改的百分比。

当然你可以修改函数以传递任何其他比例来保持而不是将绘图区域保持为正方形。