c# - 不同系列之间的堆叠条形边框

标签 c# .net c#-4.0 charts mschart

我想要的是在 StackedBar 中设置两个系列之间的边框,就像这张图片蓝色和绿色之间的粗黑线

enter image description here

我想不出任何指定边框的想法,我尝试通过此代码将边框设置为系列

                chart.Series["series0"].BorderWidth = 2;
                chart.Series["series0"].BorderColor = Color.Black;
                chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;

但这就是我得到的结果

enter image description here enter image description here

这是我的代码

 double l = Convert.ToDouble(query1[i - 1][0]) - 10;
                    string n = query1[i - 1][1];
                    int count = 0;
                for (double t = l; t < l + 10; t++)
                {

                        //Next line Calc. the occurence of character in a text file
                        count = n.Split('C').Length - 1;
                        //Multiple the occurence by 10 so it become percent
                        chart.Series["series0"].Points.AddXY(t, count * 10);
                        chart.Series["series0"]["PointWidth"] = "1";
                        chart.Series["series0"].BorderWidth = 2;
                        chart.Series["series0"].BorderColor = Color.Black;
                        chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;


                        count = n.Split('o').Length - 1;
                        chart.Series["series1"].Points.AddXY(t, count * 10);
                        chart.Series["series1"]["PointWidth"] = "1";

                }

如何使用StackedBar实现首图效果? ,如果我不能使用 StackedBar,您建议使用什么图表类型??

最佳答案

没有任何内置图表元素可以轻松地成为这两个系列之间的边界线。 (创建 LineAnnotations 来实现这一点将是一场噩梦......)

因此添加线条的方法是将它们绘制到图表的表面上。这在 PostPaint 事件中最自然地完成,仅为此类装饰提供。

这里的轴具有方便的函数来在数据值和像素位置之间进行转换。我们需要 ValueToPixelPosition 方法。

我将带您了解图表绘制的各种变化,随着我们接近最终版本,这些变化逐渐变得更加复杂......:

让我们从一个简单的示例开始:让我们构建并装饰一个 StackedArea 图表;这是绘图代码:

private void chart2_PostPaint(object sender, ChartPaintEventArgs e)
{
    Series s = chart1.Series[0];
    ChartArea ca = chart1.ChartAreas[0];
    var pp = s.Points.Select(x=>
        new PointF( (float)ca.AxisX.ValueToPixelPosition(x.XValue),
                    (float)ca.AxisY.ValueToPixelPosition(x.YValues[0])  )   );

    if (s.Points.Count >  1) 
        using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
            e.ChartGraphics.Graphics.DrawLines(pen, pp.ToArray());
}

Points.Select 实际上只是循环的简写;因此,在创建像素点列表后,我们只需绘制它即可。

现在,如您所见,StackedArea 图表是尖头的,看起来不像 StackedBarStackedColumn 图表。因此,让我们通过添加一些额外的点来欺骗和“纠正”面积图:

void rectifyArea(Series s)
{
    for (int i = s.Points.Count - 1; i > 0; i--)
        s.Points.InsertXY(i, i - 1, s.Points[i].YValues[0]);
}

结果: enter image description here enter image description here

现在这并不难;不幸的是,你不能将 StackedArea 从左到右而不是自下而上。所以我们最终需要将图表类型更改为 Bar 类型..

这里的挑战是找到这些条形的右上角和右下角。我们确实有 DataPoint 值,但这些值位于条形的中间。因此,我们需要添加/减去条形宽度的一半以获得。为此,我们需要宽度

虽然您已使用 PointWidth 属性将其设置为 1,但我们真正需要的是像素宽度。我们最好通过减去两个相邻点的像素坐标来得到它。

这使得 PostPaint 事件有点长,但仍然不太复杂;我们将从 StackedColumn 图表开始,为每个数据点添加两个角点:

private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
    Series s = chart1.Series[0];
    ChartArea ca = chart1.ChartAreas[0];
    if (s.Points.Count <= 0) return;

    // calculate width of a column:
    int pp1 = (int)ca.AxisX.ValueToPixelPosition(s.Points[0].XValue);
    int pp2 = (int)ca.AxisX.ValueToPixelPosition(s.Points[1].XValue);
    float w2 = Math.Abs(pp2 - pp1) / 2f;

    List<PointF> points = new List<PointF>();
    for (int i = 0; i < s.Points.Count; i++)
    {
        DataPoint dp = s.Points[i];
        points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) - w2,
                               (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));

        points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) + w2,
                               (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
    }

    if (points.Count > 1)
        using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
            e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
}

现在这看起来与我们的“修正面积图”的假版本几乎相同。我们需要更改什么才能将其应用于 StackedBar 图表?几乎没有!我们需要注意的唯一两件事是

  • y 轴的方向。由于点向上移动,但 GDI+ 图形的像素坐标向下移动,我们需要以相反的顺序创建两个角点。
  • 我们需要反转 x 和 y 坐标,因为所有类型的条形图 图表的轴都是相反的。

这是两个带有边框的堆叠图表:

enter image description here enter image description here

这是 StackBar 图表的循环:

for (int i = 0; i < s.Points.Count;  i++)
{
   points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
                           (float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) + w2));
   points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
                           (float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) - w2));
}

请注意,我使用 4 像素的固定笔宽度进行绘制。要使其与图表一起缩放,您可能需要动态计算笔宽度..

更新

enter image description here

要在多个系列的顶部绘制边框,您可以将代码放入如下循环中:

private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
    Chart  chart = chart1;
    Series s0 = chart.Series[0];
    ChartArea ca = chart.ChartAreas[0];

    // calculate width of a bar:
    int pp1 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[0].XValue);
    int pp2 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[1].XValue);
    float delta = Math.Abs(pp2 - pp1) / 2f;

    for (int s = 0; s < chart.Series.Count; s++)
    {
       List<PointF> points = new List<PointF>();
       for (int p = 0; p < chart.Series[s].Points.Count; p++)
       {
         DataPoint dp = chart.Series[s].Points[p];
         double v = GetStackTopValue(chart, s, p);
         points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
                                (float)ca.AxisX.ValueToPixelPosition(dp.XValue) + delta));
         points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
                               (float)ca.AxisX.ValueToPixelPosition(dp.XValue) - delta));
        }
        using (Pen pen = new Pen(Color.DarkOliveGreen, 3f))
            e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
    }
}

double GetStackTopValue(Chart chart, int series, int point)
{
    double v = 0;
    for (int i = 0; i < series + 1; i++)
        v += chart.Series[i].Points[point].YValues[0];
    return v;
}

关于c# - 不同系列之间的堆叠条形边框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38175238/

相关文章:

c# - Application.Exit() 哪个操作是第一个

c# - 确定主机是否已解析 DNS 名称或 IP

c# - 作为 Entity Framework 生成代码的结果的命名空间

c# - 将匿名类型转换为名义类型

c# - C# 中具有多个常量表达式的 Switch 语句。可能吗?

c# - 我的程序太长了。我想用循环方法缩短它

c# - 如何将 PolicyHttpMessageHandler 用作 "standalone"?

c# - 从 WinForms 应用程序发送带有附件的电子邮件?

c# - 将数组作为参数传递给另一个

c# - 为什么我必须创建 `IEnumerable<T>` 的具体实现才能修改其成员?