Epplus insert chart ColumnStacked3D switch row/column - c#

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):

Related

MSVisio Characters custom color

I know, that I can change color of all text in shape this way:
Shape.CellsU["Char.Color"].FormulaForceU = "RGB(255,255,255)";
There is also way to change color of certain characters in shape this way:
Characters.Begin = 2;
Characters.End = 5;
Characters.set_CharProps((short)MSVisio.VisCellIndices.visCharacterColor, (short)MSVisio.VisDefaultColors.visRed;);
But I can't find any way to pass custom RGB (or HEX or any type) value of color to just certain characters in shape. I don't want to cut shape into little shapes.
Can you please help? Thanks
The CharProps property allows you to set an index into the predefined document colors collection. For a custom RGB you can set the corresponding cell formula by first getting the row index with CharPropsRow like this:
var shp = vApp.ActiveWindow.Selection.PrimaryItem;
var shpChars = shp.Characters;
shpChars.Begin = 2;
shpChars.End = 5;
//shpChars.set_CharProps((short)Visio.VisCellIndices.visCharacterColor, (short)Visio.VisDefaultColors.visRed);
var targetRow = shpChars.CharPropsRow[0];
shp.CellsSRC[(short)Visio.VisSectionIndices.visSectionCharacter,
targetRow,
(short)Visio.VisCellIndices.visCharacterColor].FormulaU = "RGB(40,220,40)";
and that should give you similar results to this:
[Update] The above assumes you're targeting existing formating and changing it. To add new runs you can use CharProps first to add the row and then the CharPropsRow to target that new run. So you can run this code against a new page:
var vPag = vApp.ActivePage;
var shp = vPag.DrawRectangle(3,3,5,4);
shp.Text = "GoodMorning";
var shpChars = shp.Characters;
shpChars.Begin = 0;
shpChars.End = 4;
var targetRow = shpChars.CharPropsRow[(short)Visio.VisCharsBias.visBiasLetVisioChoose];
shp.CellsSRC[(short)Visio.VisSectionIndices.visSectionCharacter,
targetRow,
(short)Visio.VisCellIndices.visCharacterColor].FormulaU = "RGB(220,40,40)";
shpChars.Begin = 4;
shpChars.End = 11;
shpChars.set_CharProps((short)Visio.VisCellIndices.visCharacterColor, (short)Visio.VisDefaultColors.visBlack);
targetRow = shpChars.CharPropsRow[(short)Visio.VisCharsBias.visBiasLetVisioChoose];
shp.CellsSRC[(short)Visio.VisSectionIndices.visSectionCharacter,
targetRow,
(short)Visio.VisCellIndices.visCharacterColor].FormulaU = "RGB(40,200,40)";
...and this should result in the following:

C# Excel Generation - Range Copy (undesired behavior) using last cell as first cell of correct data

List<someClass> aList = new List<someClass>();
...
XLWorkbook workbook = new XLWorkbook();
IXLWorksheet worksheet = workbook.AddWorksheet("Data");
worksheet.Hide();
workbook.CalculateMode = XLCalculateMode.Manual;
var data = aList.ToArray();
var firstCell = worksheet.Cell(1, 1);
var lastCell = worksheet.Cell(numRows, numColumns); // numRows = 150, numColumns = 8
var writeRange = worksheet.Range(firstCell, lastCell); //firstCell = A1, LastCell = H150
writeRange.Value = data;
workbook.SaveAs(mySavePath);
This bit of code is meant to copy the data in aList into a new excel document, only, instead of starting at cell A1(1,1) and ending at cell H150 it copies the value from cell 1 into all of the cells between A1 and H299, the row values that should be in row 1 into column H-O from row 1-150, and finally my correctly grouped data from H150-O299.
Basically, my data starts at the offset of the range instead of filling in the range, and everything above and left of that range is default copied (garbage) data. someClass is just a public class with public fields for ints and strings.
Can anyone tell me why this is happening and how I can correct it?
As a solution, I found that using the same index for first and last cell will properly input my data, however why Range does not work as (assumed) intended has yet to be answered.
var firstCell = worksheet.Cell(1, 1);
var writeRange = worksheet.Range(firstCell, firstCell); //firstCell = A1, LastCell = H150
This will work as intended. I've completely removed lastCell as it is unnecessary.

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();
}
}

Epplus number of drop down items limitation in excel file

Consider this code:
var dropDown = sheet.DataValidations.AddListValidation(cells[2, colIndex, maxCol, colIndex].Address);
foreach (var bb in brokerBranchs)
{
dropDown.Formula.Values.Add(bb.Title);
}
With 29 of dropDown items everything is ok and created excel file works fine but as the number of items exceeds 29, opening created file shows following error:
Opening corrupted result file discards all drop down items associated with all columns.
What is possible solution to this problem? Any help will be appreciated.
you must add new sheet to add drop down item insert to this sheet
ExcelWorksheet ddList = excelPackage.Workbook.Worksheets.Add("DropDownList");
now add data to ddList in first column
var brokerBranchs = accountingUnitOfWork.BrokerServiceAccessor.GetBrokerBranchByBrokerId(firmId).OrderBy(x => x.Title).ToList();
var val = sheet.DataValidations.AddListValidation(cells[2, colIndex, maxCol, colIndex].Address);
for (int index = 1; index <= brokerBranchs.Count; index++)
{
ddList.Cells[index, 1].Value = brokerBranchs[index - 1].Title;
}
now create address for use in formula
var address = ddList.Cells[1, 1, brokerBranchs.Count(), 1].Address.ToString();
var arr = address.Split(':');
var char1 = arr[0][0];
var num1 = arr[0].Trim(char1);
var char2 = arr[1][0];
var num2 = arr[1].Trim(char2);
now use address in formula
val.Formula.ExcelFormula = string.Format("=DropDownList!${0}${1}:${2}${3}", char1, num1, char2, num2);
val.ShowErrorMessage = true;
val.Error = "Select from List of Values ...";

Setting data used by a line graph using EPPlus

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).

Categories

Resources