【问题标题】:Set Color dynamically based on value Java Swing根据值 Java Swing 动态设置颜色
【发布时间】:2014-03-11 18:23:55
【问题描述】:

我正在使用 Java Swing。我想根据我计算的双精度值显示颜色。

编辑 - 我需要填充 Path2D 对象的颜色。目前,我就是这样做的

Path2D shape;
// some code here
g2d.setColor(Color.Red);
g2d.fill(shape);

现在我不希望将颜色固定为Color.Red,但需要根据我计算的值进行设置。双精度值可以是负数或正数。值越负,颜色应该越深。颜色不必是红色。我该怎么做?

【问题讨论】:

  • 你的范围是多少?最终,颜色会根据您移动的方向变为黑色或白色......
  • @MadProgrammer 我们可以做一些近似映射,因为我的值是在运行时动态生成的 - 所以我无法获得确切的最大/最小值。在计算出每个新值后,它们将不断变化。

标签: java swing colors


【解决方案1】:

作为建议hereTableCellRenderer 方法的具体示例,实现Icon 接口,如here 所示。不要改变大小,而是使用 Color.getHSBColor() 根据不同的饱和度或亮度选择颜色,如图所示 here

附录:与example cited 一样,下面的渲染假定数据值在区间[0, 1) 内进行了标准化。您需要改为扩展到数据模型的最小值和最大值。如果模型经常更新,每次添加时更新这些值可能是值得的。

/**
 * @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;
    }
}

【讨论】:

  • 如果您想实现这些相同的图像(单元格中的圆圈),但仅根据不取决于单元格当前文本的其他值绘制圆圈?
  • @219CID:根据您的程序设计,TableModel 可能会访问所需的值。 getTableCellRendererComponent() 的渲染器实现将在调用时看到此类的实例。
  • 如果您访问paintIcon 中的tablemodel,您如何确定当前正在绘制哪个单元格?除了当前单元格的 x 和 y 坐标吗?
  • @219CID:正如hereAPI 中所建议的那样,value 参数是“要呈现的单元格的值”。检查类型,强制转换并调用您的访问器。
【解决方案2】:

您必须知道double 值的可能范围。 (至少,这会使事情更容易理解)。在任何情况下,您都可以将 double 值转换为 [0,1] 范围内。无论如何,在许多情况下进行这种规范化是有益的。

所以你可以创建一个方法

private static double normalize(double min, double max, double value) {
    return (value - min) / (max - min);
}

此方法会将最小值和最大值之间的任何“值”转换为 [0,1] 中的值。为了将此归一化值映射到颜色,您可以使用

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);
}

此方法会将 0.0 到 1.0 之间的值转换为颜色:0.0 为黑色,1.0 为红色。

所以假设你在“min”和“max”之间有一个双“值”,你可以将它映射到这样的颜色:

g2d.setColor(colorFor(normalize(min, max, value)));
g2d.fill(shape);

编辑:回复评论:我认为基本上有两种选择:

  • 您可以持续跟踪您目前遇到的当前最小值/最大值。
  • 或者,您可以使用 sigmoidal 函数将 [-Infinity,+Infinity] 的区间映射到区间 [0,1]

第一个选项的缺点是先前与某种颜色相关联的值可能突然具有不同的颜色。例如,假设您将区间 [0,1] 映射到颜色范围 [black, red]。现在您获得了一个新的最大值,例如 100000。然后您必须调整您的范围,值 0 和 1 将不再有视觉差异:它们都将映射到“黑色”。

第二个选项的缺点是初始值的微小差异不会对颜色产生明显影响,具体取决于这些差异发生的范围。例如,您将无法在此处看到 10000 和 10001 之间的差异。

所以你必须清楚你想要什么颜色映射和行为。

但是,这里有一个例子,它使用sigmoidal函数将[-Infinity,+Infinity]的区间映射到区间[0,1],并将这个区间映射到一个颜色:

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);
    }    
}

【讨论】:

  • 我们可以做一些近似映射,因为我的值是在运行时动态生成的 - 所以我无法获得最大/最小值。在计算出每个新值后,它们将不断变化。
  • @Dinesh 添加了一个编辑。也许你必须澄清你的问题描述。
【解决方案3】:

问题缺乏上下文。所有 Swing 组件都有setBackground 方法,可以改变不透明组件的背景。

如果要更改JTable 单元格或JListJTableJComboBox 的背景颜色,则需要提供能够执行此任务的自定义单元格渲染器,在这种情况下你应该看看...

根据 OP 问题的更新更新了示例

以下是颜色混合算法的示例,可让您指定要使用的颜色以及它们应该以什么比例/比例出现。这个例子很简单,它只使用了三种颜色均匀分布在整个范围内,但您可以根据需要添加更多颜色或调整权重。

该示例采用一系列随机生成的值并将它们归一化,因此当值趋于 0(或较低的正常范围)时,它会变暗,当它接近 1 时,它会变亮。

如果您愿意,您可以更改算法以分别标准化正值和负值。

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;
    }

}

您可以在以下位置查看正在使用的这种颜色混合算法:

【讨论】:

  • @Dinesh:你选好渲染器了吗?
猜你喜欢
  • 2013-01-03
  • 1970-01-01
  • 2011-12-27
  • 2014-07-25
  • 2023-03-04
  • 2018-07-14
  • 2019-05-09
  • 2015-07-17
  • 1970-01-01
相关资源
最近更新 更多