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?
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:
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:
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);
});
}
}
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?
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
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.
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”:
Mano, eu fiquei com dúvidas no teu método “executaSQL”.
Tu nao deveria “inicializar” (nao sei se o término é esse) ele antes assim…?
private XYDataset createDataset(String SQL)
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);
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.