File: Otlp\Model\ColorGenerator.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Globalization;
 
namespace Aspire.Dashboard.Otlp.Model;
 
public sealed class GeneratedColor
{
    public required string Hex { get; init; }
    public required int Red { get; init; }
    public required int Green { get; init; }
    public required int Blue { get; init; }
}
 
public class ColorGenerator
{
    private static readonly string[] s_colorsHex =
    [
        "#17B8BE", "#F8DCA1", "#B7885E", "#FFCB99", "#F89570",
        "#829AE3", "#E79FD5", "#1E96BE", "#89DAC1", "#B3AD9E",
        "#12939A", "#DDB27C", "#88572C", "#FF9833", "#EF5D28",
        "#162A65", "#DA70BF", "#125C77", "#4DC19C", "#776E57"
    ];
    public static readonly ColorGenerator Instance = new ColorGenerator();
 
    private readonly List<GeneratedColor> _colors;
    private readonly ConcurrentDictionary<string, Lazy<int>> _colorIndexByKey;
    private int _currentIndex;
 
    private ColorGenerator()
    {
        _colors = new List<GeneratedColor>();
        _colorIndexByKey = new ConcurrentDictionary<string, Lazy<int>>(StringComparer.OrdinalIgnoreCase);
        _currentIndex = 0;
 
        foreach (var hex in s_colorsHex)
        {
            var rgb = GetHexRgb(hex);
            _colors.Add(new GeneratedColor
            {
                Hex = hex,
                Red = rgb.Red,
                Green = rgb.Green,
                Blue = rgb.Blue
            });
        }
    }
 
    private static (int Red, int Green, int Blue) GetHexRgb(string s)
    {
        if (s.Length != 7)
        {
            return (0, 0, 0);
        }
 
        var r = int.Parse(s.AsSpan(1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
        var g = int.Parse(s.AsSpan(3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
        var b = int.Parse(s.AsSpan(5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
 
        return (r, g, b);
    }
 
    private int GetColorIndex(string key)
    {
        return _colorIndexByKey.GetOrAdd(key, k =>
        {
            // GetOrAdd is run outside of the lock.
            // Use lazy to ensure that the index is only calculated once for an app.
            return new Lazy<int>(() =>
            {
                var i = _currentIndex;
                _currentIndex = ++_currentIndex % _colors.Count;
                return i;
            });
        }).Value;
    }
 
    public string GetColorHexByKey(string key)
    {
        var i = GetColorIndex(key);
        return _colors[i].Hex;
    }
 
    public void Clear()
    {
        _colorIndexByKey.Clear();
        _currentIndex = 0;
    }
}