Criar visualizações a partir dos dados abertos da Justiça: Tutorial

by MINJUST

Introdução

Com este tutorial pretende-se demonstrar como usar os dados disponíveis no site dados.justica.gov.pt.

Irão ser mostrados alguns exemplos de como criar visualizações de dados (gráficos) através de alguma tecnologias open-source em formato aberto (XML, CSV e JSON).

Visualizações Simples

Charts

› Inserir a API do CKAN (JSON)

$url= 'http://ckandev.northeurope.cloudapp.azure.com';
$urlPath = '/api/action/datastore_search';
$graph = file_get_contents($url.$urlPath.'?resource_id=e692486c-e84c-48db-a119-e7bfea4f45e1');

› Incluir a library ChartJS (documentação)

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.bundle.min.js"></script>

› Criar um canvas para desenhar o gráfico

<canvas id="graph" class="embedGraph" style="height:200px;"></canvas>

› Preparar os dados vindos da API

var graphData = <?php echo $graph; ?>;
graphData = graphData.result.records;

function getLabels(data){
    var temp = [];
    data.forEach(function(result){
        temp.push(result.Year.toString());
    });
    return temp;
}
function getDatasetdata(data, property) {
    var temp = [];
    data.forEach(function(result){
        temp.push(result[property]);
    });
    return temp;
}

var graphData = {
    labels: getLabels(graphData),
    datasets: [{
        label: 'Número de Jovens',
        backgroundColor: "rgba(13, 206, 169, 0.3)",
        borderColor: "rgba(13, 206, 169, 1)",
        borderWidth: 1,
        data: getDatasetdata(graphData, 'NumeroJovens')
    }]
};

› Renderizar os dados

new Chart(document.getElementById("graph").getContext('2d'), {
    type: 'line',
    data: graphData,
    options: {
        responsive: true,
        tooltips: {
            mode: 'index',
            intersect: false,
        },
        hover: {
            mode: 'nearest',
            intersect: true
        }
    }
});

› Screencast



Google Maps

› Incluir as libraries JQuery, Google API (é necessário gerar uma API para projecto, que irá substituir a variável "API_KEY" no URL a baixo) e Marker Clustering

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY=init" async defer></script>
<script src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js"></script>

› Criar o elemento que irá conter o gráfico

<style type="text/css" media="screen">
    #map{
        width: 100vw;
        height: 100vh;
    }
    .labels{
        color: white;
    }
</style>
<div id="map"></div>

› Definir a cor dos clusters

const colors=['#FF530D','#E82C0C','#FF0000'];

› Gerar um SVG customizado para o markercluster

function getGoogleClusterInlineSvg(color){
    let encoded = window.btoa('')
    return ('data:image/svg+xml;base64,' + encoded)
};

› Criar os diferentes estilos de clusters

let cluster_styles = [
    {
        width: 40,
        height: 40,
        url: getGoogleClusterInlineSvg(colors[0]),
        textColor: 'white',
        textSize: 12
    },
    {
        width: 50,
            height: 50,
        url: getGoogleClusterInlineSvg(colors[1]),
        textColor: 'white',
        textSize: 14
    },
    {
        width: 60,
            height: 60,
        url: getGoogleClusterInlineSvg(colors[2]),
        textColor: 'white',
        textSize: 16
    }
]

› Adicionar a CKAN API, o parent HTML e as map options ao método getData

function init(){
    getData('http://ckandev.northeurope.cloudapp.azure.com/api/action/datastore_search_sql?sql=SELECT%20*%20from%20%2233b2ce95-7079-45e3-a35b-e504ef8f24ae%22%20WHERE%20%22Year%22=2017','http://ckandev.northeurope.cloudapp.azure.com/api/action/datastore_search_sql?sql=SELECT * from "bbac8a5d-0201-45bc-91d1-73e1b87f7e2f"',
        document.getElementById('map'),
    {
        center:{lat:38.73,lng:-9.14},
        zoom:5
    })
}

› Funções auxiliares

//creates a map object
function createMap(mapOptions,element){
    return new google.maps.Map(element, mapOptions)
}

//transforms the data and creates clusters
function transformData(data,locations,map){
    data.map(d => {
        let loc;
    locations.forEach(location => {
        if(location.Concelho === d.Location){
        loc = location
        return
    }
})
    Object.assign(d,{
        pos:{
            lat:parseFloat(loc.Latitude),
            lng:parseFloat(loc.Longitude)
        }
    })
    return d

})
    let markers = data.map(d =>
        addMarker({
            data:d.Total,
            pos:d.pos
        },map))

    let markerCluster=new MarkerClusterer(map,markers,{
        styles:cluster_styles
    })

}

//getData gets the data  and creates the map
function getData(url, url2, element, mapOptions){
    let map = createMap(mapOptions, element)
    request(url)
        .then(res => {
        request(url2).then(locations => transformData(res.result.records, locations.result.records, map))
})
    .catch(console.error)
}

//api call to ckan
function request(url){
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            dataType: 'jsonp',
            success: data => resolve(data),
            fail:err => reject(err)
        })
    })
}

//creates circles based on the size of the values
function createCircle(value){
    let i = Math.floor(colors.length/3000 * value)
    if(i >= colors.length) i = colors.length-1
    let color = colors[i]
    console.log(color)
    return {
        path: google.maps.SymbolPath.CIRCLE,
        fillColor: color,
        fillOpacity: 1,
        scale:value/100 + 20,
        strokeColor: 'white',
    strokeWeight: 2
    };
}

//creates a marker
function addMarker(d, map){
    console.log(d)
    let circle = createCircle(d.data)
    let label = {
        text:d.data + "",
        color:'white',
        fontFamily:'Roboto',
        fontWeight:'bolder',
        fontSize:'20px'
    }
    return new google.maps.Marker({
        position: d.pos,
        icon: circle,
        label:label,
        labelClass:'labels',
        map: map,
        marker_data:d.data
    });
}

› Screencast



Boas Práticas por tipos de dados

› Quando deve ser usado um gráfico de barras

Este tipo de representação utiliza barras tanto verticais como horizontais para ilustrar comparações (sendo este primeiro mais conhecido como gráfico de colunas), sendo o comprimento proporcional aos valores que ele representa.
Os gráficos de barras permitem estabelecer facilmente comparações e têm um forte impacto visual, porém, só podem ser usados para transmitir informações simples.

Um exemplo real utilizando estes dados:

› Quando deve ser usado um gráfico de linhas

O gráfico de linhas é um tipo de gráfico que exibe informações com uma série de pontos de dados chamados de marcadores, ligados por segmentos de linha reta.
Um gráfico de linhas é muitas vezes usado para visualizar uma tendência nos dados em intervalos de tempo - uma série de tempo -, assim, a linha é muitas vezes construída por ordem cronológica. O gráfico de linhas é composto por dois eixos, um vertical e outro horizontal, e por uma linha que mostra a evolução de um fenómeno ou processo.

A sua vantagem principal é que todos os dados da amostra aparecem no gráfico. Não é necessário construir previamente uma tabela de frequências. Dá uma interpretação visual sobre a forma como os dados se distribuem, porém, não são aconselháveis quando há muitos ou poucos caules. Dá pouca informação no caso dos dados serem muito dispersos.

› Quando deve ser usado um gráfico que cruze informação de vários datasets

Este tipo de gráficos devem ser usados quando os conjuntos de dados inseridos necessitam de ser divididos para que possam ser comparados uns com os outros.

Um bom exemplo seria se estivéssemos a tentar mostrar o crescimento da população ao longo do tempo por região. Seria mais apropriado para esse tipo de dados, pois está a tentar comparar vários conjuntos de dados. Estes gráficos são codificados por cores e têm uma aparência ordenada e clara, que permitem ver vários conjuntos de dados de forma correcta.

Um exemplo real utilizando estes dados: