I am using Java Swing. I would like to display colors based on a double values which I compute.
Edit - I need to fill the color of a Path2D
object. Cur
As a concrete example of the TableCellRenderer
approach suggested here, implement the Icon
interface, as shown here. Instead of varying the size, choose a color based on varying saturation or brightness using Color.getHSBColor()
, as shown here.
Addendum: Like the example cited, the render below assumes that the data values are normalized in the interval [0, 1)
. You'll need to scale to your data model's minimum and maximum values instead. If the model is updated frequently, it may be worth updating these values with each addition.
/**
* @see https://stackoverflow.com/a/21756629/230513
* @see https://stackoverflow.com/a/2834484/230513
*/
private static class DecRenderer extends DefaultTableCellRenderer implements Icon {
private static final int N = 256;
private static final int SIZE = 32;
private static List<Color> clut = new ArrayList<>(N);
private DecimalFormat df;
public DecRenderer(DecimalFormat df) {
this.df = df;
this.setIcon(this);
this.setHorizontalAlignment(JLabel.RIGHT);
this.setBackground(Color.lightGray);
for (float i = 0; i < N; i++) {
clut.add(Color.getHSBColor(1, 1, i / N));
}
}
@Override
protected void setValue(Object value) {
setText((value == null) ? "" : df.format(value));
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double v = Double.valueOf(this.getText());
final int i = (int) (v * N - 1);
g2d.setColor(clut.get(i));
g2d.fillOval(x, y, SIZE, SIZE);
}
@Override
public int getIconWidth() {
return SIZE;
}
@Override
public int getIconHeight() {
return SIZE;
}
}
The question lacks context. All Swing components have the setBackground
method which can change the background of opaque components.
If you want to change the background color of a JTable
cell or a JList
or a JTable
or JComboBox
, then you need to provide a custom cell renderer capable of perfoming this task, in which case you should have a look at...
Updated with example, based on updates to the OP's question
The following is an example of color blending algorithm that allows you to specify the colors you want to use and at what ratio/fraction they should appear. The example is very simply, it uses only three colors spread evenly across the range, but you could put more colors in or adjust the weight according to your requirements.
The example takes a series of randomly generated values and normalises them, so that as the value tends towards 0 (or the lower normal range) it will get darker, as it approaches 1, it will get lighter.
You could change the algorithm to normalise positive and negative values separately if you chose.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ColorBars {
public static void main(String[] args) {
new ColorBars();
}
public ColorBars() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private PlotPane plotPane;
private JSpinner valueAdd;
public TestPane() {
setLayout(new BorderLayout());
plotPane = new PlotPane();
add(plotPane);
}
}
public class PlotPane extends JPanel {
private Color[] colorRange = new Color[]{Color.BLACK, Color.RED, Color.WHITE};
private float[] ratioRanges = new float[]{0f, 0.5f, 1f};
// private Color maxColor = Color.WHITE;
// private Color minColor = Color.RED;
private List<Double> values;
private double min = Double.MAX_VALUE;
private double max = Double.MIN_VALUE;
public PlotPane() {
values = new ArrayList<>(25);
Random rnd = new Random();
for (int index = 0; index < 10; index++) {
addValue((rnd.nextDouble() * 2000) - 1000);
}
}
public void addValue(double value) {
max = Math.max(max, value);
min = Math.min(min, value);
values.add(value);
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
int height = getHeight();
int width = getWidth();
int barWidth = width / values.size();
int x = 0;
for (Double value : values) {
double norm = value - min;
norm /= (max - min);
int barHeight = (int) (height * norm);
System.out.println(NumberFormat.getInstance().format(norm));
Color color = blendColors(ratioRanges, colorRange, (float)norm);
g2d.setColor(color);
g2d.fillRect(x, height - barHeight, barWidth, barHeight);
x += barWidth;
}
g2d.dispose();
}
}
public static Color blendColors(float[] fractions, Color[] colors, float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(fractions, progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
public static int[] getFractionIndicies(float[] fractions, float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
public static Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
You can see this color blend algorithm in use at:
You have to know the possible range of the double
values. (At least, this would make things easier to understand). In any case, you can transform your double
value to be in the range [0,1]. And it's in many cases beneficial to do such a kind of normalization anyhow.
So you can create a method
private static double normalize(double min, double max, double value) {
return (value - min) / (max - min);
}
This method will convert any "value" between min and max to a value in [0,1]. In order to map this normalized value to a color, you can use
private static Color colorFor(double value) {
value = Math.max(0, Math.min(1, value));
int red = (int)(value * 255);
return new Color(red,0,0);
}
This method will convert a value between 0.0 and 1.0 into a color: 0.0 will be black, and 1.0 will be red.
So assuming you have a double "value" between "min" and "max", you can map it to a color like this:
g2d.setColor(colorFor(normalize(min, max, value)));
g2d.fill(shape);
EDIT: Reply to the comment: I think there are basically two options:
The first option has the disadvantage that values that have previously been associated with a certain color may suddenly have a different color. For example assume that you mapped the interval [0,1] to the color range [black, red]. Now you obtain a new maximum value like 100000. Then you have to adjust your range, the values 0 and 1 will no longer have a visual difference: They will both be mapped to 'black'.
The second option has the disadvantage that small differences in the initial values will not have a noticable effect on the color, depending on the range in which these differences occur. For example, you will not be able to see a difference between 10000 and 10001 there.
So you have to be clear about what color mapping and behavior you want.
However, here is an example that uses a sigmoidal function to map the interval of [-Infinity,+Infinity] to the interval [0,1], and this interval to a color:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
class ColorRange
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ColorRangePanel());
f.setSize(1000, 200);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ColorRangePanel extends JPanel
{
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
double values[] = {
-1e3, -1e2, -1e1, -1, -0.5, 0.0, 0.5, 1, 1e1, 1e2, 1e3
};
for (int i=0; i<values.length; i++)
{
double value = values[i];
int w = getWidth() / values.length;
int x = i * w;
g.setColor(Color.BLACK);
g.drawString(String.valueOf(value), x, 20);
g.setColor(colorFor(value));
g.fillRect(x, 50, w, 100);
}
}
private static Color colorFor(double value)
{
double v0 = value / Math.sqrt(1 + value * value); // -1...1
double v1 = (1 + v0) * 0.5; // 0...1
return colorForRange(v1);
}
private static Color colorForRange(double value)
{
value = Math.max(0, Math.min(1, value));
int red = (int)(value * 255);
return new Color(red,0,0);
}
}