I have been trying to use data to create a graph in an excel sheet. My data source is the same workbook. Data is in sheet 1 and I want to create the graph in sheet 2. I have read through almost all the StackOverflow threads and other web resources. I can't seem to find where I have gone wrong. The graph does not show anything but the axes correctly scaled.
I want to plot column A(X Axis) against B, C, D and E in four different line/bar charts. I use Microsoft.Office.Interop.Excel namespace.
This is the code snippet.
Worksheet sheet2 = workBook.Worksheets[2];
ChartObjects chart = sheet2.ChartObjects(Type.Missing);
ChartObject[] chartObj = {chart.Add(10, 10, 550, 300), chart.Add(580, 10, 550, 300), chart.Add(10, 350, 550, 300), chart.Add(580, 350, 550, 300) };
int[,] rangeVal = { { 0, 400 }, { 0, 100 }, { 0, 300 }, { 0, 5 } };
for (int col = 2; col <= colsCount; col++)
{
Chart myChart = chartObj[col-2].Chart;
chartObj[col - 2].Select();
myChart.ChartType = Microsoft.Office.Interop.Excel.XlChartType.xlLine;
SeriesCollection seriesCollection = myChart.SeriesCollection();
Series series1 = seriesCollection.NewSeries();
series1.Name = sheet1.Cells[1,col].Value.ToString();
series1.XValues = sheet1.get_Range("A2","A" + (rowsCount + 1).ToString());
series1.Values = sheet1.get_Range((char)((int)'A' + col - 1) + "2", (char)((int)'A' + col - 1) + (rowsCount + 1).ToString());
series1.ChartType = XlChartType.xlLine;
myChart.PlotBy = XlRowCol.xlRows;
myChart.Axes(XlAxisType.xlValue).MinimumScale = rangeVal[col-2,0];
myChart.Axes(XlAxisType.xlValue).MaximumScale = rangeVal[col-2,1];
//myChart.SetSourceData(sheet1.get_Range((char)((int)'A' + col - 1) + "2", (char)((int)'A' + col - 1) + (rowsCount + 1).ToString()));
}
This is the screenshot of the graph with commented SetSourceData statement:
This is the screenshot of the graph with SetSourceData enabled:
And my data set does not have any zero values.
Any help would be appreciated!
For anyone who is looking for answers. This was my scenario:
I read data from MySQL into a DataTable and the problem was how I saved the data in sheet1. I stored them as string values. And that's why the part where I read the data from sheet1 doesn't convert string values to numerical values. It simply takes it as 0.
The code above works perfectly otherwise.
Silly mistake! :)
Related
I am creating an Excel export that contains several pie charts which are dynamically created based on data from a database that is dumped into a range of cells within the spreadsheet. I need the data labels for the pie charts to be on the outside end of the pie chart. The data labels by default are set to best fit.
I have tried to manipulate the data label positions with eLabelPosition which has an OutEnd property but have had no luck. Doing this causes the data labels to completely disappear.
Below is the method I am using to generate my pie charts.
private void AddPieChart(ExcelWorksheet worksheet, int firstDataRow, int firstDataColumn, int lastDataRow, int nextColumn, int firstColumn)
{
ExcelPieChart pieChart = worksheet.Drawings.AddChart(worksheet.Name.ToString() + " Pie Chart", eChartType.Pie3D) as ExcelPieChart;
var serie = pieChart.Series.Add(ExcelCellBase.GetAddress(firstDataRow + 3, firstDataColumn, lastDataRow + 3, firstDataColumn),
ExcelCellBase.GetAddress(firstDataRow + 3, firstColumn, lastDataRow + 3, firstColumn));
pieChart.DataLabel.ShowCategory = true;
pieChart.DataLabel.ShowLeaderLines = true;
pieChart.DataLabel.Font.Bold = true;
pieChart.DataLabel.Font.Size = 12;
pieChart.Legend.Remove();
pieChart.SetSize(500, 350);
pieChart.SetPosition(lastDataRow + 5, 0, nextColumn - 1, 0);
pieChart.Border.Fill.Color = Color.White;
pieChart.Series.Chart.RoundedCorners = false;
var pieSerie = (ExcelPieChartSerie)serie;
pieSerie.DataLabel.Position = eLabelPosition.OutEnd;
}
It appears that if you have DataLabel properties assigned to your ExcelPieChart and then also attempt to assign the DataLabel properties on the ExcelPieChartSerie is does not apply the property to the ExcelPieChartSerie. In order to fix this remove any of the DataLabel properties on the ExcelPieChart and instead add them to the ExcelPieChartSerie as shown below.
private void AddPieChart(ExcelWorksheet worksheet, int firstDataRow, int firstDataColumn, int lastDataRow, int nextColumn, int firstColumn)
{
ExcelPieChart pieChart = worksheet.Drawings.AddChart(worksheet.Name.ToString() + " Pie Chart", eChartType.Pie3D) as ExcelPieChart;
var serie = pieChart.Series.Add(ExcelCellBase.GetAddress(firstDataRow + 3, firstDataColumn, lastDataRow + 3, firstDataColumn),
ExcelCellBase.GetAddress(firstDataRow + 3, firstColumn, lastDataRow + 3, firstColumn));
pieChart.Legend.Remove();
pieChart.SetSize(500, 350);
pieChart.SetPosition(lastDataRow + 5, 0, nextColumn - 1, 0);
pieChart.Border.Fill.Color = Color.White;
pieChart.Series.Chart.RoundedCorners = false;
var pieSerie = (ExcelPieChartSerie)serie;
pieSerie.DataLabel.ShowCategory = true;
pieSerie.DataLabel.ShowLeaderLines = true;
pieSerie.DataLabel.Font.Bold = true;
pieSerie.DataLabel.Font.Size = 12;
pieSerie.DataLabel.Position = eLabelPosition.OutEnd;
}
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 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 am building an Excel file with EEPlus under MVC-5 C# application. Everything goes as planned until I set a height on a row (so an image can fit).
I load de images and set the height on column 20, like so:
Image cfPhoto = null;
Bitmap cfBm = null;
ExcelPicture pictureCf = null;
var photoInitialColumn = 0;
i++;
completedFormPhotos.ForEach(delegate(CompletedFormPhoto cfP)
{
cfPhoto = Image.FromFile(HostingEnvironment.MapPath("~/Content/Images/FormPhotos/" + cfP.Id + ".jpg"));
cfBm = new Bitmap(cfPhoto, new Size(215, 170));
pictureCf = worksheet.Drawings.AddPicture(cfP.Id.ToString(), cfBm);
pictureCf.SetPosition(i+1, 0, photoInitialColumn, 10);
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Merge = true;
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Value = cfP.comment;
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Style.WrapText = true;
photoInitialColumn += 2;
//HERE I SET THE HEIGHT. At this point, i == 18
worksheet.Row(i+2).Height = 180;
});
But, I have a company logo at the top of the Excel file (A1 cell) which gets resized as well (on height). That is defined like this:
Image _keyLogo = Image.FromFile(HostingEnvironment.MapPath("~/Content/Images/key_logo.png"));
var pictureLogo = worksheet.Drawings.AddPicture("Logo Key Quimica", _keyLogo);
pictureLogo.SetPosition(0, 0, 0, 0);
Resulting on this:
Any help would be really appreciated.
Here is the excel file in question.
It comes down to the EditAs setting of the picture logo. By default it will be set to OneCell but setting it to TwoCell I believe will solve your problem. The documentation on it (looking at EPP 4.0.1) is rather cryptic but it basically says this setting will tell the drawing to maintain its original row/column position and size. The names seem a bit counter intuitive though.
I had to guess what your code looks like (let me know if I got it wrong) and I was able to reproduce the problem you were having and then solve with the EditAs setting:
[TestMethod]
public void Image_Stretch_Test()
{
//http://stackoverflow.com/questions/27873762/weird-behavior-when-setting-a-rows-height-on-epplus
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var package = new ExcelPackage(existingFile))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets.Add("newsheet");
var _keyLogo = Image.FromFile("C:/Users/Ernie/Desktop/key_logo.png");
var pictureLogo = worksheet.Drawings.AddPicture("Logo Key Quimica", _keyLogo);
pictureLogo.SetPosition(0, 0, 0, 0);
pictureLogo.EditAs = eEditAs.TwoCell; //REMOVE THIS TO SHOW THE STRETCH PROBLEM
var cfPhoto = Image.FromFile("C:/Users/Ernie/Desktop/Main_Pic.png");
var cfBm = new Bitmap(cfPhoto, new Size(215, 170));
var pictureCf = worksheet.Drawings.AddPicture("Main_Pic", cfBm);
pictureCf.SetPosition(10, 0, 0, 0);
worksheet.Row(11).Height = 280;
package.Save();
}
}
The other thing that fix it was add the logo AFTER the row resize but not sure if that is practical to what you are trying to do.
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");
}