File size: 3,174 Bytes
917b070
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@using TaskTrackingSystem.WebApp.Components.Shared.Charts

<div class="@WrapperClass">
    @if (Points.Count < 2)
    {
        <div class="flex h-[260px] items-center justify-center text-sm text-slate-400">@EmptyText</div>
    }
    else
    {
        <svg viewBox="0 0 760 340" class="h-[340px] w-full">
            <defs>
                <linearGradient id="@GradientId" x1="0" x2="0" y1="0" y2="1">
                    <stop offset="0%" stop-color="@Color" stop-opacity="0.18" />
                    <stop offset="100%" stop-color="@Color" stop-opacity="0" />
                </linearGradient>
            </defs>

            @for (var i = 0; i < 5; i++)
            {
                var y = 60 + (i * 55);
                <line x1="48" y1="@y" x2="730" y2="@y" stroke="#e2e8f0" stroke-width="1" />
            }

            @for (var i = 0; i < 6; i++)
            {
                var x = 60 + (i * 125);
                <line x1="@x" y1="40" x2="@x" y2="280" stroke="#e2e8f0" stroke-width="1" />
            }

            <path d="@AreaPath" fill="url(#@GradientId)" />
            <path d="@LinePath" fill="none" stroke="@Color" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />

            @foreach (var point in SvgPoints)
            {
                <circle cx="@point.X" cy="@point.Y" r="5" fill="@Color" stroke="#ddd6fe" stroke-width="3" />
            }
        </svg>

        <div class="mt-4 flex items-center justify-between text-xs text-slate-500">
            @foreach (var point in Points)
            {
                <span class="text-center">@point.Label</span>
            }
        </div>
    }
</div>

@code {
    private readonly string GradientId = $"lineFill{Guid.NewGuid():N}";

    [Parameter] public IReadOnlyList<LineChartPoint> Points { get; set; } = Array.Empty<LineChartPoint>();
    [Parameter] public string Color { get; set; } = "#2563eb";
    [Parameter] public string EmptyText { get; set; } = "No data available";
    [Parameter] public string WrapperClass { get; set; } = string.Empty;

    private IReadOnlyList<(double X, double Y)> SvgPoints => BuildSvgPoints();
    private string LinePath => BuildLinePath();
    private string AreaPath => string.IsNullOrWhiteSpace(LinePath)
        ? string.Empty
        : $"{LinePath} L {SvgPoints.Last().X:0},{280:0} L {SvgPoints.First().X:0},{280:0} Z";

    private IReadOnlyList<(double X, double Y)> BuildSvgPoints()
    {
        const double left = 48;
        const double right = 730;
        const double top = 48;
        const double bottom = 280;
        var max = Math.Max(1, Points.Select(point => point.Value).DefaultIfEmpty(0).Max());

        return Points.Select((point, index) =>
        {
            var x = left + (index * ((right - left) / Math.Max(Points.Count - 1, 1)));
            var y = bottom - Math.Min(point.Value, max) / max * (bottom - top);
            return (x, y);
        }).ToList();
    }

    private string BuildLinePath()
    {
        if (!SvgPoints.Any())
        {
            return string.Empty;
        }

        return $"M {string.Join(" L ", SvgPoints.Select(point => $"{point.X:0},{point.Y:0}"))}";
    }
}