Nesta seção vamos compartilhar algumas informações sobre o uso da biblioteca Plotchart.
Plotchart é uma biblioteca “totalmente escrita” em Tcl/Tk pelo desenvolvedor Arjen Markus com o objetivo de facilitar a criação de gráficos XY, de barra e outros tipos de gráficos mais comuns.
Em programação há sempre um compromisso entre “facilidade de uso” e “flexibilidade”. Ou seja, para facilitar a vida do usuário é necessário “simplificar e reduzir” as opções de uso. E nesse caso podemo dizer que o Plotchart prioriza a “facilidade” em detrimento da “flexibilidade”.
Algumas referências: ActiveTcl User Guide - Plotchart, https://core.tcl.tk/tklib - Plotchart e http://wiki.tcl.tk/11265.
A figura Q.1 mostra alguns exemplos de gráficos produzidos com Plotchart.
Figura Q.1. Alguns exemplos de gráficos produzidos com o uso do pacote Plotchart. (Fonte: Plotchart gallery)

O Plotchart faz parte do metapacote Tklib e portanto para instalar o pacote basta o comando:
# apt-get install tklib
Após a instalação você encontrará vários exemplos de uso da biblioteca no diretório /usr/share/doc/tklib/examples/plotchart/.
Podemos então criar um gráfico XY simples com os comandos:
#!/usr/bin/env wish
package require Plotchart
#Cria o widget canvas onde será construído o gráfico
canvas .c -background white -width 400 -height 200
pack .c -fill both
# Cria o gráfico com os eixos x e y
set s [::Plotchart::createXYPlot .c {0.0 100.0 10.0} {0.0 100.0 20.0}]
# Loop foreach que executa o comando "plot" para cada par xy
foreach {x y} {0.0 32.0 10.0 50.0 25.0 60.0 78.0 11.0 } {
$s plot series1 $x $y
}
# Define o nome do gráfico
$s title "Gráfico XY simples"
O código acima produz o gráfico da figura Q.2.
Primeiro é criado um “widget canvas” que irá exibir o gráfico:
canvas .c -background white -width 400 -height 200
Este comando cria o widget canvas com o nome .c e as opções de fundo branco (-background white), largura de 400 pixels (-width 400) e altura de 200 pixels (-height 200).
E o comando pack .c, usa o gerenciador de posição pack para exibir o widget .c expandindo o widget em x e y nos limites disponíveis (-fill both).
O comando ::Plotchart::createXYPlot é um comando da biblioteca Plotchart para a criação de um gráfico XY e apresenta a seguinte estrutura:
::Plotchart::createXYPlot canvas eixo_x eixo_y opções
canvas - nome do widget canvas que irá acomodar o gráfico
eixo_x - lista com 3 elementos contendo: mínimo, máximo e intervalo de cada marcação do eixo x (nessa ordem). Para um eixo em ordem decrescente colocar (nessa ordem): máximo, mínimo e intervalo de cada marcação (com sinal negativo).
eixo_y - lista com 3 elementos contendo: mínimo, máximo e intervalo do eixo y (nessa ordem). Para um eixo em ordem decrescente colocar (nessa ordem): máximo, mínimo e intervalo (com sinal negativo).
opções - zero ou mais opções que determinam a forma de exibição do gráfico:
-xlabels {lista_de_labels} labels (rótulos ou etiquetas) que são exibidos nas marcações do eixo x substituindo as marcações automáticas. Mas são funcionam se for omitido o intervalo de cada marcação no eixo x. Se os labels forem numéricos serão posicionados de acordo com a escala dada, caso contrário serão posicionados em espaços equidistantes com base no número de labels.
-ylabels {lista_de_labels} da mesma forma que xlabels, são labels personalizados para o eixo y.
Veja outras opções disponíveis na seção https://core.tcl.tk/tklib - Plotchart
Por exemplo, se quisermos substituir os labels do eixo x basta editarmos a linha:
...
set s [::Plotchart::createXYPlot .c {0.0 100.0 {}} {0.0 100.0 20.0} -xlabels {"mínima" "média" "máxima"}]
...
E o resultado é:
Um outro tipo de gráfico muito útil para o monitoramento contínuo de uma variável é o stripchart. A diferença básica do stripchart em relação a um gráfico XY é que o eixo x será automaticamente ajustado quando a coordenada x de um novo ponto exceder o limite máximo.
Por exemplo o código seguinte gera pares xy variando x em incrementos de 15 unidades e y variando aleatoriamente. (Fonte: plotdemo2.tcl)
#!/usr/bin/env wish
package require Plotchart
#Cria o widget canvas onde será construído o gráfico
canvas .c -background white -width 400 -height 200
pack .c -fill both
set s [::Plotchart::createStripchart .c {0.0 100.0 20.0} {0.0 100.0 20.0}]
proc gendata {slipchart xold xd yold yd} {
set xnew [expr {$xold+$xd}]
set ynew [expr {$yold+(rand()-0.5)*$yd}]
$slipchart plot series1 $xnew $ynew
if { $xnew < 200 } {
after 500 [list gendata $slipchart $xnew $xd $ynew $yd]
}
}
after 100 [list gendata $s 0.0 15.0 50.0 30.0]
$s title "Monitoramento Contínuo"
Vamos usar como exemplo os passos iniciais para a exibição das leituras de um pluviômetro.
Neste projeto utilizamos uma placa Arduino para realizar as leituras do número de descargas do reservatório de um pluviômetro do tipo báscula.
A documentação completa deste projeto está disponível na seção www.c2o.pro.br/proj/pluviometro.
Implementamos o código no Arduino para permitir a comunicação serial com o PC pelo envio de mensagens com o formato: [instrumento];[valor];[unidade].
Onde [instrumento] é o nome do instrumento (pluviômetro), [valor] é um número inteiro que corresponde ao número de descargas (escoamento) do reservatório e [unidade] (D) é a unidade da leitura. No caso do pluviômetro criamos uma unidade arbitrária "D" que significa Descarga (Discharge) do reservatório do pluviômetro.
Para permitir a visualização de dados usamos a função random(min, max) no Sketch do Arduino para gerar números inteiros aleatórios no intervalo definido por “min” e “max”.
O código do Arduino usado apenas para demonstrar os funcionamento da biblioteca Plotchart ficou:
/*
Pluviometro OO
*/
int reed_switch_pin = 8;
boolean last_reed_switch_state = LOW;
unsigned long tc = millis();
unsigned long ts = millis();
int clicks;
float volume;
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
pinMode(reed_switch_pin, INPUT);
}
void loop() {
unsigned long int_click = millis() - tc;
if ( int_click > 50 ) {
readPin();
}
unsigned long int_send = millis() - ts;
if ( int_send > 5000 ) {
sendData();
}
//Outras futuras atividades
}
void readPin () {
int reed_switch_state = digitalRead(reed_switch_pin);
if (last_reed_switch_state == LOW && reed_switch_state == HIGH) {
clicks++;
// Serial.print("Click - ");
// Serial.println(clicks);
}
last_reed_switch_state = reed_switch_state;
tc = millis();
}
//7.02 é o volume de cada báscula medido com o frasco de Mariotte
void sendData () {
volume = clicks * 7.02;
// Serial.print("Clicks em 10 segundos - ");
// Serial.println(clicks);
// Serial.print("Volume em 10 segundos - ");
// Serial.println(volume);
clicks = random(0, 10);
Serial.print("pluviometro;");
Serial.print(clicks);
Serial.println(";D");
clicks = 0;
ts = millis();
}
Com esse código a placa Arduino envia a cada 5 segundos uma mensagem com 3 campos separados por ";" do tipo: pluviometro;5;D
Para fazer a leitura dessas mensagens e exibir em um gráfico XY fizemos um código inicial simplificado com as funcionalidades essenciais para uma demonstração:
#!/bin/sh
#A proxima linha reinicia usando wish \
exec wish "$0" "$@"
package require Plotchart
#Cria o canvas para exibir o gráfico
set canvas_grafico [canvas .c -background white -width 450 -height 200]
#Exibe o canvas com o gerenciador de posição pack
pack $canvas_grafico
#Cria o gráfico XY com os limites de 0 a 60 e intervalos de 5 unidades no eixo X
#e limites de 0 a 10 e intervalo de 1 unidade no eixo Y
set grafico_pluviometro [::Plotchart::createXYPlot $canvas_grafico {0 60 5} {0 10 1}]
#Procedimento para criar um canal de comunicação serial
proc criarCanal { } {
global canal tempo_inicial
#cria o canal
set canal [ open /dev/ttyACM0 r+ ]
#configura os parâmetros de comunicação
fconfigure $canal -mode 9600,n,8,1
#configura para não esperar caractere de final de linha
#e não bloquear a execução do procedimento
fconfigure $canal -blocking 0
#determina o instante inicial do monitoramento
set tempo_inicial [clock seconds]
#associa a chamada da rotina lerCanal quando houver dados para leitura no canal
fileevent $canal readable [list lerCanal $canal]
}
#Procedimento que é executado sempre que houver dados para leitura na porta serial
proc lerCanal { canal } {
global tempo_inicial grafico_pluviometro
#armazena na variável “leitura” a mensagem no canal
set leitura [gets $canal]
#verifica se os dados enviados são caracteres não imprimíveis (lixo)
if {$leitura == ""} { return }
#determina o instante da leitura
set tempo_final [clock seconds]
#calcula o intervalo da leitura
set tempo_leitura [expr $tempo_final - $tempo_inicial]
#verifica se o canal foi encerrado
if { [eof $canal ] } {
puts stderr "Fechando $canal"
catch { close $canal }
return }
#converte o conteúdo da variável leitura em uma lista
#usando o caractere “;” como delimitador
set lista_leitura [split $leitura ";"]
#extrai o segundo elemento da lista
set N [lindex $lista_leitura 1]
#comando para exibir as leituras no gráfico
$grafico_pluviometro plot serie_leitura $tempo_leitura $N
#força a atualização do gráfico na tela
update
}
criarCanal
update
Foi possível visualizar o gráfico da figura Q.4.
Gastei algum tempo até perceber que a placa Arduino envia alguns caracteres “não imprimíveis” que disparam a chamada do procedimento lerCanal. Por isso foi necessário incluir o teste if {$leitura == ""} { return } logo no início do procedimento, mas também foi necessário incluir a opção fconfigure $canal -blocking 0 no procedimento criarCanal.
Podemos usar o “stripchart”, um tipo de gráfico XY onde o eixo horizontal é ajustado automaticamente para valores que ultrapassam o limite original, mantendo a amplitude dos dados no eixo X.
Basta modificar a linha de criação do gráfico substituindo ::Plotchart::createXYPlot por ::Plotchart::createStripchart.
.
.
.
set grafico_pluviometro [::Plotchart::createXYPlot $canvas_grafico {0 60 5} {0 10 1}]
.
.
.
Outra opção é o gráfico de barras (Barchart), ou gráfico de colunas, e consiste em um gráfico com barras retangulares e comprimento proporcional aos valores que ele representa. As barras podem ser desenhadas verticalmente ou horizontalmente.
Formato do comando para Barchart:
::Plotchart::createBarchart w xlabels yaxis noseries args
Onde>
w - nome do canvas que irá exibir o gráfico
xlabels - lista dos identificadores (labels) para o eixo x
yaxis - lista com 3 elementos contendo: mínimo, máximo e intervalo do eixo y (nessa ordem)
noseries e args - opções adicionais que determinam a forma de exibição do gráfico (Ver ::Plotchart::createBarchart)
Código para exibição das leituras em gráfico de barras (Barchart) e o resultado na figura Q.5:
#!/bin/sh
#A proxima linha reinicia usando wish \
exec wish "$0" "$@"
package require Plotchart
#Incluindo cálculo do volume e legendas com unidades
#Volume do reservatório V = 7.02 mL = 7,02 cm³ = 7020 mm³
#Área de coleta do pluviômetro A = 165,13 cm² = 16513 mm²
#Volume do reservatório em mm (V_mm) = V / A = 0.425 mm
set V_mm 0.425
set canvas_grafico [canvas .c -background white -width 800 -height 400]
pack $canvas_grafico
set list_xlabels {60 55 50 45 40 35 30 25 20 15 10 5 0}
set list_yaxis {0 0 0 0 0 0 0 0 0 0 0 0 0}
set count_loop 0
set grafico_pluviometro [::Plotchart::createBarchart $canvas_grafico $list_xlabels {0 5 1} 2.5]
$grafico_pluviometro xtext "Tempo (s)"
$grafico_pluviometro ytext "Volume (mm)"
puts "grafico_pluviometro: $grafico_pluviometro"
$grafico_pluviometro plot serie_leitura $list_yaxis blue
proc criarCanal { } {
global canal tempo_inicial
set canal [ open /dev/ttyACM0 r+]
fconfigure $canal -mode 9600,n,8,1
fconfigure $canal -blocking 0
set tempo_inicial [clock seconds]
fileevent $canal readable [list lerCanal $canal]
puts "criarCanal: canal -> $canal tempo_inicial $tempo_inicial"
}
proc lerCanal { canal } {
global tempo_inicial
global canvas_grafico grafico_pluviometro
global list_xlabels list_yaxis
global V_mm
set tempo_final [clock seconds]
set tempo_leitura [expr $tempo_final - $tempo_inicial]
puts "tempo_inicial: $tempo_inicial tempo_final: $tempo_final tempo_leitura: $tempo_leitura"
set leitura [gets $canal]
if {$leitura == ""} { return }
if { [eof $canal ] } {
puts stderr "Fechando $canal"
catch { close $canal }
return }
set lista_leitura [split $leitura ";"]
set N [lindex $lista_leitura 1]
set V_total_mm [expr $N * $V_mm]
lappend list_yaxis $V_total_mm
set new_list_yaxis [ lrange $list_yaxis 1 end ]
set list_yaxis $new_list_yaxis
$canvas_grafico delete all
set grafico_pluviometro [::Plotchart::createBarchart $canvas_grafico $list_xlabels {0 5 1} 1]
$grafico_pluviometro xtext "Tempo (s)"
$grafico_pluviometro vtext "Volume (mm)"
$grafico_pluviometro plot serie_leitura $new_list_yaxis blue
update
}
criarCanal
update
O programa funcionou mas aparecia um erro na centésima atualização com a seguinte mensagem:
bad window path name "0.c"
bad window path name "0.c"
while executing
"winfo width $w"
(procedure "WidthCanvas" line 13)
invoked from within
"WidthCanvas $w"
(procedure "MarginsRectangle" line 75)
invoked from within
"MarginsRectangle $w $args"
(procedure "::Plotchart::createBarchart" line 21)
invoked from within
"::Plotchart::createBarchart $canvas_grafico $list_xlabels {0 10 1} 1.5"
(procedure "lerCanal" line 50)
invoked from within
"lerCanal file6"
Para tentar identificar e corrigir o erro incluí algumas saídas (tracer) nos arquivos plotchart.tcl (::Plotchart::createBarchart) e plotpriv.tcl (::Plotchart::widthcanvas, ::Plotchart::MarginsRectangle e NewPlotInCanvas), para visualizar o status de algumas variáveis.
Para tentar remediar o problema do limite de 100 atualizações, comentei a linha:
incr scaling($c,plots)
no procedimento ::Plotchart::NewPlotInCanvas do arquivo plotpriv.tcl.
Mas esse procedimento é usado por diversos outros procedimentos dentro de plotchart.tcl.
Mais tarde, tentando uma solução mais adequada, descomentei a linha de incremento (incr scaling($c,plots)) e resolvi alterar os procedimentos que extraem uma substring a partir de uma string nos seguintes procedimentos:
O comando original (set c [string range $w 2 end]) extraía uma substring c a partir do segundo caracter da string w. Até 99 atualizações esse processo funcionava (99.c -> .c) mas a partir de 100 atualizações (100.c -> 0.c) a substring incluía um número antes do ponto.
Usamos o comando [string first "." $w] para obter a posição do caracter "." na string e usar esse índice para extrair corretamente a substring de w correspondente ao widget canvas.
Também alterei o procedimento ::Plotchart::WidthCanvas no arquivo plotpriv.tcl:
#Identifica a posição do ponto na string w
#para extrair apenas a partir do ponto e armazenar
#na variável c
set position_point [string first "." $w]
if { [string match {[0-9]*} $w] } {
# set w [string range $w 2 end]
set w [string range $w $position_point end]
}
E o procedimento ::Plotchart::DrawMask no arquivo plotpriv.tcl.
Todos esses arquivos estão no diretório /usr/share/tcltk/tklib0.6/plotchart.
Também pedi ajuda na lista comp.lang.tcl e foi feita a mesma sugestão ou então baixar os arquivos mais atualizados no link https://core.tcl.tk/tklib/dir?ci=tip clicando no link que vem depois de “"Files of check-in [...”.