Gerar gráficos em PHP com o Google Charts

Artigo que mostra exemplos de como gerar gráficos em PHP de forma fácil utilizando o webservice do Google Charts.

Exemplo de Gráfico de Torta
Introdução

Em 2011, escrevi o artigo "Criando Gráficos com o Google Chart e PHP", que mostrava como gerar gráficos através da passagem de parâmetros para URL de geração de gráficos provida pelo Google. Porém, este recurso foi depreciado em abril de 2012 e aparentemente renomeado para "Image Charts" (uma seção do Google Charts).

Neste artigo, veremos o funcionamento do novo Google Charts e quais suas vantagens e desvantagens em relação ao recurso depreciado.


As limitações do Image Charts

O "Image Charts", citado no artigo de 2011, funcionava através da montagem de uma URL com os parâmetros necessários para se montar o gráfico. Esta URL era usada diretamente no atributo src de uma tag <img>. Esta URL apontava para um web service do Google, que recebia os parâmetros, gerava a imagem e transferia para o usuário.

Esta estratégia, embora muito prática, tinha alguns problemas/limitações:

  • Para gerar gráficos com muitos dados, a URL gerada poderia ficar muito grande e ultrapassar o tamanho limite de URL. Consequentemente o gráfico não seria gerado.
  • O gráfico era plotado em um arquivo binário, no formato de imagem png. Isso significa que o custo para envio da imagem para o cliente era relativamente alto.
  • Por serem arquivos estáticos, os gráficos não proviam interação nativa com o usuário (por exemplo, parar o mouse sobre uma barra de um gráfico de barras e ver alguma informação extra).

As características do novo Google Charts

O funcionamento do novo Google Charts se difere daquele provido no "Image Charts". Agora, o gráfico é gerado dinamicamente no cliente, via JavaScript. Basicamente, o que você precisa fazer é:

  1. Carregar a biblioteca básica do Google Charts via JavaScript;
  2. Montar uma função JavaScript que utiliza a biblioteca para carregar os recursos necessários, preencher os dados do gráfico e plotá-lo em algum elemento do documento HTML;
  3. Agendar a execução da função assim que a biblioteca básica seja carregada.

Bem, agora vamos aos pontos positivos desta nova abordagem:

  • A biblioteca básica do Google Charts tem cerca de 6 KB, ou seja, é muito leve. Além disso, ela fica armazenada em cache no navegador, o que significa que ela não precisa ser carregada sempre pelo navegador.
  • Os recursos necessários para gerar o gráfico são carregados sob demanda. Alguns chegam a ter mais de 100 KB, que é relativamente alto, embora também sejam guardados em cache no navegador, não sendo necessário carregamento sempre.
  • O gráfico é gerado diretamente no documento HTML, possibilitando interações com o usuário, e é muito mais leve que uma imagem em um arquivo binário.
  • A geração do gráfico é processada no próprio navegador do cliente, não sendo necessária a transferência de grandes volumes de dados ou muito processamento no lado do servidor.
  • A biblioteca oferece diferentes formas de se informar os dados usados para montar o gráfico (por exemplo, através de chamada de métodos ou através de um pacote de dados JSON).

O principal inconveniente que encontrei foi que não existe uma forma nativa de salvar a imagem do gráfico em um arquivo binário (para arquivamento, por exemplo). Por outro lado, existem soluções de terceiros que convertem o SVG gerado pela biblioteca em uma imagem usando o canvas do HTML5.

Além disso, esta nova estratégia requer JavaScript habilitado no navegador. Ou seja, não provê alternativas para acessibilidade da informação nativamente, embora isso possa ser feito por opção do desenvolvedor (por exemplo, apresentando os dados do gráfico em uma tabela HTML, que provê os dados de forma acessível, e gerar o gráfico dinamicamente, através de consultas aos dados da tabela).


Utilização básica do Google Charts

Segue um exemplo de utilização do Google Charts para geração de um gráfico de torta (ou pizza) que mostra a quantidade de alunos por gênero em uma turma:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <title>Exemplo de gráfico</title>

    <!-- Carregar a API do google -->
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>

    <!-- Preparar a geracao do grafico -->
    <script type="text/javascript">

      // Carregar a API de visualizacao e os pacotes necessarios.
      google.load('visualization', '1.0', {'packages':['corechart']});

      // Especificar um callback para ser executado quando a API for carregada.
      google.setOnLoadCallback(desenharGrafico);

      /**
       * Funcao que preenche os dados do grafico
       */
      function desenharGrafico() {
        // Montar os dados usados pelo grafico
        var dados = new google.visualization.DataTable();
        dados.addColumn('string', 'Gênero');
        dados.addColumn('number', 'Quantidades');
        dados.addRows([
          ['Masculino', 14],
          ['Feminino', 20]
        ]);

        // Configuracoes do grafico
        var config = {
            'title':'Quantidade de alunos por gênero',
            'width':400,
            'height':300
        };

        // Instanciar o objeto de geracao de graficos de pizza,
        // informando o elemento HTML onde o grafico sera desenhado.
        var chart = new google.visualization.PieChart(document.getElementById('area_grafico'));

        // Desenhar o grafico (usando os dados e as configuracoes criadas)
        chart.draw(dados, config);
      }
    </script>
  </head>

  <body>
    <div id="area_grafico"></div>
  </body>
</html>

Embora, a primeira vista, o código possa parecer complexo, isso vai se amenizando conforme há um entendimento sobre cada etapa.

Carregar a API do Google, carregar a API de visualização com os pacotes necessários, e registrar a função callback são passos fundamentais para a geração dos gráficos. O seu trabalho maior ficará na implementação da função que pode ser quebrada em 3 partes básicas: (1) montagem dos dados usados para geração do gráfico; (2) montagem das configurações do gráfico; e (3) renderização do gráfico.

A montagem dos dados usados pelo gráfico pode ser feita de forma estática (como no exemplo), mas também pode ser feita de forma dinâmica. Por exemplo, consultando os dados do gráfico via Ajax (onde um servidor recebe a requisição e devolve os dados via JSON), ou consultando, via JavaScript, os dados de uma tabela HTML que possui os dados necessários para gerar o gráfico. Além disso, o próprio código JavaScript pode ser gerado dinamicamente por uma linguagem Web, como o PHP, embora seja uma solução que precisa de algumas medidas de segurança.

Veja um exemplo em que os dados são gerados em JSON via PHP no servidor (a partir de uma consulta num banco de dados) e é carregado via Ajax para renderização do gráfico:

Arquivo getDadosGrafico.php
<?php

// Estrutura basica do grafico
$grafico = array(
    'dados' => array(
        'cols' => array(
            array('type' => 'string', 'label' => 'Gênero'),
            array('type' => 'number', 'label' => 'Quantidade')
        ),  
        'rows' => array()
    ),
    'config' => array(
        'title' => 'Quantidade de alunos por gênero',
        'width' => 400,
        'height' => 300
    )
);

// Consultar dados no BD
$pdo = new PDO('mysql:host=localhost;dbname=teste', 'usuario', 'senha');
$sql = 'SELECT genero, COUNT(*) as quantidade FROM alunos GROUP BY genero';
$stmt = $pdo->query($sql);
while ($obj = $stmt->fetchObject()) {
    $grafico['dados']['rows'][] = array('c' => array(
        array('v' => $obj->genero),
        array('v' => (int)$obj->quantidade)
    ));
}

// Enviar dados na forma de JSON
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($grafico);
exit(0);
Arquivo teste.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <title>Exemplo de gráfico</title>

    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript" src="jquery.js"></script>

    <script type="text/javascript">
      google.load('visualization', '1.0', {'packages':['corechart']});
      google.setOnLoadCallback(function(){
        var json_text = $.ajax({url: "getDadosGrafico.php", dataType:"json", async: false}).responseText;
        var json = eval("(" + json_text + ")");
        var dados = new google.visualization.DataTable(json.dados);

        var chart = new google.visualization.PieChart(document.getElementById('area_grafico'));
        chart.draw(dados, json.config);
      });
    </script>
  </head>

  <body>
    <div id="area_grafico"></div>
  </body>
</html>

Note que, neste exemplo, utilizei a biblioteca jQuery para fazer a requisição Ajax e que, ao invés de declarar uma função para ser usada como callback, declarei a função no momento em que a passava como parâmetro (closure). Isso deu uma bela reduzida no código JavaScript. Por outro lado, a lógica para montar os dados usados pelo gráfico ficou no lado do servidor com PHP (o que é o mais convencional).

Observe, ainda, que coloquei tanto os dados do gráfico quanto suas configurações no mesmo pacote JSON, mas, no código JavaScript, usei eles separadamente onde foram necessários.

Para conhecer como é a notação do JSON esperado pela função DataTable, as possíveis configurações e outros tipos de gráfico, consulte a API do Google Charts. Este artigo teve como objetivo apenas apresentar o assunto com alguns exemplos básicos, mas a biblioteca possui vários outros recursos interessantes para serem explorados.

79 comentários

Alexander disse...

Excelente post.
Não sei se tu pode me ajudar, estou fritando a mente tentando fazer o seguinte: Quero gerar gráficos com o Google Charts ou qualquer outro com base em arquivos .json que na verdade, são os logs que minhas aplicações geram. Porém não sei como importar os .json no DataTable() e se quer se isso é possível, ou seja, se o Google Charts identifica de algum modo o padrão que o arquivo tem e gera um gráfico ou se existe como criar um padrão de linhas e colunas e quando eu importo o .json o Charts vai preenchendo conforme os campos se assemelham.
Ex: tenho nos arquivos .json o campo IP, queria saber se é possível fazer o Charts identificar este campo e criar uma linha ou coluna com os todos dados que pertencem a IP.
Espero ter me explicado direito..
Obrigado desde já.

Rubens Takiguti Ribeiro (autor do blog) disse...

Alexander, creio que não exista algo para identificar padrões no seu JSON. Você até pode fazer uma função genérica que converte o seu padrão para o padrão do Google Charts, mas, ainda assim, precisaria passar parâmetros extras (por exemplo, o título do gráfico e seu tamanho).
Essa conversão pode ser feita tanto do lado do servidor quanto do cliente. Para mais detalhes sobre como trabalhar com JSON no PHP ou no JavaScript, sugiro a leitura do artigo:
http://rubsphp.blogspot.com.br/2011/02/json-javascript-object-notation.html

Unknown disse...

Rubens, antes de mais nada excelente post, tenho uma dúvida eu trabalho em uma empresa que fabrica estação climática, com medidor de Co² e sensor Luz PAR, e esta estação recebe dados, gostaria de saber se voce tem alguma dica, onde eu possa estar criando um aplicativo algo do tipo, onde posso acessar os dados da estação através de celular ipad, ou algo do tipo..

Rubens Takiguti Ribeiro (autor do blog) disse...

Alexsandro, não entendi direito o objetivo do aplicativo. Você disse que trabalha numa empresa que "fabrica estação climática"? Como assim? Estação climática não é o local onde são feitas as medições? Ou existe alguma aparelho chamado "estação climática"? (não sou da área e não faço ideia sobre isso).

A questão é que se é um local que faz medições ou se é um aparelho, os dados medidos devem ser acessíveis de alguma forma. Se você quer criar um aplicativo para celular, uma alternativa seria fazer um web service e o celular faz as requisições ao serviço desejado e mostra para o usuário de forma amigável. O processamento mais pesado ficaria no lado do servidor, que realiza de fato a medição (ou a consulta a uma medição existente) e devolve apenas o resultado para o cliente via web.

Gisele Brugger disse...

Olá Rubens,
Parabéns pelo post.
Consegui gerar os gráficos como queria num relatório com html e css porém agora ainda preciso exportar esse relatório para PDF utilizando o TCPDF.

Os gráficos só consigo se utilizar o método antigo (via url) assim por exemplo:

$pdf->Image("http://chart.googleapis.com/chart?cht=r&chxt=y,x&chls=4&chco=E26F1E&chs=580x510&chts=000000,20&chxr=0,0.0,100.0&chd=t:10,20,30,40,50,60,70&chtt=Self+Assesment+Results&chxp=0,0,20,40,60,80,100&chxs=0,000000,12|1,000000,12&chxl=1:|Storage+/+Handling|Dispensing|Contamination+Control|Oil+Analysis|Oil+Sampling|Training+/+Certification&chm=s,E26F1E,0,-1,12,0|s,FFFFFF,0,-1,8,0", 90, 90, 120, 0, 'PNG');

tem alguma ideia de como poderei exportar esses gráficos no formato novo do google chart?

Obrigada


Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Gisele
Para incorporar uma imagem em um PDF, você precisa da imagem em um formato binário, salva no servidor (nem que seja temporariamente). A forma como foi arquitetada a nova solução do Google Charts não propicia isso, afinal, ela tem o foco na geração de gráficos para serem incorporados dinamicamente em páginas Web, no lado do cliente.

Se o propósito da geração do PDF é simplesmente impressão, há uma alternativa de baixo custo, que é oferecendo um botão para o usuário imprimir a própria página Web. Para ajustar estilos de formatação, você pode usar um CSS específico para impressão (@media print). Ou então, usar algum plugin no navegador para "imprimir para PDF".

A solução mais hardcore (e que soa como gambiarra, já que é um procedimento de contorno de uma limitação da arquitetura da solução) seria tentar gerar o arquivo de imagem binário no próprio servidor. Para isso, minha sugestão seria assim:

1 - Gerar o gráfico com o Google Charts no lado do cliente (o gráfico é gerado em SVG, que é um formato de imagem vetorial, baseado em XML, portanto é texto, e que é incorporado diretamente ao HTML).
2 - Depois de gerar o gráfico, você faz uma rotina em JavaScript que:
a - Pega o conteúdo do SVG gerado (de forma textual).
b - Faz uma requisição Ajax mandando o conteúdo do SVG, de forma textual, para um script PHP.
c - O script PHP recebe o SVG e chama alguma library para converter de SVG para um formato binário, aceito pelo PDF (por exemplo PNG ou JPG).
3 - Quando for gerar o PDF, usa a imagem binária.

Rubens Takiguti Ribeiro (autor do blog) disse...

Gisele,

Fiz um experimento aqui no meu computador e funcionou. Adicionei o seguinte código JavaScript após chamar o método "draw" do Google Charts:

// Obter SVG e envia-lo para um PHP
var svg = $("#area_grafico svg")[0].outerHTML;
jQuery.post("http://localhost/salvar_grafico.php", {"svg": svg});

(Note que usei o jQuery para fazer a requisição em Ajax para montar o SVG gerado e mandei para um http://localhost/salvar_grafico.php)

No arquivo salvar_grafico.php, coloquei apenas o seguinte código (troquei < por [[ e > por ]] para poder postar aqui nos comentários):

require_once('svglib/svglib.php');

// Criando arquivo SVG temporario
$arq = tempnam('tmp/', 'tmp');
file_put_contents($arq, "[[?xml version=\"1.0\" ?]]\n" . $_POST['svg']);

// Convertendo de SVG para PNG
$svg = SVGDocument::getInstance($arq);
$svg->export('tmp/saida.png');

// Apagando arquivo SVG temporario
unlink($arq);
exit(0);

Note que usei a library PHPSVG (que usa o Imagick) para converter de SVN para PNG, e salvei num diretório tmp, que tinha permissão de escrita para o Apache.

Baixei o PHPSVG por aqui: http://code.google.com/p/phpsvg/

Unknown disse...

ola rubens, muito bom o material.

estou com um problema, ao gerar o grafico aparece um erro, "nenhum dados"

pode me ajudar?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Lucas
Você utilizou algum dos exemplos do artigo? Se usou o primeiro exemplo, deveria funcionar normalmente. Se usou o segundo, precisa do PHP configurado. Você pode imprimir o valor da variável json_text para saber se o JSON chegou corretamente na requisição em Ajax.

Unknown disse...

Olá Rubens, atualizei o php.ini para extension=php_pdo_mysql.dll, extension=php_pdo.dll
estou falando do segundo exemplo.

echo json_encode($grafico);

{"dados":{"cols":[{"type":"string","label":"Genero"},{"type":"number","label":"Quantidade"}],"rows":[]},"config":{"title":"Quantidade de alunos por genero","width":400,"height":300}}

Rubens Takiguti Ribeiro (autor do blog) disse...

Lucas,

Como o JSON está sendo gerado corretamente no lado do PHP, então experimente ver o que chega no lado do JavaScript.

Inclua uma linha como esta no JavaScript, após a chamada do ajax:

window.alert(json_text);

Unknown disse...

Rubens, no lado do JavaScript recebe o valor idêntico ao lado PHP, no caso, creio que o erro seja em "rows":[], onde deveria conter os dados do gráfico correto? como no exemplo 1.

agradeço pela atenção, e espero que possa me ajudar nesse dilema... rsrs

Rubens Takiguti Ribeiro (autor do blog) disse...

Lucas, acabo de ver um erro no meu código. No segundo exemplo fiz uma consulta e ao invés de colocar o resultado dentro do $grafico, só coloquei dentro de $linha, que ia pra lugar nenhum. Por isso o "rows" que fica dentro do $grafico estava vazio. Já vou corrigir.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Lucas
Na verdade não pretendo colocar mais exemplos do Google Charts. O objetivo do artigo era exemplificar o básico, para quem está começando pegar o jeito. O ideal, a partir daí, é acessar a própria documentação do Google Charts e checar os parâmetros para cada tipo de gráfico e fazer os testes.

Carlos Telles disse...

Olá Rubens,
Estou com um problema aqui em que o sigo seu segundo exemplo, porém pelo que verifiquei não está imprimindo nada na função json_encode quando chega na parte do código echo json_encode($grafico);
Porém teste colocando, por exemplo, echo json_encode($grafico['dados']['rows']); e imprime um vetor.
Na parte do javascript não dá erros, porém o gráfico não aparece. Já realizei também o teste do window.alert e não imprime nada no alert.
Pode me ajudar?
Obrigado!

Carlos Telles disse...

Rubens, realmente eu tinha meu cabecalho declarado da seguinte maneira:
header('Content-Type: application/json; charset=ISO-8859-1');
Porém alterando para UTF-8 continua com o mesmo problema, simplesmente não imprime nada.

Segue meu código php:
// Estrutura basica do grafico
$grafico['dados']['cols'][] = array(
'dados' => array(
'cols' => array(
array('type' => 'string', 'label' => 'PERÍODO'),
array('type' => 'number', 'label' => 'ENTRADAS'),
array('type' => 'number', 'label' => 'SAÍDAS'),
array('type' => 'number', 'label' => 'OUTRAS')
),
'rows' => array()
),
'config' => array(
'title' => 'Quantidade x Período x Tipo',
'width' => 400,
'height' => 300
)
);

// Consultar dados no BD

//inclui arquivo de conexao
include '../classes/Conexao.class.php';
//objetos
$conexao = new Conexao;
$conn = $conexao->conn();
//recebe variaveis
$unidade = 3;//$_POST['unidade'];
$usuario = 1;//$_POST['usuario'];
//Comando que realiza consulta no banco
$sql = "exec sp_justnew_erp_xml_pesquisa ";
$sql .= "@operacao = 42";
$sql .= ",@idunidade = '{$unidade}'";
$sql .= ",@idusuario = '{$usuario}'";
$sql .= ",@asp = ' '";
$sql .= ",@session = '0'";
$query = sqlsrv_query($conn, $sql);

if( $query === false ) {
echo $sql;
die( print_r( sqlsrv_errors(), true));
} else{
//echo $sql;
while($resultado = sqlsrv_fetch_object($query)){
$grafico['dados']['rows'][] = array('c' => array(
array('v' => $resultado->periodo),
array('v' => (int)$resultado->entradas_total),
array('v' => (int)$resultado->saidas_total),
array('v' => (int)$resultado->outras_total)
));
}
}

// Enviar dados na forma de JSON
header('Content-Type: application/json; charset=ISO-8859-1');

echo json_encode($grafico);
exit(0);

E aqui está o código javascript:
google.load('visualization', '1.0', {'packages':['corechart']});
google.setOnLoadCallback(function(){
var json_text = $.ajax({url: "ajax/grafico_xml_ajax.php", dataType:"json", async: false}).responseText;
window.alert(json_text);
var json = eval("(" + json_text + ")");
var dados = new google.visualization.DataTable(json.dados);

var chart = new google.visualization.ColumnChart(document.getElementById('grafico_xml'));
chart.draw(dados, json.config);
});

Uso php v5.5. ;)

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Carlos

Ao modificar o header do html, não significa que as variáveis mudarão de codificação. O header só serve para informar para o navegador qual é a codificação que está o texto do seu documento. Ou seja, se o texto é ISO e você fala que é UTF-8, vão ser exibidos de forma incorreta pelo navegador.

Para modificar a codificação de uma string de ISO-8859-1 para UTF-8, você pode usar a função utf8_encode:

$string_utf = utf8_encode($string_iso);

Recomendo a leitura destes artigos para entender melhor:
http://rubsphp.blogspot.com.br/2010/10/unicode.html
http://rubsphp.blogspot.com.br/2011/07/problemas-com-charset-nunca-mais.html

Carlos Telles disse...

Olha Rubens, fiz tudo isso aí e nada. Mas acredito que o problema não esteja no codificação, pois como disse anteriormente, se mando imprimir echo json_encode($grafico['dados']['rows']); ele imprime corretamente. O problema é quando mando imprimir echo json_encode($grafico);
Será que não é na forma como estou montando o array? Apesar de ter feito igual a seu exemplo...

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Carlos

Se conseguiu fazer o json_encode sobre o rows, então o conteúdo do rows está ok. Se não está conseguindo sobre o $grafico, então deve ser por causa de alguma string no $grafico que está com codificação errada.

Agora que vi que você usou acentos no "cols". Então precisa usar o utf8_encode sobre as strings. Por exemplo:
Mudar de:
array('type' => 'string', 'label' => 'PERÍODO'),
Para:
array('type' => 'string', 'label' => utf8_encode('PERÍODO')),

Ao invés de "echo", use o var_dump para depurar, que verá que ele está retornando NULL por causa do erro durante a conversão para JSON.

Se você estiver usando o PHP 5.5, então também pode ver o resultado da função json_last_error_msg, que mostra o último erro durante a conversão para JSON.

No exemplo que passei, o arquivo PHP precisa ser UTF-8. Se você estiver fazendo isso em um sistema existente e ele já é ISO-8859-1, pode ser um pouco mais complicado migrar tudo para UTF-8, então é melhor usar o utf8_encode mesmo.

Gustavo Silveira disse...

Amigo,
Agradeço muito pelo post. Sou iniciante em programação e ainda "sofro" bastante pra entender algumas coisas. Eu consegui fazer funcionar, etc... Porém, caso eu queira fazer um dashboard e cruzar diferentes dados de tabelas diferentes, como eu faria? Por exemplo, cruzar 3 ou mais informações diferentes. Se for possível explicar em cima do seu exemplo e utilizando a classe PDO, seria excelente.
Desde já, muito obrigado pela ajuda.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Gustavo
Sua pergunta parece estar mais relacionada à obtenção dos dados em um banco de dados. Para isso, precisa se estudar sobre como fazer queries, utilizar JOIN, etc.
Note que você não precisa fazer mil malabarismos para que uma única query resolva tudo e já devolva os dados necessários para o gráfico. Aliás, são coisas bem diferentes: uma etapa é coletar os dados (model), que não necessariamente precisam estar em um banco de dados, e outra etapa é a passagem destes dados para serem plotados (view).
Pesquise sobre queries e, se tiver algum caso mais específico que queira saber, fique a vontade para perguntar.

Segue um exemplo de query envolvendo 2 tabelas
SELECT
p.tipo, COUNT(p.id_projeto) as total
FROM
projetos p INNER JOIN usuarios u ON (p.id_usuario = u.id_usuario)
WHERE
u.situacao = 1
GROUP BY
p.tipo

Ela consulta a quantidade de projetos por tipo, mas apenas os projetos de usuários com situação 1.

Gustavo Silveira disse...

Rubens, muito obrigado pela resposta. Vou estudar o link e tentar implementar aqui!
Já consegui avançar no meu projeto, partindo de suas explicações. Porém, estou quebrando a cabeça para colocar estilo nas colunas do ColumnChart.
No seu exemplo você coloca título e medidas através do próprio php por meio da variável 'config'. Eu compreendi que posso desativar essa variável 'config' e utilizar uma variável 'options' direto no javascript de exibição,por exemplo. Porém, gostaria de manter o controle de estilos no arquivo php, assim como você fez, mas por lá eu não consigo alterar as cores e introduzir estilos como borda, opacity, etc nas colunas.
Caso não seja possível manter esse controle no php, como faço para fazer essa alteração através do javaScript, porém, ainda puxando os dados do Banco de Dados via php pdo?

Gustavo Silveira disse...

Por favor, se puder, me diga o que estou fazendo de errado nesse código, minha intenção é criar comparações anuais do desempenho de vendas x custos:

$grafico = array(
'dados' => array(
'cols' => array(
array('type' => 'string', 'label' => 'Ano'),
array('type' => number', 'label' => 'Vendas'),
array('type' => 'number', 'label' => 'Custos')
),
'rows' => array()
),
'config' => array(
'width' => 600,
'height' => 330
)
);

// Consultar dados no BD
include 'conexao.php';
$sql = 'SELECT ano, vendas, custos COUNT(*) as ano, vendas, custos FROM books GROUP BY ano';
$stmt = $pdo->query($sql);
while ($obj = $stmt->fetchObject()) {
$grafico['dados']['rows'][] = array('c' => array(
array('v' => $obj->ano),
array('v' => (int)$obj->ano),
array('v' => $obj->vendas),
array('v' => (int)$obj->vendas),
array('v' => $obj->custo),
array('v' => (int)$obj->custo)
));
}


// Enviar dados na forma de JSON
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($grafico);
exit(0);

Rubens Takiguti Ribeiro (autor do blog) disse...

Gustavo, parece que você entendeu bem a ideia. Para formatação de cores, acho que você já deve ter visto isso:
https://google-developers.appspot.com/chart/interactive/docs/gallery/columnchart#Colors

Aparentemente a mudança é simples.

Ao invés de passar os dados para o JavaScript e consumí-lo com isso:
google.visualization.DataTable(json.dados);

Você consome com isso:
google.visualization.arrayToDataTable(json.dados);

Só que a estrutura dos "dados" que você montar no PHP precisa ser compatível com o que será recebido no JavaScript. Dê uma olhada no link que passei que acho que não terá problemas. Primeiro você consulta os dados normalmente, depois você pode fazer um outro loop para colocar as cores desejadas.

A grande questão aqui é que o Google Charts é uma API enorme e constantemente surgem novos recursos. É praticamente impossível acompanhar tudo isso e manter sua camada de PHP conseguir gerar todas as estruturas esperadas pela API do Google. Por isso, acho que o ideal é: primeiro você faz um gráfico estático no Google Charts sem PHP (igual ao meu primeiro exemplo), depois você bola um jeito de fazer o PHP passar os dados para o JavaScript de forma dinâmica, conforme a necessidade. A forma que eu acho mais simples é com Ajax + JSON, mas ainda há outras alternativas.

Unknown disse...

Muito bom, só não estou achando o arquivo jquery.js. aparece a seguinte mensagem
{"dados":{"cols":[{"type":"string","label":"Local"},{"type":"number","label":"Quantidade"}],"rows":[{"c":[{"v":"embelezamentos"},{"v":1}]},{"c":[{"v":"informatica"},{"v":1}]},{"c":[{"v":"mercados"},{"v":1}]},{"c":[{"v":"petshop"},{"v":1}]},{"c":[{"v":"restaurantes"},{"v":1}]},{"c":[{"v":"vendas"},{"v":1}]}]},"config":{"title":"Quantidade de moradores por locais","width":400,"height":300}}

Unknown disse...

continua esta mesma mensagem acima, o que pode ser? A principio ele pegou os dados das tabelas do banco mas parece que só não exibe o gráfico

Rubens Takiguti Ribeiro (autor do blog) disse...

Opa, tem razão. Se vieram os dados do banco, o jQuery tá ok, afinal, ele que faz a requisição.

Aparentemente seu JSON parece OK. Porém, se está aparecendo essa mensagem, parece que você passou o JSON inteiro para o Google Charts. Na verdade o JSON que montei de exemplo no PHP possui "dados" e "config". Você precisa passar os dados para um lugar e o config para outro, assim como mostrado no exemplo.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo
Acesse o link a seguir para ver todos os tipos de gráfico do Google Charts:
https://google-developers.appspot.com/chart/interactive/docs/gallery

Neste aqui tem a forma de se montar um gráfico de linha:
https://google-developers.appspot.com/chart/interactive/docs/gallery/linechart

Anônimo disse...

Olá, Rubens!

Gostaria de uma ajuda!

Tenho este select:
select m.descricao as descricao, l.quantidade as quantidade
FROM lojas_instaladas l
join usuarios u on u.usuarios_id = l.usuarios_id
join mes m on m.mes_id = l.mes_id
where l.usuarios_id = 5
order by m.mes_id

Onde neste me retorna o mês e quantidade de itens daquele mês de um determinado usuário. Meu problema esta quando dentro da tabela lojas_instaladas contém mais de dois registros, o gráfico não é mostrado (muito estranho). Quando deixo apenas dois registros na tabela mostra o gráfico certinho. Estou utilizando a mesma estrutura que você montou no exemplo acima, porém quando na tabela tem mais de 2 registros, o gráfico não é mostrado.

Obrigado

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo

Note que no exemplo que coloquei, por ser um gráfico de pizza, informei duas colunas e, por coincidência, duas linhas.

Creio que o número de colunas deve ser sempre dois mesmo e o número de linhas é que pode ser maior, mas não pode ter duas linhas com o mesmo nome.

Talvez o seu problema seja ao consultar vários anos diferentes. Neste caso, podem vir dois registros do mesmo mês, mas de anos diferentes. Experimente concatenar o ano junto do mês no seu SELECT. Além disso, você não colocou nenhum agrupamento, então se existirem dois registros de janeiro/2014 para o mesmo usuário, também causará conflito de nome. Neste caso, precisa colocar um "GROUP BY l.usuarios_id".

Tenta aí e qualquer coisa é só falar.

Daniel disse...

Olá Rubens, obrigado pelas dicas acima, porém não deu certo. (agora com usuário logado =) )

Olhe só, na minha tabela de lojas_instaladas existirá apenas 1 mês por ano para cada usuário, eles não nunca serão repetidos, para ficar mais claro, os registros de testes estão assim na tabela do banco:

lojas_instaladas_id usuarios_id mes_id ano quantidade
1 5 1 2014 20
2 5 4 2014 32
3 5 5 2014 13
4 5 7 2014 19

Agradeço desde já!

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Daniel

Entendi. Não repete o mesmo mês/ano por usuário. Porém, ainda pode repetir o mesmo mês, mas de anos diferentes, do mesmo usuário.

Você tentou retornar CONCAT(mes_id, ' ', ano) como "rótulo"? Se preferir, me manda o trecho de código por e-mail que posso ver o que há de errado: rubs33@gmail.com.

Daniel disse...

Olá, Rubens!

Encaminhei o email como você me pediu. Gostaria de informar que concatenando, aparece o gráfico, porém ao invés de mostrar o id e ano, gostaria de mostrar apenas o nome do mês ou nome do mês e ano!

Obrigado!

Daniel disse...

Olá Rubens, não sei se recebeu meu email, mas vai a duvida aqui.

Gostaria de verificar contigo a possibilidade de me ajudar na geração de outro tipo de gráfico, o ColumnChart.

Gostaria de apresentar este resultado no gráfico ColumnChart onde no rodapé apresentaria os meses e acima dos meses as coluna com suas respectiva quantidades, semelhante ao arquivo em anexo. Tentei montar mas me apresenta um erro: "All series on a given axis must be of the same data type". O código esta assim:


$grafico = array(
'dados' => array(
'cols' => array(
array('type' => 'string', 'label' => 'descricao'),
array('type' => 'string', 'label' => 'mes'),
array('type' => 'number', 'label' => 'quantidade')
),
'rows' => array()
),
'config' => array(
'legend' => 'top',
'width' => 900,
'height' => 500,
'backgroundColor' => 'transparent',
'is3D' => true
)
);


// Consultar dados no BD
$pdo = new PDO('mysql:host=localhost;dbname=xxx; charset=utf8', 'root', '');
$sql2 = 'select av.descricao as descricao, m.descricao as mes, a.quantidade as quantidade
from avaliacao a
join mes m on m.mes_id = a.mes_id
join usuarios u on u.usuarios_id = a.usuarios_id
join avaliacao_tipo av on av.avaliacao_tipo_id = a.avaliacao_tipo_id
where a.usuarios_id = '.$id_usu.'
group by m.mes_id, av.avaliacao_tipo_id
order by m.mes_id';

$stmt = $pdo->query($sql2);
while ($obj = $stmt->fetchObject()) {
$grafico['dados']['rows'][] = array('c' => array(
array('v' => $obj->descricao),
array('v' => $obj->mes),
array('v' => (int)$obj->quantidade)
));
}

// Enviar dados na forma de JSON
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($grafico);
exit(0);

Este seria o gráfico que desejo montar -> http://tinypic.com/view.php?pic=2nq61w7&s=8#.VBBZcfldU02

Unknown disse...

segue o arquivo .json gerado pelo app..:

{"SurveyTemplate":{"SurveyQuestion":[{"__prefix":"oe","customTag":"InstitutionSite","_type":"integer","_required":true,"htmlType":"select1","group":"","label":{"name":"Institution Site"},"__text":1},{"__prefix":"oe","customTag":"AcademicPeriod","_type":"integer","_required":true,"htmlType":"select1","group":"","label":{"name":"Academic Period"},"__text":6},{"_id":"16","__prefix":"oe","_type":"integer","_required":false,"htmlType":"select1","group":"What is your level of experience using software","label":{"name":"Experience using similar software for your needs"},"options":{"25":"much experience","26":"some experience","27":"little experience","28":"no experience"},"__text":"26"},{"_id":"17","__prefix":"oe","_type":"integer","_required":false,"htmlType":"select1","group":"What is your level of experience using software","label":{"name":"Experience using our software for your needs"},"options":{"29":"much experience","30":"some experience","31":"little experience","32":"no experience"},"__text":"29"},{"_id":"19","__prefix":"oe","_type":"integer","_required":false,"htmlType":"select1","group":"Please tell us about your experience using our software","label":{"name":"Ease of data entry"},"options":{"33":"very easy/intuitive","34":"reasonably easy/manageable","35":"somewhat difficult/not intuitive","36":"very difficult"},"__text":"34"},{"_id":"20","__prefix":"oe","_type":"integer","_required":false,"htmlType":"select1","group":"Please tell us about your experience using our software","label":{"name":"Ease of producing reports"},"options":{"37":"very easy/intuitive","38":"reasonably easy/manageable","39":"somewhat difficult/not intuitive","40":"very difficult"},"__text":"37"},{"_id":"22","__prefix":"oe","_type":"string","_required":false,"htmlType":"textarea","group":"Others","label":{"name":"Please comment on the user-friendliness of our software"},"__text":"Sei la"},{"_id":"24","__prefix":"oe","_type":"string","_required":false,"htmlType":"input","group":"Training","label":{"name":"Where was the training held?"},"__text":"como"}

Unknown disse...

Olá Rubens.
sou um tanto leigo e preciso muito de sua ajuda por favor

Tenho um app android de pesquisa de opinião publica sabe, é para fins acadêmicos e ele gera um arquivo .json qdo se conclui as respostas do questionário sabe...
obs: postei ele acima em outra postagem meio sem querer pois não queria caber aki entende...

o q necessito na verdade primeiro é saber como envio "estes" arquivos .json para um banco de dados (o app tem um campo de url para upload) e como organizo tudo para q "não se repita" na alimentação dos gráficos entende?

sei q é muito básico isso e não é exatamente o conteúdo do seu post mas preciso disso pra então poder aplicar o método q vc nos ensinou de gerar gráficos certo. meu conhecimento é apenas em html e css o resto sou uma negação rs

me ajude por favor...
obrigado e sucesso!!!

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Emerson

Parece que seu JSON faltou um pedacinho, mas tudo bem, pois deu pra entendê-lo. Sobre suas dúvidas, primeiro você precisa modelar seu banco de dados para que tenha tabelas para guardar tanto as perguntas quanto as respostas separadamente (talvez vai precisar vincular a um questionário também). No seu JSON, notei que existem as duas coisas (as perguntas e respostas) de forma mesclada.

O que você precisa fazer é enviar esse JSON para um servidor, por exemplo, usando REST. No seu servidor (onde estará o banco de dados), você receberá esse JSON e deverá separar o que é pergunta do que é resposta e cadastrá-los adequadamente no BD. Para tanto, é preciso uma forma de identificar se a pergunta já foi cadastrada ou não no BD. A princípio, achei que você poderia usar o atributo "_id", mas parece que algumas perguntas não tem esse atributo. Enfim, isso você que precisa identificar e, se possível, ajustar na app android para gerar um JSON adequado.

Depois de armazenados os dados das perguntas e respostas, você consegue gerar os gráficos.

Uma coisa importante para se levar em conta é que existem vários tipos de perguntas e algumas, inclusive, vão precisar de tabelas extras no banco de dados, dependendo da forma como você modelar. Por exemplo, perguntas com alternativas terão as perguntas em uma tabela, mas as alternativas em outra (pois se trata de uma relação 1:N, ou seja, uma pergunta tem N alternativas). Mas talvez você opte por armazenar o próprio JSON da pergunta no BD, diretamente. Neste caso, você vai precisar receber o JSON, manipulá-lo (removendo itens desnecessários, como a resposta do usuário), depois guardá-lo no BD.

Bom, acho que já te passei o caminho das pedras. O resto é com você.

Unknown disse...

rapaiz isso é um bicho de sete cabeças rs
nesse aplicativo android só possui um campo de url q faz o upload, ou seja, como vou enviar isso pra um servidor ou bd sem as devidas permissões ou FTP..? afff rs
existe como? tava lendo aki e não entendi mas é possivel por w3.org ou sitemap?

complicou rs

Unknown disse...

o nome do app é "openEMIS survey" e a URL original de download e upload do app é: https://demo.openemis.org/

então o q preciso é criar uma URL q envia o arquivo para o BD ai eu organizaria conforme vc citou por "_id" as perguntas e assim estaria na jogada certo?!

mas como poderia fazer isso sem ser por ftp? apenas com uma URL? no app e no site desta URL de origem não diz nada como se faz parece até q eles não utilizam mais o app e ja faz dias q enviei um pedido por contato e não responderam...

alguma ideia sobre um método de upload por URL https sem protocolos e permissões Rubens?
obrigado!!!

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Emerson

Se o app só oferece um campo de URL para onde ele enviará o arquivo JSON, então você precisa de um servidor com alguma linguagem de programação para receber este arquivo, processá-lo e armazenar os dados no BD. Por exemplo, ter um servidor com Apache + PHP + MySQL. Se o app envia o JSON por upload, você deve conseguir receber no PHP desta forma:
$json = stream_get_contents('php://input');
$dados = json_decode($json, true);

Ou seja, você cria um arquivo PHP para receber o arquivo, por exemplo, "receber_json.php". Você configura no app para enviar para "http://seudominio/receber_json.php". Então faz a programação do arquivo PHP e solta o debug em algum arquivo de log, para você saber se está recebendo o arquivo corretamente.

Comece com um script como este abaixo, depois vai ajustando conforme necessário para guardar no BD:

<?php
$json = stream_get_contents('php://input');
$dados = json_decode($json, true);

// Gerar log com o JSON recebido no arquivo /tmp/json-debug-*
file_put_contents(strftime('/tmp/json-debug-%Y-%m-%d-%H-%M-%S.log'), $json, FILE_APPEND);

Unknown disse...

Nossa era tudo q eu precisava Rubens!!!
eu entendi agora e vou fazer tudo calmamente, a começar vendo se meu servidor tem apache q ainda não sei mas se for preciso contrato um q tenha...

muito obrigado amigo! sucesso! vou ja curtir e compartilhar seus posts! Parabéns!

Wellington Modesto disse...

Otimo tutorial, me ajudou bastante aqui, obrigado =D
Eu tenho uma duvida rubens e gostaria de saber se tem como vc me responder.
Eu tentei puxar uma tabela aqui com mais de 10 dados inseridos nela e gera um erro, isto acontece pela api permitir uma busca de somente 10 resultados ou pode ser algum erro meu na hora da consulta?

Luciano Machado disse...

Bom dia Rubens, caso tenho um filtro no arquivo teste.html como enviar parametros para get_dados.php, passo fazer isso em um botao no arquivo teste.html e enviar por url para a query, e depois chamar a div area_grafico, quando o script for executar ele ainda tera os paramentros? abraços

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Luciano
Se você precisa aplicar um filtro, você pode criar um formulário no seu teste.html e associar evento ao formulário para que, quando sejam submetidos os dados (onsubmit), você faça uma nova requisição em ajax, passando os dados preenchidos no formulário para a requisição em ajax.

No exemplo que mostrei, usei o método ajax da biblioteca jQuery.
http://api.jquery.com/jquery.ajax/

Seria algo parecido com isso:
http://pastebin.com/BpnMxSKd

Luciano Machado disse...

Oi Rubens a 2 semanas estou tralhando e estudando nesse gráfico e ainda não consegui fazer funcionar com paramentros apenas som o sql de forma estatica, olha só a estrutura do meu codigo getDadosGrafico.php http://pastebin.com/BUP2DeY8 ,quando apliquei seu exemplo da resposta anterior nao funcionou, parece que nao le os dados de getDadosGrafico, ou nao envia ou parametros. http://pastebin.com/355KmrKv, é desse form que envio os dados bem como é o mesmo arquivo do script do google chart.
Acho estranho ter tanto trabalho para implementar isso é uma funcionalidade comum para qualquer sistema.
abraços.

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi Luciano
Fiz um exemplo aqui e funcionou normalmente. Não consegui ver seu formulário. Em todo caso, notei que você está pegando os dados via post. Só que para isso funcionar, sua requisição AJAX precisa enviar os dados via post.

No meu exemplo, a requisição AJAX manda os dados via get mesmo.

Seguem os arquivos:
teste.html: http://pastebin.com/X142EsLP
getDadosGrafico.php: http://pastebin.com/ggR9ga1J

Coloquei os dos numa pasta "chart" pra chamar assim: http://localhost/chart/teste.html

Luciano Machado disse...

Também acho Sérvio. Parabéns Rubens, e obrigado por compartilhar um pouco que sabe, com certeza além auxiliar no aprendizado de muitos, serve de inspiração para aqueles que trabalham com desenvolvimento.

Luciano Machado disse...

Também acho Sérvio. Parabéns Rubens, e obrigado por compartilhar um pouco que sabe, com certeza além auxiliar no aprendizado de muitos, serve de inspiração para aqueles que trabalham com desenvolvimento.

seniDEac disse...

parabens pelo post!!

excelente!!

e uma duvida.... como eu poderia pegar os daods do meu site que estao no analytics.... e gerar meus proprios graficos?

ja usou essa API?
https://ga-dev-tools.appspot.com/embed-api/basic-dashboard/

Rubens Takiguti Ribeiro (autor do blog) disse...

seniDEac,

Nunca usei essa API. Mas pelo visto, ela é própria para obter gráficos do Google Analytics. Já o Google Charts serve para gerar gráficos sobre qualquer coisa que você tenha acesso aos dados.

Esse link que você passou já tem as instruções para gerar os gráficos. Você só precisa ficar esperto de trocar o "REPLACE WITH YOUR CLIENT ID" pelo seu ID do Analytics.

Outra forma seria usar a API do Google Analytics apenas para obter os dados, que você poderia jogar num banco de dados, e depois gerar os gráficos com o Google Charts como mostrado no artigo.

seniDEac disse...

valeu pelo retorno rubens!!

pouca gente da atencao para os blogs que escrevem!

bom... a saber .. eu configurei tudo certinho... essa API resolveria todos os meus problemas... mas ...

logo, sem querer te encher....
conhece alguma solucao para pegar os dados do analytics e usar local?

brigadao!!!

seniDEac disse...

quem sabe voce nao que fazer um post sobre isso!!

pegar os dados do analytics e fazer graficos!

rsrsrsrs ! !

contribuo com o que tenho!!
:D

Unknown disse...

Boa tarde Rubens. Não sei se já aconteceu com vc, mas preciso gerar 2 gráficos na mesma página e o segundo não aparece de forma alguma, encontrei alguma coisa mas tudo que achei até agora não gera 2 gráficos nem ferrando, teria alguma dica? até tentei duplicar o código na mesma pagina mas o segundo gráfico não aparece, só aprece se cometar a chamada do primeiro, ai o segundo vem mas nunca os 2 juntos.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Carlos Henrique
Nunca passei por isso, mas imagino que o problema seja que você esteja imprimindo cada gráfico na mesma área.

O que você precisa fazer é definir duas áreas (por exemplo, duas tags "div") na sua página e cada uma ter o atributo "id" com um valor diferente. E então, no código javascript, você deve notar a linha que contém a chamada ao método "getElementById". Lá você deve capturar o elemento desejado e imprimir o gráfico, depois capturar o outro elemento e imprimir o outro gráfico.

Anônimo disse...

ola Rubens..

vi seus exemplos e estudo utilizando no meu trabalho, mas quando insiro meus dados do banco ele não dá erro mas também não aparece o gráfico.

getDadosGráficos.php

array(
'cols' => array(
array('type' => 'string', 'label' => 'Sexo'),
array('type' => 'number', 'label' => 'Quantidade')
),
'rows' => array()
),
'config' => array(
'title' => 'Quantidade de cadastrado por Sexo',
'width' => 400,
'height' => 300
)
);

// Consultar dados no BD
include ('conexao.php');
$sql = 'SELECT sexo, COUNT(*) as quantidade FROM cadastro GROUP BY sexo';
$stmt = $pdo->query($sql);
while ($obj = $stmt->fetchObject()) {
$grafico['dados']['rows'][] = array('c' => array(
array('v' => $obj->sexo),
array('v' => (int)$obj->quantidade)
));
}

// Enviar dados na forma de JSON
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($grafico);
exit(0);

?>





pode me ajudar?
obrigada

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Carlos.

Para usar cores personalizadas no gráfico, você precisa passá-las no array de config, conforme mostrado na API:
https://developers.google.com/chart/interactive/docs/lines#fullhtml

Dentro do array config, você passa a opção "series" que deve ser um array com as cores que você pretende usar.

Portanto, basta criar o array "series" antes do while (vazio), depois incluir essa linha dentro do while:

$grafico['config']['series'][] = $obj->color;

Unknown disse...

Olá Rubens tudo bem?
Parabéns pelo seu tutorial, eu estou usando ele em um projeto para medir avaliações de departamento e gostaria de saber como se tem com eu filtrar por período e departamento especifico.

Você teria um exemplo de buscar o departamento e as datas em formulário usando GET e depois isso gerar o Gráfico.