Java Graphics Drawing Tool

Asked

Viewed 1,021 times

6

I wonder if you have any tools with Ireport that I can use to make a graphic similar to this image. I’ve used Jfreechart, but I couldn’t make it look like this! The bar values have to be at the bottom and the labels at the top depending on the image. gráfico de barras que eu quero

1 answer

10


I think maybe you have to draw the graphics in hand. I made a program that does this.

Here is the code:

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Victor
 */
public class GraficoDeBarras {

    // Formato é AARRGGBB, aonde A é o alpha, R é o vermelho, G é o verde e B é o azul.
    private static final int AZUL = 0xFF99D8EA;
    private static final int VERMELHO = 0xFFEA9999;

    private final int larguraRetangulo;
    private final int alturaRetangulo;
    private final int alturaTotal;
    private final int yBaseBarra;
    private final int xBaseBarra;
    private final int larguraBarra;
    private final int profundidade;
    private final int yRetangulo;
    private final int yRotulo;
    private final int yValor;

    public GraficoDeBarras(
            int larguraRetangulo,
            int alturaRetangulo,
            int alturaTotal,
            int yBase,
            int xBase,
            int larguraBarra,
            int profundidade,
            int yRetangulo,
            int yRotulo,
            int yValor)
    {
        this.larguraRetangulo = larguraRetangulo;
        this.alturaRetangulo = alturaRetangulo;
        this.alturaTotal = alturaTotal;
        this.yBaseBarra = yBase;
        this.xBaseBarra = xBase;
        this.larguraBarra = larguraBarra;
        this.profundidade = profundidade;
        this.yRetangulo = yRetangulo;
        this.yRotulo = yRotulo;
        this.yValor = yValor;
    }

    private static class Barra {
        private final int alturaBarra;
        private final int cor;
        private final String valor;
        private final String rotulo;

        public Barra(int alturaBarra, int cor, String valor, String rotulo) {
            this.alturaBarra = alturaBarra;
            this.cor = (cor & 0x00FFFFFF) | 0xFF000000; // Elimina a transparência.
            this.valor = valor;
            this.rotulo = rotulo;
        }

        public void desenhar(GraficoDeBarras grafico, int x, int y, Graphics2D g) {
            g.setColor(Color.BLACK);
            Color preenchimento = new Color(cor);

            // Desenha as bordas externas.
            int x1 = x;
            int x2 = x + grafico.larguraRetangulo - 1;
            int y1 = y + grafico.yRetangulo;
            int y2 = y + grafico.yRetangulo + grafico.alturaRetangulo - 1;
            g.drawLine(x1, y1, x2, y1);
            g.drawLine(x1, y1, x1, y2);
            g.drawLine(x2, y1, x2, y2);
            g.drawLine(x1, y2, x2, y2);

            // Prepara o desenho do paralelepípedo.
            int xBarra1 = x1 + grafico.xBaseBarra;
            int xBarra2 = x1 + grafico.xBaseBarra + grafico.larguraBarra;
            int yBarra1 = y1 + grafico.yBaseBarra;
            int yBarra2 = y1 + grafico.yBaseBarra - alturaBarra;

            // Se a altura for negativa, precisará inverter o yBarra2 e o yBarra1.
            if (yBarra2 > yBarra1) {
                int t = yBarra2;
                yBarra2 = yBarra1;
                yBarra1 = t;
            }

            // Desenha as bordas frontais do paralelepípedo.
            int[] xFrente = {xBarra1, xBarra1, xBarra2, xBarra2};
            int[] yFrente = {yBarra1, yBarra2, yBarra2, yBarra1};
            g.setColor(preenchimento);
            g.fillPolygon(xFrente, yFrente, 4);
            g.setColor(Color.BLACK);
            g.drawPolygon(xFrente, yFrente, 4);

            // Desenha as bordas laterais do paralelepípedo.
            int[] xLateral = {xBarra2, xBarra2, xBarra2 + grafico.profundidade, xBarra2 + grafico.profundidade};
            int[] yLateral = {yBarra1, yBarra2, yBarra2 - grafico.profundidade, yBarra1 - grafico.profundidade};
            g.setColor(preenchimento);
            g.fillPolygon(xLateral, yLateral, 4);
            g.setColor(Color.BLACK);
            g.drawPolygon(xLateral, yLateral, 4);

            // Desenha as bordas superiores do paralelepípedo.
            int[] xSuperior = {xBarra1, xBarra1 + grafico.profundidade, xBarra2 + grafico.profundidade, xBarra2};
            int[] ySuperior = {yBarra2, yBarra2 - grafico.profundidade, yBarra2 - grafico.profundidade, yBarra2};
            g.setColor(preenchimento);
            g.fillPolygon(xSuperior, ySuperior, 4);
            g.setColor(Color.BLACK);
            g.drawPolygon(xSuperior, ySuperior, 4);

            // Escreve o valor e o rótulo.
            FontMetrics fm = g.getFontMetrics();
            int larguraValor = fm.charsWidth(valor.toCharArray(), 0, valor.length());
            int larguraRotulo = fm.charsWidth(rotulo.toCharArray(), 0, rotulo.length());
            g.drawString(valor, x + (grafico.larguraRetangulo - larguraValor) / 2, y + grafico.yValor);
            g.drawString(rotulo, x + (grafico.larguraRetangulo - larguraRotulo) / 2, y + grafico.yRotulo);
        }
    }

    private BufferedImage desenharBarras(List<Barra> barras) {
        BufferedImage bi = new BufferedImage((larguraRetangulo - 1) * barras.size() + 1, alturaTotal, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        int x = 0;
        for (Barra b : barras) {
            b.desenhar(this, x, 0, g);
            x += larguraRetangulo - 1;
        }
        return bi;
    }

    public static final class Dado {
        private final int valor;
        private final String rotulo;

        public Dado(int valor, String rotulo) {
            this.valor = valor;
            this.rotulo = rotulo;
        }

        public int getValor() {
            return valor;
        }

        public String getRotulo() {
            return rotulo;
        }
    }

    public static BufferedImage desenhar(int alturaTotalDasBarras, List<Dado> dados) {
        if (dados == null || dados.isEmpty()) {
            throw new IllegalArgumentException("Apenas uma lista de dados não-vazia pode ter um gráfico gerado.");
        }
        if (alturaTotalDasBarras < 2) {
            throw new IllegalArgumentException("A altura total das barras deve ser de no mínimo 2 pixels.");
        }

        // Define detalhes fixos das dimensões do gráfico.
        int larguraBarra = 40;
        int profundidade = 10;
        int margem = 10;
        int yRetangulo = 20;

        // Encontra o maior e o menor valor e o tamanho da faixa de valores. Sempre considerando que 0 está dentro desta faixa.
        int max = 0;
        int min = 0;
        for (Dado d : dados) {
            int valor = d.getValor();
            if (valor > max) max = valor;
            if (valor < min) min = valor;
        }
        int variacao = Math.max(1, max - min); // min nunca será positivo. Se todos os números forem zeros, não queremos dividir por zero.

        // Agora que "variacao" foi calculada, percorre a lista novamente para calcular a altura de cada barra em pixels.
        List<Barra> lista = new ArrayList<Barra>(dados.size());
        for (Dado d : dados) {
            int valor = d.getValor();
            int altura = (int) (((double) valor) * alturaTotalDasBarras / variacao);
            Barra barra = new Barra(altura, valor >= 0 ? AZUL : VERMELHO, String.valueOf(d.getValor()), d.getRotulo());
            lista.add(barra);
        }

        // Recalcula os tamanhos das barras em pixels ao invés dos valores brutos.
        int maxPixels = (int) (((double) max) * alturaTotalDasBarras / variacao);
        int minPixels = (int) (((double) min) * alturaTotalDasBarras / variacao);
        int variacaoPixels = Math.max(1, maxPixels - minPixels); // min nunca será positivo.

        // Calcula os demais detalhes das dimensões do gráfico.
        int larguraRetangulo = larguraBarra + profundidade + 2 * margem;
        int alturaRetangulo = variacaoPixels + profundidade + 2 * margem;
        int alturaTotal = alturaRetangulo + 40;
        int yBase = alturaRetangulo + minPixels - margem;
        int yRotulo = yRetangulo - 4;
        int yValor = alturaTotal - 4;

        // Cria e desenha o gráfico.
        GraficoDeBarras g = new GraficoDeBarras(larguraRetangulo, alturaRetangulo, alturaTotal, yBase, margem, larguraBarra, profundidade, yRetangulo, yRotulo, yValor);
        return g.desenharBarras(lista);
    }

    public static BufferedImage desenharTamanhoFixo(int larguraTotal, int alturaTotal, List<Dado> dados) {
        if (dados == null || dados.isEmpty()) {
            throw new IllegalArgumentException("Apenas uma lista de dados não-vazia pode ter um gráfico gerado.");
        }

        // Define detalhes fixos das dimensões do gráfico.
        int profundidade = 10;
        int margem = 10;
        int yRetangulo = 20;

        // Calcula detalhes sobre a altura e largura.
        int alturaRetangulo = alturaTotal - 40;
        int alturaTotalDasBarras = alturaRetangulo - profundidade - 2 * margem;
        if (alturaTotalDasBarras <= 1) throw new IllegalArgumentException("A altura da imagem é insuficiente.");
        int larguraRetangulo = 1 + ((larguraTotal - 1) / dados.size());
        int larguraBarra = larguraRetangulo - profundidade - 2 * margem;
        if (larguraBarra <= 1) throw new IllegalArgumentException("A largura da imagem é insuficiente.");

        // Encontra o maior e o menor valor e o tamanho da faixa de valores. Sempre considerando que 0 está dentro desta faixa.
        int max = 0;
        int min = 0;
        for (Dado d : dados) {
            int valor = d.getValor();
            if (valor > max) max = valor;
            if (valor < min) min = valor;
        }
        int variacao = Math.max(1, max - min); // min nunca será positivo. Se todos os números forem zeros, não queremos dividir por zero.

        // Agora que "variacao" foi calculada, percorre a lista novamente para calcular a altura de cada barra em pixels.
        List<Barra> lista = new ArrayList<Barra>(dados.size());
        for (Dado d : dados) {
            int valor = d.getValor();
            int altura = (int) (((double) valor) * alturaTotalDasBarras / variacao);
            Barra barra = new Barra(altura, valor >= 0 ? AZUL : VERMELHO, String.valueOf(d.getValor()), d.getRotulo());
            lista.add(barra);
        }

        // Recalcula os tamanhos das barras em pixels ao invés dos valores brutos.
        int minPixels = (int) (((double) min) * alturaTotalDasBarras / variacao);

        // Calcula os demais detalhes das dimensões do gráfico.
        int yBase = alturaRetangulo + minPixels - margem;
        int yRotulo = yRetangulo - 4;
        int yValor = alturaTotal - 4;

        // Cria e desenha o gráfico.
        GraficoDeBarras g = new GraficoDeBarras(larguraRetangulo, alturaRetangulo, alturaTotal, yBase, margem, larguraBarra, profundidade, yRetangulo, yRotulo, yValor);
        return g.desenharBarras(lista);
    }
}

To test I made this class:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * @author Victor
 */
public class FazGrafico {

    public static void main(String[] args) {
        List<GraficoDeBarras.Dado> dados = Arrays.asList(
                new GraficoDeBarras.Dado(5, "Janeiro"),
                new GraficoDeBarras.Dado(8, "Fevereiro"),
                new GraficoDeBarras.Dado(3, "Março"),
                new GraficoDeBarras.Dado(0, "Abril"),
                new GraficoDeBarras.Dado(14, "Maio"),
                new GraficoDeBarras.Dado(-5, "Junho"));
        BufferedImage imagem = GraficoDeBarras.desenharTamanhoFixo(500, 180, dados);
        //BufferedImage imagem = GraficoDeBarras.desenhar(100, dados);
        // Java 8: EventQueue.invokeLater(() -> mostraImagem(imagem));
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                mostraImagem(imagem);
            }
        });
    }

    private static void mostraImagem(BufferedImage imagem) {
        JFrame jf = new JFrame("Teste do gráfico");
        JPanel jp = new JPanel() {
            @Override
            public void paintComponent(Graphics g) {
                g.drawImage(imagem, 0, 0, null);
            }
        };
        jf.add(jp, BorderLayout.CENTER);
        jp.setPreferredSize(new Dimension(imagem.getWidth(), imagem.getHeight()));
        jf.setResizable(false);
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        jf.setLocation((screen.width - jf.getWidth()) / 2, (screen.height - jf.getHeight()) / 2);
        jf.setVisible(true);
    }
}

And here are some of the generated graphics:

This is using GraficoDeBarras.desenhar(100, dados) with the values {5, 8, 3, 0, 14}:

gráfico 1

This is using GraficoDeBarras.desenhar(100, dados) with the values {5, 8, 3, 0, 14, -5}:

gráfico 2

This is using GraficoDeBarras.desenharTamanhoFixo(500, 180, dados) with the values {5, 8, 3, 0, 14, -5}:

gráfico 3

In the method desenhar, the first parameter is the maximum height the front face of a bar can have. The other values needed to calculate the size of the image, as well as the size and position of its elements are based on this.

In the method desenharTamanhoFixo, the first parameter is the total width of the image and the second is the total height. The other values needed to calculate the size and position of your elements are based on this.

In both methods, the bars have 10 pixels of depth and a margin of 10 pixels for the edges of the rectangle on the right, left, below and above. There is also a margin of 20 pixels above and below the rectangles where the values and labels will be placed, which each has its own baseline 4 pixels above the lower edge of the respective range. In the case of the method desenhar, the front face of the bar is always 40 pixels wide. The colors used for the positives are blue 0xFF99D8EA (RGB = (153, 216, 234)) and red for negatives 0xFFEA9999 (RGB = (234, 153, 153)). Changing this should not be difficult or laborious.

Also, the code is quite flexible for you to put the variations you want, as evidenced in the class constructor GraficoDeBarras. You should have no great difficulty changing the width and depth of the bars, the size of the margins between the bars and the edges of the rectangle that contains them, the width and height of these rectangles, the colors, the positions of the text, and obviously the values and labels, among other possibilities.

  • Hi, Victor, I appreciate your help. I wonder which version of java you are using, because it is giving error in this part that has these values: 0xF_99_D8_EA : 0xF_EA_99_99

  • Victor has a way of making the labels spacing?

  • @Renatoromão I am using Java 8. From Java 7 it is allowed to use _ in numeric literals. Example: 1_234_567 instead of 1234567. For Java 6 or lower you will have to remove the _ (stays 0xFF99D8EA : 0xFFEA9999). Besides, where are you ArrayList<> only works from Java 7 up, in java 6 should be ArrayList<Barra>. Finally, in the sample program, I am using EventQueue.invokeLater(() -> mostraImagem(imagem));, which in Java 7 and below will have to use the anonymous class: EventQueue.invokeLater(new Runnable() { @Override public void run() { mostraImagem(imagem); } });.

  • @Renatoromão What exactly do you mean by "label spacing?"

  • 1

    @Renatoromão I changed the code to be compatible with Java 6. I also put some comments to explain 0xFF99D8EA and 0xFFEA9999.

  • Leave labels centered along with bars. Type stay in the same position as bars.

  • Victor is to leave the labels centered to stay in the same position as the bars

  • @Renatoromão Implemento. I centralized the labels and updated the figures. :)

  • Vitor thanks, I’m already getting to do what I needed, helped me a lot with this code. A duvia I can leave a fixed size on the image type Width and height is because I will use it in the ireport passing an image.

  • @Renatoromão Calm down I’m trying to fix this...

  • Vitor I can already do, I make him draw the outer edges, but not draw the bars. That’s just what I needed. Now I can make 15 columns even without appearing the bars.

  • @Renatoromão Still got it, see the method desenharTamanhoFixo. I also took the opportunity to improve a little the organization of the code and add more comments.

  • Victor thank you for your attention. This code has helped me a lot...

  • @Renatoromão Valeu, it was even fun to develop it. :)

Show 9 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.