Desenvolvimento

9 jan, 2019

Gerando gráficos em Excel com Java usando Apache POI

Publicidade

Olá, caros leitores!

Este é o meu primeiro artigo no portal. Venho compartilhar algumas soluções técnicas que demorei pra encontrar em alguns cenários bem específicos.

Acredito que grande parte dos desenvolvedores Java já conhecem a API de documentos Microsoft Apache POI e já fizeram uso dela para gerar relatórios simples. Hoje venho apresentar uma das funcionalidades que a biblioteca também oferece, que é a criação de gráficos.

Para a exemplificação, criei um projeto maven com o spring boot, instalando a dependência WEB, um arquivo index.html para realizar a requisição do arquivo e um controlador responsável por gerar o arquivo, conforme a estrutura definida na imagem abaixo:

A versão escolhida do POI foi a 3.17 (suportada por versões mais antigas do Java) e para que a solução funcione, é preciso importar duas bibliotecas no arquivo pom.xml conforme abaixo:

<dependency>
 <groupId>org.apache.poi</groupId>
 <artifactId>poi</artifactId>
 <version>3.17</version>
</dependency>
<dependency>
 <groupId>org.apache.poi</groupId>
 <artifactId>poi-ooxml</artifactId>
 <version>3.17</version>
</dependency>

Nesta versão do POI temos algumas limitações sobre criar gráficos em um nível alto de representação (modo simples). Então, são “suportados” pelo apache poi, nesta versão, apenas gráficos de linhas e de dispersão, mas isso não quer dizer que só se pode gerar esses dois tipos de gráficos.

O POI oferece para os outros tipos presentes no Excel, a biblioteca poi-ooxml-schemas, que disponibiliza objetos subjacentes baseados no xml gerado por arquivos xlsx.

Então, caso precise gerar outros tipos de gráficos na versão 3.17, será um pouco mais trabalhoso, mas não impossível (disponibilizarei posteriormente artigos ensinando a gerar outros tipos de gráficos nessa versão do poi).

Hoje já temos a versão 4.0, que oferece, em um nível alto de representação, suporte para outros tipos de gráficos. Porém, esta versão só pode ser utilizada em ambientes cuja versão mínima do Java é a 8 (assuntos para os próximos artigos).

Para que o código não ficasse muito extenso, utilizei uma planilha pronta como template, que exibe os gastos em determinadas categorias do primeiro semestre do ano de 2018. Nela, lemos os dados e geramos o gráfico do tipo linha conforme a imagem abaixo:

Os passos para gerar gráficos são simples – utilizamos classes e interfaces definidas nos pacotes.

· org.apache.poi.ss.usermodel
· org.apache.poi.xssf.usermodel
· org.apache.poi.ss.util

O principal é deixar bem claro o mapeamento dos dados dentro da planilha, para que ao selecionar as células que gerarão o gráfico, não ocorra erros (você só saberá se ocorreu erro de seleção de dados após abrir o arquivo gerado).

O código do controlador que gera o arquivo está abaixo, contendo comentários sobre os passos para criar o gráfico.

package com.teste.poi.testeExcel.controller;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.poi.ss.usermodel.charts.AxisPosition;
import org.apache.poi.ss.usermodel.charts.ChartAxis;
import org.apache.poi.ss.usermodel.charts.ChartDataSource;
import org.apache.poi.ss.usermodel.charts.DataSources;
import org.apache.poi.ss.usermodel.charts.LegendPosition;
import org.apache.poi.ss.usermodel.charts.LineChartData;
import org.apache.poi.ss.usermodel.charts.LineChartSeries;
import org.apache.poi.ss.usermodel.charts.ValueAxis;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.charts.XSSFChartLegend;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("excel")
public class ExcelController {

    @GetMapping("/grafico")
    public ResponseEntity<String> exportExcelGrafico(HttpServletResponse response){

        try{

            InputStream input = ExcelController.class.getResourceAsStream("/templates/gastos.xlsx");

            XSSFWorkbook book = getExcelFile(input);

            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition",
                    "attachment; filename=\""
                            + "Gerando Gráfico excel"
                            + ".xlsx\"");

            book.write(response.getOutputStream());
            return new ResponseEntity<String>(HttpStatus.OK);
        }
        catch(Exception e){
            return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        
    }

    public XSSFWorkbook getExcelFile(InputStream fileTemplate) throws Exception{

        try{
            XSSFWorkbook book = new XSSFWorkbook(fileTemplate);
            XSSFSheet abaSheet = book.getSheet("gastos");
            int numRows = abaSheet.getPhysicalNumberOfRows();

            XSSFDrawing designer = abaSheet.createDrawingPatriarch();
            // criamos a posição do gráfico
            XSSFClientAnchor position = designer.createAnchor(0, 0, 0, 0, 0, numRows+1, 12, numRows +12);
            XSSFChart graphic = designer.createChart(position);
            // aqui definimos aonde serão exibidos as categorias dos gastos
            XSSFChartLegend legend = graphic.getOrCreateLegend();
            legend.setPosition(LegendPosition.BOTTOM);

            // aqui definimos o título que é apresentado dentro do gráfico
            graphic.setTitleText("Gastos do primeiro semestre de 2018");

            // aqui criamos o tipo do gráfico, no caso um gráfico de linha
            LineChartData data = graphic.getChartDataFactory().createLineChartData();

            // aqui criamos o eixo y  que exibirá os valores minímos e máximos na horizontal
            ValueAxis yAxis = graphic.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);

            // aqui criamos o eixo x que exibirá os meses do semestre  para marcar os valores no gráfico
            //  de cada categoria 
            ChartAxis xAxis = graphic.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
            yAxis.setMinimum(0);
        
            /*
                Aqui  criamos uma referência para  as categorias através da posição de cada uma delas.
                Como o nome does meses estão presentes na primeira linha da planilha e temos apenas 6 meses,
                definimos um range da linha 0  começando da coluna 1 , até a 6 coluna
            */
            ChartDataSource<String> columns = DataSources.fromStringCellRange(abaSheet, new CellRangeAddress(0,0,1,6));

            /*
                Agora iremos , através do contador i utilizado para percorrer as lnnhas,
                 selecionar as linhas das categorias com os dados de cada mes.
            */
            for(int i = 1 ; i < numRows; i++){
                // aqui  criamos referência aos dados de cada linha da categoria de gastos
                // novamente utilizamos um range de colunas de 1 a 6 , pois é onde os gastos de cadas mes estão
                ChartDataSource<Number> valueLinhas = DataSources.fromNumericCellRange(abaSheet, new CellRangeAddress(i,i,1,6));
                
                /*
                    Nesta linha adicionamos os valores da categoria de gastos , relacionados com os meses definidos
                    na variável columns, e atribuímos a váriavel série uma referência a categoria de dados que acabou de 
                    ser colocada nos dados do gráfico
                */
                LineChartSeries serie = data.addSeries(columns, valueLinhas);
                /*
                 * Aqui criamos referência a célula que contém o título da categoria de gastos e atríbuimos a varíavel de referência 'serie'
                 * Observação : utilizamos a string 'gastos' porque é o nome da aba em que estamos trabalhando,
                 *  se colocarmos nomes nas abas do arquivo , é necessário que criemos a referência da célula,
                 *  com o nome da aba em que ela se encontra
                 */
                CellReference titleSerie = new CellReference("gastos",i,0,false,false);
                serie.setTitle(titleSerie);
            }

            /*            
                E   por último atribuímos a váriavel gráphic todads as definições de dados do gráfico
                com os eixos definidos.
            */
            graphic.plot(data, new ChartAxis[] { xAxis , yAxis });

            return book;
        }catch(IOException ex){
            System.out.print("Erro ao carregar arquivo :" + ex.getMessage());
            throw ex;
        }     
    }
}

Temos o resultado como na imagem abaixo:

Como podem ver, não é muito difícil e também não é muito utilizado em vários cenários. Contudo, talvez um dia seja necessário.

Tendo em vista que a versão 4.0 do poi foi lançada no dia 07/09/2018, e foi retirado suporte para o Java 6 e 7, muitas aplicações que utilizam a biblioteca ainda não foram atualizados, ou – por restrição do Java – não podem atualizar.

Espero que tenha ajudado!