Setting data used by a line graph using EPPlus - c#

I am trying to add a simple line graph in excel automatically by using EPPlus. I have and know the range of cells that contain the data that I want to use. I want the graph to look like the following:
The Before and After columns go on for a lot more than what is shown.
I am using this code to create the graph and position it, but I am not sure how to set the data that the graph uses:
ExcelChart ec = ws.Drawings.AddChart("Line Time Graph", OfficeOpenXml.Drawing.Chart.eChartType.Line);
ec.Title.Text = "Benchmarks";
ec.SetPosition(_times.Count + 2, 0, 1, 0);
It might have something to do with ec.Series.Add but I am not sure how to properly use it. If you could point me in the right direction that would be great.

Hope this helps you:
ExcelChart ec = ws.Drawings.AddChart("Line Time Graph", OfficeOpenXml.Drawing.Chart.eChartType.Line);
ec.Title.Text = "Benchmarks";
ec.SetPosition(_times.Count + 2, 0, 1, 0);
ec.SetSize(800, 600);
var ran1 = sheet.Cells["A3:A10"];
var ran2 = sheet.Cells["G3:G10"]; // label range if there is. Otherwise, let select blank range then edit XML data later to remove 'c:cat' tags (bellow example)
var serie1 = ec.Series.Add(ran1, ran2);
// use serie1 variable to format and so on
// set serie1.HeaderAddress to A2 also
var ran1 = sheet.Cells["B3:B10"];
var serie2 = ec.Series.Add(ran1, ran2);
// use serie2 variable to format and so on
Another full example I've just tested:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OfficeOpenXml;
using System.IO;
using OfficeOpenXml.Drawing.Chart;
namespace TestExcelEPPluss
{
class Program
{
static void Main(string[] args)
{
ExcelPackage package = new ExcelPackage();
var sheet = package.Workbook.Worksheets.Add("TestingGraph");
Random r = new Random();
var cell = sheet.Cells["A1"];
cell.Value = "Before";
cell = sheet.Cells["B1"];
cell.Value = "After";
for (int i = 0; i < 100; i++)
{
cell = sheet.Cells[i + 2, 1];
cell.Value = r.Next(300, 500);
cell = sheet.Cells[i + 2, 2];
cell.Value = r.Next(300, 500);
}
ExcelChart ec = (ExcelLineChart)sheet.Drawings.AddChart("chart_1", eChartType.Line);
ec.SetPosition(1, 0, 3, 0);
ec.SetSize(800, 300);
//ec.Legend.Add();
var ran1 = sheet.Cells["A2:A101"];
var ran2 = sheet.Cells["0:0"];
var serie1 = (ExcelLineChartSerie)ec.Series.Add(ran1, ran2);
serie1.Header = sheet.Cells["A1"].Value.ToString();
ran1 = sheet.Cells["B2:B101"];
var serie2 = ec.Series.Add(ran1, ran2);
serie2.Header = sheet.Cells["B1"].Value.ToString();
var xml = ec.ChartXml;
var lst = xml.GetElementsByTagName("c:lineChart");
foreach (System.Xml.XmlNode item in lst[0].ChildNodes)
{
if (item.Name.Equals("ser"))
{
foreach (System.Xml.XmlNode subitem in item.ChildNodes)
{
if (subitem.Name.Equals("c:cat"))
{
item.RemoveChild(subitem);
break;
}
}
}
}
string path = #"C:\test1.xlsx";
File.WriteAllBytes(path, package.GetAsByteArray());
package.Dispose();
Console.WriteLine("Done - Path: {0}", path);
Console.ReadLine();
}
}
}
c:cat is the category for ser series, let remove c:cat tags from charts xml, then your chart will use 1,2,3,4,5,... as default for the category (x axis here).

Related

NPOI linechart or ScatterChart not showing correct in excel2019

using NPOI Version2.5.6
using npoi-examples LineChart
I want to plot (x1,y1) and (x2,y2) in the same chart, in excel 2019 like image1,but open it by WPS like image2,image2 is what I want.
what can I do correctly in excel 2019, or NPOI currently could not well support it?
public void LineChart(ISheet sheet, string serie1, string serie2, CellRangeAddress x1Range = null, CellRangeAddress y1Range = null, CellRangeAddress x2Range = null, CellRangeAddress y2Range = null)
{
IDrawing drawing = sheet.CreateDrawingPatriarch();
IClientAnchor anchor = drawing.CreateAnchor(0, 0, 0, 0, 0, 0, 16, 32);
XSSFChart chart = (XSSFChart)drawing.CreateChart(anchor);
ILineChartData<double, double> data = chart.ChartDataFactory.CreateLineChartData<double, double>();
IChartLegend legend = chart.GetOrCreateLegend();
legend.Position = LegendPosition.Bottom;
IChartAxis bottomAxis = chart.ChartAxisFactory.CreateCategoryAxis(AxisPosition.Bottom);
IValueAxis leftAxis = chart.ChartAxisFactory.CreateValueAxis(AxisPosition.Left);
leftAxis.Crosses = AxisCrosses.AutoZero;
IChartDataSource<double> xs1 = DataSources.FromNumericCellRange(sheet, x1Range);
IChartDataSource<double> ys1 = DataSources.FromNumericCellRange(sheet, y1Range);
IChartDataSource<double> xs2 = DataSources.FromNumericCellRange(sheet, x2Range);
IChartDataSource<double> ys2 = DataSources.FromNumericCellRange(sheet, y2Range);
var s1 = data.AddSeries(xs1, ys1);
s1.SetTitle(serie1);
var s2 = data.AddSeries(xs2, ys2);
s2.SetTitle(serie2);
chart.Plot(data, bottomAxis, leftAxis);
//add major gridline, available since NPOI 2.5.5
var plotArea = chart.GetCTChart().plotArea;
plotArea.catAx[0].AddNewMajorGridlines();
plotArea.valAx[0].AddNewMajorGridlines();
}

Add List or array with LoadFromCollection to row range using EPPLUS

Trying to add a List or array to a row in excel with EPPLUS using ExcelRange and LoadFromCollection but is filling a column instead of a a row. code :
List<string> cabeceras = new List<string>();
cabeceras.Add("Fecha");
foreach (ValidationParameterData estacion in informacionSeleccion.Parameters) {
foreach (string parameter in estacion.Parameters) {
cabeceras.Add(estacion.Station + parameter);
cabeceras.Add("L");
}
}
string[] vals = cabeceras.ToArray();
using (ExcelRange rng = ws.Cells[4,1,4,cabeceras.Count])
{
rng.Style.Font.Size = 16;
rng.Style.Font.Bold = true;
rng.LoadFromCollection(vals);//or cabeceras directly
}
tried with both array and list also with:
ExcelRange rng = ws.Cells["A4:M4"]
same thing happens, column A is filled from top to bottom instead of filling the row from left to right, what am I doing wrong? is there another function to do this?
Thanks and regards
Here is how I solved the problem. See underneath this code for my trials and tribulations re the class Range.
using System;
using OfficeOpenXml;
using System.Collections.Generic;
using System.IO;
namespace eeplusPractice
{
class Program
{
static void Main(string[] args)
{
FileInfo newFile = new FileInfo(#"C:\Users\Evan\Desktop\new.xlsx");
ExcelPackage pkg = new ExcelPackage(newFile);
ExcelWorksheet wrksheet = pkg.Workbook.Worksheets[0];
List<string> cabeceras = new List<string>();
for (int x = 1; x < 5; x++)
{
cabeceras.Add("HELLO");
}
int row = 4;
for(int col = 1; col <= cabeceras.Count; col++)
{
int counter = col;
wrksheet.Cells[row, col].Value = cabeceras[counter];
}
pkg.Save();
}
}
}
I tried to iterate through rng object using foreach loop but only one class was able to run and that did the same as the problem you encountered above. It must not treat each cell in range as distinct object that can be accessed. Method
I may write a method LoadRangeHorizontal if I get the time, but for now up top should work OK.
GL lemme know if it works.

Exporting excel chart as image

I have an Excel file which has a charts, these charts represent data in columns, in my program I change these data in columns and chart changes also, after that I export these charts in .png files, but there I met an exception HRESULT: 0x80030020 (STG_E_SHAREVIOLATION)
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using RTO.Models;
using Novacode;
using System.Drawing;
using Word = Microsoft.Office.Interop.Word;
using System.Reflection;
using CommonLib.SharedModels;
namespace RTO
{
class Program
{
public static void ReportRTO(RtoCommonData cmnData, List<Antenna> antennas)
{
Novacode.Image imageh, imagev, image1, image2;
Picture pictureh, picturev, picture1, picture2;
Paragraph pimg;
var exApp = new Excel.Application();
exApp.ScreenUpdating = false;
var exBook = exApp.Workbooks.Open(fileLeaf);
var exSheet = exBook.Worksheets[1] as Excel.Worksheet;
Excel.Range r1 = exSheet.get_Range("A1", "A360");
Excel.Range r2 = exSheet.get_Range("B1", "B360");
double[,] d1 = new double[360, 1];
double[,] d2 = new double[360, 1];
int w = 1;
var application = new Excel.Application();
application.ScreenUpdating = false;
var workbook = application.Workbooks.Open(fileExcel);
var worksheet = workbook.Worksheets[1] as Excel.Worksheet;
Excel.Range rng1 = worksheet.get_Range("A1", "A361");
Excel.Range rng2 = worksheet.get_Range("B1", "B361");
Excel.Range rng3 = worksheet.get_Range("C1", "C361");
Excel.Range rng4 = worksheet.get_Range("D1", "D361");
double[,] data1 = new double[361, 1];
double[,] data2 = new double[361, 1];
double[,] data3 = new double[361, 1];
double[,] data4 = new double[361, 1];
int flnmadd = 1;
for (int i = 0; i < antennas.Count; i++)
{
//Save chart as image
w = 1;
foreach (Excel.Worksheet ws in exBook.Worksheets)
{
Excel.ChartObjects chartObjects = (Excel.ChartObjects)(ws.ChartObjects(Type.Missing));
foreach (Excel.ChartObject co in chartObjects)
{
co.Select();
Excel.Chart chart = co.Chart;
chart.Export(exportPath + #"\leaf" + w + ".png", "PNG", false);
w++;
}
}
//Insert image to doc
image1 = doc.AddImage(leafimg1);
picture1 = image1.CreatePicture();
picture1.Width = 310;
picture1.Height = 310;
image2 = doc.AddImage(leafimg2);
picture2 = image2.CreatePicture();
picture2.Width = 310;
picture2.Height = 310;
pimg = doc.InsertParagraph();
pimg.AppendPicture(picture1);
pimg.AppendPicture(picture2);
for (int j = 0; j < boztrows; j++)
{
data1[j, 0] = sumbozres[i].Rxhor[j];
data2[j, 0] = sumbozres[i].Rzhor[j];
data3[j, 0] = sumbozres[i].Rxver[j];
data4[j, 0] = sumbozres[i].Rzver[j];
}
data1[boztrows, 0] = data1[0, 0];
data2[boztrows, 0] = data2[0, 0];
data3[boztrows, 0] = data3[0, 0];
data4[boztrows, 0] = data4[0, 0];
rng1.Value = data1;
rng2.Value = data2;
rng3.Value = data3;
rng4.Value = data4;
//Save chart as image
flnmadd = 1;
foreach (Excel.Worksheet ws in workbook.Worksheets)
{
Excel.ChartObjects chartObjects = (Excel.ChartObjects)(ws.ChartObjects(Type.Missing));
foreach (Excel.ChartObject co in chartObjects)
{
co.Select();
Excel.Chart chart = co.Chart;
chart.Export(exportPath + #"\charthv" + flnmadd + ".png", "PNG", false);
flnmadd++;
}
}
//Insert image to doc
if (antennas[i].Type == "БС")
{
imageh = doc.AddImage(charthimg);
pictureh = imageh.CreatePicture();
pictureh.Width = 624;
pictureh.Height = 357;
imagev = doc.AddImage(chartvimg);
picturev = imagev.CreatePicture();
picturev.Width = 624;
picturev.Height = 156;
pimg = doc.InsertParagraph();
pimg.AppendPicture(pictureh);
pimg = doc.InsertParagraph();
pimg.AppendPicture(picturev);
}
else if (antennas[i].Type == "РРС")
{
imageh = doc.AddImage(rrsimg);
pictureh = imageh.CreatePicture();
pictureh.Width = 624;
pictureh.Height = 156;
pimg = doc.InsertParagraph();
pimg.AppendPicture(pictureh);
}
trsprev += trs;
freqs = "";
pows = "";
koefgs = "";
koefgrazs = "";
poteri = "";
poteriraz = "";
freqAvg = 0;
}
exBook.Save();
exBook.Close();
exApp.Workbooks.Close();
exApp.Quit();
workbook.Save();
workbook.Close();
application.Workbooks.Close();
application.Quit();
}
}
}
Could be that your program has two instances of the same file. Another thing can be save the file before you're trying to save the picture.
As pointed out in the question comments HRESULT: 0x80030020 (STG_E_SHAREVIOLATION) is "Access denied because another caller has the file open and locked" more information here. The file was simply still open/in use, and can be resolved by first deleting the old files.
There are some options, and it depends on how you intend to use the program. Adding a try/catch statement will keep the program from crashing. Besides that I don't see any particular best practice, it depends on usage. In my opinion it is entierly reasonable for the program to exit if it fails to save.
In the interest of offering a solution that you can adjust to your liking: first a method for saving charts that returns true if successful (not pretty but does the job):
using System.IO;
private static bool SaveExcelChartAsPNG(ChartObject co,
string path, string filename)
{
try
{
string filenamePNG = Path.ChangeExtension(filename, "png");
string fullFilenamePNG = Path.Combine(path, filenamePNG);
co.Select();
co.Chart.Export(fullFilenamePNG, "PNG", false);
}
catch
{
// Save was not successful
return false;
}
return true;
}
This solution will exit on unsuccessful save:
foreach (var co in chartObjects)
{
if (!SaveExcelChartAsPNG(exportPath, #"\leaf" + w + ".png"))
Application.Exit();
}
A longer example that will retry saving 10 times by incrementing the 'w' parameter, then try a random filename. If that doesnt work the program will exit.
//Save chart as image
w = 1;
foreach (var ws in exBook.Worksheets)
{
var chartObjects = (Excel.ChartObjects)(ws.ChartObjects(Type.Missing));
foreach (var co in chartObjects)
{
int retry = 0;
bool successfulSave = false;
while (!successfulSave && retry < 10) // retry by incerementing w parameter 10 times)
{
successfulSave = SaveExcelChartAsPNG(exportPath, #"\leaf" + w + ".png"))
retry++;
w++;
}
if (!successfulSave)
{
// Try again with random filename, otherwise exit
string filename = Path.GetRandomFileName();
if (!SaveExcelChartAsPNG(exportPath, filename))
{
// Save still not successful, exit
Application.Exit();
}
}
}
}
And a remark regarding the above code: (First off it's flawed, because if you already generate 10 charts 11 times you will always first generate charts 0-99 then you will end up with 10 charts with completely random names. In which case you might just want to generate random names to begin with.)
In most cases it is not good to catch all exceptions and return true/false. Future problems can arise when some other Exception is raised that is not related to filenames. Both the user and programmer will be left oblivious to what happened. It is better to demand filenames that can be used, perhaps make 'w' or the output filenames an input parameter to the program to offer some flexibility.
One last option could be to instead create a new random output directory to guarantee that it is empty and output the prefered filenames there. Also by using Path.GetRandomFileName() that has the benefit over Path.GetTempFileName() of not creating the file.

Epplus insert chart ColumnStacked3D switch row/column

I'm using epplus to create excel in my program!
I need insert a column chart.
This is my code:
//Add the chart to the sheet
var chart = sheet.Drawings.AddChart(chartTitle, eChartType.ColumnStacked3D);
chart.SetPosition(positionRow, 2, positionCol, 2);
chart.Title.Text = chartTitle;
chart.Title.Font.Bold = true;
chart.Title.Font.Size = 18;
chart.SetSize(width, height);
//Set the data range
chart.Series.Add("D17:D22", "B17:B22");
chart.Series.Add("P17:P22", "B17:B22");
And I get result:
But I want result as:
After I created excel file from program, I open it and change the chart:
Right click in the chart/Select data/Switch row/column.
How can I Switch row/column in my code? Or how to insert the chart like the below picture?
Sorry for not good in English
Thank you very much!
That button in excel just switches the data and rebuilds the chart. Rather then try to mimic it better to build the chart the right way from the start.
What I mean is your original chart is treating the data as 2 data series but what you really want is 6 series.
The only problem is values in the x axis - there is no direct way with Epplus it seems to get to the category (horizontal) axis labels of the series. So you have to do it through XML manipulation as below.
So change your code to this:
//Set the data range
//chart.Series.Add("D17:D22", "B17:B22");
//chart.Series.Add("P17:P22", "B17:B22");
for (var i = 0; i < opt.Count; i++)
{
var datarange = sheet.Cells[$"Bar!D{17 + i},Bar!P{17 + i}"];
var ser = chart.Series.Add(datarange.Address, $"B{17 + i}:B{17 + i}");
ser.HeaderAddress = sheet.Cells[$"$B{17 + i}"];
}
//have to remove cat nodes from each series so excel autonums 1 and 2 in xaxis
var chartXml = chart.ChartXml;
var nsm = new XmlNamespaceManager(chartXml.NameTable);
var nsuri = chartXml.DocumentElement.NamespaceURI;
nsm.AddNamespace("c", nsuri);
//Get the Series ref and its cat
var serNodes = chartXml.SelectNodes("c:chartSpace/c:chart/c:plotArea/c:bar3DChart/c:ser", nsm);
foreach (XmlNode serNode in serNodes)
{
//Cell any cell reference and replace it with a string literal list
var catNode = serNode.SelectSingleNode("c:cat", nsm);
catNode.RemoveAll();
//Create the string list elements
var ptCountNode = chartXml.CreateElement("c:ptCount", nsuri);
ptCountNode.Attributes.Append(chartXml.CreateAttribute("val", nsuri));
ptCountNode.Attributes[0].Value = "2";
var v0Node = chartXml.CreateElement("c:v", nsuri);
v0Node.InnerText = "opening";
var pt0Node = chartXml.CreateElement("c:pt", nsuri);
pt0Node.AppendChild(v0Node);
pt0Node.Attributes.Append(chartXml.CreateAttribute("idx", nsuri));
pt0Node.Attributes[0].Value = "0";
var v1Node = chartXml.CreateElement("c:v", nsuri);
v1Node.InnerText = "closing";
var pt1Node = chartXml.CreateElement("c:pt", nsuri);
pt1Node.AppendChild(v1Node);
pt1Node.Attributes.Append(chartXml.CreateAttribute("idx", nsuri));
pt1Node.Attributes[0].Value = "1";
//Create the string list node
var strLitNode = chartXml.CreateElement("c:strLit", nsuri);
strLitNode.AppendChild(ptCountNode);
strLitNode.AppendChild(pt0Node);
strLitNode.AppendChild(pt1Node);
catNode.AppendChild(strLitNode);
}
pck.Save();
Which gives this as the output in my unit test (made up the numbers):

EPPlus: ExcelChartSerie.Header loads once but doesn't update when cell is changed

I am trying to create an EPPlus chart. I am having problems with series headers. Even when ExcelChartSerie.HeaderAddress is used, it seems to have no effect.
Each serie is initialized with the following piece of code
ExcelBarChartSerie serie = (OfficeOpenXml.Drawing.Chart.ExcelBarChartSerie)
chartClustered.Series.Add(ExcelRange.GetAddress(fromRow, fromCol, toRow, toCol),
ExcelRange.GetAddress(fromRow, fromColH, toRow, fromColH));
ExcelAddressBase headerAddr = new ExcelAddressBase(headRow, headCol, headRow, headCol);
serie.HeaderAddress = headerAddr;
serie.Header = (string)ws.Cells[headRow, headCol].Value;
Everything works great but I am having problems with the Header updating correctly with the rest of the graph. The serie.Header variable controls the legend, which is where I'm having the problem. I understand this is an awkwardly specific question, but perhaps someone out there can offer some insight. Here is some pictures to show you exactly where I am having my problem.
Initial plot before a change (correct):
Plot after label data changed (incorrect):
The problem is the you are creating a new Excel Address that is NOT attached to a worksheet when setting HeaderAddress. Its a very subtle but important difference because Excel would not know which sheet the address is actually associated with when looking for the header value (it would not assume the one the chart is on). Take a look at this:
[TestMethod]
public void ExcelChartSerie_Header()
{
//http://stackoverflow.com/questions/27866521/epplus-excelchartserie-header-loads-once-but-doesnt-update-when-cell-is-change
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var package = new ExcelPackage(existingFile))
{
var workbook = package.Workbook;
var ws = workbook.Worksheets.Add("newsheet");
//Some data
ws.Cells["A1"].Value = "Stuff";
ws.Cells["A2"].Value = "bar1";ws.Cells["A3"].Value = "bar2";ws.Cells["A4"].Value = "bar3";ws.Cells["A5"].Value = "bar4";ws.Cells["A6"].Value = "bar5";
ws.Cells["C1"].Value = "Canadian";
ws.Cells["C2"].Value = 53;ws.Cells["C3"].Value = 36;ws.Cells["C4"].Value = 43;ws.Cells["C5"].Value = 86;ws.Cells["C6"].Value = 86;
ws.Cells["D1"].Value = "Saudi Arabia";
ws.Cells["D2"].Value = 53;ws.Cells["D3"].Value = 36;ws.Cells["D4"].Value = 43;ws.Cells["D5"].Value = 86;ws.Cells["D6"].Value = 86;
ws.Cells["E1"].Value = "Alaskan";
ws.Cells["E2"].Value = 53; ws.Cells["E3"].Value = 36; ws.Cells["E4"].Value = 43; ws.Cells["E5"].Value = 86; ws.Cells["E6"].Value = 86;
ws.Cells["F1"].Value = "Indian";
ws.Cells["F2"].Value = 53; ws.Cells["F3"].Value = 36; ws.Cells["F4"].Value = 43; ws.Cells["F5"].Value = 86; ws.Cells["F6"].Value = 86;
//Create the chart
var chart = (ExcelBarChart)ws.Drawings.AddChart("Chart1", eChartType.ColumnClustered);
chart.SetSize(400, 300);
chart.SetPosition(10, 400);
//Apply header
for (var i = 0; i < 4; i++)
{
var serie = (ExcelBarChartSerie) chart.Series.Add(
ExcelCellBase.GetAddress(2, i + 3, 6, i + 3),
ExcelCellBase.GetAddress(2, 1, 6, 1)
);
//var headerAddr = new ExcelAddressBase("C1"); //THIS IS NOT ASSOCIATED WITH WORKSHET 'ws'
var headerAddr = ws.Cells[1, i + 3]; //THIS IS
serie.HeaderAddress = headerAddr;
//serie.Header = (string) ws.Cells[1, i + 3].Value;
}
package.Save();
}
}

Categories

Resources