Imagem geral da aplicação

Para além das tarefas mais comezinhas numa aplicação deste tipo (tralha de bases de dados, eventos dos botões e por aí), há duas áreas com algum interesse: o processo do método d’Hondt e o desenho dos assentos coloridos da representação da Assembleia.

Método d’Hondt

O algoritmo para se aplicar a distribuição d’Hondt demora menos tempo a explicar do que o processo em si. Basicamente, executam-se tantas rondas como os lugares a atribuir, dentro de cada ronda itera-se pelos grupos a calcular os quocientes necessários (número de elementos do grupo por número de elementos atribuídos mais um) e incrementa-se o número de elementos atribuídos ao grupo que obtiver o maior quociente nessa ronda.

Em pseudo-código:

Seja atribuidos = Mapa[grupo, num_elementos];
Seja grupos = Mapa[grupo, qtd_votos];

Para i = 0; enquanto i < mandatos_a_atribuir; i++

    Seja quocientes_da_ronda = Mapa[grupo, num_elementos];
    Para cada grupo existente
        Seja quocientes_da_ronda[grupo] = grupos[grupo] / (atribuidos[grupo] + 1);

    atribuidos[índice do maior valor dos mapa quocientes_da_ronda]++;

Devolver atribuidos;

Em C++ (usando a Qt Framework), o código correspondente leva algumas alterações para evitar resultados anómalos, nomeadamente a passagem por uma ronda zero, para inicializar o mapa das atribuições:

QMap<qint32, qint32> atribuidos;
for(qint32 ronda = 0; ronda <= mandatos; ronda++)
{
    QMap<qint32, qint32> quocientes;

    if(ronda != 0)
    {
        foreach(qint32 elemento, elementos.keys())
            quocientes[elemento] = elementos[elemento] / (atribuidos[elemento] + 1);

        atribuidos[quocientes.key(getListMax(quocientes.values()))]++;
    }
    // fazer aqui a ronda zero
    else
    {
        foreach(qint32 elemento, elementos.keys())
        {
            // para inicializar o mapa
            atribuidos[elemento] = 0;
            quocientes[elemento] = 0;
        }
    }
}

return atribuidos;

Grafismo da Assembleia

Para alojar o grafismo da assembleia, usei um componente QGraphicsView, e o correspondente objecto QGraphicsScene, para facilidade no desenho. Fazer umas cadeiritas estilizadas foi o menos, extendendo a classe QGraphicsItem.

A parte pior foi mesmo o desenho em arco. Vejamos, a coisa ficava perceptível (se calhar, até mais) com uns gráficos de barra, ou coisa do género. Mas, como dizem no anúncio, não era a mesma coisa.

Não nos podemos esquecer que, como a ideia é verificar as variações da quantidade de deputados, não podia simplesmente copiar a estrutura da Assembleia actual – tinha mesmo que desenhar para cada quantidade. O primeiro passo seria calcular a quantidade de filas, em semi-círculos concêntricos.

void MainWindow::desenharAssembleia(QMap<qint32, qint32> deputados, qint32 cadeiras)
{
    // (...)

    qint32 numFilas = 0;

    /*  estas definições são fixas - o raio mínimo, em pixeis,
        para as filas não começarem no centro */
    qint32 minRadius = 150;
    /*  e o espaço ocupado por cada cadeira, de frente,
        contando com algum espaçamento de cada lado */
    qint32 espacoCadeira = 28;

    qint32 cadeirasLivres = cadeiras;
    /*  enquanto existirem cadeiras livres, incrementar o número
        de filas enquanto se subtrai o número de cadeiras que
        cabem na fila actual */
    while(cadeirasLivres > 0)
        cadeirasLivres -= qCeil((Pi * (minRadius + ((++numFilas - 1) * espacoCadeira))) / espacoCadeira);

Agora que já temos o número de filas, é preciso desenhá-las, vazias. Confesso que tentei desenhá-las logo pintadas com as cores dos partidos, mas revelou-se uma impossibilidade; para saber onde desenhar a próxima cadeira, tenho sempre de fazer dois ciclos: primeiro pelas filas e depois pela ordem da cadeira, ou pela ordem da cadeira, mudando de fila quando atingisse o limite de cadeiras da mesma.

A solução mais simples, então, é desenhá-las todas a eito e a preto (a cor pode ser mudada posteriormente, visto que não é um desenho directo, e sim uma classe própria, que herda de QGraphicsItem), inicializando a fila actual como a exterior e iterando pelas cadeiras todas, mudando de fila quando apropriado. Se depois derem uma vista de olhos pelos ficheiros fonte, verão mais algum código, para aperfeiçoamentos (como centrar as cadeiras da fila mais interior, caso sejam muito menos do que as que a fila levaria normalmente).

qint32 fila = numFilas;
qint32 index = 1;

qint32 cadeirasEmFalta = cadeiras;

QList<CadeiraItem*> parlamento;
for(qint32 i = 0; i < cadeiras; i++)
{
    qint32 numCadeirasFila = qCeil((Pi * (minRadius + ((fila - 1) * espacoCadeira))) / espacoCadeira);

    qreal anguloInit = ((Pi * (minRadius + ((fila - 1) * espacoCadeira))) - (numCadeirasFila * espacoCadeira)) / 2 / (minRadius + ((fila - 1) * espacoCadeira));
    qreal anguloEspacoCadeira = qreal(espacoCadeira) / qreal(minRadius + ((fila - 1) * espacoCadeira));

    qreal anguloActual = (anguloInit + (anguloEspacoCadeira / 2) + anguloEspacoCadeira * (index - 1)) - Pi;
    qint32 distanciaActual = minRadius + ((fila - 1) * espacoCadeira);
    qint32 posXActual = qRound(distanciaActual * qCos(anguloActual));
    qint32 posYActual = qRound(distanciaActual * qSin(anguloActual));

    /*  actualização de radianos para graus, agora que
        já foram feitas todas as contas em radianos,
        e visto que precisamos de graus já a seguir */
    anguloActual = (anguloActual + Pi / 2) * 180 / Pi;

    // posicionamento e rotação da cadeira actual
    CadeiraItem *cadeira = new CadeiraItem();
    cadeira->setPos(posXActual, posYActual);
    cadeira->setRotation(anguloActual);

    // acrescentar à cena...
    scene->addItem(cadeira);
    // ... e à lista de referências
    parlamento.append(cadeira);

    // actualização do índice de cadeira
    index++;
    // potencial mudança de fila
    if(index > numCadeirasFila)
    {
        fila--;
        index = 1;
    }
}

Posto isto, e com a assembleia já desenhada, bastava-me iterar pelos partidos, ordenados pelo seu índice de espectro político (sendo os negativos à esquerda, o zero no centro, e os positivos à direita), e pintar cada cadeira a que tivessem direito com a sua cor respectiva (fui roubar as cores dos partidos, onde possível, ao seus respectivos kits de design – por isso, algumas cores são muito parecidas, tende paciência).

Claro que isto é mais fácil de dizer do que de fazer… Como é que eu percorrer a assembleia já desenhada, da esquerda para a direita, em curva, para pintar as cadeiras correctamente? Sem situações completamente contra-intuitivas, como dois deputados na primeira fila completamente desenquadrados do resto do seu partido nas filas de trás (aconteceram-me várias destas situações em testes)?

Poderia ter usado mais trigonometria – afinal, eu desenhei as cadeiras, pelo que sei onde estão, matematicamente. Mas aproveitei os algoritmos de colisões embutidos na classe QGraphicsItem (e suas descendentes) – desenhei uma linha do lado esquerdo da assembleia, e fi-la percorrer, em rotação, até à direita, funcionando como um radar, ou um limpa-pára-brisas, se quiserem. A cada passo (metade do ângulo ocupado por uma cadeira na última fila), pegava nas cadeiras em colisão com a linha, e pintava-as da cor do partido em curso, até se acabarem as cadeiras, ou os deputados desse partido a distribuir.

Mais uma vez, o código constante dos ficheiros fonte é mais completo, contemplando outras situações (como as etiquetas com o nome e quantidade de deputados dos partidos):

QGraphicsLineItem *scanner = new QGraphicsLineItem(0, 0, 0, 0);
scene->addItem(scanner);

qreal actualAngle = minAngulo;
qreal angleStep = qreal(espacoCadeira)/qreal(maxRadius) / 2;

/*  tblPartidos é um objecto representante da tabela SQLite
    correspondente, ordenado pelo índice de espectro político */
for(qint32 i = 0; i < tblPartidos->rowCount(); i++)
{
    qint32 partido_id = tblPartidos->record(i).value(EleicoesNumDeputadosTools::Partidos_partido_id).toInt();
    QString cor = tblPartidos->record(i).value(EleicoesNumDeputadosTools::Partidos_cor).toString();

    // caso não tenha deputados, não se desenha nada
    if(deputados.contains(partido_id) && (deputados[partido_id] > 0))
    {
        qint32 sentados = 0;
        // enquanto houver deputados por sentar
        while(sentados < deputados[partido_id])
        {
            scanner->setLine(qreal(minRadius) * qCos(actualAngle), qreal(minRadius) * qSin(actualAngle), qreal(maxRadius) * qCos(actualAngle), qreal(maxRadius) * qSin(actualAngle));
            QList<QGraphicsItem*> colisions = scanner->collidingItems();
            
            // iterar pelas cadeiras em colisão com a linha
            foreach(QGraphicsItem *item, colisions)
            {
                CadeiraItem *cadeira = static_cast<CadeiraItem*>(item);
                if(!cadeira->isSeated)
                {
                    cadeira->isSeated = true;
                    cadeira->setCor(cor);
                    sentados++;
                }

                // acabaram os deputados
                if(sentados >= deputados[partido_id])
                    break;
            }

            /* acabaram as cadeiras em colisão, vamos fazer
                avançar o nosso "radar" de cadeiras */
            if(sentados < deputados[partido_id])
                actualAngle += angleStep;
        }
    }
}

O resto são trivialidades muito específicas, fora do âmbito deste post. Isto foi o que deu mais luta, mas valeu a pena, que ficou muito pipoca.

Partilhar no Sapo Links Partilhar no del.icio.us Partilhar no Digg Partilhar no Twitter Partilhar no StumbleUpon Partilhar no MySpace Partilhar no Facebook

Comentários Deixar um comentário

 Categorias
 Arquivo
 Projectos em Destaque
 Últimas Postas no Blog
 Últimos Comentários do Blog