Gerar gráfico com dados do banco

Já fiz uma pergunta parecida aqui, mas resolvi “voltar algumas casas” porque nao consegui sair do zero.

Qual a melhor maneira de gerar gráficos com dados saindo do banco? Pra trazer mais contexto, a ideia é que o usuário escolha quais colunas do banco quer no gráfico e que os dados tenham um limite de máx/min mostrados no gráfico.

Como voces recomendam fazer isso? O FreeChart é a melhor opcao? Há outras melhores?

jfreechart mesmo http://bethecoder.com/applications/tutorials/charts/jfreechart/jdbc-pie-chart.html

Pesquise no google sobre o tipo de gráfico que quiser.

1 curtida

Recomendo o JavaFx, tem uma documentação bem detalhada para implementar gráficos em um sistema.

O tutorial da Oracle é bem explicativo, didático, fácil. Dá uma verificada:

https://docs.oracle.com/javafx/2/charts/pie-chart.htm#CIHFDADD

https://docs.oracle.com/javafx/2/charts/jfxpub-charts.htm

2 curtidas

O JavaFX é bom, mas se você já fez o seu software em Swing não é necessário mudar. Eu também achava que o JFreeChart era bem feinho e que se eu usasse, só ia estragar o que tinha feito.

Mas depois que comecei a dar uma explorada melhor, vi que era uma ótima solução, a maioria dos exemplos, mostram algo bem básico e bem sem sal, só que dando uma pequena fuçada, você já consegue deixar o visual bem diferente:

http://zetcode.com/java/jfreechart/

1 curtida

EDIT Eu nao to em casa, por isso pode ter alguns erros no código, tem algumas coisas que eu lembro de cabeça, mas outras não. Mas não são coisas que vão atrapalhar o funcionamento, é mais inversão de algo da parte visual.


Tutorial do Código:

Vamos pegar um exemplo deles, o primeiro:

image

public class LineChartEx extends JFrame {

    public LineChartEx() {
        initUI();
    }

    private void initUI() {
        //Esse método é só um resumo de tudo, é onde
        //a gente adiciona o gráfico no painel.

        XYDataset dataset = createDataset(); 
        //XYDataset é o estilo do nosso gráfico,
        // por XY nós entendemos como: |_

        JFreeChart chart = createChart(dataset);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
        chartPanel.setBackground(Color.white);
        //ChartPanel é autoexplicativo, é o painel do gráfico, setamos o gráfico
        //nele, pra depois setar no JPanel normal.

        add(chartPanel);

        pack();
        setTitle("Line chart");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private XYDataset createDataset() {
        //Como eu falei acima, XY é o estilo do nosso gráfico
        //Aqui nós setamos os dados que vão ser inseridos.

        XYSeries series = new XYSeries("2016");
        //Esse "2016" eu nao sei explicar exatamente o nome correto
        //mas basicamente é assim:
        // vc ta usando a linha azul, vermelha e amarela.
        //ele vai separar tudo por categoria, cada categoria tem o seu dataset.
        //então resumindo é uma categoria kkkkkkk

        //Agora como você quer algo dinâmico, é só usar assim:
        
        //abre conexao
        conecta.conexao();
        
        //aqui vc adiciona os dados dinamicamente (atraves do banco):
        do{

        int idade = conecta.rs.getInt("idade");
        int jogadas = conecta.rs.getInt("jogadas ");

        series.add(idade, jogadas);
        } while(conecta.rs.next());
                   
        /* Aqui vc adiciona os dados manualmente:
        series.add(20, 612);
        series.add(25, 800);
        series.add(30, 980);
        series.add(40, 1410);
        series.add(50, 2350);
        */

        XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);

        //jogamos os dados pra um dataset que vai ser retornado
        //sempre que chamarmos o método.

        return dataset;
    }

    private JFreeChart createChart(XYDataset dataset) {
        //Aqui já começa a parte visual do nosso gráfico
        JFreeChart chart = ChartFactory.createXYLineChart(
                "Média de Jogadas",  //Titulo
                "Idade", //Aqui ficam os dados horizontais (X)
                "Jogadas", //Aqui ficam os dados verticais (Y)
                dataset, //aqui é aquele dataset que fizemos acima
                PlotOrientation.VERTICAL, //O estilo de como vai ficar os dados da horizontal. (Vertical, Inclinado....)
                true, //aqui eu nao lembro direito, mas acho que é: titulo
                true, //legenda
                false  //tooltip
        );

        //daqui pra baixo é só a parte visual, se vc souber ingles é autoexplicativo.
        XYPlot plot = chart.getXYPlot();

        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        renderer.setSeriesPaint(0, Color.RED); //cor da linha, nesse exemplo é vermelho
        renderer.setSeriesStroke(0, new BasicStroke(2.0f)); //grossura da linha

        plot.setRenderer(renderer);
        plot.setBackgroundPaint(Color.white); //Cor de fundo do display (a parte do grafico)

        //aqui são as linhas de fundo, verticais e horizontais
        //eu nao lembro direito qual é qual, mas se vc deixar como false
        //vc vai escondear a linha

        //acho que é vertical
        plot.setRangeGridlinesVisible(true); 
        plot.setRangeGridlinePaint(Color.BLACK); //cor da linha

        //e horizontal
        plot.setDomainGridlinesVisible(true);
        plot.setDomainGridlinePaint(Color.BLACK); //cor da linha

        chart.getLegend().setFrame(BlockBorder.NONE);

        chart.setTitle(new TextTitle("Média de Jogadas",
                        new Font("Serif", java.awt.Font.BOLD, 18) //aqui é a fonte do titulo
                )
        );

        return chart; //e aqui ele retorna o gráfico, que tudo vai ser aplicado lá no primeiro método. :D

    }

    public static void main(String[] args) {
        //e aqui a gente mostra o nosso frame funcionando com o gráfico.
        SwingUtilities.invokeLater(() -> {
            LineChartEx ex = new LineChartEx();
            ex.setVisible(true);
        });
    }
}
1 curtida

Quase ninguem usa JavaFx no mercado, nao caia nessa de mudar pra JavaFx.

2 curtidas

Show mano, clarificou muita coisa pra mim. Minha é ideia é fazer um gráfico da seguinte forma:

O principal é ter datas como base do gráfico e aí os dados que serao mostrados vao ser os dessas datas. Ex: No dia xx/xx/xxxx, a umidade foi de 10%. No dia xx/xx/xxx, 12%, etc, etc, etc. Minha ideia seria de que abaixo (eixo X) estivessem as datas e as linhas fossem os dados (que serao retirados das colunas do banco).

Também teriam vários dados (linhas) ao mesmo tempo. O banco tem 10 colunas e a ideia é que pelos menos a metade estivesse no gráfico. Ou seja, vários dados estariam sendo mostrados no gráfico.

Tenho dúvida a respeito de qual o melhor tipo de gráfico pra atender a essas “exigencias”. Acredito que o LineChart (XY) (igual esse do seu exemplo) seria a melhor opcao, mas nao sei se é possível colocar todas essas características. Tenho medo de estar quebrando a cabeca pra fazer algo que nao é possível. Tu sabe dizer se é possível fazer um gráfico assim? Já viu algo parecido? E a melhor maneira é um de YX mesmo?

Qual banco de dados vc usa?

Postgres.

Blz, eu entendi mais ou menos.

Você quer fazer isso? Só que com várias linhas?

1 curtida

Isso, o ideal seria ter a data completa embaixo (xx/xx/xxxx), mas é algo assim mesmo, com várias linhas.

Onde tu conseguiu esse exemplo? Ou tu fez?

Eu que fiz. Mas essa tabela é dinâmica, ou seja, ele pega conforme o mês atual.

Só tem um problema, eu ainda não consegui mostrar a data completa no gráfico, o método não permite, to pesquisando ainda.

Primeiro vc precisa criar uma função no Postgres

create function dias_do_mes(dias timestamptz=now())
returns table(diasdate) as $$
select d::date from generate_series(
    date_trunc('month',dias),
    date_trunc('month',dias) + '1 month' - '1 day'::interval,
    '1 day'
) as series(d);

Depois faz um join com a tabela q vc quer, vc vai precisar de algo na sua tabela tenha algo relacionado à data, não precisa ser do tipo Date.

SELECT dias, COUNT(qualquer_coluna) AS qtd
FROM dias_do_mes() 
LEFT JOIN sua_tabela on dias= to_date(sua_tabela.data, 'DD/MM/YYYY') 
GROUP BY dias_do_mes.dias ORDER BY dias ASC

Quando vc fizer essa query, ele vai retornar os dados de acordo com cada dia, dá uma tentada, quando conseguir eu passo o restante

1 curtida

Boa, vou tentar isso e volto aqui.

Mano, outra pergunta de novato pra vc: Como eu faco pra gerar esse gráfico através de um botao (“Gerar Gráfico”)?

Tenho uma tela que já tem algumas coisas e queria terminar ela adicionando o gráfico. Mas empaquei nessa de clicar no botao e gerar/mostrar o gráfico nessa tela.

Eu jogaria todo esse código dentro do botao ou apenas alguma(s) parte(s) específica(s)? O código sofre alteracoes pra entrar no botao? Porque eu tentei, tentei e nao saiu.

Coloca isso no final do código:

SwingUtilities.updateComponentTreeUI(this);
1 curtida

Abner, tu chegou a provar esse código? Pra mim sai uma NullPointerException. A única alteracao que fiz foi adicionar um try/catch no método createDataset. No código nao acusa nada mas quando eu rodo dá erro.

O método ficou assim:

    private XYDataset createDataset(){
    XYSeries series = new XYSeries("Teste");
    conex.conectar();
    
    try{
    do{
        double rcv = conex.rs.getDouble("rcv_arena_verde"); 
        double rth = conex.rs.getDouble("rth_arena_verde"); 
    series.add(rcv, rth);
    
    } while (conex.rs.next());
    }catch (SQLException ex){ 
        JOptionPane.showMessageDialog(null, "Erro ao preencher grafico \n " +ex.getMessage());
    }
    
    XYSeriesCollection dataset = new XYSeriesCollection();
    dataset.addSeries(series); 
    return dataset;
} 

A primeira linha que “acusa” no erro é a primeiro dentro do “do”:

double rcv = conex.rs.getDouble("rcv_arena_verde");

Sabe o que pode ser, cara? Algum problema na hora de chamar a conexao?

A vantagem de usar o grafico do javaFX é de ser mais rapido que no velho e bom swing.

Em relação a ser menos usado no mercado que o swing, isso não importa, o que importa é a qualidade do seu software.

Beleza, agora você aplica aquele SELECT q eu te passei acima, ou qualquer outro q vc quiser:

private XYDataset createDataset() {
        XYSeriesCollection dataset = null;
        try {
            conecta.conexao();
            conecta.executaSQL(SQL);

            XYSeries series = new XYSeries("");
            while (conecta.rs.next()) {
                double rcv = conex.rs.getDouble("rcv_arena_verde"); 
                double rth = conex.rs.getDouble("rth_arena_verde");
                series.add(rcv, rth);
            }

            dataset = new XYSeriesCollection();
            dataset.addSeries(series);
        } catch (SQLException ex) {
            Logger.getLogger(VisitanteDiario.class.getName()).log(Level.SEVERE, null, ex);
        }
        return dataset;
    }

Mano, eu fiquei com dúvidas no teu método “executaSQL”.

  1. Tu nao deveria “inicializar” (nao sei se o término é esse) ele antes assim…?

     private XYDataset createDataset(String SQL)
    
  2. Eu tenho uma classe na qual eu jogo os dados do banco pra uma tabela e faco dessa forma que te falei, mas ao tentar isso nessa classe do gráfico sai um erro “method createDataset in class GraficosArenaProcesso cannot be applied to given types” aqui:

     private void initUI(){
     XYDataset dataset = createDataset(); // LINHA ONDE ACUSA O ERRO
     JFreeChart chart = createChart(dataset);
    

E meu método createDataset tá assim:

     private XYDataset createDataset(String Sql){
    
    XYSeriesCollection dataset = null;
    
    conex.conectar();
    conex.executaSql(Sql);

Por que na classe da tabela ele roda tranquilo, mas na do gráfico dá esse erro?

Tem como tu postar teu método “executaSQL” pra eu tentar entender a diferenca?

Opa, vamos lá:

essa é minha classe de conexão, nela tem:

método para conectar ao banco de dados,
metodo para desconectar do banco e
método pra executar alguma query (SQL),

Segue:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 *
 * Connect to the database 
 *
 */
public class ConectaBanco {

    public Statement stm;
    public ResultSet rs;
    public Connection conn;
    private final String driver = "org.postgresql.Driver";
    private final String caminho = "jdbc:postgresql://localhost:5432/bancodedados";//
    private final String user = "usuario";
    private final String pass = "senha";

    /**
     * Method responsible for opening the connection to the database.
     * <br><br>
     * <b>Required:</b>
     * <br>
     * Yes. This method must be invoked whenever you perform an operation that
     * involves the database.
     * <br><br>
     * <b>Example:</b>
     * <br>
     * ConectaBanco conecta = new ConectaBanco();
     * <br>
     * conecta.conexao();
     *
     * @exception Error Database not exists, driver not configured correctly,
     * library was not added to the project
     */
    public void conexao() {
        try {
            System.setProperty("jdbc.Driver", driver);
            conn = DriverManager.getConnection(path, user, pass);
        } catch (SQLException e) {
            System.out.println("Error: " + e);
        }
    }

    /**
     * Method responsible for closing the connection to the database.
     * <br><br>
     * <b>Required:</b>
     * <br>
     * No. but it is advisable to close the connection every time you perform a
     * database operation.
     * <br><br>
     * <b>Example:</b>
     * <br>
     * ConectaBanco conecta = new ConectaBanco();
     * <br>
     * conecta.conexao();
     * <br>
     * ...
     * <br>
     * conecta.desconecta();
     *
     * @exception Error Connection not started.
     */
    public void desconecta() {
        try {
            conn.close();
        } catch (SQLException e) {
            System.out.println("Error: " + e);
        }
    }

    /**
     * Method is used to execute SELECT query. It returns the object of
     * ResultSet.
     *
     * @param SQL ex: "SELECT * FROM table"
     * <br><br>
     * <b>Example:</b>
     * <br>
     * ConectaBanco conecta = new ConectaBanco();
     * <br>
     * conecta.conexao();
     * <br>
     * conecta.executaSQL("SELECT * FROM table");
     * <br>
     * conecta.desconecta();
     *
     * @exception Error Connection not started.
     */
    public void executaSQL(String SQL) {
        try {
            stm = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
            rs = stm.executeQuery(SQL);
        } catch (Exception e) {
            System.out.println("Error: " + e);
        }
    }
}

Sobre a dúvida do método ser sobrecarregado

Isso pode ser uma personalização sua na verdade, eu não preciso disso, então a minha query é sempre uma só, sempre a mesma, entende? Mas se você quiser fazer algo diferente à cada clique, você pode fazer isso sim


Esse sql vai ser a QUERY que você precisa executar, a query seria algo mais ou menos assim:

SELECT * FROM suaTabela

A tabela vai ser a que tem as colunas que você citou ali em cima


Você está tentando chamar um método sobrecarregado, mas não está aplicando os atributos dentro dele. Ou está fazendo o contrário.

Se você quer fazer isso:

private XYDataset createDataset(String Sql){
    conex.executaSql(Sql);
}

precisa chamar

createDataset("SELECT * FROM tabela");

Agora se você quer fazer sem o atributo, é só tirar o String sql, e preencher a query direto no método:

private XYDataset createDataset(){
     conex.executaSql("SELECT * FROM suaTabela");
}

Olha um código que eu tenho, eu insiro ele dentro de um painel:

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.sql.SQLException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;

public class VisitanteDiario extends JFrame {

    public VisitanteDiario(JPanel panel) {
        initUI(panel);
    }

    private void initUI(JPanel panel) {
        XYDataset dataset = createDataset();
        JFreeChart chart = createChart(dataset);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
        chartPanel.setBackground(Color.white);
        panel.add(chartPanel, BorderLayout.CENTER);
    }

    private XYDataset createDataset() {
        XYSeriesCollection dataset = null;
        try {
            ConectaBanco conecta = new ConectaBanco();
            conecta.conexao();
            conecta.executaSQL("SELECT the_date, COUNT(nome) AS visita FROM dias_do_mes() LEFT JOIN visitantes on the_date = to_date(visitantes.dataentrada,'DD/MM/YYYY') GROUP BY dias_do_mes.the_date ORDER BY the_date ASC");

            XYSeries series = new XYSeries("");
            while (conecta.rs.next()) {
                int total = conecta.rs.getInt("visita");
                Date data = conecta.rs.getDate("the_date");
                series.add(data.getDate(), total);
            }

            dataset = new XYSeriesCollection();
            dataset.addSeries(series);
        } catch (SQLException ex) {
            Logger.getLogger(VisitanteDiario.class.getName()).log(Level.SEVERE, null, ex);
        }
        return dataset;
    }

    private JFreeChart createChart(XYDataset dataset) {

        JFreeChart chart = ChartFactory.createXYLineChart(
                "",
                "",
                "Qtd. de Visitas",
                dataset,
                PlotOrientation.VERTICAL,
                true,
                true,
                false
        );
        XYPlot plot = chart.getXYPlot();
        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        renderer.setSeriesPaint(0, new Color(77, 71, 221));
        renderer.setSeriesStroke(0, new BasicStroke(3.0f));
        plot.setRenderer(renderer);

        //Mudar fonte das informações laterais e rodapé
        Font font3 = new Font("Montserrat Medium", 0, 14);
        plot.getDomainAxis().setAutoRangeMinimumSize(20);
        plot.getDomainAxis().setLabelFont(font3);
        plot.getRangeAxis().setLabelFont(font3);
        //

        //Setando intervalo
//        plot.getDomainAxis().setAutoRangeMinimumSize(1);
//        plot.getDomainAxis().setAutoRange(false);

        NumberAxis xAxis = (NumberAxis) plot.getDomainAxis();
        xAxis.setTickUnit(new NumberTickUnit(1));
        //

        //Valor inteiro ao invés de float
        plot.getDomainAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        plot.getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        //

        //Trocar cor do bg e detalhes...
        plot.setBackgroundPaint(Color.white);
        plot.setRangeGridlinesVisible(true);
        plot.setRangeGridlinePaint(new Color(230, 230, 230));

        plot.setDomainGridlinesVisible(false);
        plot.setDomainGridlinePaint(new Color(230, 230, 230));
        plot.setOutlinePaint(null);
        //

        //configurações da legenda
        chart.getLegend().setFrame(BlockBorder.NONE);
        chart.setBorderVisible(false);
        chart.getLegend().setPosition(RectangleEdge.TOP);
        //

        return chart;
    }

}
1 curtida