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:
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");
}
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.
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.
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.)
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?
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?
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?
Do users require any ability to drill down into the graph, or is simply being able to view the image sufficient?
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");
}
Related
I created a new user defined function in excel using c#, excelDna Add-In,what i want to get is when a user open an excel file, clic on a cell and write =myFunction() press enter, data should be displayed in the excel file. these data are retrieved from sql server database and stocked in a 2D array of object, my problem is when i try to display this array in excel range i got this exception
Exception de HRESULT : 0x800A03EC
Below is my code :
public static void LoadViewData()
{
var target = (ExcelReference)XlCall.Excel(XlCall.xlfCaller);
var sheetName = (string)XlCall.Excel(XlCall.xlSheetNm, target);
var application = (Microsoft.Office.Interop.Excel.Application)ExcelDnaUtil.Application;
var sheet = application.Sheets[Regex.Replace(sheetName, #"\[[^]]*\]", string.Empty)];
object[,] result = LoadFromDbData();
var startCell =sheet.Cells[target.RowFirst + 1, target.ColumnFirst];
var endCell =sheet.Cells[target.RowFirst+ result.GetUpperBound(0) - result.GetLowerBound(0) + 1,
target.ColumnFirst+ result.GetUpperBound(1) - result.GetLowerBound(1) + 1];
var writeRange = sheet.Range[startCell, endCell];
writeRange.Value2 = result;
}
target returns the correct value of the cell where the user has written the formula (=myFunction())
sheetName returns the correct activeSheet in which the user writes the formula
result contains the data retrieved from sql server, it is an array of object[854,8]
startcell and endcell represents the range from which cell to which cell data will be displayed
when debugging, all variables contain the correct values, the exception appears in this instruction :
writeRange.Value2 = result;
Anyone has already worked with this or can help please ?
Thanks
I think your "LoadFromDbData()" is returning the data as typed. Try converting each value to a string. Here is a sample (and I can recreate that error code if I do not convert to string):
void Main()
{
var tbl = new System.Data.DataTable();
new SqlDataAdapter(#"
WITH tally ( OrderNo, UniqueId, RandNumber )
AS (
SELECT TOP 50000
ROW_NUMBER() OVER ( ORDER BY t1.object_id ),
NEWID(),
CAST(CAST(CAST(NEWID() AS VARBINARY(4)) AS INT) AS DECIMAL) / 1000
FROM master.sys.all_columns t1
CROSS JOIN master.sys.all_columns t2
)
SELECT OrderNo,
DATEADD(DAY, -OrderNo, GETDATE()) as OrnekDate,
UniqueId, RandNumber,
abs(RandNumber)%100 / 100 as pct
FROM [tally];", #"server=.\SQLExpress;Database=master;Trusted_Connection=yes;").Fill(tbl);
object[,] arr = new object[tbl.Rows.Count + 1, tbl.Columns.Count];
for (int i = 0; i < tbl.Columns.Count; i++)
{
arr[0, i] = tbl.Columns[i].Caption;
}
for (int i = 0; i < tbl.Rows.Count; i++)
{
for (int j = 0; j < tbl.Columns.Count; j++)
{
arr[i + 1, j] = tbl.Rows[i][j].ToString(); // without .ToString() you should have the error
}
}
// Excel dosya yarat ve arrayi koy
Microsoft.Office.Interop.Excel.Application xl = new Microsoft.Office.Interop.Excel.Application();
var workbook = xl.Workbooks.Add();
xl.Visible = true;
Worksheet sht = ((Worksheet)workbook.ActiveSheet);
Range target = (Range)sht.Range[ (Range)sht.Cells[1,1], (Range)sht.Cells[arr.GetUpperBound(0)+1,arr.GetUpperBound(1)+1] ];
target.Value2 = arr;
}
Note: As a side note, why would you transfer the data as a 2D array? That is one of the ways, but beware it is limited (I don't know a good value for the upper limit - try a high number like 200K rows).
Getting data into excel is best done via QueryTables.Add or CopyFromRecordSet to my experience. Depending on your needs you might also directly use the Excel file itself as a data table and do inserts. There is also EPPlus library on Nuget but that would be a little bit slow + may not contain all the capabilities you need.
I have an Issue with C# charting.
This is using System.Windows.Forms.DataVisualization.Charting
My series are configured to be indexed as such:
{
Name = name,
Color = color,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType =
System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine
};
I have 3 series in this chart
I am adding data to the series as it arrives via an Event Handler
for (int i = 0; i < samples; i++)
{
double time_point = DData[i * 4]
chart1.Series[0].Points[lastSample + i].XValue = time_point;
chart1.Series[0].Points[lastSample + i].YValues[0] = DData[i * 4 + 1];
chart1.Series[1].Points[lastSample + i].XValue = time_point;
chart1.Series[1].Points[lastSample + i].YValues[0] = DData[i * 4 + 2];
chart1.Series[2].Points[lastSample + i].XValue = time_point;
chart1.Series[2].Points[lastSample + i].YValues[0] = DData[i * 4 + 3];
}
lastSample = (lastSample + samples) % maxPlotPoints;
chart1.Invalidate();
The issue is as follows:
If I turn on each series individually (via chart1.Series[0].Enabled), they all work fine
If I turn on more than 1 series at a time, a big red X appears instead oft eh chart, and I have to restart the application to resume streaming charts. This either happens immediately or after a few seconds.
If I set time_point to some other number, like 0, this issue doesn't happen, and all 3 charts can be displayed simultaneously
Next, I understand that this happens when each series has a different X-value for the same Point[] location. But I am explicitly setting all 3 series to use the same exact time_point
My next assumption was that the event handler was executing the tread before the previous thread finishes.
So I added a lock around the graphing call, it did not help
private Object thisLock = new Object();
lock (thisLock)
{
}
MY questions are:
Does anyone know if there is another reason why this may be caused?
Is it at all possible to use just the X-indexes from the first series for the chart but to display all 3 series simultaneuously?
EDIT: This fix did not work, the problem disappeared, then came back after a while
The comment above by TaW helped me fix this issue.
This really seems to be a problem with Microsoft charts
I originally though it was due to EventHandler firing multiple times before it finishes, and I opted out to use a Queue that would be filled with an EventHandler and Dequeued on a separate thread.
The problem was still there.
The solution to the problem was to simply set IsXValueIndexed to false
then to set it to true before drawing the chart
Below is the code used
In case anyone is wondering what this does:
It graphs 3 Values simultaneously on the chart with increasing time.
The chart updates from left to right instead of scrolling (think EKG machine in a hospital)
There is a fixed total number of points that is set to maxPlotPoints
The EventHandler receives some Data packet (made up here) that contains a certain number of points which = samplePoints.
It constructs a DataPoints struct and adds it to a queue
A separate thread MainTask dequeues an element from DataPoints and uses it to populate a 100 point section of each of the three series. That section then increments to the next 100 points, and so on.
This is useful if you want to continuously chart a fixed number of points
Unfortunately, I can't seem to mark the answer above from TaW as correct because it's in a comment. But I would be happy to try if you can explain how to do this. Thanks
private struct DataPoint
{
public double timePoint;
public double X;
public double Y;
public double Z;
}
private struct DataPoints
{
public DataPoints(int samples)
{
dataPoints = new DataPoint[samples];
}
public DataPoint[] dataPoints;
}
Queue queue = new Queue();
int lastSample = 0;
static int maxPlotPoints = 1000;
static int samplePoints = 100;
public ChartForm(UdpMgrControl RefUdpMgr, byte GyroChartNum, string ConfigFile)
{
for ( Row = 0; Row < 3; Row++)
{
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = name,
Color = Color.Blue,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine
};
//Fill in chart with blank points
for (int i = 0; i < maxPlotPoints; i++)
{
series1.Points.AddY(0);
}
chart1.Series.Add(series1);
}
chart1.Series[0].Enabled = true;
chart1.Series[1].Enabled = true;
chart1.Series[2].Enabled = true;
running = true;
Thread main_thread = new Thread(new ThreadStart(this.MainTask));
}
private void OutputHandler(byte[] DData)
{
if (running)
{
DataPoints data_element = new DataPoints(samplePoints);
for (int i = 0; i < samplePoints; i++)
{
data_element.dataPoints[i].X = DData[i].X;
data_element.dataPoints[i].Y = DData[i].Y;
data_element.dataPoints[i].Z = DData[i].Z;
data_element.dataPoints[i].timePoint = DData[i].timePoint;
}
queue.Enqueue(data_element);
}
}
private void MainTask()
{
while (running)
{
try
{
if (queue.Count > 0)
{
chart1.Series[0].IsXValueIndexed = false;
chart1.Series[1].IsXValueIndexed = false;
chart1.Series[2].IsXValueIndexed = false;
DataPoints data_element = (DataPoints)queue.Dequeue();
for (int i = 0; i < data_element.dataPoints.Length; i++)
{
chart1.Series[0].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[0].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].X;
chart1.Series[1].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[1].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].Y;
chart1.Series[2].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[2].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].Z;
}
//Adjust the next location to end of first
lastSample = (lastSample + samples) % maxPlotPoints;
chart1.Series[0].IsXValueIndexed = true;
chart1.Series[1].IsXValueIndexed = true;
chart1.Series[2].IsXValueIndexed = true;
GC_UpdateDataGrids();
chart1.Invalidate();
}
else
{
Thread.Sleep(10);
}
}
catch (Exception error)
{
LogErrors.AddErrorMsg(error.ToString());
}
}
}
How do I update values that have already been created?
Like, I have this chart:
And then I want to update the chart values with other values. I've already tried many things but I don't manage to make it work...
My code:
int ratio = (int)PlayerStats.Kills - PlayerStats.Deaths;
string[] seriesArray = { "Kills", "Assists", "Deaths", "MVPS" , "Headshots", "Ratio" };
int[] pointsArray = { PlayerStats.Kills, PlayerStats.Assists, PlayerStats.Deaths, PlayerStats.MVPS, PlayerStats.Headshots, ratio };
this.chart1.Titles.Add("Statistics Chart");
for (int i = 0; i < seriesArray.Length; i++)
{
Series series = series = this.chart1.Series.Add(seriesArray[i]);
series.Points.Add(pointsArray[i]);
}
chart1.ChartAreas[0].AxisX.Enabled = AxisEnabled.False;
chart1.ChartAreas[0].AxisY.Enabled = AxisEnabled.False;
double realRatio = Math.Round((double)PlayerStats.Kills / PlayerStats.Deaths, 2);
double hsRatio = Math.Round((double)PlayerStats.Headshots / PlayerStats.Kills, 2);
label6.Text = PlayerStats.Kills.ToString();
label7.Text = PlayerStats.Assists.ToString();
label8.Text = PlayerStats.Deaths.ToString();
label11.Text = PlayerStats.MVPS.ToString();
label10.Text = String.Format("{0:P2}", hsRatio);
label9.Text = realRatio.ToString();
When I try to run the function a second time (to update the values) it gives me a exception: (inside the loop, first line)
Additional information: There is already a chart element with the name 'Kills' in 'SeriesCollection'.
I've managed to that that specific function (chart1.Series.Add(...)) only called once on the startup, but then the other function inside the loop gave me exceptions too.
Well, I hope that you understand my english :P
Are you clearing out the chart elements before looping through to add them again? Something like
this.chart1.Series.Clear();
I am generating pie charts by pulling data from SQL tables. The data is an accumulation of hours for different projects.
The charts are building fine but I would like the pie graph to display the respective percentage of each slice of the pie that is being used when the graphs are generated.
I am creating the charts in the method shown below.
// Fill Chart with usable data
for (int i = 0; i <= index - 1; i++)
{
Chart6.Series["Series1"].Points.AddXY(project[i], projTime[i]);
}
Chart6.Series[0]["PieLabelStyle"] = "Disabled";
I was hoping there would be a simple command I could pass in the code behind to create this but scouring the web has provided no usable results. The best I have found is this method but these are not options in Visual Studio Express 2013.
Prepare like this:
Series S = Chart6.Series["Series1"];
S.ChartType = SeriesChartType.Pie;
S.IsValueShownAsLabel = true;
S["PieLabelStyle"] = "Outside";
Create DataPoints like this if you know the total:
DataPoint p = new DataPoint(project[i], projTime[i]);
// check your data type for the calculation!
p.Label = p.YValues[0] + "h =\n" +
(100d * p.YValues[0] / total).ToString("00.00") + "%\n"; // my format
If you don't know the total, first set the points, then calculate the total and finally set the label:
for (int i = 0; i <= index - 1; i++)
{
S.Points.AddXY(project[i], projTime[i]);
}
// calculate the total:
double total = S.Points.Sum(dp => dp.YValues[0]);
// now we can set the percentages
foreach (DataPoint p in S.Points)
{
p.Label = p.YValues[0] + "h =\n" +
(100d * p.YValues[0] / total).ToString("00.00") + "%\n"; // my format
}
chart1.Titles.Add(S.Points.Count + " projects, total " +
total.ToString("###,##0") + " hours");
chart1.Titles[0].Font = new System.Drawing.Font("Arial", 14f);
I've having a bit of a nightmare with an Excel Add-In I've written. The customers workbook used to be populated from a SQL connection and has loads of formulas setup around named tables etc. I'm trying to populate some the same tables that connection populated (using the existing headers and footers) with the data from a WCF service while maintaining formatting and formulas (ie: not break anything).
Getting the data in is fine. The problem I'm hitting is this: The data being replaced may be more or less data than currently exists in the named range. I can't seem to find a way of removing the exising rows and replacing them with my new data and having the named range resize to the new data.
Many thanks in advance.
Range range = activeWorksheet.get_Range("Name", MissingValue);
range.Clear();
object[,] data = new object[result.Length, 26];
range.get_Resize(result.Length, 26);
... fill data....
range.Value2 = data;
Ok, managed to solve it with the code below. Also removed the "range.Clear()" call which stopped the formatting from being removed.
Range range = activeWorksheet.get_Range("Name", MissingValue);
int totalMissingRows = 0;
if (range.Rows.Count < result.Length)
{
totalMissingRows = result.Length - range.Rows.Count;
for (int i = 0, l = totalMissingRows; i < l; i++)
{
Excel.Range rng = range;
rng = (Excel.Range)rng.Cells[rng.Rows.Count, 1];
rng = rng.EntireRow;
rng.Insert(Excel.XlInsertShiftDirection.xlShiftDown, MissingValue);
}
}
//delete extra lines
//remove left over data
for (int i = result.Length, l = range.Rows.Count; i < l; i++) { range.Cells[range.Rows.Count, 1].EntireRow.Delete(null); }
Since, you are already getting an array of data, why not directly write it to the excel like this:
int startRow, startCol;
var startCell = (Range)worksheet.Cells[startRow, startCol];
var endCell = (Range)worksheet.Cells[startRow + result.Length, startCol + 26];
var writeRange = worksheet.get_Range(startCell, endCell);
writeRange.Value2 = data;
Here, I have used the lengths of your array as per your question and data is the 2d array of data.