1

I have a JTable, let say 100x100, of integers. I normalized this matrix to make corresponding value from 0 to 1

Now I need to make a heat map (like we do conditional formatting in excel)/ Example figure enter image description here

This is created by using 3 way color scaling with middle and maximum set to percentile 50 and 90.

Now, I am trying to do generate the same in JTable, and I am not seeing similar color gradient.

enter image description here

I need to replicate as much smoothness in the color as I can get. My code is

int[][] color = { { 99, 190, 123 }, { 255, 235, 132 }, { 248, 105, 107 } };
    int idx1 = 0;
    int idx2 = 0;
    ArrayList<Integer> rgbList = new ArrayList<Integer>();

    if (value == 0.0)
    {
        idx1 = 0;
        idx2 = 0;
    }
    else if (value < fiftyPercentile)
    {
        idx1 = 0;
        idx2 = 1;
    }
    else if (value > nintyPercentile)
    {
        idx1 = 2;
        idx2 = 2;
    }
    else
    {
        idx1 = 1;
        idx2 = 2;
    }


    double r = ((color[idx2][0] - color[idx1][0]) * value + color[idx1][0]);
    double g = ((color[idx2][1] - color[idx1][1]) * value + color[idx1][1]);
    double b = ((color[idx2][2] - color[idx1][2]) * value + color[idx1][2]);

    rgbList.add((int) (r));
    rgbList.add((int) (g));
    rgbList.add((int) (b));
    return rgbList;
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
user1631306
  • 4,350
  • 8
  • 39
  • 74

2 Answers2

4

I need to replicate as much smoothness in the color as I can get.

Use Color.getHSBColor() to get a palette of N equally spaced hues, as shown here.

private List<Color> clut = new ArrayList<>(N); // color lookup table
…
for (int i = 0; i < n; i++) {
    clut.add(Color.getHSBColor((float) i / N, 1, 1));
}

You can limit the spectrum of hues, as shown here; because your palette goes from green through yellow to red, you'll want to invert 0.0 (red) to 0.33… (green). You can use them in your TableCellRenderer, as shown here for alternating colors and here for a palette of brightness values. The variation of @aterai's example below illustrates the effect for an N–color heat map that goes from green through yellow and orange to red.

List<Color> palette = new ArrayList<>(N);
…
float gHue = 1 / 3f;
for (int i = 0; i < N; i++) {
    palette.add(Color.getHSBColor(gHue - ( i * gHue / N), 0.5f, 1));
}

image

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

/**
 * @see https://stackoverflow.com/a/44557115/230513
 * @see https://stackoverflow.com/a/44559171/230513
 */
public class TableTest {

    private static final int N = 100;

    public JComponent makeUI() {
        String[] columnNames = {"1", "2"};
        Object[][] data = {
            {0d, 1d}, {.5, .6}, {.66, .77},
            {.85, .89}, {.78, .99}, {.95, .88}
        };
        DefaultTableModel model = new DefaultTableModel(data, columnNames) {
            @Override
            public Class<?> getColumnClass(int column) {
                return Double.class;
            }
        };
        Random r = new Random();
        double d = r.nextDouble();
        for (int i = 0; i < 100; i++) {
            model.addRow(new Double[]{r.nextDouble(), r.nextDouble()});
        }
        JTable table = new JTable(model);
        List<Color> palette = makeHSBPalette();
        table.setDefaultRenderer(Double.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(
                JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
                super.getTableCellRendererComponent(
                    table, value, isSelected, hasFocus, row, column);
                if (value instanceof Double) {
                    setBackground(getColor(palette, (Double) value));
                }
                return this;
            }
        });
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        return p;
    }

    private static List<Color> makeHSBPalette() {
        List<Color> palette = new ArrayList<>(N);
        float gHue = 1 / 3f;
        for (int i = 0; i < N; i++) {
            palette.add(Color.getHSBColor(gHue - ( i * gHue / N), 0.5f, 1));
        }
        return palette;
    }

    private static Color getColor(List<Color> palette, double v) {
        if (v < 0f || v > 1f) {
            throw new IllegalArgumentException("Parameter outside of expected range");
        }
        return palette.get((int) (Math.min(v * N, N - 1)));
    }

    public static void main(String... args) {
        EventQueue.invokeLater(() -> {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            f.getContentPane().add(new TableTest().makeUI());
            f.setSize(320, 240);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I think I didnt get it. is there something I can do in my algorithm to make this working. I got this algorithm from http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients – user1631306 Jun 15 '17 at 14:05
  • Oh, I though you wanted a smoother palette; set `N = 100` and use the 0th, 50th and 90th colors for values in the corresponding percentile. – trashgod Jun 15 '17 at 19:14
4

Another option is to use a LinearGradientPaint and PixelGrabber:

private static int[] makeGradientPallet() {
    BufferedImage image = new BufferedImage(100, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2  = image.createGraphics();
    Point start    = new Point(0, 0);
    Point end      = new Point(99, 0);
    float[] dist   = {0.5f, 0.9f, 1.0f};
    Color[] colors = {new Color(99, 190, 123),
                      new Color(255, 235, 132),
                      new Color(248, 105, 107)};
    g2.setPaint(new LinearGradientPaint(start, end, dist, colors));
    g2.fillRect(0, 0, 100, 1);
    g2.dispose();

    int width = image.getWidth(null);
    int[] pallet = new int[width];
    PixelGrabber pg = new PixelGrabber(image, 0, 0, width, 1, pallet, 0, width);
    try {
        pg.grabPixels();
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    return pallet;
}

Screenshot

enter image description here

TableTest.java

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.*;

public class TableTest {
  public JComponent makeUI() {
    String[] columnNames = {"1", "2"};
      Object[][] data = {
          {0d, 1d}, {.5, .6}, {.66, .77},
          {.85, .89}, {.78, .99}, {.95, .88}
      };
    DefaultTableModel model = new DefaultTableModel(data, columnNames) {
      @Override public Class<?> getColumnClass(int column) {
        return Double.class;
      }
    };
    Random r = new Random();
    double d = r.nextDouble();
    for (int i = 0; i < 100; i++) {
      model.addRow(new Double[] {r.nextDouble(), r.nextDouble()});
    }
    JTable table = new JTable(model);
    int[] pallet = makeGradientPallet();
    table.setDefaultRenderer(Double.class, new DefaultTableCellRenderer() {
      @Override public Component getTableCellRendererComponent(
          JTable table, Object value, boolean isSelected,
          boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(
          table, value, isSelected, hasFocus, row, column);
        if (value instanceof Double) {
          Color bgc = getColorFromPallet(pallet, (Double) value);
          setBackground(bgc);
        }
        return this;
      }
    });
    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(table));
    return p;
  }
  private static int[] makeGradientPallet() {
    BufferedImage image = new BufferedImage(100, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2  = image.createGraphics();
    Point start    = new Point(0, 0);
    Point end      = new Point(99, 0);
    float[] dist   = {0.5f, 0.9f, 1.0f};
    Color[] colors = {
        new Color(99, 190, 123),
        new Color(255, 235, 132),
        new Color(248, 105, 107)
    };
    g2.setPaint(new LinearGradientPaint(start, end, dist, colors));
    g2.fillRect(0, 0, 100, 1);
    g2.dispose();

    int width = image.getWidth(null);
    int[] pallet = new int[width];
    PixelGrabber pg = new PixelGrabber(image, 0, 0, width, 1, pallet, 0, width);
    try {
      pg.grabPixels();
    } catch (InterruptedException ex) {
      ex.printStackTrace();
    }
    return pallet;
  }
  private static Color getColorFromPallet(int[] pallet, double v) {
    if (v < 0f || v > 1f) {
      throw new IllegalArgumentException("Parameter outside of expected range");
    }
    int i = (int)(pallet.length * v);
    int max = pallet.length - 1;
    int index = Math.min(Math.max(i, 0), max);
    return new Color(pallet[index]);
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new TableTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
  • Not sure, how this would help me getting different colors according to the values in the Jtable – user1631306 Jun 15 '17 at 14:06
  • Excellent! Am I correct to infer that this avoids having to invert the palette for green-yellow-red? – trashgod Jun 15 '17 at 19:09
  • @trashgod Thanks for the response. Now I added a example, but I'm not sure I understand the intentions of the questioner correctly... – aterai Jun 16 '17 at 12:39
  • @user1631306: I think aterai's (previously up-voted) approach is superior to mine for your use case; please don't hesitate to accept this answer. – trashgod Jun 16 '17 at 15:59
  • @user1631306: I agree that aterai's approach is superior to mine for your use case; for reference, I've adapted an N–color variation [here](https://stackoverflow.com/a/44557115/230513). – trashgod Jun 17 '17 at 09:45