Im trying to configure kendo chart to display data of my model:
public class CallByCountry
{
public DateTime Date { get; set; }
public int Month { get; set; }
public int Year { get; set; }
public DateTime Period { get { return new DateTime(Year, Month, 1); } }
public string Country { get; set; }
public int CallsCount { get; set; }
}
Sample data:
Month Year Country CallsCount
7 2015 USA 5
8 2015 USA 3
8 2015 UK 9
...
My chart:
#(Html.Kendo().Chart<CallByCountry>()
.Name("CallByCountry")
.ChartArea(chartArea => chartArea
.Background("transparent")
)
.DataSource(dataSource => dataSource
.Read(read => read.Action("CallsByCountry", "Reports"))
.Group(group => { group.Add(model => model.Country); })
.Sort(sort => sort.Add(model => new { model.Period}).Ascending())
)
.Series(series =>
{
series.Line(model => model.CallsCount)
.Name("#= group.value #").Style(ChartLineStyle.Smooth);
})
.Legend(legend => legend
.Position(ChartLegendPosition.Bottom)
)
.ValueAxis(axis => axis.Numeric().Labels(l => l.Format("{0:n0}")).MajorUnit(1))
.CategoryAxis(axis => axis
.Categories(model => model.Period)
.Date().BaseUnit(ChartAxisBaseUnit.Months)
.Labels(lbl => lbl.Format("{0:MM/yyyy}"))
)
.Tooltip(tooltip => tooltip
.Visible(true)
.Template("#= series.name #: #= value #"))
)
Controller:
public ActionResult CallsByCountry()
{
List<CallByCountry> callsByCountry = new List<CallByCountry>();
foreach (var call in _callsRepo.GetAll().ToList())
{
var callByCountry = new CallByCountry();
callByCountry.Date = call.StartDate.Date;
callByCountry.Country = _contactRepo.Find(call.ContactID).Country;
callsByCountry.Add(callByCountry);
}
IEnumerable<CallByCountry> data = callsByCountry.GroupBy(i => new { i.Date.Month, i.Date.Year, i.Country })
.Select(group => new CallByCountry()
{
Country = group.Key.Country,
Month = group.Key.Month,
Year = group.Key.Year,
CallsCount = group.Count()
}).OrderBy(x => x.Period);
return Json(data, JsonRequestBehavior.AllowGet);
}
However, I get incorrect representation of my data. The category X-axis shows only one month "7/2015", and some of data for August is displayed in July category.
I guess that can be json parsing issue, which occurs with dates, but im using only month and year.
Please advise, what am I doing wrong?
I'd appreciate any help!
I've adjusted a few things.
I would recommend instead of doing the Period property on your model, do something like this (creating a DateTime rather than a string):
public DateTime Date { get { return new DateTime(Year, Month, 1); } }
This will allow you to leverage the .Date() builder on the CategoryAxis of the grid, like so:
.CategoryAxis(axis => axis
.Categories(model => model.Date)
.Date().BaseUnit(ChartAxisBaseUnit.Months)
.Labels(lbl => lbl.Format("{0:MM/yyyy}"))
)
There also seems to be an issue with the sorting of the data. I adjusted your .Sort() to be
.Sort(sort => sort.Add(model => new {model.Date}).Ascending())
but I noticed the data still was not appearing properly. In your CallsByCountry() action method, sort the data before you return it.
A full example: https://github.com/mmillican/KendoMvcExamples/commit/9ebaa7c4b5c2ddd2a65890cf3d5d77a484d8a3aa
Related
I am trying to create a tree view that would essentially break down like so:
- Year
- Month
- Related Item
So we might have the Year 2022, that has several related items within the several months.
I have created the following model:
public class TreeYear
{
public string NodeYear { get; set; }
public DateTime CreatedDateTime { get; set; }
public List<TreeMonth> Months { get; set; }
}
public class TreeMonth
{
public int MonthID { get; set; }
public string MonthName { get; set; }
public quoteSummary QuoteSummary{ get; set; }
}
I have written some code in my controller which currently returns every item like so:
var allQuotes = QuoteSummary.ToList();
var tree = new TreeYear();
foreach (var quote in allQuotes)
{
tree.NodeYear= quote.CreatedTime.Year.ToString();
tree.CreatedDateTime = quote.CreatedTime;
tree.Months = new List<TreeMonth>()
{
new TreeMonth() {
MonthID = quote.CreatedTime.Month,
MonthName = getAbbreviatedName(quote.CreatedTime.Month),
QuoteSummary = quote
}
};
}
But obviously over here you can see that it has all 41 records of which none are grouped up by year.
I thought maybe I could write some linq something like but at the moment incorrect:
var groups = TheResponse.Details
.GroupBy(
d => Int32.Parse(d.NodeYear),
(key, g) => g.GroupBy(
d => d.Months.Select(x => x.MonthID)),
(key2, g2) => g2.GroupBy(d => d.CreatedDateTime)
)
);
Or would I need to change the model for this idea to work?
If I understood your question correctly, then you need to flatten the inner list and then group by months again.
var groups = TheResponse.Details
.GroupBy(d => Int32.Parse(d.NodeYear))
.Select(d => new
{
Year = d.Key,
MonthObj = d.SelectMany(m => m.Months)
.GroupBy(m => m.MonthID)
.Select(x => new
{
MonthID = x.Key,
RelatedItem = x.ToList()
})
});
I have simplified it by using anonymous types, but you can obviously tweek it based on your resp. Model.
In my ASP.NET Core-5 Entity Framework I have this model:
public class Sales
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public DateTime? SalesDate { get; set; }
}
DTO:
public class YearlyPercentDto
{
public decimal SalesTotal { get; set; }
public int SalesPercent { get; set; }
public string Year { get; set; }
}
public List<YearlyPercentDto> GetYearlySalesSummary()
{
var salesDetail = _context.sales
.GroupBy(o => new
{
Year = o.CreatedDate.Value.Year
})
.Select(u => new YearlyPercentDto
{
SalesPercent = u.Sum(x => x.Amount),
Year = u.Key.Year.ToString()
}).ToList();
return salesDetail;
}
I want to get the total_sales, percentage_sales for each year in the past 5 years as shown below:
Year (Past 5 Years) SalesTotal SalesPercent
2021 200000 18
2020 4300000
2019 1290000
2018 5400000
2017 3322220
How do I achieve this?
I think I'd just pull the totals from the DB and have C# work out the percentage:
public List<YearlyPercentDto> GetYearlySalesSummary()
{
var salesDetail = _context.sales
.Where(s => o.CreatedDate > DateTime.Now.AddYears(-5)
.GroupBy(o => o.CreatedDate.Value.Year)
.Select(u => new YearlyPercentDto
{
SalesTotal = u.Sum(x => x.Amount),
Year = u.Key.ToString() //why is Year a string?
}
).ToList();
//grand total
var tot = salesDetail.Sum(s => s.SalesTotal);
//apply percentage to each element
salesDetail.ForEach(s => s.SalesPercent = (int)(100.0 * s.SalesTotal/tot));
return salesDetail;
}
There seems little point in bullying the DB to provide this info when C# can quickly work it out - the extra hoops to jump through to get the DB to do it don't seem worth it
I would like to know how I can group dates by year, month and date respectively.
So far I managed to group by month and date, however I am getting some trouble understanding to group b a year
So, I created several classes that represent Day, Month and Year:
public class Day
{
public DateTime Date { get; set; }
}
public class Month
{
public int MonthNumber { get; set; }
public List<Day> Days { get; set; }
}
public class Year
{
public int YearNumber { get; set; }
public List<Month> Months { get; set; }
}
So first I got a range of dates overall and add them to list of days.
var startDate = DateTime.Parse("12/08/2019");
var endDate = DateTime.Parse("01/03/2020");
var days = new List<Day>();
var months = new List<Month>();
var years = new List<Year>();
if (endDate >= startDate)
{
for (var date = startDate; date <= endDate; date=date.AddDays(1))
{
var dayModel = new Day();
dayModel.Date = date;
days.Add(dayModel);
}
}
Then I tried to group days by months and add them to list of Months:
var grouppedMonths = days.GroupBy(y => y.Date.Month).ToList();
foreach (var month in grouppedMonths)
{
var monthModel = new Month();
monthModel.Days = month.Select(d => d).ToList();
months.Add(monthModel);
}
After groupping by months I want to group actual result by the year.
I tried something like this:
var grouppedYears = months.GroupBy(x => x.Days.Select(y => y.Date.Year));
but it did not work for me.
Overall I want to get
list of years
that contain list of months
that contain list of days.
I guess you want this:
var results = days
.GroupBy(d => d.Date.Year)
.Select(y => new Year
{
YearNumber = y.Key,
Months = y
.GroupBy(d => d.Date.Month)
.Select(m => new Month
{
MonthNumber = m.Key,
Days = m.ToList()
})
.ToList()
})
.ToList();
I have a list like follows :-
List
EDIT (Years can be different fot Birthdays and anniversaries)
- EmpGuid -265d317b-b819-4171-ba12-e64388746d81
Name - abc
Birthday - 15 Aug 2000
Anniversary- 12 july 1989
EmpGuid - 265d317b-b819-4171-ba12-e64388746d82
Name - xyz
Birthday - 24 Jan 2000
Anniversary- 15 Aug 1988
EmpGuid - 265d317b-b819-4171-ba12-e64388746d83
Name - mno
Birthday - 15 aug 2000
Anniversary- 24 Jan 1987
And I want to group the list according to the dates like so :-
12 July - abc anniversary
15 aug - abc Birthday
mno Birthday
xyz Anniversary
24 jan - xyz birthday
mno anniversary
I tried doing this :-
var groupedEmpList = FinalList.GroupBy(u => u.Birthdate)
.Select(grp =>new { GroupID =grp.Key, FinalList = grp.ToList()})
.ToList()
The the above does not give me the desired output. Any help on this would be appreciated
This solution uses SelectMany to get a flattened list of all the dates on which you can group by the items:
var result = FinalList.Select(item => new
{
Date = new [] { item.Birthday.ToString("ddMM"), item.Anniversary.ToString("ddMM") },
Item = item
})
.SelectMany(item => item.Date.Select(date => new { Date = date, Item = item.Item }))
.GroupBy(item => item.Date)
.Select(grouping => new { Date = grouping.Key, Events = grouping.ToList() }).ToList();
One can also perform the first select within the SelectMany - for the purpose of the answer I kept it separately
For adding the type of the event (and on the way removing the first select):
var result = FinalList.SelectMany(item => new List<dynamic>
{
new { Date = item.Birthday.ToString("ddMM"), Event = "Birthday", Item = item },
new { Date = item.Anniversary.ToString("ddMM"), Event = "Anniversary", Item = item }
})
.GroupBy(item => item.Date)
.Select(grouping => new { Date = grouping.Key, Events = grouping.ToList() }).ToList();
For outputing these results you can:
public enum EventTypes
{
Birthday,
Anniversary
}
public class Event
{
public string Date { get; set; }
public EventTypes Type { get; set; }
public IEnumerable<dynamic> Items { get; set; }
}
var result = FinalList.SelectMany(item => new List<dynamic>
{
new { Date = item.Birthday.ToString("ddMM"), Type = EventTypes.Birthday, Item = item },
new { Date = item.Anniversary.ToString("ddMM"), Type = EventTypes.Anniversary, Item = item }
})
.GroupBy(item => new { item.Date, item.Type })
.Select(grouping => new Event { Date = grouping.Key.Date, Type = grouping.Key.Type, Items = grouping.ToList() }).ToList();
Now the result list is List<Event> and inside you have also your oridinal objects in the Items (replace the dynamic of that list to your original class type - I just don't know it)
Since you need to duplicate items (to put same item into up to 2 groups based on different dates) you need to perform that step separately as LINQ does not duplicate items with basic commands.
Simple option - just have 2 lists combined first - one for birthdays and one for anniversary and extract date with its type to wrapping type similar to:
var mergedList =
FinalList.Select(x => new {
Date = x.Birthdate, Type = "Birthday", Value = x})
.Concat(
FinalList
.Where(x => x.Birthday != x.Anniversary) // if needed
.Select(x => new {
Date = x.Anniversary, Type = "Anniversary", Value = x});
// Now list have all unique dates - can group and extract any info
var grouped = mergedList
.GroupBy(u => u.Date)
.Select(grp => new {
Date = grp.Key,
ListOfNames = grp.Select(x => new {x.Value.Name, x.Type}).ToList()
})
.ToList();
Assuming that you have a class:
class Info
{
public Guid EmpGuid { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public DateTime Anniversary { get; set; }
}
You need to unpivot your class into the following class:
class UnpivotInfo
{
public Guid EmpGuid { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public string Type { get; set; }
}
where your Type is either "Birthday" or "Anniversary", by doing this:
var unpivoted = list.SelectMany(i => new[]
{
new UnpivotInfo {EmpGuid = i.EmpGuid, Date = i.Birthday, Type = "Birthday", Name = i.Name}
, new UnpivotInfo {EmpGuid = i.EmpGuid, Date = i.Anniversary, Type = "Anniversary", Name = i.Name}
});
You can then group your data by date:
var groups = unpivoted.GroupBy(p => p.Date);
I have a table with Logs and I am counting the Logs per day as follows:
// Count logs by day
IList<DataModel> models = _context.Logs
.Where(x => x.Created >= dateMinimum && x.Created <= dateMaximum)
.GroupBy(x => new { Year = x.Created.Year, Month = x.Created.Month, Day = x.Created.Day })
.Select(x => new { Year = x.Key.Year, Month = x.Key.Month, Day = x.Key.Day, Count = x.Count() })
.AsEnumerable()
.Select(x => new DataModel { Date = new DateTime(x.Year, x.Month, x.Day), LogsCount = x.Count })
.ToList();
// Fill empty days with dates which contains all days in range
models.AddRange(dates.Where(x => !models.Any(y => y.Date == x.Date)).Select(x => new DataModel { Date = x, LogsCount = 0 }));
This is working if I want to count all logs by day independently of the type.
But I would like to count logs by day and type (Error, Warn, Info, ...).
I tried to add x.Type to group but at the end I get only 3 items.
At the moment my DataModel is the following:
public class DataModel
{
public DateTime Date { get; set; }
public Int32 LogsCount { get; set; }
}
But maybe it should be something like:
public class DataModel
{
public DateTime Date { get; set; }
public KeyValuePair<String, Int32> LogsCount { get; set; }
}
Where LogsCount has a string which holds the Type and Int32 which contains the count.
How can I do this?
Might want to consider using entity functions for grouping by date.
Example:
var results = query.GroupBy(r => new
{
SentDate = System.Data.Objects.EntityFunctions.TruncateTime(r.Launch.EmailDeliveredDate),
EventSubTypeID = r.EmailEventSubtypeID
})
.Select(x => new
{
x.Key.SentDate,
x.Key.EventSubTypeID,
NumResults = x.Count()
})
.ToList();
Did you try something like this?
IList<DataModel> models = Logs
.Where(x => x.Created >= dateMinimum && x.Created <= dateMaximum)
.GroupBy(x => new { Year = x.Created.Year, Month = x.Created.Month, Day = x.Created.Day, Type = x.Type })
.Select(x => new { Year = x.Key.Year, Month = x.Key.Month, Day = x.Key.Day, Count = x.Count(), Type = x.Key.Type })
.AsEnumerable()
.Select(x => new DataModel { Date = new DateTime(x.Year, x.Month, x.Day), LogsCount = x.Count, Type = x.Type })
.ToList()
public class DataModel
{
public DateTime Date { get; set; }
public Int32 LogsCount { get; set; }
public string Type { get; set; }
}