I have a pivot table with a datafield sourced from a single column "Animals" of data in another sheet. This column contains values that I know in advance, let's say these are up to 6 animals : "dog", "cat", "monkey", "fox", "mouse", "sheep" (but some of them could not be present).
And I want the pivot table to report the count of each kind of animal.
//data fields
var field = pivotTable.DataFields.Add(pivotTable.Fields["Animals"]);
field.Function = DataFieldFunctions.Count;
This works well however I would like to always have the animals displayed in the pivot table in a custom fixed order, for example "dog" should always be the fist reported column, then "cat","fox" and "mouse". This can be done from Excel by manually reordering the columns but I fail to do it within EPPlus.
I came with this piece of code which is not working...
private static void ReorderColumns(ExcelPivotTable pivotTable)
{
string[] customOrder = { "dog", "cat", "monkey", "fox", "mouse", "sheep" };
var newIndex = 0;
var diffStatusField = pivotTable.DataFields.First().Field;
diffStatusField.Items.Refresh();
foreach (var label in customOrder)
{
for (int i = newIndex; i < diffStatusField.Items.Count; ++i)
{
if (diffStatusField.Items[i].Value as string == label)
{
if (i != newIndex)
{
Swap(diffStatusField.Items, i, newIndex);
++newIndex;
}
else
{
++newIndex;
break;
}
}
}
}
}
This is not working because I cannot write the Swap method. The API doesn't allow Items mutability, the setter is private.
I also tried to write it in VBA which kind of works out of the EPPlus stuff but I fail to bind the VBA and EPPlus together, it looks like I cannot call VBA code from EPPlus. All I can do is write the VBA code but not run it.
I have also tried to use the AutoSort method with the Conditions without success.
It seems I may use the xml directly but don't know where to start.
Edit : I tried to write the xml directly, I came with this :
private static void ReorderColumns(ExcelPivotTable pivotTable)
{
// Reorder the columns
string[] customOrder = { "dog", "cat", "monkey", "fox", "mouse", "sheep" };
var newIndex = 0;
var diffStatusField = pivotTable.DataFields.First().Field;
diffStatusField.Items.Refresh();
var pivotTableXml = pivotTable.PivotTableXml;
var nsManager = new XmlNamespaceManager(pivotTableXml.NameTable);
nsManager.AddNamespace("d", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
nsManager.AddNamespace("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main");
var topNode = pivotTableXml.DocumentElement;
var nodes = topNode.SelectNodes("d:pivotFields/d:pivotField[#axis=\"axisCol\"]/d:items/d:item", nsManager);
foreach (var label in customOrder)
{
for (int i = newIndex; i < diffStatusField.Items.Count; ++i)
{
if (diffStatusField.Items[i].Value as string == label)
{
if (i != newIndex)
{
Swap(nodes, i, newIndex);
++newIndex;
}
else
{
++newIndex;
break;
}
}
}
}
}
private static void Swap(XmlNodeList collection, int index1, int index2)
{
var tmp = collection[index1].Attributes["x"].Value;
((XmlElement)collection[index1]).SetAttribute("x", collection[index2].Attributes["x"].Value);
((XmlElement)collection[index2]).SetAttribute("x", tmp);
}
It's not working either as I did not find the way to make EPPlus use the new xml.
Related
I'm trying to add values into an HashSet<string> based to its content.
I would love the code to look like this:
public static void AddModifiedValues(HashSet<string> properties)
{
if (properties is null)
{
return;
}
foreach (var element in properties)
{
result.Add(element + "id");
}
}
The problem here is that I obviously get an exception as I'm modifying the collection I'm iterating on.
The current code ends up looking like this:
//different signature
public static HashSet<string> AddModifiedValues(HashSet<string> properties)
{
//allocation
var result = new HashSet<string>();
if (properties is null)
{
return result;
}
foreach (var element in properties)
{
result.Add(element);
result.Add(element + "id");
}
return result;
}
Is there a way to do what I'm looking for without allocating a new HashSet?
Unfortunately you can't do that with Hashset type.
One of the reasons is you can't track where old values in collection ( in this case set ) end as every insert to hashet causes side effect internally.
However with List it is possible.
var properties = new List<string>()
{
"test1",
"test2"
};
var length = properties.Count;
for(var i = 0 ; i < length ; i++) {
properties.Add(properties[i] + "id");
}
--Edit
Looks like I was wrong and HashSet doesnt sort collection after inserting so what you can do is to use ElementAt to access element.
var properties = new HashSet<string>()
{
"test1",
"test2"
};
var length = properties.Count;
for(var i = 0 ; i < length ; i++) {
properties.Add(properties.ElementAt(i) + "id");
}
I have the following code that successfully splits filepaths into the constituent folders and filename:
var allfiles = FileLister.GetListOfFiles(_path, "*", SearchOption.AllDirectories).ToList();
List<string[]> n = new List<string[]>();
foreach (var line in allfiles)
{
var str = line.Split(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
n.Add(str);
}
The result (n) is a two dimensional array that looks like this:
Given a "node" position e.g n[0] as seen in the image, I need to transfer the data in n0 to n[0][6] to a winform that has five listboxes as shown:
So my final desired result is that listbox1 will have all the data in n0, n1, n2 and so on. Listbox two will have all the data in n[0][3], n1[3], n2[3] and so on..
Im very new to C# and just dont know how to code this. I would appreciate some help please or even suggestions on an alternative way to do this.
cheers
Here's some sample code to point you in the right direction. Agree with Taw's comments regarding having enough listboxes depending on the depth of your filesystem. This example assumes that you don't have a folder structure that goes deeper than 4 levels:
public partial class Form1 : Form
{
// list of ListBoxes - makes it easy to work out which one to add filepath item to
List<ListBox> boxes = new List<ListBox>();
public Form1()
{
InitializeComponent();
boxes.Add(this.listBox1);
boxes.Add(this.listBox2);
boxes.Add(this.listBox3);
boxes.Add(this.listBox4);
// populate the ListBoxes
populateLists();
}
public void populateLists()
{
// simulate getting data from filesystem
List<string[]> n = new List<string[]>();
n.Add(new string[] { "C:", "foo1", "bar1", "test1.txt" });
n.Add(new string[] { "D:", "foo2", "bar2", "test2.txt" });
n.Add(new string[] { "E:", "foo3", "bar3", "test3.txt" });
n.Add(new string[] { "F:", "foo4", "bar4", "test4.txt" });
n.Add(new string[] { "G:", "foo5", "bar5", "test5.txt" });
// loop through list items
for (var i = 0; i < n.Count; i++)
{
// loop through arrays for each list item
for (var j = 0; j < n[i].Length; j++)
{
boxes[j].Items.Add(n[i][j]);
}
}
}
}
Output:
I have a .csv with the following headers and an example line from the file.
AgentID,Profile,Avatar,In_Time,Out_Time,In_Location,Out_Location,Target_Speed(m/s),Distance_Traveled(m),Congested_Duration(s),Total_Duration(s),LOS_A_Duration(s),LOS_B_Duration(s),LOS_C_Duration(s),LOS_D_Duration(s),LOS_E_Duration(s),LOS_F_Duration(s)
2177,DefaultProfile,DarkGreen_LowPoly,08:00:00,08:00:53,East12SubwayportalActor,EWConcourseportalActor,1.39653,60.2243,5.4,52.8,26.4,23,3.4,0,0,0
I need to sort this .csv by the 4th column (In_time) by increasing time ( 08:00:00, 08:00:01) and the 6th (In_Location) by alphabetical direction (e.g. East, North, etc).
So far my code looks like this:
List<string> list = new List<string>();
using (StreamReader reader = new StreamReader("JourneyTimes.csv"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
line.Split(',');
list.Add(line);
}
I read in the .csv and split it using a comma (there are no other commas so this is not a concern). I then add each line to a list. My issue is how do I sort the list on two parameters and by the headers of the .csv.
I have been looking all day at this, I am relatively new to programming, this is my first program so I apologize for my lack of knowledge.
You can use LINQ OrderBy/ThenBy:
e.g.
listOfObjects.OrderBy (c => c.LastName).ThenBy (c => c.FirstName)
But first off, you should map your CSV line to some object.
To map CSV line to object you can predefine some type or create it dynamically
from line in File.ReadLines(fileName).Skip(1) //header
let columns = line.Split(',') //really basic CSV parsing, consider removing empty entries and supporting quotes
select new
{
AgentID = columns[0],
Profile = int.Parse(columns[1]),
Avatar = float.Parse(columns[2])
//other properties
}
And be aware that like many other LINQ methods, these two use deferred execution
You are dealing with two distinct problems.
First, ordering two columns in C# can be achieved with OrderBy, ThenBy
public class SpreadsheetExample
{
public DateTime InTime { get; set; }
public string InLocation { get; set; }
public SpreadsheetExample(DateTime inTime, string inLocation)
{
InTime = inTime;
InLocation = inLocation;
}
public static List<SpreadsheetExample> LoadMockData()
{
int maxMock = 10;
Random random = new Random();
var result = new List<SpreadsheetExample>();
for (int mockCount = 0; mockCount < maxMock; mockCount++)
{
var genNumber = random.Next(1, maxMock);
var genDate = DateTime.Now.AddDays(genNumber);
result.Add(new SpreadsheetExample(genDate, "Location" + mockCount));
}
return result;
}
}
internal class Class1
{
private static void Main()
{
var mockData = SpreadsheetExample.LoadMockData();
var orderedResult = mockData.OrderBy(m => m.InTime).ThenBy(m => m.InLocation);//Order, ThenBy can be used to perform ordering of two columns
foreach (var item in orderedResult)
{
Console.WriteLine("{0} : {1}", item.InTime, item.InLocation);
}
}
}
Now you can tackle the second issue of moving data into a class from Excel. VSTO is what you are looking for. There are lots of examples online. Follow the example I posted above. Replace your custom class in place of SpreadSheetExample.
You may use a DataTable:
var lines = File.ReadAllLines("test.csv");
DataTable dt = new DataTable();
var columNames = lines[0].Split(new char[] { ',' });
for (int i = 0; i < columNames.Length; i++)
{
dt.Columns.Add(columNames[i]);
}
for (int i = 1; i < lines.Length; i++)
{
dt.Rows.Add(lines[i].Split(new char[] { ',' }));
}
var rows = dt.Rows.Cast<DataRow>();
var result = rows.OrderBy(i => i["In_time"])
.ThenBy(i => i["In_Location"]);
// sum
var sum = rows.Sum(i => Int32.Parse(i["AgentID"].ToString()));
i have 2 List that i want to put into my Listbox
the first List contain names and the second contain numbers
my problem is that some of the names long so the numbers cannot a display in the same line
how can i put in in appropriate way ?
listBox.Items.Add("Name" + "\t\t\t" + "Number");
for (int i = 0; i < lists.Count; i++)
{
listBox.Items.Add(lists._namesList[i] + "\t\t\t" + lists._numbersList[i]);
}
Update here is what I tried with a ListView
listViewProtocols.View = View.Details;
listViewProtocols.Columns.Add("Name");
listViewProtocols.Columns.Add("Number");
for (int i = 0; i < lists._protocolsList.Count; i++)
{
listViewProtocols.Items.Add(new ListViewItem{ lists._nameList[i], lists._numbersList[i].ToString()});
}
Consider using a ListView component, with Details style. As #Yuck mentioned in the comments it will give you the effect you need.
It is a bit akward to populate from 2 separate lists but it is doable with the code below:
listView1.View=View.Details;
listView1.Columns.Add("Name");
listView1.Columns.Add("Number");
string[] names= { "Abraham", "Buster", "Charlie" };
int[] numbers= { 1018001, 1027400, 1028405 };
for(int i=0; i<names.Length; i++)
{
listView1.Items.Add(
new ListViewItem(new string[] {
names[i], numbers[i].ToString() }));
}
I would strongly recommend doing an array of structures instead of separate lists like this:
public struct Record
{
public string name;
public int number;
public string[] ToStringArray()
{
return new string[] {
name,
number.ToString() };
}
}
and used like this:
listView1.View=View.Details;
listView1.Columns.Add("Name");
listView1.Columns.Add("Number");
Record[] list=new Record[] {
new Record() { name="Abraham", number=1018001 },
new Record() { name="Buster", number=1027400 },
new Record() { name="Charlie", number=1028405 }
};
for(int i=0; i<list.Length; i++)
{
listView1.Items.Add(
new ListViewItem(list[i].ToStringArray()));
}
There are couple of options I can think of:
Make the listbox wider so that it can accomodate longer text or add a horizontal scrollbar to it.
Constrain the max length of names to, let's say, 20 chars and replace extra characters with ....
Probably the best solution is to use grid instead of listbox - you need to display two columns of data, which is exactly the grid is for.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Delete row of 2D string array in C#
i have a 2d string array, I want to delete a specified row from the array.
string[] a = new string[] { "a", "b" }; //dummy string array
int deleteIndex = 1; //we want to "delete" element in position 1 of string
a = a.ToList().Where(i => !a.ElementAt(deleteIndex).Equals(i)).ToArray();
dirty but gives the expected result (foreach through the array to test it)
EDIT missed the "2d array" detail, here is the right code for the job
string[][] a = new string[][] {
new string[] { "a", "b" } /*1st row*/,
new string[] { "c", "d" } /*2nd row*/,
new string[] { "e", "f" } /*3rd row*/
};
int rowToRemove = 1; //we want to get rid of row {"c","d"}
//a = a.ToList().Where(i => !i.Equals(a.ElementAt(rowToRemove))).ToArray(); //a now has 2 rows, 1st and 3rd only.
a = a.Where((el, i) => i != rowToRemove).ToArray(); // even better way to do it maybe
code updated
As has been said above you cant remove from an array.
If you are going to need to remove rows quite often maybe change from using a 2d array to a list containing an array of string. This way you can make use of the remove methods that list implements.
Ok so I said you can't "delete" them. That's still true. You'll have to create a new array instance with enough space for the items you want to keep and copy them over.
If this is a jagged array, using LINQ here could simplify this.
string[][] arr2d =
{
new[] { "foo" },
new[] { "bar", "baz" },
new[] { "qux" },
};
// to remove the second row (index 1)
int rowToRemove = 1;
string[][] newArr2d = arr2d
.Where((arr, index) => index != rowToRemove)
.ToArray();
// to remove multiple rows (by index)
HashSet<int> rowsToRemove = new HashSet<int> { 0, 2 };
string[][] newArr2d = arr2d
.Where((arr, index) => !rowsToRemove.Contains(index))
.ToArray();
You could use other LINQ methods to remove ranges of rows easier (e.g., Skip(), Take(), TakeWhile(), etc.).
If this is a true two-dimensional (or other multi-dimensional) array, you won't be able to use LINQ here and will have to do it by hand and it gets more involved. This still applies to the jagged array as well.
string[,] arr2d =
{
{ "foo", null },
{ "bar", "baz" },
{ "qux", null },
};
// to remove the second row (index 1)
int rowToRemove = 1;
int rowsToKeep = arr2d.GetLength(0) - 1;
string[,] newArr2d = new string[rowsToKeep, arr2d.GetLength(1)];
int currentRow = 0;
for (int i = 0; i < arr2d.GetLength(0); i++)
{
if (i != rowToRemove)
{
for (int j = 0; j < arr2d.GetLength(1); j++)
{
newArr2d[currentRow, j] = arr2d[i, j];
}
currentRow++;
}
}
// to remove multiple rows (by index)
HashSet<int> rowsToRemove = new HashSet<int> { 0, 2 };
int rowsToKeep = arr2d.GetLength(0) - rowsToRemove.Count;
string[,] newArr2d = new string[rowsToKeep, arr2d.GetLength(1)];
int currentRow = 0;
for (int i = 0; i < arr2d.GetLength(0); i++)
{
if (!rowsToRemove.Contains(i))
{
for (int j = 0; j < arr2d.GetLength(1); j++)
{
newArr2d[currentRow, j] = arr2d[i, j];
}
currentRow++;
}
}
Instead of array you can use List or ArrayList class. Using it you can dynamically add element and remove based on your requirement. Array is fixed in size, which can not be manipulated dynamically.
The best way is to work with a List<Type>! The items are ordered in the way the are added to the list and each of them can be deleted.
Like this:
var items = new List<string>;
items.Add("One");
items.Add("Two");
items.RemoveAt(1);