C#/ Excel:チャート上の最大シリーズサイズの回避
質問
1つのExcelシリーズに収まるよりも多くのポイントをプログラムでグラフ化するのに助けが必要です。
http://office.microsoft.com/en-usによる/excel/HP100738491033.aspx Excel 2007チャートに表示できる最大ポイント数は256000です。各シリーズが32000ポイントで上限になるとすると、256000ポイント全体をプロットするには8シリーズが必要です。私の顧客は、データセットが大きいため、チャートごとに最大ポイント数をプロットする必要があります。
C#/ Excel相互運用の経験が中程度なので、プログラムでワークシートを作成し、32000ポイントの各セットをループしてシリーズとしてグラフに追加し、データが完全にプロットされたら停止するのは簡単だと思いましたまたは8シリーズがプロットされました。適切に色付けされている場合、8シリーズは1つのシリーズと視覚的に区別できません。
残念ながらここにいます。私が遭遇する主な問題は:
(フルサイズ) 2-Dグラフのデータ系列で使用できるデータポイントの最大数は32,000です... http://img14.imageshack.us/img14/9630/errormessagen.png
このポップアップは、奇妙なことに、次の行を実行すると表示されます。
そして以下を伴う:
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");
}
解決
アクティブセルがデータブロック内にある場合、Excelは範囲をプロットすることを前提とする場合があります。
データの隣にない空白のセルを選択し、チャートを挿入します。事前に入力されるのではなく、空白になります。
他のヒント
グラフは実際にはExcelにある必要がありますか?そのように多くのデータポイントがあると、パフォーマンスは恐ろしくなります。
1つの提案は、サードパーティのコンポーネントを使用してグラフを生成することです。これを実現するための具体的な手法は、Excelでデータを表示できるようにする必要があるか、出力グラフを他の場所で使用可能にする必要があるかどうかによって異なります。
Excel内でグラフを表示する必要がない場合は、データポイントを渡して、グラフ作成アプリケーションまたはWebブラウザで画像を表示します。
Excelでグラフを表示する必要がある場合は、外部のグラフ作成アプリケーションを呼び出して、データポイントのコレクションを渡すことができます。画像が返されたら、vbaでExcelに挿入するだけです。
必要に応じて、両方のアプローチに関する詳細情報を提供できます。
また、他の考慮事項には、グラフのドリルダウン機能が必要かどうかが含まれる場合があります。このように多くのデータポイントがあるので、そうなるとは想像できません。
次の質問に答えることができれば、より良い答えを定式化するのに役立つかもしれません。
-
これらのアイテムの出力を表示するユーザーインターフェイスはどのようなものですか? (例:Excel、ASP.NET Webアプリケーション、Windowsフォーム、WPF、Silverlight、その他)
-
これらのグラフは、ユーザーのリクエストに応じてリアルタイムで生成されるはずですか、それとも生成されて保存されますか?それらがオンデマンドで生成される場合、ユーザーが待機してもよいと考える最大時間はどれくらいですか?
-
実際にExcelを使用することはどれほど重要ですか?ディスプレイの要件であるため、それを使用していますか、それとも便利なものですか?
-
「Wow factor」はどれほど重要ですか?グラフの表示のために?単にグラフを持っているだけですか、それとも非常に美しくなければなりませんか?
-
ユーザーはグラフにドリルダウンする機能を必要としますか、それとも単に画像を表示するだけで十分ですか?
将来これに遭遇した人を助けるために、Jonが修正した完全な機能を以下に示します。
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");
}