C#WinForms图表控件:获取Bar的大小,区域,位置

时间:2017-07-09 23:17:00

标签: c# winforms charts

有没有办法获得stackcolumn图表栏的矩形?

这段代码片段是如何工作的,但它非常难看:

send: b'POST /forum/ucp.php?mode=login HTTP/1.1
Host: osu.ppy.sh
Accept-Encoding: identity
Content-Length: 70'


send: b'redirect=index.php&sid=&login=Login&username=USERNAME-HERE&password=PASSWORD-HERE'
reply: 'HTTP/1.1 200 OK'


header: Date
header: Content-Type
header: Transfer-Encoding
header: Connection
header: Set-Cookie
header: Cache-Control
header: Expires
header: Pragma
header: X-Frame-Options
header: X-Content-Type-Options
header: Server
header: CF-RAY

我会试着描述我的目的: 我想从各种测试结果创建一个图表,并将其作为HTML文件提供。生成的图表作为图像文件插入HTML文档中。现在,我想将图表区域的每个部分从图表链接到外部文档。由于图形是静态的,我只能使用“MAP Area”元素将任何区域作为HTML的链接。 “map”元素需要“矩形”或这些坐标。这就是我需要Bar的每个部分的协调员的原因。 我必须提到我还不熟悉Chart控件。

enter image description here

图形生成testweise。

[解决] 我得到了解决方案:

            var points = new List<Point>();
            for (int x = 0; x < chart.Size.Width; x++)
            {
                for (int y = 0; y < chart.Size.Height; y++)
                {
                    var hp = chart.HitTest(x, y, false, ChartElementType.DataPoint);
                    var result = hp.Where(h => h.Series?.Name == "Cats");
                    if (result.Count() > 0)
                    {
                        points.Add(new Point(x, y));
                    }
                }
            }
            var bottomright = points.First();
            var topleft = points.Last();

此解决方法适用于stackcolumn,点从x轴= 0开始。 只需要手动设置PixelPointWidth属性以获得正确的宽度。我还没有找到一种方法来获得默认的条形宽度..

2 个答案:

答案 0 :(得分:0)

这非常棘手,我真的希望我知道如何从某些图表功能中获取界限!

您的代码段实际上是解决方法的良好开端。我同意它有问题:

  • 很难看
  • 并不总是有效
  • 表现糟糕

让我们逐一解决这些问题:

  • 是的,它很难看,但那就是解决方法。我的解决方案更加丑陋; - )
  • 我发现有两件事不起作用:

    • 您无法在HitTest事件期间致电Pre/PostPaint或发生可怕的事情,例如某些Series失踪,异常或其他崩溃......
    • 最后 Series宽度的结果是关闭 1-2个像素。
  • 即使对于小图表,测试图表中每个像素的性能也会很糟糕,但是当您放大图表时会变得越来越糟。虽然这是相对容易预防的:

我们正在搜索的是每个DataPoint的每个Series的边界矩形。

矩形由左右或宽度加上顶部和底部或高度定义。

我们可以通过使用y值中的轴函数ValueToPixelPosition和每个0来获得顶部底部的精确值点。这很简单便宜。

除此之外,我们仍然需要找到点的边缘。要做到这一点,我们需要沿着零线进行测试。 (所有积分将在那里开始或结束!)

这大大减少了测试次数。

我决定分别对每个系列进行测试,每次都在0进行测试。为了获得更好的性能,我们可以一次性完成所有工作。

这是一个为给定List<Rectangle>返回Series的函数:

List<Rectangle> GetColumnSeriesRectangles(Series s, Chart chart, ChartArea ca)
{
    ca.RecalculateAxesScale();

    List<Rectangle> rex = new List<Rectangle>();
    int loff = s == chart.Series.Last() ? 2 : 0; ;
    int y0 = (int)ca.AxisY.ValueToPixelPosition(0);
    int left = -1;
    int right = -1;
    foreach (var dp in s.Points)
    {
        left = -1;
        int delta = 0;
        int off = dp.YValues[0] > 0 ? delta : -delta;
        for (int x = 0; x < chart.Width; x++)
        {
            var hitt = chart.HitTest(x, y0 +off );

            if (hitt.ChartElementType == ChartElementType.DataPoint &&
                ((DataPoint)hitt.Object) == dp)
            {
                if (left < 0) left = x;
                right = x;
            }
            else if (left > 0 && right > left) break;
        }
        int y =  (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]);
        rex.Add(new Rectangle(left, Math.Min(y0, y), 
                              right - left + 1 - loff, Math.Abs(y - y0)));
        left = -1;
    }
    return rex;
}

一些注意事项:

  • 我从RecalculateAxesScale开始,因为在计算当前布局之前我们无法进行Hittest。

  • 我使用辅助变量loff来保存上一个Series中宽度的偏移量。

  • 我开始在最后一个x坐标处搜索,因为这些点应该按顺序排列。如果他们没有,因为您使用了有趣的x值或插入点,则可能需要从0开始..

  • 我使用y0作为hittesting y和点'base的零值的基线。

  • 我使用一点数学来获得正负y值的界限。

这是一个结构,用于保存所有Series的矩形和代码以收集它们:

Dictionary<string, List<Rectangle>> ChartColumnRectangles = null;

Dictionary<string, List<Rectangle>> GetChartColumnRectangles(Chart chart, ChartArea ca)
{
    Dictionary<string, List<Rectangle>> allrex = new Dictionary<string, List<Rectangle>>();
    foreach (var s in chart.Series)
    {
        allrex.Add(s.Name, GetColumnSeriesRectangles(s, chart, ca));
    }
    return allrex;
}

每当我们添加点或调整图表大小时,我们需要重新计算矩形;每当轴视图改变时也是如此。 AxisViewChangedClientSizeChangedResize的常用代码以及您添加或删除点的任何位置可能如下所示:

Chart chart= sender as Chart;
GetChartColumnRectangles(chart, chart.ChartAreas[0]);

让我们用Paint事件测试结果:

private void chart1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    chart1.ApplyPaletteColors();
    foreach (var kv in ChartColumnRectangles)
    {
        {
            foreach (var r in kv.Value)
                g.DrawRectangle(Pens.Black, r);
        }
    }
}

这是在行动:

enter image description here

答案 1 :(得分:0)

好吧,我一直走这条路,对我来说,大的问题是'PixelPointWidth'的自定义属性就是这样-它是自定义的。除非已设置,否则无法检索。我需要物品的宽度-必须自己整理/计算。请记住,许多图表都可以平移/缩放,因此一旦走上这条路,就需要重新计算并为图表预涂事件设置它。

这是我做的一个粗略的小功能(比教育用途要冗​​长得多,并且没有错误处理:)):

        private int CalculateChartPixelPointWidth(Chart chart, ChartArea chartArea, Series series)
    {
        // Get right side - takes some goofy stuff - as the pixel location isn't available
        var areaRightX = Math.Round(GetChartAreaRightPositionX(chart, chartArea));
        var xValue = series.Points[0].XValue;
        var xPixelValue = chartArea.AxisX.ValueToPixelPosition(xValue);

        var seriesLeftX = chart.Location.X + xPixelValue;
        var viewPointWidth = Math.Round((areaRightX - seriesLeftX - (series.Points.Count * 2)) / series.Points.Count, 0);

        return Convert.ToInt32(viewPointWidth);
    }

这也是:

        private double GetChartAreaRightPositionX(Chart chart, ChartArea area)
    {
        var xLoc = chart.Location.X;
        return xLoc + (area.Position.Width + area.Position.X) / 100 * chart.Size.Width;
    }

之所以这样计算,是因为我需要在普通图表项目对象(出于自己的目的而自己绘制)的顶部绘制一些图形叠加层。

在图表的“ prepaint”事件中,我需要计算与当前图表视图匹配的“ PixelPointWidth”(可能会被平移/缩放)。然后,我使用该值将图表自定义属性设置为match。 。 。这样正常的图表实体和MINE会正确对齐/缩放(确保我们位于正确的“ x”轴位置):

在我的绘画前事件中-在绘制图形实体之前,请执行以下操作:

                // Pretty close scwag . . .
                var viewPointWidth = CalculateChartPixelPointWidth(e.Chart, e.Chart.ChartAreas[0], e.Chart.Series[0]);

                // Set the custom property and use the same point width for my own entities .  .
                chart1.Series[0].SetCustomProperty("PixelPointWidth", viewPointWidth.ToString("D"));
                // . . .  now draw my entities below . . .