Skip to content

Commit 2d95fad

Browse files
Add supoprt for Palette.Invert() and Colormap.Invert() (ScottPlot#5094)
* Add a (hopefully_ colorblind friendly dark palette * Color.Invert() and Color.InvertHue() * IPalette: Invert() and InvertHue() * CoordinateRect: UnitSquare * Cookbook: inverted palette * IColormap: Invert() and InvertHue() * Update CHANGELOG.md * ColorblindFriendlyDark: refacor to use new API --------- Co-authored-by: Scott W Harden <swharden@gmail.com>
1 parent c3bc123 commit 2d95fad

11 files changed

Lines changed: 173 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ _Not yet on NuGet..._
99
* Interactive Plottables: Created a new collection of interactive plottables which do not require manually wiring mouse events to support hover and click-drag manipulation
1010
* Controls: Added `SetCursor()` functionality to `IPlotControl` to improve cross-platform support for mouse interactions
1111
* SignalXY: Reduced heap allocations by modifying `ISignalXYSource` to use `IReadOnlyList<Pixel>` instead of `Pixel[]` (#5091, #5078) @bclehmann
12+
* Palette: Added `Invert()` and `InvertHue()` to `IPalette` so palettes for light backgrounds can be reused for dark backgrounds (#5094) @NeilMacMullen
13+
* Colormap: Added `Invert()` and `InvertHue()` to `IPalette` so palettes for light backgrounds can be reused for dark backgrounds (#5094) @NeilMacMullen
14+
* Palettes: Added a `ColorblindFriendlyDark` palette to improve accessibility of categorical plots with dark backgrounds (#5094) @NeilMacMullen
1215

1316
## ScottPlot 5.0.56
1417
_Published on [NuGet](https://www.nuget.org/profiles/ScottPlot) on 2025-08-22_

src/ScottPlot5/ScottPlot5 Cookbook/Recipes/General/Styling.cs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using SkiaSharp;
2-
31
namespace ScottPlotCookbook.Recipes.Introduction;
42

53
public class Styling : ICategory
@@ -77,8 +75,8 @@ public class Palette : RecipeBase
7775
{
7876
public override string Name => "Palettes";
7977
public override string Description => "A palette is a set of colors, and the Plot's palette " +
80-
"defines the default colors to use when adding new plottables. ScottPlot comes with many " +
81-
"standard palettes, but users may also create their own.";
78+
"defines the default colors to use when adding new plottables. " +
79+
"https://scottplot.net/cookbook/5.0/palettes/ displays all palettes included with ScottPlot.";
8280

8381
[Test]
8482
public override void Execute()
@@ -95,6 +93,90 @@ public override void Execute()
9593
}
9694
}
9795

96+
public class PaletteInvert : RecipeBase
97+
{
98+
public override string Name => "Inverted Palettes";
99+
public override string Description => "Palettes can be inverted. " +
100+
"Palettes that work well on light backgrounds typically work well " +
101+
"on dark backgrounds if they are inverted.";
102+
103+
[Test]
104+
public override void Execute()
105+
{
106+
var palette1 = new ScottPlot.Palettes.ColorblindFriendly();
107+
var palette2 = palette1.Inverted();
108+
var palette3 = palette1.InvertedHue();
109+
110+
for (int x = 0; x < palette1.Count(); x++)
111+
{
112+
CoordinateRect rect1 = CoordinateRect.UnitSquare.WithTranslation(x, 4);
113+
CoordinateRect rect2 = CoordinateRect.UnitSquare.WithTranslation(x, 2);
114+
CoordinateRect rect3 = CoordinateRect.UnitSquare.WithTranslation(x, 0);
115+
var shape1 = myPlot.Add.Rectangle(rect1);
116+
var shape2 = myPlot.Add.Rectangle(rect2);
117+
var shape3 = myPlot.Add.Rectangle(rect3);
118+
119+
// set color using the palette
120+
shape1.FillColor = palette1.Colors[x];
121+
shape2.FillColor = palette2.Colors[x];
122+
shape3.FillColor = palette3.Colors[x];
123+
124+
shape1.LineColor = shape1.FillColor;
125+
shape2.LineColor = shape2.FillColor;
126+
shape3.LineColor = shape3.FillColor;
127+
128+
}
129+
130+
myPlot.Add.Text("Standard", 0, 5.5);
131+
myPlot.Add.Text("Inverted", 0, 3.5);
132+
myPlot.Add.Text("Inverted Hue", 0, 1.5);
133+
myPlot.HideGrid();
134+
}
135+
}
136+
137+
public class Colormaps : RecipeBase
138+
{
139+
public override string Name => "Colormaps";
140+
public override string Description => "A colormap is a continuous gradient of multiple colors. " +
141+
"It can be used to color continuous data like heatmaps and images, but colormaps may also " +
142+
"be sampled directly to create collections of colors. " +
143+
"https://scottplot.net/cookbook/5.0/colormaps/ displays all colormaps included with ScottPlot.";
144+
145+
[Test]
146+
public override void Execute()
147+
{
148+
var colormap1 = new ScottPlot.Colormaps.Viridis();
149+
var colormap2 = colormap1.Invert();
150+
var colormap3 = colormap1.InvertHue();
151+
152+
int steps = 20;
153+
for (int x = 0; x < steps; x++)
154+
{
155+
CoordinateRect rect1 = CoordinateRect.UnitSquare.WithTranslation(x, 4);
156+
CoordinateRect rect2 = CoordinateRect.UnitSquare.WithTranslation(x, 2);
157+
CoordinateRect rect3 = CoordinateRect.UnitSquare.WithTranslation(x, 0);
158+
var shape1 = myPlot.Add.Rectangle(rect1);
159+
var shape2 = myPlot.Add.Rectangle(rect2);
160+
var shape3 = myPlot.Add.Rectangle(rect3);
161+
162+
// set color using the colormap
163+
double fraction = (double)x / (steps - 1);
164+
shape1.FillColor = colormap1.GetColor(fraction);
165+
shape2.FillColor = colormap2.GetColor(fraction);
166+
shape3.FillColor = colormap3.GetColor(fraction);
167+
168+
shape1.LineColor = shape1.FillColor;
169+
shape2.LineColor = shape2.FillColor;
170+
shape3.LineColor = shape3.FillColor;
171+
}
172+
173+
myPlot.Add.Text("Standard", 0, 5.5);
174+
myPlot.Add.Text("Inverted", 0, 3.5);
175+
myPlot.Add.Text("Inverted Hue", 0, 1.5);
176+
myPlot.HideGrid();
177+
}
178+
}
179+
98180
public class ArrowShapeNames : RecipeBase
99181
{
100182
public override string Name => "Arrow Shapes";
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace ScottPlot.Colormaps;
2+
3+
internal class Inverted(IColormap original) : IColormap
4+
{
5+
public string Name => $"{Original.Name} (inverted)";
6+
7+
private readonly IColormap Original = original;
8+
9+
public Color GetColor(double position)
10+
{
11+
return Original.GetColor(position).Inverted();
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace ScottPlot.Colormaps;
2+
3+
internal class InvertedHue(IColormap original) : IColormap
4+
{
5+
public string Name => $"{Original.Name} (inverted hue)";
6+
7+
private readonly IColormap Original = original;
8+
9+
public Color GetColor(double position)
10+
{
11+
return Original.GetColor(position).InvertedHue();
12+
}
13+
}

src/ScottPlot5/ScottPlot5/Interfaces/IColormap.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,14 @@ public static Image GetImageVertical(this IColormap colormap, int height = 256,
133133

134134
return new Image(bmp);
135135
}
136+
137+
public static IColormap Invert(this IColormap original)
138+
{
139+
return new Inverted(original);
140+
}
141+
142+
public static IColormap InvertHue(this IColormap original)
143+
{
144+
return new InvertedHue(original);
145+
}
136146
}

src/ScottPlot5/ScottPlot5/Interfaces/IPalette.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,19 @@ public static Color[] GetColors(this IPalette palette, int count)
2929
{
3030
return Enumerable.Range(0, count).Select(palette.GetColor).ToArray();
3131
}
32+
33+
public static IPalette Inverted(this IPalette palette)
34+
{
35+
return new Palettes.Custom(palette.Colors.Select(x => x.Inverted()), $"{palette.Name} (inverted)", palette.Description);
36+
}
37+
38+
public static IPalette InvertedHue(this IPalette palette)
39+
{
40+
return new Palettes.Custom(palette.Colors.Select(x => x.InvertedHue()), $"{palette.Name} (inverted hue)", palette.Description);
41+
}
42+
43+
public static int Count(this IPalette palette)
44+
{
45+
return palette.Colors.Length;
46+
}
3247
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace ScottPlot.Palettes;
2+
3+
/// <summary>
4+
/// Inverted version of <see cref="ColorblindFriendly"/>
5+
/// which is useful for plots with dark backgrounds
6+
/// </summary>
7+
public class ColorblindFriendlyDark : IPalette
8+
{
9+
private readonly IPalette Palette = new ColorblindFriendly().Inverted();
10+
public string Name => Palette.Name;
11+
public string Description => Palette.Description;
12+
public Color[] Colors => Palette.Colors;
13+
public Color GetColor(int index) => Palette.GetColor(index);
14+
}

src/ScottPlot5/ScottPlot5/Palettes/Custom.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ public class Custom : IPalette
88

99
public string Description { get; }
1010

11-
public Custom(Color[] colors, string? name, string? description = null)
11+
public Custom(IEnumerable<Color> colors, string? name, string? description = null)
1212
{
13-
Colors = colors;
13+
Colors = colors.ToArray();
1414
Name = name ?? "unnamed";
1515
Description = description ?? "no description";
1616
}

src/ScottPlot5/ScottPlot5/Primitives/Color.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,19 @@ static byte DarkenByte(byte color, double fraction)
434434
return new Color(r, g, b, Alpha);
435435
}
436436

437+
public Color Inverted()
438+
{
439+
return new Color(
440+
(byte)(255 - R),
441+
(byte)(255 - G),
442+
(byte)(255 - B));
443+
}
444+
445+
public Color InvertedHue()
446+
{
447+
return Color.FromHSL(Hue + .5f, Saturation, Luminance);
448+
}
449+
437450
/// <summary>
438451
/// Return this color mixed with another color.
439452
/// </summary>

src/ScottPlot5/ScottPlot5/Primitives/Colors.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public struct Colors
176176
readonly static public Color[] Category10 = new Category10().Colors;
177177
readonly static public Color[] Category20 = new Category20().Colors;
178178
readonly static public Color[] ColorblindFriendly = new ColorblindFriendly().Colors;
179+
readonly static public Color[] ColorblindFriendlyDark = new ColorblindFriendlyDark().Colors;
179180

180181
public static Color ScottPlotPurple => Color.FromHex("#67217a");
181182
public static Color ScottPlotPink => Color.FromHex("#9a4993");

0 commit comments

Comments
 (0)