Skip to content

Commit

Permalink
Refactored color schemas to use ColorGradient class
Browse files Browse the repository at this point in the history
Issue: #4306
  • Loading branch information
buchen committed Nov 29, 2024
1 parent 1c5c05a commit 7ea526e
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package name.abuchen.portfolio.ui.util;

import org.eclipse.swt.graphics.Color;

/**
* Inspired by
* http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients
*/
public class ColorGradient
{
public static class ColorPoint
{
public final Color color;
public final float position;

public ColorPoint(Color color, float position)
{
this.color = color;
this.position = position;
}
}

public static final ColorGradient RED_TO_GREEN = new ColorGradient( //
Colors.getColor(201, 46, 37), // C92E25
Colors.getColor(253, 127, 118), // FD7F76
Colors.getColor(253, 154, 147), // FD9A93
Colors.getColor(252, 187, 183), // FCBBB7
Colors.getColor(232, 232, 232), // E8E8E8
Colors.getColor(187, 228, 145), // BBE491
Colors.getColor(161, 215, 113), // A1D771
Colors.getColor(128, 194, 94), // 80C25E
Colors.getColor(57, 123, 39) // 397B27
);

public static final ColorGradient ORANGE_TO_BLUE = new ColorGradient( //
Colors.getColor(179, 110, 58), // B36E3A
Colors.getColor(253, 156, 82), // FD9C52
Colors.getColor(255, 206, 170), // FFCEAA
Colors.getColor(221, 221, 221), // DDDDDD
Colors.getColor(158, 203, 236), // 9ECBEC
Colors.getColor(60, 151, 218), // 3C97DA
Colors.getColor(42, 105, 153) // 2A6999
);

private final ColorPoint[] colors;

public ColorGradient(ColorPoint... colors)
{
this.colors = colors;
}

public ColorGradient(Color... colors)
{
this.colors = new ColorPoint[colors.length];
for (int ii = 0; ii < colors.length; ii++)
this.colors[ii] = new ColorPoint(colors[ii], ii / (float) (colors.length - 1));
}

public Color getColorAt(float value)
{
if (value <= 0)
return colors[0].color;

if (value >= 1)
return colors[colors.length - 1].color;

for (int ii = 0; ii < colors.length; ii++)
{
var current = colors[ii];
if (value <= current.position)
{
var previous = colors[Math.max(0, ii - 1)];
var diff = current.position - previous.position;
if (diff == 0)
return current.color;
else
return new Color(Colors.interpolate(previous.color.getRGB(), current.color.getRGB(),
(value - previous.position) / diff));
}
}

return colors[colors.length - 1].color;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ public void setHyperlink(RGBA color)

// use a darker green to improve readability for the green-white-red schema
public static final Color HEATMAP_DARK_GREEN = getColor(104, 229, 23); // 68E517
public static final Color HEATMAP_ORANGE = getColor(255, 165, 0); // FFA500

public static final Color ICON_ORANGE = getColor(241, 143, 1); // F18F01
public static final Color ICON_BLUE = getColor(14, 110, 142); // 0E6E8E
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.eclipse.swt.graphics.RGB;

import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.util.ColorGradient;
import name.abuchen.portfolio.ui.util.ColorGradient.ColorPoint;
import name.abuchen.portfolio.ui.util.Colors;

enum ColorSchema
Expand Down Expand Up @@ -34,96 +36,44 @@ DoubleFunction<Color> buildColorFunction(ResourceManager resourceManager)
// Normalize performance between -0.07 and +0.07 and map it to a
// hue-like scale between 0 (red) and 60 (yellow) or 120
// (green).
double p = normalizePerformance(performance);
float hue = (float) p * 120f;
var p = normalizePerformance(performance);
var hue = p * 120f;

return resourceManager.createColor(new RGB(hue, 0.9f, 1f));
};

case GREEN_WHITE_RED -> performance -> {
// Performance is normalized and interpolated between green
// (positive) and the background (neutral) or red (negative)
double p = Math.min(MAX_PERFORMANCE, Math.abs(performance)) / MAX_PERFORMANCE;

RGB color;

if (performance > 0f)
{
color = Colors.interpolate(Colors.theme().defaultBackground().getRGB(),
Colors.HEATMAP_DARK_GREEN.getRGB(), (float) p);

// Adjusted transition to yellow earlier
if (p > 0.4)
{
// 0.4 to 1.0 to 0 to 1.0
float yellowFactor = (float) ((p - 0.4) / 0.6);
color = Colors.interpolate(color, Colors.YELLOW.getRGB(), yellowFactor);
}
}
else
{
// Using -p for correct mapping of negatives
color = Colors.interpolate(Colors.theme().defaultBackground().getRGB(), Colors.RED.getRGB(),
(float) -p);
}

return resourceManager.createColor(color);
var p = normalizePerformance(performance);

return new ColorGradient(//
Colors.RED, //
Colors.theme().defaultBackground(), //
Colors.HEATMAP_DARK_GREEN //
).getColorAt(p);
};

case GREEN_GRAY_RED -> performance -> {
// Performance interpolates between green (positive) and gray
// (neutral) or red (negative)
double p = Math.min(MAX_PERFORMANCE, Math.abs(performance)) / MAX_PERFORMANCE;
RGB color;

if (performance > 0)
color = Colors.interpolate(Colors.GRAY.getRGB(), Colors.HEATMAP_DARK_GREEN.getRGB(), (float) p);
else
color = Colors.interpolate(Colors.GRAY.getRGB(), Colors.RED.getRGB(), (float) p);

return resourceManager.createColor(color);
var p = normalizePerformance(performance);
return ColorGradient.RED_TO_GREEN.getColorAt(p);
};

case BLUE_GRAY_ORANGE -> performance -> {
// Performance interpolates between blue (stable) and gray
// (neutral) or orange (volatile)
double p = Math.min(MAX_PERFORMANCE, Math.abs(performance)) / MAX_PERFORMANCE;
RGB color;

if (performance > 0)
color = Colors.interpolate(Colors.GRAY.getRGB(), Colors.BLUE.getRGB(), (float) p);
else
color = Colors.interpolate(Colors.GRAY.getRGB(), Colors.HEATMAP_ORANGE.getRGB(), (float) p);

return resourceManager.createColor(color);
var p = normalizePerformance(performance);
return ColorGradient.ORANGE_TO_BLUE.getColorAt(p);
};

case YELLOW_WHITE_BLACK -> performance -> {
// Performance interpolates between yellow (moderate) and white
// (neutral) or black (extreme)
double p = normalizePerformance(performance);
RGB color;

if (performance > 0.05)
{
// Transition between yellow and black
if (p > 0.6)
{
// 0.6 to 1.0 to (0 to 1.0 buchen's normalization)
float blackFactor = (float) ((p - 0.6) / 0.4);
color = Colors.interpolate(Colors.YELLOW.getRGB(), Colors.BLACK.getRGB(), blackFactor);
}
else
{
color = Colors.interpolate(Colors.YELLOW.getRGB(), Colors.theme().defaultBackground().getRGB(), (float) p);
}
}
else
{
color = Colors.interpolate(Colors.theme().defaultBackground().getRGB(), Colors.YELLOW.getRGB(), (float) p);
}

return resourceManager.createColor(color);
var p = normalizePerformance(performance);

// cutover from yellow to black at +0.05 performance
var cutover = (0.07f + 0.05f) / 0.14f;

return new ColorGradient(//
new ColorPoint(Colors.theme().defaultBackground(), 0), //
new ColorPoint(Colors.YELLOW, cutover), //
new ColorPoint(Colors.getColor(91, 91, 0), cutover), //
new ColorPoint(Colors.BLACK, 1) //
).getColorAt(p);
};

default -> throw new IllegalArgumentException("Unsupported color schema: " + this); //$NON-NLS-1$
Expand All @@ -139,11 +89,11 @@ DoubleFunction<Color> buildColorFunction(ResourceManager resourceManager)
* the input performance value
* @return a normalized performance value between 0 and 1
*/
private double normalizePerformance(double performance)
private float normalizePerformance(double performance)
{
performance = Math.max(-MAX_PERFORMANCE, performance);
performance = Math.min(MAX_PERFORMANCE, performance);
return (performance + MAX_PERFORMANCE) / (2 * MAX_PERFORMANCE);
return (float) ((performance + MAX_PERFORMANCE) / (2 * MAX_PERFORMANCE));
}

@Override
Expand Down

0 comments on commit 7ea526e

Please sign in to comment.