I use the following c# class to scale between 3 colours (typically red for negative, yellow for the mid point and green for positive numbers:
using System.Drawing; namespace Utils.Maths; public class ColourScaler { public static (int r, int g, int b) HighestColor = (99, 190, 123); public static (int r, int g, int b) MidColor = (255, 235, 132); public static (int r, int g, int b) LowestColor = (248, 105, 107); public double MidValue { get; set; } = 0; public double HighestValue { get; set; } = 0; public double LowestValue { get; set; } = 0; public IEnumerable<double> Values { set { var items = value.OrderBy(o => o).Distinct().ToArray(); if (items.Length > 0) { LowestValue= items[0]; HighestValue = items[items.Length - 1]; } } } public ColourScaler(IEnumerable<double>? values = null, double midValue = 0) { Values = values ?? Enumerable.Empty<double>(); MidValue = midValue; } public (int r, int g, int b) GetRgb(double value) { if (value < LowestValue || value > HighestValue) throw new ArgumentOutOfRangeException().AddData("value", value); if (value >= 0) { if (HighestValue - MidValue == 0) return HighestColor; var ratio = (value - MidValue) / (HighestValue - MidValue); var r = (int)(MidColor.r - (MidColor.r - HighestColor.r) * ratio); var g = (int)(MidColor.g - (MidColor.g - HighestColor.g) * ratio); var b = (int)(MidColor.b - (MidColor.b - HighestColor.b) * ratio); return (r, g, b); } else { if (MidValue - LowestValue == 0) return LowestColor; var ratio = (MidValue - value) / (MidValue - LowestValue); var r = (int)(MidColor.r - (MidColor.r - LowestColor.r) * ratio); var g = (int)(MidColor.g - (MidColor.g - LowestColor.g) * ratio); var b = (int)(MidColor.b - (MidColor.b - LowestColor.b) * ratio); return (r, g, b); } } public Color GetColour(double value) { var rgb = GetRgb(value); return Color.FromArgb(rgb.r, rgb.g, rgb.b); } }
And here are some tests:
namespace Tests.ColourScaler; [TestClass] public class ColourScalerTests { [TestMethod] public void ColourScaler_Values() { var item = new ColourScaler { Values = new double[] { 13, -2, -3, 4, 3, -3, 4, 1, 0 } }; Assert.AreEqual(-3, item.LowestValue); Assert.AreEqual(0, item.MidValue); Assert.AreEqual(13, item.HighestValue); item = new ColourScaler { Values = new double[] { } }; Assert.AreEqual(0, item.LowestValue); Assert.AreEqual(0, item.MidValue); Assert.AreEqual(0, item.HighestValue); } [TestMethod] public void ColourScaler_New() { var item = new ColourScaler(new double[] { 13, -2, -3, 4, 3, -3, 4, 1, 0 }, 1); Assert.AreEqual(-3, item.LowestValue); Assert.AreEqual(1, item.MidValue); Assert.AreEqual(13, item.HighestValue); item = new ColourScaler(new double[] { 13, -2, -3, 4, 3, -3, 4, 1, 0 }); Assert.AreEqual(-3, item.LowestValue); Assert.AreEqual(0, item.MidValue); Assert.AreEqual(13, item.HighestValue); item = new ColourScaler(new double[] { }); Assert.AreEqual(0, item.LowestValue); Assert.AreEqual(0, item.MidValue); Assert.AreEqual(0, item.HighestValue); } [TestMethod] public void ColourScaler_GetRgb() { var item = new ColourScaler { Values = new double[] { -7, -5, -3, -1, 0, 2, 4, 6 } }; var rgb = item.GetRgb(-7); Assert.AreEqual(248, rgb.r); Assert.AreEqual(105, rgb.g); Assert.AreEqual(107, rgb.b); rgb = item.GetRgb(-6); Assert.AreEqual(249, rgb.r); Assert.AreEqual(123, rgb.g); Assert.AreEqual(110, rgb.b); rgb = item.GetRgb(-5); Assert.AreEqual(250, rgb.r); Assert.AreEqual(142, rgb.g); Assert.AreEqual(114, rgb.b); rgb = item.GetRgb(-2); Assert.AreEqual(253, rgb.r); Assert.AreEqual(197, rgb.g); Assert.AreEqual(124, rgb.b); rgb = item.GetRgb(-1); Assert.AreEqual(254, rgb.r); Assert.AreEqual(216, rgb.g); Assert.AreEqual(128, rgb.b); rgb = item.GetRgb(0); Assert.AreEqual(255, rgb.r); Assert.AreEqual(235, rgb.g); Assert.AreEqual(132, rgb.b); rgb = item.GetRgb(1); Assert.AreEqual(229, rgb.r); Assert.AreEqual(227, rgb.g); Assert.AreEqual(130, rgb.b); rgb = item.GetRgb(2); Assert.AreEqual(203, rgb.r); Assert.AreEqual(220, rgb.g); Assert.AreEqual(129, rgb.b); rgb = item.GetRgb(4); Assert.AreEqual(151, rgb.r); Assert.AreEqual(205, rgb.g); Assert.AreEqual(126, rgb.b); rgb = item.GetRgb(5); Assert.AreEqual(125, rgb.r); Assert.AreEqual(197, rgb.g); Assert.AreEqual(124, rgb.b); rgb = item.GetRgb(6); Assert.AreEqual(99, rgb.r); Assert.AreEqual(190, rgb.g); Assert.AreEqual(123, rgb.b); } }