quinta-feira, 11 de agosto de 2011

Script (em Python) para gerar gráficos "waterfall-like" no SciDAVis

Como todos devem saber, o SciDAVis ainda não possui ferramentas para gerar gráficos no estilo waterfall, como faz o Origin®, mas, felizmente, existem maneiras de contornar a situação e obter o que eu vou chamar de "gráfico waterfall-like", como é mostrado na figura 1, a seguir:

Figura 1: Gráfico no estilo waterfall.

Um gráfico do tipo waterfall envolve 3 coordenadas mas não é obtido através de um "plot3D". Geralmente estes gráficos surgem do fato de termos dados em que uma variável independente não se altera com a mudança de valores de alguma outra variável independente, mas a variável dependente sim. Em um exemplo prático seria assim: as leituras de X não mudam conforme alteramos os valores de Z, mas os valores de Y variam.
No caso da figura 1, os valores de comprimento de onda não variam com o tempo, mas a intensidade varia. E os dados que foram utilizados para gerar o gráfico estavam, inicialmente, dispostos em uma tabela com uma coluna X e 13 colunas Y.
Para gerar um gráfico como o da figura 1, a ideia básica é que os valores de X e Y sejam "deslocados na diagonal" a partir do segundo conjunto de dados. Para isto, basta somar valores adequados a cada conjunto (Xi,Yi) de dados. O script a seguir, juntamente com seus comentários, explica uma das maneiras de se fazer isto sem termos que estipular tais valores "na marra".
IMPORTANTE: As finalidades de se gerar este tipo de gráfico no SciDAVis são (apenas): visualização e apresentação. Quaisquer análises que dependam dos valores verdadeiros de X e Y, como suavização e ajustes de curvas, devem ser realizadas em cima das tabelas originais.
IMPORTANTE 2: é possível que algumas opções do script não funcionem em versões do SciDAVis anteriores à 0.2.4 (não testei pra saber).
IMPORTANTE 3: utilize este script apenas como base para o seu próprio, fazendo adaptações necessárias para os tipos de dados com os quais você trabalha.

Agora sim, o script:

# Partindo do pressuposto que tenhamos uma tabela, com o nome de
# "Tabela1", com uma coluna X e várias colunas Y
t1=table("Tabela1") # acessando a "Tabela1" com 't1'
nrt1=t1.numRows() # obtendo o número de linhas de 't1'
nct1=t1.numCols() # obtendo o número de colunas de 't1'
t2=newTable("Tabela2",2*(nct1-1),nrt1) # criando uma nova tabela com 
# o dobro de colunas Y de 't1', mas com o mesmo número de linhas
nrt2=t2.numRows() # número de linhas de 't2'
nct2=t2.numCols() # número de colunas de 't2'
# Primeiro definimos as colunas ímpares como X (abcissas)
#*** há um mistério a ser resolvido aqui: começando com 2, ao invés de 3?
for i in range(2,nct2,2):
 col=t2.column(i) # acessando a coluna 'i', de 't2'
 col.setPlotDesignation("X") # definindo a coluna 'i' como X
## Definindo os valores das coordenadas X na segunda tabela
# Não é possível fazer operações com uma coluna inteira de uma só vez.
# Logo, é necessário alterar os valores célula por célula
for j in range(1,nct2,2):
# calculando o incremento em cada coluna X ('xincr') como o intervalo
# de valores X dividido pelo número de conjuntos de (X,Y) que queremos;
# o fator (j-1)/3 vem do número da coluna dividido por um termo de ajuste
# da distância entre uma curva e outra. No meu caso, usei o número 3, mas
# isto pode ser definido de acordo com a necessidade: curvas mais próximas
# -> números maiores, mais distantes -> números menores
 xincr=(t1.cell(1,nrt1)-t1.cell(1,1))/(nct1-1)*(j-1)/3
 for k in range(1,nrt2):
  t2.setCell(j,k,xincr+t1.cell(1,k))
 
## Definindo os valores de Y na segunda tabela
# Aqui, partimos do pressuposto que o valor da primeira célula da primeira
# coluna Y é um valor adequado para utilizar como incremento. Isto deve ser
# verificado previamente e alterado de acordo com suas necessidades
for j in range(2,nct2+1,2):
 yincr=(t1.cell(2,1))*(j-2)/2
 for k in range(1,nrt2):
  t2.setCell(j,k,yincr+t1.cell(j-(j-2)/2,k))
## Gerando o gráfico
# Aqui, vou plotar a primeira coluna Y e adicionar as outras depois. Faço isto por
# querer todas as curvas na mesma cor
g=plot(t2,"2",0) # plotando a coluna 2 de 't2'
l=g.activeLayer() # acessando a camada ativa (a única no caso) de 'g'
#  e agora, insiro as demais curvas no gráfico
for n in range(4,nct2+1,2):
 l.insertCurve(t2,str(n),0,0)
# Pronto, a partir daqui, já podemos executar o script e teremos nosso gráfico
# no estilo "Waterfall"

## Extra: adicionando seta e legenda para a seta
seta = ArrowMarker() # ArrowMarker() =  desenhador de seta :-)
seta.setStart(515,200) # definindo o ponto inicial: setStart(x,y)
seta.setEnd(525,1200) # definindo o ponto final: setEnd(x,y)
seta.setWidth(1) # definindo largura da linha
seta.drawStartArrow(False) # sem ponta no começo
seta.drawEndArrow(True) # com ponta no final
seta.setColor(Qt.black) # cor da seta
l.addArrow(seta) # adicionando a seta à camada (só aparecerá após um l.replot())
legenda = l.newLegend("Tempo (ms)") # definindo texto da legenda
legenda.setTextColor(Qt.black) # cor do texto na legenda
legenda.setFont(QtGui.QFont("Arial",10)) # fonte do texto na legenda
legenda.setFrameStyle(0) # tipo de borda: 0 - nenhuma, 1 - retângulo e 2 - retângulo com sombra
legenda.setOriginCoord(518.0,800.0) # origem da legenda
l.replot() # redesenhando o conteúdo da camada
# Fim do script. Executando-o, obteremos um gráfico "waterfall-like"
Espero que este script seja útil. Um abraço e até a próxima.