Group items by content of list - c#

Lets say i got a something like this:
public class Subscriber{
public string Name {get; set;}
}
public class SomeData{
public string Content {get; set;}
}
public class InputData {
public Subscriber Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
}
public class QueueItem {
public IEnumerable<Subscriber> Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
}
Now lets say i get a List<InputData> full of "Subscribers" with a list of data for each subscriber.
Now i want to compare the list of data of each subscriber, and end up with a List<QueueItem>, where if 2 subscribers have the same set of data items, they would be 1 QueueItem.
Hope this makes sense

The technique is using EqualityComparer with Enumerable.SequenceEqual()
public class Subscriber
{
public string Name { get; set; }
// For compare
public override bool Equals(object obj) { return string.Equals(this.Name, ((Subscriber)obj).Name); }
public override int GetHashCode() { return this.Name.GetHashCode(); }
}
public class SomeData
{
public string Content { get; set; }
// For compare
public override bool Equals(object obj) { return string.Equals(this.Content, ((SomeData)obj).Content); }
public override int GetHashCode() { return this.Content.GetHashCode(); }
}
public class InputData
{
public Subscriber Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
// Should always initialize an empty collection
public InputData() { this.DataItems = new List<SomeData>(); }
}
public class QueueItem
{
public IEnumerable<Subscriber> Subscribers { get; set; }
public IEnumerable<SomeData> DataItems { get; set; }
// Should always initialize an empty collection
public QueueItem() { this.Subscribers = new List<Subscriber>(); this.DataItems = new List<SomeData>(); }
}
public class DataItemsEqualityComparer : EqualityComparer<IEnumerable<SomeData>>
{
public override bool Equals(IEnumerable<SomeData> x, IEnumerable<SomeData> y)
{
return Enumerable.SequenceEqual(x.OrderBy(i => i.Content), y.OrderBy(i => i.Content));
}
public override int GetHashCode(IEnumerable<SomeData> obj)
{
return obj.Select(i => i.GetHashCode()).Sum().GetHashCode();
}
}
Usage
var data = new List<InputData>();
var fruits = new[] { new SomeData() { Content = "apple" }, new SomeData() { Content = "pear"} };
var colors = new[] { new SomeData() { Content = "red" }, new SomeData() { Content = "blue" }, new SomeData() { Content = "green" } };
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Alice" }, DataItems = new List<SomeData>(fruits) });
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Bob" }, DataItems = new List<SomeData>(colors) });
data.Add(new InputData() { Subscribers = new Subscriber() { Name = "Charlie" }, DataItems = new List<SomeData>(fruits) });
List<QueueItem> groupedData = data.GroupBy(
i => i.DataItems,
i => i.Subscribers,
new DataItemsEqualityComparer())
.Select(i => new QueueItem() { Subscribers = i, DataItems = i.Key }).ToList();
Result
QueueItem :
Subscribers:
- Alice
- Charlie
Data:
- apple
- pear
QueueItem :
Subscribers:
- Bob
Data:
- red
- blue
- green

var queue = Dictionary(Subscriber, List<SomeData>);
//And lets just for example add some data
var items1 = new List<SomeData>();
items1.Add(new SomeData("test"));
items1.Add(new SomeData("test2"));
var items2 = new List<SomeData>();
items2.Add(new SomeData("test"));
queue.Add(new Subscriber("Peter"), items1);
queue.Add(new Subscriber("Luke"), items1);
queue.Add(new Subscriber("Anna"), items2);
Dictionary<Subscriber, List<SomeData>> myDictionary = queue
.GroupBy(o => o.PropertyName)
.ToDictionary(g => g.Key, g => g.ToList());

Related

Populate a list in constructor

I'm looking for the better way to initialize a list directly in the constructor of my object. The goal is to put the initialize code at the same place, without taking several many lines.
public class Object1
{
public string data1 { get; set; }
public string data2 { get; set; }
public List<Object2> myList { get; set; }
}
public class Object2
{
public int Id { get; set; }
public string myData { get; set; }
}
public class Object3
{
public int Id { get; set; }
public string myOtherData { get; set; }
}
There is my way in order to initialize myList from Objet1 :
public static void myProgram()
{
List<Object3> test = new List<Object3>
{
new Object3
{
Id = 1,
myOtherData = "blabla"
},
new Object3
{
Id = 8,
myOtherData = "blabla"
}
};
/* That is what I do now */
Object1 myObject = new Object1
{
data1 = "toto",
data2 = "titi",
myList = test != null && test.Count > 0 ? new List<Object2>() : null
};
foreach (Object3 obj3 in test)
{
if (obj3.Id > 3)
{
myObject.myList.Add(new Object2
{
Id = obj3.Id,
myData = $"lol {obj3.myOtherData}"
});
}
}
}
I'm looking for a way which avoid to use a foreach after construction of my object. Something like this :
/* That is what I try to do */
Object1 myObject = new Object1
{
data1 = "toto",
data2 = "titi",
myList = test != null && test.Count > 0 ? new List<Object2>().AddRange( /* PUT THE FOREACH HERE */)
}
I think we can use lambda / delegates in order to create anonymous function which will fill the list, but I don't how it works.
}
You can do it with Linq.
myList = test?.Where(x => x.Id > 3).Select(x => new Object2
{
Id = x.Id,
myData = $"lol {x.myOtherData}"
}).ToList()

Automapping child injecting data

I'm trying to map a parent/child to another parent/child where the child.id need to be read from a key/value dictionary.
I could do a foreach loop to map the children but I'm interrested to see if there is another way. The code is a simplified version of my model and the remoteDict is read async so using a IValueResolver seems unusable?
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentFrom, ParentTo>()
.ForMember(d => d.Children, o => o.MapFrom(s => s.Children));
cfg.CreateMap<(ChildFrom child, Dictionary<string, int> dict), ChildTo>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.dict.GetValueOrDefault(s.child.ExternalId)));
});
var mapper = config.CreateMapper();
Dictionary<string, int> remoteDict = new Dictionary<string, int>();
remoteDict.Add("A1", 1);
remoteDict.Add("B1", 1);
ParentFrom p = new ParentFrom() { Id = 1001 };
p.Children.Add(new ChildFrom() { ExternalId = "A1" });
p.Children.Add(new ChildFrom() { ExternalId = "B1" });
ParentTo p2 = mapper.Map<ParentTo>(p);
/*
Missing type map configuration or unsupported mapping.
Mapping types:
ChildFrom -> ChildTo
mapping.ChildFrom -> mapping.ChildTo
*/
}
}
public class ParentFrom
{
public int Id { get; set; }
public List<ChildFrom> Children { get; set; } = new List<ChildFrom>();
}
public class ChildFrom
{
public string ExternalId { get; set; }
}
public class ParentTo
{
public int Id { get; set; }
public List<ChildTo> Children { get; set; }
}
public class ChildTo
{
public int Id { get; set; }
}
UPDATE
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentFrom, ParentTo>()
.ForMember(d => d.Children, o => o.MapFrom(s => s.Children));
cfg.CreateMap<ChildFrom, ChildTo>()
.ForMember(d => d.Id, o => o.MapFrom<FromRemote>());
});
var mapper = config.CreateMapper();
Dictionary<string, int> remoteDict = new Dictionary<string, int>();
remoteDict.Add("A1", 1);
remoteDict.Add("B1", 1);
ParentFrom p = new ParentFrom() { Id = 1001 };
p.Children.Add(new ChildFrom() { ExternalId = "A1" });
p.Children.Add(new ChildFrom() { ExternalId = "B1" });
ParentTo p2 = mapper.Map<ParentTo>(p, opt => opt.Items["dict"] = remoteDict);
}
}
public class FromRemote : IValueResolver<ChildFrom, ChildTo, int>
{
public int Resolve(ChildFrom source, ChildTo destination, int destMember, ResolutionContext context)
{
var dict = context.Items["dict"] as Dictionary<string, int>;
return dict.GetValueOrDefault(source.ExternalId);
}
}
public class ParentFrom
{
public int Id { get; set; }
public List<ChildFrom> Children { get; set; } = new List<ChildFrom>();
}
public class ChildFrom
{
public string ExternalId { get; set; }
}
public class ParentTo
{
public int Id { get; set; }
public List<ChildTo> Children { get; set; }
}
public class ChildTo
{
public int Id { get; set; }
}

Converting table in tree

Is there any scope for improvement in my program which converts flat db entity to a tree data structure.
I don't want to loose the Generic flexibility as i should be able to use the same method for any other DBEntity class
Interface for db entity class
public interface IDbEntityNode
{
int Id { get; set; }
int ParentId { get; set; }
}
Example of db Entity class
public class ExceptionCategory :IDbEntityNode
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Data { get; set; }
public ExceptionCategory(string data, int id, int parentId)
{
Id = id;
ParentId = parentId;
Data = data;
}
}
Generic class which holds the structure of tree node
public class GenericNode<T>
{
public T NodeInformation { get; set; }
public GenericNode<T> Parent { get; set; }
public List<GenericNode<T>> Children { get; set; } = new List<GenericNode<T>>();
}
Method which coverts flat list to tree
public static List<GenericNode<T>> CreateGenericTree<T>(List<T> flatDataObject,Func<T,bool> IsRootNode) where T : IDbEntityNode
{
var lookup = new Dictionary<int, GenericNode<T>>();
var rootNodes = new List<GenericNode<T>>();
var noOfElements = flatDataObject.Count;
for (int element = 0; element < noOfElements; element++)
{
GenericNode<T> currentNode;
if (lookup.TryGetValue(flatDataObject[element].Id, out currentNode))
{
currentNode.NodeInformation = flatDataObject[element];
}
else
{
currentNode = new GenericNode<T>() { NodeInformation = flatDataObject[element] };
lookup.Add(flatDataObject[element].Id, currentNode);
}
if (IsRootNode(flatDataObject[element]))
{
rootNodes.Add(currentNode);
}
else
{
GenericNode<T> parentNode;
if (!lookup.TryGetValue(flatDataObject[element].ParentId, out parentNode))
{
parentNode = new GenericNode<T>();
lookup.Add(flatDataObject[element].ParentId, parentNode);
}
parentNode.Children.Add(currentNode);
currentNode.Parent = parentNode;
}
}
return rootNodes;
}
Execution:
private static void Main(string[] args)
{
List<IDbEntityNode> flatDataStructure = new List<IDbEntityNode>
{
new ExceptionCategory("System Exception",1,0),
new ExceptionCategory("Index out of range",2,1),
new ExceptionCategory("Null Reference",3,1),
new ExceptionCategory("Invalid Cast",4,1),
new ExceptionCategory("OOM",5,1),
new ExceptionCategory("Argument Exception",6,1),
new ExceptionCategory("Argument Out Of Range",7,6),
new ExceptionCategory("Argument Null",8,6),
new ExceptionCategory("External Exception",9,1),
new ExceptionCategory("Com",10,9),
new ExceptionCategory("SEH",11,9),
new ExceptionCategory("Arithmatic Exception",12,1),
new ExceptionCategory("DivideBy0",13,12),
new ExceptionCategory("Overflow",14,12),
};
var tree = CreateGenericTree(flatDataStructure, IsRootNode);
}
private static bool IsRootNode(IDbEntityNode dbEntity)
{
bool isRootNode = false;
if (dbEntity.ParentId == 0 )
isRootNode = true;
return isRootNode;
}
Created a generic approach, table objects need to follow the dbSet interface and TreeNode objects need to follow the ITreeNode. I used binarySerach to make this as fast as possible. No recursion needed. The logic ensures that you do not need to have the items in a particular order. I did not throw an error when out of the loop when there are still unassigned objects, this can be easy added.
using System.Collections.Generic;
public interface ITreeNode
{
string ParentId { get; set; }
string Id { get; set; }
dbItem item { get; set; }
List<ITreeNode> Nodes { get; set; }
}
public class TreeNode : ITreeNode
{
public TreeNode()
{ }
public string ParentId { get; set; }
public string Id { get; set; }
public dbItem item { get; set; }
public List<ITreeNode> Nodes { get; set; }
}
public class dbItem
{
public string ParentId { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
class app
{
static void Main()
{
List<dbItem> dbSet = new List<dbItem>();
dbSet.Add(new dbItem() { Id = "5", ParentId = "1", Name = "Jan" });
dbSet.Add(new dbItem() { Id = "25", ParentId = "1", Name = "Maria" });
dbSet.Add(new dbItem() { Id = "1", Name = "John" });
dbSet.Add(new dbItem() { Id = "8", ParentId = "2", Name = "Cornelis" });
dbSet.Add(new dbItem() { Id = "2", Name = "Ilse" });
dbSet.Add(new dbItem() { Id = "3", Name = "Nick" });
dbSet.Add(new dbItem() { Id = "87", ParentId = "5", Name = "Rianne" });
dbSet.Add(new dbItem() { Id = "67", ParentId = "3000", Name = "Rianne" });
dbSet.Add(new dbItem() { Id = "3000", Name = "Max" });
List<TreeNode> result = BuildTree<TreeNode>(dbSet);
}
private class ParentComparer<T> : IComparer<ITreeNode> where T: ITreeNode
{
public int Compare(ITreeNode x, ITreeNode y)
{
if (x.ParentId == null) return -1; //have the parents first
return x.ParentId.CompareTo(y.ParentId);
}
}
private class IdComparer<T> : IComparer<ITreeNode> where T : ITreeNode
{
public int Compare(ITreeNode x, ITreeNode y)
{
return x.Id.CompareTo(y.Id);
}
}
static private List<T> BuildTree<T> (List<dbItem> table) where T: ITreeNode, new()
{
//temporary list of tree nodes to build the tree
List<T> tmpNotAssignedNodes = new List<T>();
List<T> tmpIdNodes = new List<T>();
List<T> roots = new List<T>();
IComparer<T> pc = (IComparer<T>) new ParentComparer<T>();
IComparer<T> ic = (IComparer<T>) new IdComparer<T>();
foreach (dbItem item in table)
{
T newNode = new T() { Id = item.Id, ParentId = item.ParentId, item = item };
newNode.Nodes = new List<ITreeNode>();
T dummySearchNode = new T() { Id = item.ParentId, ParentId = item.ParentId };
if (string.IsNullOrEmpty(item.ParentId))
roots.Add(newNode);
else
{
int parentIndex = tmpIdNodes.BinarySearch(dummySearchNode, ic);//Get the parent
if (parentIndex >=0)
{
T parent = tmpIdNodes[parentIndex];
parent.Nodes.Add(newNode);
}
else
{
parentIndex = tmpNotAssignedNodes.BinarySearch(dummySearchNode, pc);
if (parentIndex < 0) parentIndex = ~parentIndex;
tmpNotAssignedNodes.Insert(parentIndex, newNode);
}
}
dummySearchNode.ParentId = newNode.Id;
//Cleanup Unassigned
int unAssignedChildIndex = tmpNotAssignedNodes.BinarySearch(dummySearchNode, pc);
while (unAssignedChildIndex >= 0 && unAssignedChildIndex < tmpNotAssignedNodes.Count)
{
if (dummySearchNode.ParentId == tmpNotAssignedNodes[unAssignedChildIndex].ParentId)
{
T child = tmpNotAssignedNodes[unAssignedChildIndex];
newNode.Nodes.Add(child);
tmpNotAssignedNodes.RemoveAt(unAssignedChildIndex);
}
else unAssignedChildIndex--;
}
int index = tmpIdNodes.BinarySearch(newNode, ic);
tmpIdNodes.Insert(~index, newNode);
}
return roots;
}
}
Try following solution using recursive code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
public static List<IDbEntityNode> flatDataStructure = null;
public static Dictionary<int?, List<IDbEntityNode>> dict = null;
static void Main(string[] args)
{
flatDataStructure = new List<IDbEntityNode>
{
new ExceptionCategory("System Exception",1,0),
new ExceptionCategory("Index out of range",2,1),
new ExceptionCategory("Null Reference",3,1),
new ExceptionCategory("Invalid Cast",4,1),
new ExceptionCategory("OOM",5,1),
new ExceptionCategory("Argument Exception",6,1),
new ExceptionCategory("Argument Out Of Range",7,6),
new ExceptionCategory("Argument Null",8,6),
new ExceptionCategory("External Exception",9,1),
new ExceptionCategory("Com",10,9),
new ExceptionCategory("SEH",11,9),
new ExceptionCategory("Arithmatic Exception",12,1),
new ExceptionCategory("DivideBy0",13,12),
new ExceptionCategory("Overflow",14,12),
};
dict = flatDataStructure.GroupBy(x => x.ParentId, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
GenericNode<IDbEntityNode> root = new GenericNode<IDbEntityNode>();
root.Parent = null;
int rootId = 0;
root.NodeInformation.Id = rootId;
root.NodeInformation.name = "root";
root.NodeInformation.ParentId = null;
CreateGenericTree(root);
}
public static void CreateGenericTree<T>(GenericNode<T> parent) where T : IDbEntityNode, new()
{
if (dict.ContainsKey(parent.NodeInformation.Id))
{
List<IDbEntityNode> children = dict[parent.NodeInformation.Id];
foreach (IDbEntityNode child in children)
{
GenericNode<T> newChild = new GenericNode<T>();
if (parent.Children == null) parent.Children = new List<GenericNode<T>>();
parent.Children.Add(newChild);
newChild.NodeInformation.Id = child.Id;
newChild.NodeInformation.ParentId = parent.NodeInformation.Id;
newChild.NodeInformation.name = child.name;
newChild.Parent = parent;
CreateGenericTree(newChild);
}
}
}
}
public class GenericNode<T> where T : new()
{
public T NodeInformation = new T();
public GenericNode<T> Parent { get; set; }
public List<GenericNode<T>> Children { get; set; }
}
public class IDbEntityNode
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string name { get; set; }
}
public class ExceptionCategory : IDbEntityNode
{
public string Data { get; set; }
public ExceptionCategory(string data, int id, int parentId)
{
Id = id;
ParentId = parentId;
Data = data;
}
}
}

C# - Adding data to list inside list

How can I add the following data on the table into a list called Vehicles?
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public string name { get; set; }
public IList<criterias> criteria = new List<criterias>();
}
public class stepsList
{
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria = new List<movChannels>();
}
public class vehicles
{
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria = new List<stepsList>();
}
Now, how can I add the data that I have in the table shown into a list called Vehicles? I will create other vehicles later...
You had several bad decisions, some were design flaws and some were minor C# naming convention violations.
Couple of worth mentions flaws:
vehID should have been a string and not int (Example "XPT")
Movment has Name, Value and Time. It doesn't have a list of Values and Times.
Creation:
List<Vehicle> vehicles = new List<Vehicle>();
Vehicle vehicle = new Vehicle()
{
Id = "XPT",
Description = "Average Car",
Steps = new List<Step>()
{
new Step() {
Name = "move car",
Movements = new List<Movement>()
{
new Movement("engage 1st gear", 1, 1),
new Movement("reach 10kph", 10, 5),
new Movement("maintain 10kph", 10, 12),
}
},
new Step() {
Name = "stop car",
Movements = new List<Movement>()
{
new Movement("reach 0kph", 10, 4),
new Movement("put in neutral", 0, 1),
new Movement("turn off vehicle", 0, 0),
}
}
}
};
vehicles.Add(vehicle);
Entities:
public class Movement
{
public string Name { get; set; }
public double Values { get; private set; }
public double Time { get; private set; }
public Movement(string name, double values, double time)
{
Name = name;
Values = values;
Time = time;
}
}
public class Step
{
public string Name { get; set; }
public IList<Movement> Movements { get; set; }
}
public class Vehicle
{
public string Id { get; set; } // Should be changed to string
public string Description { get; set; }
public IList<Step> Steps { get; set; }
}
You should create your classes like the following:
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public movChannels
{
criteria = new List<criterias>();
}
public string name { get; set; }
public IList<criterias> criteria { get; set; }
}
public class stepsList
{
public stepsList
{
stepChannelsCriteria = new List<movChannels>();
}
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria { get; set; }
}
public class vehicles
{
public vehicles
{
vehValCriteria = new List<stepsList>();
}
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria { get; set; }
public movChannels movments { get; set; }
}
What about that?
public class VehiclesViewModel
{
public List<vehicles> Vehicles { get; private set; }
public void Initalize()
{
this.Vehicles = new List<vehicles>();
var vehicle = new vehicles
{
vehID = 1,
vehDescription = "firstDescription",
};
var stepsList = new stepsList
{
steps = "firstStep",
};
var movChannel = new movChannels
{
name = "firstChannel",
};
var criteria = new criterias
{
values = 0.5,
time = 0.5
};
movChannel.criteria.Add(criteria);
stepsList.stepChannelsCriteria.Add(movChannel);
vehicle.vehValCriteria.Add(stepsList);
this.Vehicles.Add(vehicle);
}
}
it seems in your table the VehicleId is of type string. Make sure your VehicleId property in Vehicle class also matches the same.
You can use the collection initializers to set the values of child objects like this way:
var data = new vehicles()
{
vehID = 1,
vehDescription = "Average Car",
vehValCriteria = new List<stepsList>()
{
new stepsList()
{
steps = "Move car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "engage firstgear",
criteria = new List<criterias>()
{
new criterias()
{
values = 1,
time = 1
},
}
},
new movChannels()
{
name = "reach 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 5
},
}
},
new movChannels()
{
name = "maintain 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 12
},
}
}
}
},
new stepsList()
{
steps = "stop car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "reach okph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 4
},
}
},
new movChannels()
{
name = "put in neutral",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 1
},
}
},
new movChannels()
{
name = "turn off vehicle",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 0
},
}
}
}
}
}
};
You can fill your list by moving from top to bottom, like
Create Criterias List then Create movChannel object and add that list
to Criterias object and so on
However if you want to avoid this way, there is another way. If you are using Linq To List then follow this
Get a simple flat object to a list object
var TableData = db.Tablename.Tolist();
Then fill your own object like this
Vehicles finalList = TableData.Select(a => new Vehicles()
{
vehID = a.Id,
vehDescription = a.des,
vehValCriteria = TableData.Where(b => b.StepslistId == a.StepslistId)
.Select(c => new StepsList()
{
steps = c.Steps,
stepChannelsCriteria = TableData.Where(d => d.channelId == c.channelId)
.select(e => new MovChannels()
{
name = e.name,
criteria = TableData.Where(f => f.criteriasId = e.criteriasId)
.Select(g => new Criterias()
{
values = g.Values,
time = g.Time
}).ToList()
}).ToList()
}).ToList()
}).ToList();
This is standard way to fill list within list

Issue getting required output using LINQ query

I need to get data as per this json format.
series: [{
name: 'Marriage',
data: [1, 2, 3] // Sample Data
}, {
name: 'Chess',
data: [2, 2, 3]
}, {
name: 'Ludo',
data: [3, 4, 4]
}]
I need to create chart as here in http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/bar-stacked/
What I have tried is using group by from device id and and using for loop to get the result. But I am quite stuck here getting required output.
Here is what I have tried so far.
void Main()
{
DateTime currentDate = DateTime.UtcNow.Date.AddDays(-30);
var currentMonthData = Device_GameDevices.Where(x => x.CreatedDate >= currentDate).ToList();
// Get total game list
var gamesList = currentMonthData.Select(x => x.GameName).Distinct();
// Group the data as per the device id
var groupedData = from gameData in currentMonthData
group gameData by gameData.DeviceID
into egroup
select new {
Game = egroup.Key,
Data = from bug in egroup
group bug by bug.GameName into g2
select new { Name = g2.Key, HoursPlayed = g2.Sum(x => (x.EndTime - x.StartTime).TotalMinutes/60) }
};
Console.Write(groupedData);
List<DashboardVM.ChartData> chartDatas = new List<DashboardVM.ChartData>();
List<double> hourResultList = new List<double>();
foreach(var item in groupedData)
{
var chart = new DashboardVM.ChartData();
foreach(var gameItem in gamesList)
{
chart.GameNameResult = gameItem;
foreach(var groupedDataItem in item.Data)
{
if(gameItem == groupedDataItem.Name)
{
hourResultList.Add(groupedDataItem.HoursPlayed);
}
else
{
hourResultList.Add(0.0);
}
}
chart.HoursPlayed = hourResultList;
}
chartDatas.Add(chart);
}
Console.Write(chartDatas);
}
public class DashboardVM{
public class ChartData{
public string GameNameResult{get;set;}
public List<double> HoursPlayed{get;set;}
}
}
public class Chart
{
public string type { get; set; }
}
public class Title
{
public string text { get; set; }
}
public class XAxis
{
public List<string> categories { get; set; }
}
public class Title2
{
public string text { get; set; }
}
public class YAxis
{
public int min { get; set; }
public Title2 title { get; set; }
}
public class Legend
{
public bool reversed { get; set; }
}
public class Series
{
public string stacking { get; set; }
}
public class PlotOptions
{
public Series series { get; set; }
}
public class Series2
{
public string name { get; set; }
public List<double> data { get; set; }
}
public class RootObject
{
public Chart chart { get; set; }
public Title title { get; set; }
public XAxis xAxis { get; set; }
public YAxis yAxis { get; set; }
public Legend legend { get; set; }
public PlotOptions plotOptions { get; set; }
public List<Series2> series { get; set; }
}
void Main()
{
var Device_GameDevices = new[] {
new {ID=1,CreatedDate=DateTime.Parse("8/23/2017 06:07:30"),DeviceID="Desktop12",EndTime=DateTime.Parse("8/23/2017 06:06:30"),GameName="CyberGunner",StartTime=DateTime.Parse("8/23/2017 06:03:45")},
new {ID=2,CreatedDate=DateTime.Parse("8/23/2017 07:14:01"),DeviceID="A12" ,EndTime=DateTime.Parse("8/23/2017 11:14:01"),GameName="Marriage" ,StartTime=DateTime.Parse("8/23/2017 07:14:01")},
new {ID=3,CreatedDate=DateTime.Parse("8/23/2017 07:14:02"),DeviceID="A12" ,EndTime=DateTime.Parse("8/23/2017 08:14:01"),GameName="Marriage" ,StartTime=DateTime.Parse("8/23/2017 07:14:02")},
new {ID=4,CreatedDate=DateTime.Parse("8/23/2017 09:14:01"),DeviceID="A12" ,EndTime=DateTime.Parse("8/23/2017 09:14:01"),GameName="Chess" ,StartTime=DateTime.Parse("8/23/2017 07:14:03")},
new {ID=5,CreatedDate=DateTime.Parse("8/23/2017 07:14:03"),DeviceID="A12" ,EndTime=DateTime.Parse("8/23/2017 10:14:01"),GameName="Marriage" ,StartTime=DateTime.Parse("8/23/2017 07:14:03")},
new {ID=6,CreatedDate=DateTime.Parse("8/23/2017 09:57:28"),DeviceID="B12" ,EndTime=DateTime.Parse("8/23/2017 10:57:28"),GameName="Marriage" ,StartTime=DateTime.Parse("8/23/2017 09:57:28")},
};
DateTime currentDate=DateTime.UtcNow.Date.AddDays(-30);
var currentMonthData=Device_GameDevices
.Where(x=>x.CreatedDate>=currentDate)
.ToList();
// Get total game list
var gamesList=currentMonthData
.Select(x=>x.GameName)
.Distinct()
.ToList();
var chart=new RootObject
{
chart=new Chart{ type="bar"},
title=new Title{ text="My title" },
xAxis=new XAxis { categories=gamesList },
yAxis=new YAxis { min=0, title=new Title2 {text="Total Game Time"}},
legend=new Legend {reversed=true},
plotOptions=new PlotOptions { series=new Series {stacking="normal"}},
series=currentMonthData
.GroupBy(d=>new {d.DeviceID,d.GameName})
.Select(d=>new {
DeviceID=d.Key.DeviceID,
GameName=d.Key.GameName,
HoursPlayed=d.Sum(x=>(x.EndTime - x.StartTime).TotalMinutes)/60
})
.GroupBy(d=>d.DeviceID)
.Select(d=>new Series2 {
name=d.Key,
data=gamesList
.GroupJoin(d,a=>a,b=>b.GameName,(a,b)=>new {GameName=a,HoursPlayed=b.Sum(z=>z.HoursPlayed)})
.OrderBy(x=>gamesList.IndexOf(x.GameName))
.Select(x=>x.HoursPlayed)
.ToList()
}).ToList()
};
chart.Dump();
}
This is how the series looks:

Categories