【问题标题】:Stacked Column Chart in C#C#中的堆积柱形图
【发布时间】:2016-03-02 10:29:01
【问题描述】:

我正在尝试设置我的程序,以便用户可以显示堆积柱形图,其中显示每个部门中出现的拒绝类别的数量(例如,部门 1 中出现 5 个,部门 2 中出现 3 个等) .我在网上浏览了一下并自己尝试了一下,但我似乎无法让它工作。如果有人能够提供帮助,那就太好了。

当用户按下按钮切换到堆积柱形图时,图表当前会做什么:

代码:

private void btnStacked_Click(object sender, EventArgs e)
    {
        charRejections.Series["RFR"].Enabled = false;

        charRejections.Series["Department 1"].Points.Clear();
        charRejections.Series["Department 1"].Enabled = true;

        charRejections.Series["Department 2"].Points.Clear();
        charRejections.Series["Department 2"].Enabled = true;

        charRejections.Series["Department 3"].Points.Clear();
        charRejections.Series["Department 3"].Enabled = true;
        {
            string connectiontring = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\Database1.mdb";
            DataConnection = new OleDbConnection(connectiontring);

            try
            {

                DataConnection.Open();
                OleDbCommand command = new OleDbCommand();
                command.Connection = DataConnection;
                string query1 = "SELECT COUNT(reject_category) as reject, reject_category FROM tblReject_test GROUP BY reject_category";
                command.CommandText = query1;


                OleDbDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    charRejections.Series["Department 1"].Points.AddXY(reader["reject_category"].ToString(), reader["reject"].ToString());
                    charRejections.Series["Department 2"].Points.AddXY(reader["reject_category"].ToString(), reader["reject"].ToString());
                    charRejections.Series["Department 3"].Points.AddXY(reader["reject_category"].ToString(), reader["reject"].ToString());
                }

                DataConnection.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error " + ex);
            }
        }

        this.charRejections.Series["Department 1"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.StackedColumn;
        this.charRejections.Series["Department 2"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.StackedColumn;
        this.charRejections.Series["Department 3"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.StackedColumn;
    }

【问题讨论】:

    标签: c# charts stacked-chart


    【解决方案1】:

    创建堆叠图表很容易。如果您有正确的数据。

    创建堆积图很难。如果您没有正确的数据。

    要遵循一条规则:所有系列中的数据都需要对齐才能使堆叠起作用!

    这听起来很简单,但实际上比人们想象的要复杂得多。

    在我们开始举几个例子之前,这里是简单规则分解为的详细信息:

    • 规则 #1 您需要在 **each 系列中为您在任何系列中拥有的 每个 x 值提供一个数据点。**

    • 规则 #2 您的分数需要按正确的顺序排列,即 x 值升序

    • 规则 #3 为了让您控制要显示的点范围和图表的其他方面,所有 x 值都应该是数字

    有关堆叠图表的工作示例,请查看this post!问题是'如何避免间隙?' 但它真的归结为'如何使堆叠正常工作?'

    请注意,该示例使用来自以代码编写的 DataSource 的数据。正如您所做的那样,从数据库中读取它并没有真正的区别。

    解决方案是添加虚拟点来填补空白。

    这将是您的问题之一,因为您不能期望查询中的数据是完整的。

    要解决此问题,您可以将查询更改为填补空白的 join,或者请图表帮助您。

    我不会进入 SQL 选项,尽管它似乎是最自然的选项。但是请注意,为了遵循规则 #2,您需要在查询中添加 order 子句,以便在任何情况下都按 x 值对记录进行排序,即按您的拒绝类别。

    让我们来看看一个有趣的辅助函数Chart.DataManipulator.InsertEmptyPoints

    这个函数有几个重载;我们将使用包含我们想要对齐的所有系列名称的字符串的那个。这不仅会添加缺失的点,还会将它们插入缺失的位置,所以我们现在应该可以按照规则 #1 和 2 进行操作了!

    在进入更多细节之前(是的,甚至更多细节,叹息,但还有很多事情要做。)让我们看一下规则 #3:

    这是一个适用于所有图表类型的规则,也是图表控件的用户最常打破的一个规则,通常甚至没有注意到..:

    所有 X 值都应该是数字!

    如果改为添加字符串,这些字符串将被填充到轴标签中,否则将被丢弃。最值得注意的是,结果数据点的 x 值都是0

    只要你不需要它们就可以,但是一旦你需要它们,你就会有一个令人讨厌的惊喜。由于它们已经消失,您无法使用它们来计算任何东西,或格式化标签,或显示工具提示等,或使用它们来设置要​​显示的范围。

    请注意,即使 x 值都是 0,数据点仍然沿 x 轴分布;你只是不再控制它了..

    所以你需要决定一些方案来将你的 x 值变成数字!

    一种是设置一个数据结构,其中列出了所有值:

     List<string> catLookup = new List<string>() { "ARTEFACT", "et..cetc.."};
    

    然后您可以像这样找到每个值:

     int found = catLookup.FindIndex(x => x == someValueWeSearch);
    

    这可行,但如果您的应用程序是真实的,它应该能够随着数据增长;所以你应该从数据库中读取所有可能的值。如果设计得当,已经有一个查找表可供使用,使用它的键将是最自然的选择。

    如果不是,您仍然可以通过简单的查询读取值:

    Select distinct reject_category from tblReject_test order by reject_category;
    

    现在让我们调用来对齐我们拥有的所有系列:

      string seriesNames = String.Join(",", seriesLookup.Keys);
      chart1.DataManipulator.InsertEmptyPoints(1, IntervalType.Number, seriesNames);
    

    现在回到您的原始代码以及您需要在那里做什么:

    一方面,您的所有值都是字符串。所以你应该把你的循环改成这样:

    while (reader.Read())
    {
        string seriesName = reader[1].ToString();
        int seriesIndex   = seriesLookup.FindIndex(x => x == seriesName);
        string catName    = reader[2].ToString();
        int catIndex      = catLookup.FindIndex(x => x == catName);
    
        charRejections.Series[seriesName ].Points.AddXY(catIndex, 
                                                  Convert.ToInt16((reader["reject"]));
    }
    

    您会注意到,我不仅插入了帮助变量,使调试变得如此容易,而且还进行了第二次查找以保存创建系列所需的部门并将点添加到各自的系列中。..

    我把创建这个留给你;如果找不到类别或部门,还添加必要的检查..

    【讨论】:

    • 我假设必须更改该系列以显示“dept_id”,并且在 charRejections 语句开头的额外阅读器会创建一个错误,指出它有一些无效的参数。
    • 是的,是的。我把它写成一个建议。额外的阅读器旨在为每个部门提供系列名称。因此,您需要根据您的部门命名该系列或使用其他查找..
    • 我知道我可能对此很陌生,但我以前从未使用过堆积柱形图,而这个答案所做的一切让我更加困惑。对不起。
    • 很抱歉让您感到困惑。我已经改变了答案,但是让我警告你它要长得多,而且当它涉及到你需要的各种细节时,它可能仍然会让你感到困惑。在研究它并实现查找结构之后,你的问题确实会回来。
    • 我已经修改了答案,使用了稍微简单的 InsertEmptyPoints 函数重载。