Visualització de dades i Open Data amb la llibreria Highcharts

De wikijoan
Salta a la navegació Salta a la cerca

Introducció

Open Data API for Chart Creation with highcharts.com

Data Visualization:

Dades obertes (open data):

Ciència de dades (data science):

Exemples que utilitzarem:

Referències OpenData

Per exemple, per veure tots els municipis de Catalunya, tenim aquesta url:

Energies renovables:

Desenvolupament

Solució amb JQuery

Ex part1.png

script ex_part1.html:

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
	var year_list = [];
	var arrayString = [];
	var array_final = [];

	$(document).ready(function(){

	var url = "https://api.worldbank.org/v2/countries/NOR/indicators/NY.GDP.MKTP.KD.ZG?per_page=30&MRV=30&format=json";
	$.getJSON(url, function(json) {
		alert(json)
	$.each(json[1], function(i, data) {
		//Store indicator name
		country_name = data.country.value;

		// Store indicator label
		indicatorName = data.indicator.value;

		// fill the date array
		year_list.push(data.date);

		// fill the string data array
		arrayString.push(data.value);

		});

		for (var i = 0; i < arrayString.length; i++) {
			if (arrayString[i] != null) {
				array_final.push(parseFloat(arrayString[i]))
			} else {
				array_final.push(null)
			};
		}

		console.log (year_list);
		console.log (arrayString);
		console.log (array_final);


		var chart = new Highcharts.Chart({
		chart: {
		type: 'spline',
		renderTo: 'container'
		},
		title: {
		text: indicatorName
		},
		tooltip: {
		valueDecimals: 2
		},
		subtitle: {
		text: 'Source: World Bank Data'
		},
		xAxis: {
		categories: year_list.reverse() //.reverse() to have the min year on the left
		},
		series: [{
		name: country_name,
		data: array_final.reverse() //
		}]
		});

	});


});

</script>
</head>
<body>
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
</body>
</html>

I un exemple més complet, script ex_part2.html:

Ex part2.png
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function() {

  var region_list = ["Africa", "Arab World", "Central Europe and the Baltics", "East Asia & Pacific (all income levels)", "East Asia & Pacific (developing only)", "Europe & Central Asia (all income levels)", "Europe & Central Asia (developing only)", "European Union", "Latin America & Caribbean (all income levels)", "Latin America & Caribbean (developing only)", "Least developed countries: UN classification", "Middle East & North Africa (all income levels)", "Middle East & North Africa (developing only)", "North America", "South Asia", "Sub-Saharan Africa (all income levels)", "Sub-Saharan Africa (developing only)", "Sub-Saharan Africa (excluding high income)"];
  var income_list = ["High income", "High income: OECD", "High income: nonOECD", "Low income", "Middle income", "Lower middle income", "Upper middle income", "Low & middle income", "OECD members"];
  var world_list = ["World", "world", "WLD"];

  //Settings 
  var lenght = 20;
  var display = 4 * lenght;
  var url = "https://api.worldbank.org/v2/countries/ZWE;SSA;LIC;WLD/indicators/NY.GDP.MKTP.KD.ZG?per_page=" + display + "&MRV=" + lenght + "&format=json"
  var arrayRegion = [],
    arrayIncome = [],
    arrayCountry = [],
    arrayWorld = [],
    YearList = [];


  $.ajax({
    url: url,
    complete: function(json) {
      json = JSON.parse(json.responseText);

      $.each(json[1], function(i, data) {
        // test if country name is in region_list
        if ($.inArray(data.country.value, region_list) != -1) {
          //Store Region Name
          Region_name = data.country.value;
          // Append values to arrayRegion
          if (data.value != null) {
            arrayRegion.push(parseFloat(data.value));
          } else {
            arrayRegion.push(null)
          }
          // test if country name is in income_list
        } else if ($.inArray(data.country.value, income_list) != -1) {
          // Store Income name
          Income_name = data.country.value;
          if (data.value != null) {
            arrayIncome.push(parseFloat(data.value));
          } else {
            arrayIncome.push(null)
          }
        } else if ($.inArray(data.country.value, world_list) != -1) {
          // fill the Year list array. NB. We choose the World serie to do so, as this serie is the most likely to be complete (i.e. the one with the less missing values) 
          YearList.push(data.date);
          // store Indictor Label
          indicatorName = data.indicator.value;
          if (data.value != null) {
            arrayWorld.push(parseFloat(data.value));
          } else {
            arrayWorld.push(null)
          }
        } else {
          // Finally create the country data vector
          Country_name = data.country.value;
          if (data.value != null) {
            arrayCountry.push(parseFloat(data.value));
          } else {
            arrayCountry.push(null)
          }
        }
      });

      var chart = new Highcharts.Chart({
        chart: {
          type: 'spline',
          renderTo: 'container'
        },
        colors: ['#6e9fc5', '#ffdf51', '#a6ca6d', '#ad46d6', '#f26a2e', '#00adef', '#f4bb90'],
        title: {
          text: indicatorName
        },
        subtitle: {
          text: 'Source: World Bank Data'
        },
        xAxis: {
          categories: YearList.reverse() //.reverse() to have the min year on the left 
        },
        plotOptions: {
          series: {
            marker: {
              enabled: false
            }
          }
        },
        tooltip: {
          valueDecimals: 2,
          pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}%</b><br/>'
        },
        series: [{
          name: Country_name,
          data: arrayCountry.reverse()
        }, {
          name: Region_name,
          data: arrayRegion.reverse()
        }, {
          name: Income_name,
          data: arrayIncome.reverse()
        }, {
          name: 'World',
          data: arrayWorld.reverse()
        }]
      });
    },
    error: function() {
      console.log('there was an error!');
    }
  });
});

</script>
</head>
<body>
<script src="https://code.highcharts.com/highcharts.js"></script>

<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>

</body>
</html>

Sense JQuery, Native XMLHttpRequest Object

Des de juny de 2019 s'ha d'utilitzar només la v2 de l'API.

$ curl -si https://api.worldbank.org/v2/countries/NOR/indicators/NY.GDP.MKTP.KD.ZG?per_page=30&MRV=30&format=json

$ HTTP/1.1 200 OK
Date: Mon, 10 Sep 2018 12:49:09 GMT
Content-Type: text/xml; charset=UTF-8
Content-Length: 10523
Connection: keep-alive
Access-Control-Allow-Origin: * <<========================= IMPORTANT =========
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: X-Requested-With
X-Powered-By: ASP.NET
...

La v2 té implementat el Access-Control-Allow-Origin, com es comenta a https://groups.google.com/forum/#!topic/world-bank-api/Gjvqt6pzSOs no hi ha cap problema amb al Access-Control-Allow-Origin


script ex_part1_native_ajax.html:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>Simple use of Cross-Site XMLHttpRequest (Using Access Control)</title>
    <script>
    
    var invocation = new XMLHttpRequest();
    //important! s'ha de fer servir la versió 2 de la API, que permet fer el Access-Control-Allow-Origin
    var url = 'http://api.worldbank.org/v2/countries/NOR/indicators/NY.GDP.MKTP.KD.ZG?per_page=30&MRV=30&format=json';

    var year_list = [];
    var arrayString = [];
    var array_final = [];
    var country_name;
    var indicatorName;

    var invocationHistoryText;
    
    function callOtherDomain(){
        if(invocation)
        {    
            invocation.open('GET', url, true);
            invocation.onreadystatechange = handler;
            invocation.send(); 
        }
        else
        {
            invocationHistoryText = "No Invocation TookPlace At All";
            var textNode = document.createTextNode(invocationHistoryText);
            var textDiv = document.getElementById("textDiv");
            textDiv.appendChild(textNode);
        }
    }

    function handler(evtXHR)
    {
        if (invocation.readyState == 4)
        {
                if (invocation.status == 200)
                {
                    let resposta = invocation.responseText;
                    //console.log(resposta);
                    let arr = JSON.parse(resposta)
                    //arr[0] és una capçalera
                    //console.log(arr[1]) //aquestes són les dades de Noruega
                    //finalment ja tenim les dades que  interessen
                    let dades = arr[1];
                    //console.log(dades[0].country.value);
                    
                    country_name = dades[0].country.value
                    indicatorName = dades[0].indicator.value;
                    for (var i=0; i<dades.length;i++) {
                        //console.log(dades[i].date);
                        //console.log(dades[i].value);
                        year_list.push(dades[i].date);
                        arrayString.push(dades[i].value);
                    }

                    for (var i = 0; i < arrayString.length; i++) {
                        if (arrayString[i] != null) {
                            array_final.push(parseFloat(arrayString[i]))
                        } else {
                            array_final.push(null)
                        };
                    }

                    console.log (year_list);
                    console.log (array_final);

                    var chart = new Highcharts.Chart({
                    chart: {
                    type: 'spline',
                    renderTo: 'container'
                    },
                    title: {
                    text: indicatorName
                    },
                    tooltip: {
                    valueDecimals: 2
                    },
                    subtitle: {
                    text: 'Source: World Bank Data'
                    },
                    xAxis: {
                    categories: year_list.reverse() //.reverse() to have the min year on the left
                    },
                    series: [{
                    name: country_name,
                    data: array_final.reverse()
                    }]
                    });
					
                }
                else
                    alert("Invocation Errors Occured");
        }
    }

    </script>
</head>
<body onload="callOtherDomain()">
	<script src="https://code.highcharts.com/highcharts.js"></script>
    <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
</body>
</html>

Utilitzar HighCharts com a mòdul

Fruit consumption.png
$ npm init
es crea el fitxer package.json

$ npm install --save highcharts
$ npm install --save-dev parcel-bundler

I editem el fitxer package.json igual que vam fer amb els mapes de openstreetmaps. El fitxer package.json queda de la forma:

{
  "name": "open_data",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "parcel index.html",
    "build": "parcel build --public-url . index.html"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "highcharts": "^10.0.0"
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.5"
  }
}

Exemple bàsic de gràfica simple: les pomes, plàtans i taronges que mengen la Jane i el John:

script index.js:

	import Highcharts from 'highcharts';
	// Alternatively, this is how to load Highstock. Highmaps and Highcharts Gantt are similar.
	// import Highcharts from 'highcharts/highstock';

	// Load the exporting module.
	import Exporting from 'highcharts/modules/exporting';
	// Initialize exporting module. (CommonJS only)
	Exporting(Highcharts);

	// Generate the chart
	Highcharts.chart('container', {
	  // options - see https://api.highcharts.com/highcharts
	  		//exemple bàsic: https://www.highcharts.com/docs/getting-started/your-first-chart
            chart: {
                type: 'bar'
            },
            title: {
                text: 'Fruit Consumption'
            },
            xAxis: {
                categories: ['Apples', 'Bananas', 'Oranges']
            },
            yAxis: {
                title: {
                    text: 'Fruit eaten'
                }
            },
            series: [{
                name: 'Jane',
                data: [1, 0, 4]
            }, {
                name: 'John',
                data: [5, 7, 3]
            }]

	});

I el script index.html només conté el contenidor de la gràfica i la crida al script index.js:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Highcharts com a mòdul</title>
  </head>
  <body>
    <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
    <script src="./index.js"></script>
  </body>
</html>

Per fer funcionar l'aplicació:

$ npm start

I la tenim disponible a:

I amb:

$ npm run build

tenim a la carpeta dist/ el nostre projecte.

Gdp norway moduls.png

Combinant els exemples que tenim ja podem fer l'exemple del Producte Interior Brut amb mòduls. script index_gdp.html:

import Highcharts from 'highcharts';
// Alternatively, this is how to load Highstock. Highmaps and Highcharts Gantt are similar.
// import Highcharts from 'highcharts/highstock';

// Load the exporting module.
import Exporting from 'highcharts/modules/exporting';
// Initialize exporting module. (CommonJS only)
Exporting(Highcharts);

var invocation = new XMLHttpRequest();
//important! s'ha de fer servir la versió 2 de la API, que permet fer el Access-Control-Allow-Origin
var url = 'http://api.worldbank.org/v2/countries/NOR/indicators/NY.GDP.MKTP.KD.ZG?per_page=30&MRV=30&format=json';

var year_list = [];
var arrayString = [];
var array_final = [];
var country_name;
var indicatorName;

var invocationHistoryText;

callOtherDomain();

function callOtherDomain(){
    if(invocation)
    {    
        invocation.open('GET', url, true);
        invocation.onreadystatechange = handler;
        invocation.send(); 
    }
    else
    {
        invocationHistoryText = "No Invocation TookPlace At All";
        var textNode = document.createTextNode(invocationHistoryText);
        var textDiv = document.getElementById("textDiv");
        textDiv.appendChild(textNode);
    }
}

function handler(evtXHR)
{
    if (invocation.readyState == 4)
    {
            if (invocation.status == 200)
            {
                let resposta = invocation.responseText;
                //console.log(resposta);
                let arr = JSON.parse(resposta)
                //arr[0] és una capçalera
                //console.log(arr[1]) //aquestes són les dades de Noruega
                //finalment ja tenim les dades que  interessen
                let dades = arr[1];
                //console.log(dades[0].country.value);
                
                country_name = dades[0].country.value
                indicatorName = dades[0].indicator.value;
                for (var i=0; i<dades.length;i++) {
                    //console.log(dades[i].date);
                    //console.log(dades[i].value);
                    year_list.push(dades[i].date);
                    arrayString.push(dades[i].value);
                }

                for (var i = 0; i < arrayString.length; i++) {
                    if (arrayString[i] != null) {
                        array_final.push(parseFloat(arrayString[i]))
                    } else {
                        array_final.push(null)
                    };
                }

                console.log (year_list);
                console.log (array_final);

                //var chart = new Highcharts.Chart({
                Highcharts.chart('container', {
                chart: {
                type: 'spline',
                renderTo: 'container'
                },
                title: {
                text: indicatorName
                },
                tooltip: {
                valueDecimals: 2
                },
                subtitle: {
                text: 'Source: World Bank Data'
                },
                xAxis: {
                categories: year_list.reverse() //.reverse() to have the min year on the left
                },
                series: [{
                name: country_name,
                data: array_final.reverse()
                }]
                });
				
            }
            else
                alert("Invocation Errors Occured");
    }
}

Tasques a realitzar

Arbres per districte barres.png
Arbres per districte pie.png

Del servei opendata de l'ajuntament de Barcelona podem consultar l'arbrat dels parcs de Barcelona:

Podem descarregar les dades en format JSON o format CSV.

Descarregar el fitxer OD_Arbrat_Parcs_BCN.csv

Amb aquestes dades podem fer una gràfica? La resposta és que no. Això són moltes dades, però això no ens serveix per fer una gràfica.

Podem agrupar les dades del número d'arbres que té cada districte. Això ho podem fer amb les funcions avançades de cerca que té qualsevol editor de text una mica decent:

Ciutat Vella;3189
Eixample;1175
Sants-Montjuïc;7323
Les Corts;2439
Sarrià - Sant Gervasi;4189
Gràcia;1997
Horta-Guinardó;3324
Nou Barris;4874
Sant Andreu;1293
Sant Martí;6058

Aquestes dades ja es poden graficar. Quin tipus de gràfica creus que pot se millor. Diagrama de barres, sector circular, etc.

Pots repassar què pots fer amb la llibreria highcharts en la llista d'exemples:

Les dades que tenim són molt senzilles, i per tant no val la pena complicar-se la vida. Un diagrama de barres o de sectors circulars senzills ens valdrà.

Tasques a realitzar:

1. Crea el fitxer arbres.json amb les teves dades de districte/número d'arbres.

2. Utilitza la llibreria highcharts amb el CDN (etiqueta script). Fes un diagrama de barres.

3. Utilitza la llibreria highcharts instal·lant la llibreria i important els mòduls. Fes un diagrama de sectors circulars.

NOTA: per tal de què funcioni has de copiar el fitxer arbres.json dins la carpeta dist/.

NOTA. Fer el diagrama de barres és bastant immediat. Fer el diagrama de sectors circulars (diagrama de pastís, pie en anglès) és una mica més difícil. Has de veure els exemples.

Entrega

Entregaràs un pdf, dins del termini establert, amb les parts més significatives del codi. Hi haurà d'haver dues captures de pantalla de les teves solucions: sense mòduls i diagrama de barres; i amb mòduls i diagrama de sectors.


creat per Joan Quintana Compte, setembre 2018, abril 2020