about summary refs log tree commit diff
path: root/MatrixUtils.Web/Shared/ActivityGraph.razor
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Shared/ActivityGraph.razor')
-rw-r--r--MatrixUtils.Web/Shared/ActivityGraph.razor148
1 files changed, 148 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Shared/ActivityGraph.razor b/MatrixUtils.Web/Shared/ActivityGraph.razor
new file mode 100644
index 0000000..51fb539
--- /dev/null
+++ b/MatrixUtils.Web/Shared/ActivityGraph.razor
@@ -0,0 +1,148 @@
+@using System.Drawing
+@using System.Runtime.InteropServices
+@using System.Diagnostics
+
+@if (Data is { Count: > 0 })
+{
+    @*                                                      12*5=60 *@
+    <div style="display: grid; grid-template-columns: 35px repeat(60, 1.5em); grid-template-rows: 1.5em repeat(7, 1.5em); gap: 0;">
+        @* row 0: month labels with colspan *@
+        @* @foreach (var month in Enumerable.Range(1, 12)) *@
+        @* { *@
+        @*     <div style="grid-row: 1; grid-column: @((int)(month * 4.3) + 1);"> *@
+        @*         <span aria-hidden="true">@(new DateTime(2021, month, 1).ToString("MMM")[..3])</span> *@
+        @*     </div> *@
+        @* } *@
+
+        @* column 0: day labels *@
+        @* @for (var i = 0; i < 7; i++) *@
+        @* { *@
+        @*     <div style="text-align: left; grid-column: 1; grid-row: @(i + 2)"> *@
+        @*         @(((DayOfWeek)i).ToString()[..3]) *@
+        @*     </div> *@
+        @* } *@
+
+
+        <div style="grid-row: 1; grid-column: 5;">Jan</div>
+        <div style="grid-row: 1; grid-column: 9;">Feb</div>
+        <div style="grid-row: 1; grid-column: 13;">Mar</div>
+        <div style="grid-row: 1; grid-column: 18;">Apr</div>
+        <div style="grid-row: 1; grid-column: 22;">May</div>
+        <div style="grid-row: 1; grid-column: 26;">Jun</div>
+        <div style="grid-row: 1; grid-column: 31;">Jul</div>
+        <div style="grid-row: 1; grid-column: 35;">Aug</div>
+        <div style="grid-row: 1; grid-column: 39;">Sep</div>
+        <div style="grid-row: 1; grid-column: 44;">Oct</div>
+        <div style="grid-row: 1; grid-column: 48;">Nov</div>
+        <div style="grid-row: 1; grid-column: 52;">Dec</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 2">Sun</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 3">Mon</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 4">Tue</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 5">Wed</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 6">Thu</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 7">Fri</div>
+        <div style="text-align: left; grid-column: 1; grid-row: 8">Sat</div>
+
+
+        @* pad activity cell dates... *@
+        <div style="grid-column: 2; grid-row: 2 / span @((int)(new DateOnly(Data.Keys.First().Year, 1, 1).DayOfWeek));"></div>
+
+        @* the actual activity cells *@
+
+        @code{
+            bool needsBorder = false;
+        }
+
+        @for (DateOnly date = new DateOnly(Data.Keys.First().Year, 1, 1); date <= new DateOnly(Data.Keys.First().Year, 1, 1).AddYears(1).AddDays(-1); date = date.AddDays(1))
+        {
+            var hasData = Data.TryGetValue(date, out var color);
+            var needsTopBorder = date.Day == 1 && date.Month != 1 && date.DayOfWeek != DayOfWeek.Sunday;
+            if (date.DayOfWeek == DayOfWeek.Sunday)
+                needsBorder = date.AddDays(7).Day <= 7 && date.Month != 12;
+            var needsLeftBorder = date.Day <= 7;
+
+            <div class="activity-cell-container"
+                 style="grid-row: @((int)date.DayOfWeek + 2); border-@(needsLeftBorder ? "left" : "right"): @(needsBorder ? "2px solid white" : "none"); border-top: @(needsTopBorder ? "2px solid white" : "none");">
+                @if (hasData)
+                {
+                    <div class="activity-cell"
+                         style="background-color: rgb(@(color.R / GlobalMax.R * 255), @(color.G / GlobalMax.G * 255), @(color.B / GlobalMax.B * 255));"
+                         title="@($"{color.R} {RLabel}, {color.G} {GLabel}, and {color.B} {BLabel} on {date.ToString("D")}")">
+                    </div>
+                }
+                else
+                {
+                    <div class="activity-cell"
+                         title="@($"No data on {date.ToString("D")}")">
+                    </div>
+                }
+            </div>
+        }
+    </div>
+}
+
+
+@code {
+    private Dictionary<DateOnly, RGB> _data = new();
+    private RGB? _globalMax = null;
+
+    [Parameter]
+    public Dictionary<DateOnly, RGB> Data
+    {
+        get => _data;
+        set
+        {
+            // var sw = Stopwatch.StartNew();
+            if (value is not { Count: > 0 }) return;
+            // Console.WriteLine($"Recalculating activity graph ({value.Count} datapoints)...");
+
+
+            // var year = (int)value.Keys.Average(x => x.Year);
+            // value = value
+            // .Where(x => x.Key.Year == year)
+            // .OrderBy(x => x.Key)
+            // .ToDictionary(x => x.Key, x => x.Value);
+
+            _data = value;
+            // Console.WriteLine($"Recalculated activity graph in {sw.Elapsed}");
+            // StateHasChanged();
+        }
+    }
+
+    [Parameter]
+    public RGB GlobalMax
+    {
+        get
+        {
+            if (_globalMax is not null) return _globalMax.Value;
+            if (Data is not { Count: > 0 }) return new RGB() { R = 255, G = 255, B = 255 };
+            return new RGB()
+            {
+                R = Data.Values.Max(x => x.R),
+                G = Data.Values.Max(x => x.G),
+                B = Data.Values.Max(x => x.B)
+            };
+        }
+        set => _globalMax = value;
+    }
+
+    [Parameter] public string RLabel { get; set; } = "R";
+    [Parameter] public string GLabel { get; set; } = "G";
+    [Parameter] public string BLabel { get; set; } = "B";
+
+    [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * 3, Pack = 1)]
+    public struct RGB()
+    {
+        public float R = 0;
+        public float G = 0;
+        public float B = 0;
+
+        public RGB(float r, float g, float b) : this()
+        {
+            R = r;
+            G = g;
+            B = b;
+        }
+    }
+
+}
\ No newline at end of file