From af57c56cd7591393a1246932af1cdcb29476d2d9 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 23 Sep 2024 21:45:44 +0800 Subject: [PATCH] feature: enhanced statistics panel (#493) * replace the `YEAR` tab with `OVERVIEW` tab, which will analyze most recent 20K commits * use `LiveChartsCore.SkiaSharpView.Avalonia` instead of a custom chart view --- src/Commands/Statistics.cs | 32 +++-- src/Models/Statistics.cs | 179 +++++++++++++++---------- src/Resources/Locales/de_DE.axaml | 1 - src/Resources/Locales/en_US.axaml | 2 +- src/Resources/Locales/fr_FR.axaml | 1 - src/Resources/Locales/pt_BR.axaml | 1 - src/Resources/Locales/ru_RU.axaml | 1 - src/Resources/Locales/zh_CN.axaml | 2 +- src/Resources/Locales/zh_TW.axaml | 2 +- src/SourceGit.csproj | 3 +- src/ViewModels/Statistics.cs | 2 +- src/Views/Statistics.axaml | 18 ++- src/Views/Statistics.axaml.cs | 213 ------------------------------ 13 files changed, 147 insertions(+), 310 deletions(-) diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs index 6b2296ca..8d426d09 100644 --- a/src/Commands/Statistics.cs +++ b/src/Commands/Statistics.cs @@ -6,21 +6,35 @@ namespace SourceGit.Commands { public Statistics(string repo) { - _statistics = new Models.Statistics(); - WorkingDirectory = repo; Context = repo; - Args = $"log --date-order --branches --remotes --since=\"{_statistics.Since()}\" --pretty=format:\"%ct$%an\""; + Args = $"log --date-order --branches --remotes -20000 --pretty=format:\"%ct$%an\""; } public Models.Statistics Result() { - Exec(); - _statistics.Complete(); - return _statistics; + var statistics = new Models.Statistics(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return statistics; + + var start = 0; + var end = rs.StdOut.IndexOf('\n', start); + while (end > 0) + { + ParseLine(statistics, rs.StdOut.Substring(start, end - start)); + start = end + 1; + end = rs.StdOut.IndexOf('\n', start); + } + + if (start < rs.StdOut.Length) + ParseLine(statistics, rs.StdOut.Substring(start)); + + statistics.Complete(); + return statistics; } - protected override void OnReadline(string line) + private void ParseLine(Models.Statistics statistics, string line) { var dateEndIdx = line.IndexOf('$', StringComparison.Ordinal); if (dateEndIdx == -1) @@ -28,9 +42,7 @@ namespace SourceGit.Commands var dateStr = line.Substring(0, dateEndIdx); if (double.TryParse(dateStr, out var date)) - _statistics.AddCommit(line.Substring(dateEndIdx + 1), date); + statistics.AddCommit(line.Substring(dateEndIdx + 1), date); } - - private readonly Models.Statistics _statistics = null; } } diff --git a/src/Models/Statistics.cs b/src/Models/Statistics.cs index 21f1e65d..c3a3b3cf 100644 --- a/src/Models/Statistics.cs +++ b/src/Models/Statistics.cs @@ -1,122 +1,165 @@ using System; using System.Collections.Generic; +using LiveChartsCore; +using LiveChartsCore.Defaults; +using LiveChartsCore.SkiaSharpView; +using LiveChartsCore.SkiaSharpView.Painting; + +using SkiaSharp; + namespace SourceGit.Models { - public class StatisticsSample(string name) + public enum StaticsticsMode + { + All, + ThisMonth, + ThisWeek, + } + + public class StaticsticsAuthor(string name, int count) { public string Name { get; set; } = name; - public int Count { get; set; } = 0; + public int Count { get; set; } = count; + } + + public class StaticsticsSample(DateTime time, int count) + { + public DateTime Time { get; set; } = time; + public int Count { get; set; } = count; } public class StatisticsReport { + public static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; + public int Total { get; set; } = 0; - public List Samples { get; set; } = new List(); - public List ByAuthor { get; set; } = new List(); + public List Authors { get; set; } = new List(); + public List Series { get; set; } = new List(); + public List XAxes { get; set; } = new List(); + public List YAxes { get; set; } = new List(); - public void AddCommit(int index, string author) + public StatisticsReport(StaticsticsMode mode, DateTime start) { - Total++; - Samples[index].Count++; + _mode = mode; - if (_mapUsers.TryGetValue(author, out var value)) + YAxes = [new Axis() { + TextSize = 10, + MinLimit = 0, + SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) { StrokeThickness = .6f } + }]; + + if (mode == StaticsticsMode.ThisWeek) { - value.Count++; + for (int i = 0; i < 7; i++) + _mapSamples.Add(start.AddDays(i), 0); + + XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => WEEKDAYS[(int)v.DayOfWeek]) { TextSize = 10 }); + } + else if (mode == StaticsticsMode.ThisMonth) + { + var now = DateTime.Now; + var maxDays = DateTime.DaysInMonth(now.Year, now.Month); + for (int i = 0; i < maxDays; i++) + _mapSamples.Add(start.AddDays(i), 0); + + XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => $"{v:MM/dd}") { TextSize = 10 }); } else { - var sample = new StatisticsSample(author); - sample.Count++; - - _mapUsers.Add(author, sample); - ByAuthor.Add(sample); + XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(365), v => $"{v:yyyy/MM}") { TextSize = 10 }); } } + public void AddCommit(DateTime time, string author) + { + Total++; + + var normalized = DateTime.MinValue; + if (_mode == StaticsticsMode.ThisWeek || _mode == StaticsticsMode.ThisMonth) + normalized = time.Date; + else + normalized = new DateTime(time.Year, time.Month, 1).ToLocalTime(); + + if (_mapSamples.TryGetValue(normalized, out var vs)) + _mapSamples[normalized] = vs + 1; + else + _mapSamples.Add(normalized, 1); + + if (_mapUsers.TryGetValue(author, out var vu)) + _mapUsers[author] = vu + 1; + else + _mapUsers.Add(author, 1); + } + public void Complete() { - ByAuthor.Sort((l, r) => r.Count - l.Count); + var samples = new List(); + foreach (var kv in _mapSamples) + samples.Add(new DateTimePoint(kv.Key, kv.Value)); + + Series.Add( + new LineSeries() + { + Values = samples, + Stroke = new SolidColorPaint(SKColors.Green) { StrokeThickness = 1 }, + GeometrySize = 8, + GeometryStroke = new SolidColorPaint(SKColors.Green) { StrokeThickness = 2 } + } + ); + + foreach (var kv in _mapUsers) + Authors.Add(new StaticsticsAuthor(kv.Key, kv.Value)); + + Authors.Sort((l, r) => r.Count - l.Count); + _mapUsers.Clear(); + _mapSamples.Clear(); } - private readonly Dictionary _mapUsers = new Dictionary(); + private StaticsticsMode _mode = StaticsticsMode.All; + private Dictionary _mapUsers = new Dictionary(); + private Dictionary _mapSamples = new Dictionary(); } public class Statistics { - public StatisticsReport Year { get; set; } = new StatisticsReport(); - public StatisticsReport Month { get; set; } = new StatisticsReport(); - public StatisticsReport Week { get; set; } = new StatisticsReport(); + public StatisticsReport All { get; set; } + public StatisticsReport Month { get; set; } + public StatisticsReport Week { get; set; } public Statistics() { - _today = DateTime.Today; - _thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24 - _today.Hour * 3600 - _today.Minute * 60 - _today.Second); - _thisWeekEnd = _thisWeekStart.AddDays(7); + _today = DateTime.Now.ToLocalTime().Date; + _thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24); + _thisMonthStart = _today.AddDays(1 - _today.Day); - for (int i = 0; i < 12; i++) - Year.Samples.Add(new StatisticsSample("")); - - var monthDays = DateTime.DaysInMonth(_today.Year, _today.Month); - for (int i = 0; i < monthDays; i++) - Month.Samples.Add(new StatisticsSample($"{i + 1}")); - - string[] weekDayNames = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; - for (int i = 0; i < weekDayNames.Length; i++) - Week.Samples.Add(new StatisticsSample(weekDayNames[i])); - } - - public string Since() - { - return _today.AddMonths(-11).ToString("yyyy-MM-01 00:00:00"); + All = new StatisticsReport(StaticsticsMode.All, DateTime.MinValue); + Month = new StatisticsReport(StaticsticsMode.ThisMonth, _thisMonthStart); + Week = new StatisticsReport(StaticsticsMode.ThisWeek, _thisWeekStart); } public void AddCommit(string author, double timestamp) { var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime(); - if (time.CompareTo(_thisWeekStart) >= 0 && time.CompareTo(_thisWeekEnd) < 0) - Week.AddCommit((int)time.DayOfWeek, author); + if (time >= _thisWeekStart) + Week.AddCommit(time, author); - if (time.Month == _today.Month) - Month.AddCommit(time.Day - 1, author); + if (time >= _thisMonthStart) + Month.AddCommit(time, author); - Year.AddCommit(time.Month - 1, author); + All.AddCommit(time, author); } public void Complete() { - Year.Complete(); + All.Complete(); Month.Complete(); Week.Complete(); - - // Year is start from 11 months ago from now. - var thisYear = _today.Year; - var start = _today.AddMonths(-11); - if (start.Month == 1) - { - for (int i = 0; i < 12; i++) - Year.Samples[i].Name = $"{thisYear}/{i + 1:00}"; - } - else - { - var lastYearIdx = start.Month - 1; - var lastYearMonths = Year.Samples.GetRange(lastYearIdx, 12 - lastYearIdx); - for (int i = 0; i < lastYearMonths.Count; i++) - lastYearMonths[i].Name = $"{thisYear - 1}/{lastYearIdx + i + 1:00}"; - - var thisYearMonths = Year.Samples.GetRange(0, lastYearIdx); - for (int i = 0; i < thisYearMonths.Count; i++) - thisYearMonths[i].Name = $"{thisYear}/{i + 1:00}"; - - Year.Samples.Clear(); - Year.Samples.AddRange(lastYearMonths); - Year.Samples.AddRange(thisYearMonths); - } } private readonly DateTime _today; + private readonly DateTime _thisMonthStart; private readonly DateTime _thisWeekStart; - private readonly DateTime _thisWeekEnd; } } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 8ec97547..90445ec9 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -567,7 +567,6 @@ COMMITTER MONAT WOCHE - JAHR COMMITS: AUTOREN: SUBMODULE diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index e9717845..cebbe194 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -571,9 +571,9 @@ COMMITTER MONTH WEEK - YEAR COMMITS: AUTHORS: + OVERVIEW SUBMODULES Add Submodule Copy Relative Path diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 40df6d99..0db805ca 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -552,7 +552,6 @@ COMMITTER MONTH WEEK - YEAR COMMITS: AUTHORS: SUBMODULES diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 96cdda3e..6ff8e06c 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -545,7 +545,6 @@ COMMITTER MÊS SEMANA - ANO COMMITS: AUTORES: SUBMÓDULOS diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 56b01b0b..4b3f6402 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -571,7 +571,6 @@ ФИКСАТОРЫ МЕСЯЦ НЕДЕЛЯ - ГОД ФИКСАЦИИ: АВТОРЫ: ПОДМОДУЛИ diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 6d3474b6..9989c30d 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -569,9 +569,9 @@ 提交者 本月 本周 - 最近一年 提交次数: 贡献者人数: + 总览 子模块 添加子模块 复制路径 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 7d31e33e..7ed253dc 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -574,9 +574,9 @@ 提交者 本月 本週 - 最近一年 提交次數: 貢獻者人數: + 總覽 子模組 新增子模組 複製路徑 diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 53bc0176..036dcb57 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -45,7 +45,8 @@ - + + diff --git a/src/ViewModels/Statistics.cs b/src/ViewModels/Statistics.cs index 587b1b71..ce28cfea 100644 --- a/src/ViewModels/Statistics.cs +++ b/src/ViewModels/Statistics.cs @@ -50,7 +50,7 @@ namespace SourceGit.ViewModels switch (_selectedIndex) { case 0: - SelectedReport = _data.Year; + SelectedReport = _data.All; break; case 1: SelectedReport = _data.Month; diff --git a/src/Views/Statistics.axaml b/src/Views/Statistics.axaml index 4efb1e9d..a3364830 100644 --- a/src/Views/Statistics.axaml +++ b/src/Views/Statistics.axaml @@ -5,6 +5,7 @@ xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Statistics" x:DataType="vm:Statistics" @@ -62,6 +63,7 @@