C#/Excel:解决图表上的最大系列大小

发布于 2024-08-04 21:51:18 字数 5512 浏览 10 评论 0原文

我需要帮助以编程方式绘制比单个 Excel 系列所能容纳的更多点。

根据 http://office.microsoft.com/en-us/excel/ HP100738491033.aspx Excel 2007 图表上可显示的最大点数为 256000。鉴于每个系列的上限为 32000 点,需要 8 个系列才能绘制完整的 256000 点。由于我们使用的数据集很大,我的客户要求绘制每个图表的最大点数。

我对 C#/Excel 互操作有一定的经验,因此我认为以编程方式创建一个工作表,然后循环遍历每组 32000 个点并将它们作为系列添加到图表中,在数据完全绘制或 8 个系列时停止会很容易被策划了。如果颜色正确,8 系列在视觉上与单一系列没有什么区别。

不幸的是我在这里。我遇到的主要问题是:

(full size) 二维图表的数据系列中可以使用的最大数据点数量为 32,000 ... http://img14.imageshack.us/img14/9630/errormessagen.png

奇怪的是,当我执行以下行时,会出现此弹出窗口:

chart.ChartType = 图表类型(其中图表类型为 xlXYScatterLines)

并附有:

来自 HRESULT 的异常:0x800AC472 http://img21.imageshack.us/ img21/5153/exceptionb.png

在我指定要绘制图表的数据之前,我不明白如何生成这样的弹出/警告/异常。 Excel 是不是想耍小聪明?

作为临时解决方法,我将 Chart.ChartType = ChartType 语句放入 try-catch 块中,以便我可以继续。

如下所示,我的“分块”代码按预期工作,但在尝试向图表添加数据时仍然遇到同样的问题。 Excel 说我试图绘制太多点,但显然我没有这样做。

全尺寸图片带有监视窗口的代码块 http://img12.imageshack.us/img12/5360/snippet .png

我知道我可能还没有与每个系列正确关联的 X 值,但我正在尝试在进一步操作之前使其发挥作用。

任何帮助将不胜感激。

这是完整的代码:

public void DrawScatterGraph(string xColumnLetter, string yColumnLetterStart, string yColumnLetterStop, string xAxisLabel, string yAxisLabel, string chartTitle, Microsoft.Office.Interop.Excel.XlChartType chartType, bool includeTrendline, bool includeLegend)
    {
        int totalRows = dataSheet.UsedRange.Rows.Count; //dataSheet is a private class variable that 
                                                        //is already properly set to the worksheet
                                                        //we want to graph from

        if (totalRows < 2) throw new Exception("Not generating graph for " + chartTitle.Replace('\n', ' ') 
                                            + " because not enough data was present");

        ChartObjects charts = (ChartObjects)dataSheet.ChartObjects(Type.Missing);
        ChartObject chartObj = charts.Add(100, 300, 500, 300);
        Chart chart = chartObj.Chart;

        try { chart.ChartType = chartType; }
        catch { }   //i don't know why this is throwing an exception, but i'm
                    //going to bulldoze through this problem temporarily 

        if (totalRows < SizeOfSeries) //we can graph the data in a single series - yay!
        {
            Range xValues = dataSheet.get_Range(xColumnLetter + "2", xColumnLetter + totalRows.ToString());
            Range yValues = dataSheet.get_Range(yColumnLetterStart + "1", yColumnLetterStop + totalRows.ToString());
            chart.SetSourceData(yValues, XlRowCol.xlColumns);
            SeriesCollection seriesCollection = (SeriesCollection)chart.SeriesCollection(Type.Missing);
            foreach (Series s in seriesCollection)
            {
                s.XValues = xValues;
            }
        }
        else // we need to split the data across multiple series -- this doesn't work yet
        {
            int startRow = 1; 
            while (startRow < totalRows)
            {
                int stopRow = (startRow + SizeOfSeries)-1;  
                if (stopRow > totalRows) stopRow = totalRows;
                Range curRange = dataSheet.get_Range(yColumnLetterStart + startRow.ToString(), yColumnLetterStop + stopRow.ToString());
                try
                {
                    ((SeriesCollection)chart.SeriesCollection(Type.Missing)).Add(curRange, XlRowCol.xlColumns, 
                                                                            Type.Missing, Type.Missing, Type.Missing);
                }
                catch (Exception exc)
                {
                    throw new Exception(yColumnLetterStart + startRow.ToString() + "!" + yColumnLetterStop + stopRow.ToString() + "!" + exc.Message);
                }
                startRow = stopRow+1;
            }
        }

        chart.HasLegend = includeLegend;
        chart.HasTitle = true;
        chart.ChartTitle.Text = chartTitle;

        Axis axis;
        axis = (Axis)chart.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = xAxisLabel;
        axis.HasMajorGridlines = false;
        axis.HasMinorGridlines = false;

        axis = (Axis)chart.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = yAxisLabel;
        axis.HasMajorGridlines = true;
        axis.HasMinorGridlines = false;

        if (includeTrendline)
        {
            Trendlines t = (Trendlines)((Series)chart.SeriesCollection(1)).Trendlines(Type.Missing);
            t.Add(XlTrendlineType.xlLinear, Type.Missing, Type.Missing, 0, 0, Type.Missing, false, false, "AutoTrendlineByChameleon");
        }

        chart.Location(XlChartLocation.xlLocationAsNewSheet, "Graph");
    }

I need help programatically graphing more points than can fit in a single Excel series.

According to http://office.microsoft.com/en-us/excel/HP100738491033.aspx the maximum number of points displayable on an Excel 2007 chart is 256000. Given that each series caps out at 32000 points, 8 series are required to plot the full 256000 points. My customer requires plotting of maximum amount of points per chart due to the large data sets we work with.

I have moderate experience with C#/Excel interop so I thought it would be easy to programatically create a worksheet and then loop through each set of 32000 points and add them to the graph as a series, stopping when the data was fully plotted or 8 series were plotted. If colored properly, the 8 series would be visually indistinguishable from a single series.

Unfortunately here I am. The main problem I encounter is:

(full size)
The maximum number of datapoints you can use in a data series for a 2-D chart is 32,000... http://img14.imageshack.us/img14/9630/errormessagen.png

This pop-up, strangely enough, appears when I execute the line:

chart.ChartType = chartType (where chartType is xlXYScatterLines)

and is accompanied by:

Exception from HRESULT: 0x800AC472 http://img21.imageshack.us/img21/5153/exceptionb.png

I do not understand how I could be generating such a popup/warning/exception before I have even specified the data to be graphed. Is Excel trying to be clever here?

As a temporary workaround, I've put the chart.ChartType = chartType statement into a try-catch block so I can keep going.

As the following shows, my "chunking" code is working as intended, but I still encounter the same problem when trying to add data to the graph. Excel says I am trying to graph too many points when clearly I am not.

(full size image)
code block with watch window http://img12.imageshack.us/img12/5360/snippet.png

I understand I may not have the X Values correctly associated with each series yet, but I'm trying to get this to work before I go further.

Any help would be greatly appreciated.

Here's the full code:

public void DrawScatterGraph(string xColumnLetter, string yColumnLetterStart, string yColumnLetterStop, string xAxisLabel, string yAxisLabel, string chartTitle, Microsoft.Office.Interop.Excel.XlChartType chartType, bool includeTrendline, bool includeLegend)
    {
        int totalRows = dataSheet.UsedRange.Rows.Count; //dataSheet is a private class variable that 
                                                        //is already properly set to the worksheet
                                                        //we want to graph from

        if (totalRows < 2) throw new Exception("Not generating graph for " + chartTitle.Replace('\n', ' ') 
                                            + " because not enough data was present");

        ChartObjects charts = (ChartObjects)dataSheet.ChartObjects(Type.Missing);
        ChartObject chartObj = charts.Add(100, 300, 500, 300);
        Chart chart = chartObj.Chart;

        try { chart.ChartType = chartType; }
        catch { }   //i don't know why this is throwing an exception, but i'm
                    //going to bulldoze through this problem temporarily 

        if (totalRows < SizeOfSeries) //we can graph the data in a single series - yay!
        {
            Range xValues = dataSheet.get_Range(xColumnLetter + "2", xColumnLetter + totalRows.ToString());
            Range yValues = dataSheet.get_Range(yColumnLetterStart + "1", yColumnLetterStop + totalRows.ToString());
            chart.SetSourceData(yValues, XlRowCol.xlColumns);
            SeriesCollection seriesCollection = (SeriesCollection)chart.SeriesCollection(Type.Missing);
            foreach (Series s in seriesCollection)
            {
                s.XValues = xValues;
            }
        }
        else // we need to split the data across multiple series -- this doesn't work yet
        {
            int startRow = 1; 
            while (startRow < totalRows)
            {
                int stopRow = (startRow + SizeOfSeries)-1;  
                if (stopRow > totalRows) stopRow = totalRows;
                Range curRange = dataSheet.get_Range(yColumnLetterStart + startRow.ToString(), yColumnLetterStop + stopRow.ToString());
                try
                {
                    ((SeriesCollection)chart.SeriesCollection(Type.Missing)).Add(curRange, XlRowCol.xlColumns, 
                                                                            Type.Missing, Type.Missing, Type.Missing);
                }
                catch (Exception exc)
                {
                    throw new Exception(yColumnLetterStart + startRow.ToString() + "!" + yColumnLetterStop + stopRow.ToString() + "!" + exc.Message);
                }
                startRow = stopRow+1;
            }
        }

        chart.HasLegend = includeLegend;
        chart.HasTitle = true;
        chart.ChartTitle.Text = chartTitle;

        Axis axis;
        axis = (Axis)chart.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = xAxisLabel;
        axis.HasMajorGridlines = false;
        axis.HasMinorGridlines = false;

        axis = (Axis)chart.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = yAxisLabel;
        axis.HasMajorGridlines = true;
        axis.HasMinorGridlines = false;

        if (includeTrendline)
        {
            Trendlines t = (Trendlines)((Series)chart.SeriesCollection(1)).Trendlines(Type.Missing);
            t.Add(XlTrendlineType.xlLinear, Type.Missing, Type.Missing, 0, 0, Type.Missing, false, false, "AutoTrendlineByChameleon");
        }

        chart.Location(XlChartLocation.xlLocationAsNewSheet, "Graph");
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

过期以后 2024-08-11 21:51:18

如果活动单元格位于数据块中,Excel 可能会假设您要绘制范围。

选择不在数据旁边的空白单元格,然后插入图表。它将是空白的,而不是预先填充的。

If the active cell is in a block of data, Excel may assume you want to plot the range.

Select a blank cell which is not next to the data, then insert the chart. It will be blank, rather than prepopulated.

奈何桥上唱咆哮 2024-08-11 21:51:18

您的图表实际上必须是 Excel 格式吗?有了这么多数据点,性能将会很糟糕。

一个建议可能是使用第三方组件来生成图表。如何实现这一点的具体技术取决于您是否必须能够在 Excel 中查看数据,或者输出图表是否只需要在其他地方可用。

如果图表不需要在 Excel 中可见,则只需传递数据点并在绘图应用程序或 Web 浏览器中查看图像即可。

如果您确实需要使用 Excel 查看图表,您可以调用外部绘图应用程序并向其传递数据点集合。当它返回图像时,只需使用 vba 将其插入 excel 中即可。

如果您需要,我可以为您提供有关这两种方法的更多信息。

此外,其他考虑因素可能包括您是否需要在图表上具有向下钻取功能。有了这么多的数据点,我无法想象你会这么做。


如果您可以回答以下问题,可能会帮助人们制定更好的答案。

  1. 什么样的用户界面将呈现这些项目的输出? (例如 Excel、ASP.NET Web 应用程序、Windows 窗体、WPF、Silverlight 等。)

  2. 这些图表是否应该根据用户的请求实时生成,还是生成并存储?如果它们是按需生成的,那么您的用户认为可接受的最长时间等待是多少?

  3. 实际使用 Excel 有多重要?您使用它是因为它是显示的要求,还是只是为了方便?

  4. “令人惊叹的因素”对于图表的显示有多重要?是仅仅拥有图表,还是必须非常漂亮?

  5. 用户是否需要深入了解图表的能力,或者仅仅能够查看图像就足够了?

Does your graph actually have to be in Excel? With that many data points the performance would be horrible.

One suggestion might be to use a third party component to generate the graph. The specific technique for how to accomplish this depends on whether you have to be able to view the data in excel or whether the output graph simply needs to be available elsewhere.

If the graph does not need to be visible within Excel, then just pass the data points and view the image in the graphing application or a web browser.

If you do need to view the graph with excel, you could make a call to the external graphing application and pass it a collection of data points. When it returns the image just insert it in excel with vba.

I can give you more info on both approaches if you need.

Also, other considerations might include whether you need to have drill down capability on the graph. With this many data points, I can not imagine that you would.


If you can answer the following questions, it might help folks formulate better answers.

  1. What sort of user interface will be presenting the output of these items? (e.g. Excel, ASP.NET Web Application, Windows Forms, WPF, Silverlight, other.)

  2. Are these graphs supposed to be generated in real time at a user's request or are they generated and stored? If they are generated on demand, what is the maximum amount of time your users would consider acceptable to wait?

  3. How important is it that you actually use Excel? Are you using it because it is a requirement for display, or is that just what is handy?

  4. How important is the "Wow factor" for the display of the graphs? Is simply having the graphs, or do they have to be extremely beautiful?

  5. Do users require any ability to drill down into the graph, or is simply being able to view the image sufficient?

兮子 2024-08-11 21:51:18

为了帮助将来遇到此问题的任何人,以下是乔恩修复后的完整功能:

    public void DrawScatterGraph(string xColumnLetter, string yColumnLetterStart, string yColumnLetterStop, string xAxisLabel, string yAxisLabel, string chartTitle, Microsoft.Office.Interop.Excel.XlChartType chartType, bool includeTrendline, bool includeLegend)
    {
        int totalRows = dataSheet.UsedRange.Rows.Count; //dataSheet is a private class variable that 
                                                        //is already properly set to the worksheet
                                                        //we want to graph from

        if (totalRows < 2) throw new Exception("Not generating graph for " + chartTitle.Replace('\n', ' ') 
                                               + " because not enough data was present");

        dataSheet.get_Range("Z1", "Z2").Select();   //we need to select some empty space
                                                    //so Excel doesn't try to jam the 
                                                    //potentially large data set into the 
                                                    //chart automatically

        ChartObjects charts = (ChartObjects)dataSheet.ChartObjects(Type.Missing);
        ChartObject chartObj = charts.Add(100, 300, 500, 300);
        Chart chart = chartObj.Chart;
        chart.ChartType = chartType;
        SeriesCollection seriesCollection = (SeriesCollection)chart.SeriesCollection(Type.Missing);

        if (totalRows < SizeOfSeries) //we can graph the data in a single series - yay!
        {
            Range xValues = dataSheet.get_Range(xColumnLetter + "2", xColumnLetter + totalRows.ToString());
            Range yValues = dataSheet.get_Range(yColumnLetterStart + "1", yColumnLetterStop + totalRows.ToString());
            chart.SetSourceData(yValues, XlRowCol.xlColumns);

            foreach (Series s in seriesCollection)
            {
                s.XValues = xValues;
            }
        }
        else // we need to split the data across multiple series 
        {
            int startRow = 2; 

            while (startRow < totalRows)
            {
                int stopRow = (startRow + SizeOfSeries)-1;  
                if (stopRow > totalRows) stopRow = totalRows;

                Series s = seriesCollection.NewSeries();
                s.Name = "ChunkStartingAt" + startRow.ToString();
                s.XValues = dataSheet.get_Range(xColumnLetter + startRow.ToString(), xColumnLetter + stopRow.ToString());
                s.Values = dataSheet.get_Range(yColumnLetterStart + startRow.ToString(), yColumnLetterStop + stopRow.ToString());

                startRow = stopRow+1;
            }
        }

        chart.HasLegend = includeLegend;
        chart.HasTitle = true;
        chart.ChartTitle.Text = chartTitle;

        Axis axis;
        axis = (Axis)chart.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = xAxisLabel;
        axis.HasMajorGridlines = false;
        axis.HasMinorGridlines = false;

        axis = (Axis)chart.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = yAxisLabel;
        axis.HasMajorGridlines = true;
        axis.HasMinorGridlines = false;

        if (includeTrendline)
        {
            Trendlines t = (Trendlines)((Series)chart.SeriesCollection(1)).Trendlines(Type.Missing);
            t.Add(XlTrendlineType.xlLinear, Type.Missing, Type.Missing, 0, 0, Type.Missing, false, false, "AutoTrendlineByChameleon");
        }

        chart.Location(XlChartLocation.xlLocationAsNewSheet, "Graph");
    }

To help anyone who comes across this in the future, here's the complete function with Jon's fix:

    public void DrawScatterGraph(string xColumnLetter, string yColumnLetterStart, string yColumnLetterStop, string xAxisLabel, string yAxisLabel, string chartTitle, Microsoft.Office.Interop.Excel.XlChartType chartType, bool includeTrendline, bool includeLegend)
    {
        int totalRows = dataSheet.UsedRange.Rows.Count; //dataSheet is a private class variable that 
                                                        //is already properly set to the worksheet
                                                        //we want to graph from

        if (totalRows < 2) throw new Exception("Not generating graph for " + chartTitle.Replace('\n', ' ') 
                                               + " because not enough data was present");

        dataSheet.get_Range("Z1", "Z2").Select();   //we need to select some empty space
                                                    //so Excel doesn't try to jam the 
                                                    //potentially large data set into the 
                                                    //chart automatically

        ChartObjects charts = (ChartObjects)dataSheet.ChartObjects(Type.Missing);
        ChartObject chartObj = charts.Add(100, 300, 500, 300);
        Chart chart = chartObj.Chart;
        chart.ChartType = chartType;
        SeriesCollection seriesCollection = (SeriesCollection)chart.SeriesCollection(Type.Missing);

        if (totalRows < SizeOfSeries) //we can graph the data in a single series - yay!
        {
            Range xValues = dataSheet.get_Range(xColumnLetter + "2", xColumnLetter + totalRows.ToString());
            Range yValues = dataSheet.get_Range(yColumnLetterStart + "1", yColumnLetterStop + totalRows.ToString());
            chart.SetSourceData(yValues, XlRowCol.xlColumns);

            foreach (Series s in seriesCollection)
            {
                s.XValues = xValues;
            }
        }
        else // we need to split the data across multiple series 
        {
            int startRow = 2; 

            while (startRow < totalRows)
            {
                int stopRow = (startRow + SizeOfSeries)-1;  
                if (stopRow > totalRows) stopRow = totalRows;

                Series s = seriesCollection.NewSeries();
                s.Name = "ChunkStartingAt" + startRow.ToString();
                s.XValues = dataSheet.get_Range(xColumnLetter + startRow.ToString(), xColumnLetter + stopRow.ToString());
                s.Values = dataSheet.get_Range(yColumnLetterStart + startRow.ToString(), yColumnLetterStop + stopRow.ToString());

                startRow = stopRow+1;
            }
        }

        chart.HasLegend = includeLegend;
        chart.HasTitle = true;
        chart.ChartTitle.Text = chartTitle;

        Axis axis;
        axis = (Axis)chart.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = xAxisLabel;
        axis.HasMajorGridlines = false;
        axis.HasMinorGridlines = false;

        axis = (Axis)chart.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary);
        axis.HasTitle = true;
        axis.AxisTitle.Text = yAxisLabel;
        axis.HasMajorGridlines = true;
        axis.HasMinorGridlines = false;

        if (includeTrendline)
        {
            Trendlines t = (Trendlines)((Series)chart.SeriesCollection(1)).Trendlines(Type.Missing);
            t.Add(XlTrendlineType.xlLinear, Type.Missing, Type.Missing, 0, 0, Type.Missing, false, false, "AutoTrendlineByChameleon");
        }

        chart.Location(XlChartLocation.xlLocationAsNewSheet, "Graph");
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文