Google Apps Script ens dona eines que permeten crear qualsevol tipus de gràfic i guardar-lo en forma d'imatge que podem mostrar a la nostra aplicació; però fent servir ginys també podem crear diagrames de barres senzills però que poden ser útils i efectius en molts casos. Anem a fer, doncs, un gràfic en el qual les barres o columnes seran contenidors amb unes mides i uns colors convenientment escollits.
En l'apartat anterior teníem una finció que mostrava les dades, ara farem una altra funció diferent que ens crearà el diagrama. Més endavant això ens permetrà triar entre una forma o l'altra de mostrar les dades.
En aquest cas, farem que cada barra i el text que l'acompanya siguin d'un color diferent. Atès que hi haurà cinc barres, caldrà definir cinc colors; però, per facilitar la gestió dins un bucle, els posarem en una llista. Per organitzar millor l'aplicació, aquesta llista la crearem dins de la classe ColorsApp que definim al fitxer colors.dart que situem a la carpeta core que està dins de la carpeta lib. Per facilitar la comprensió, a continuació hi ha l'arbre de carpetes i fitxers:
lib
main.dart
core
colors.dart
data
claus_google.dart
crida_dades.dart
dades_rebudes.dart
screens
pant_principal.dart
El fitxer colors.dart contindrà:
colors.dart
import 'dart:ui';
class ColorsApp {
List<Color> colorsColumnes = [
Color(0xFF0000FF),
Color(0xFFFF0000),
Color(0xFF008800),
Color(0xFFAA00AA),
Color(0xFF000000),
];
}
La funció mostraBarres ha de retornar un giny. Primer comprovarem que tenim totes les dades necessàries (cinc llistes). Si és així, creem una llista de ginys que serà el que retornarem; atès que això ja és directament mostrable, ja que els descendents (children) d'una columna no són més que una llista de ginys. Aquestas llista està inicialment buida i l'omplim amb un bucle que recorre el contingut de les llistes de dades. Aquest bucle va creant fileres a les quals afegeix un giny de text amb el títol de la columna, un contenidor de llargada proporcional al valor (respecte al valor màxim possible) i el valor numèric amb les seves unitats. El valor ens arriba amb un punt per separar els decimals, que fàcilment podem substituir per la coma normativa. Atès que cada un dels títols de columna té una amplada diferent, els posem dins d'una caixa que els fixa l'amplada. També posem un farciment per tal que el valor no quedi enganxat a la barra.
Un cop ja hem creat la llista amb les cinc fileres, retornem una columna que les conté. Per poder separar les barres, posem la columna dins d'una caixa amb una mida fixada i li diem que deixi espai entre els elements (spaceAround); també ho posem dins d'un farciment perquè no s'enganxi als voltants.
La pantalla principal, amb la funció, ens quedarà així:
pant_principal.dart
import 'package:flutter/material.dart'; import 'package:sheets/core/colors.dart'; import 'package:sheets/data/crida_dades.dart'; import 'package:sheets/data/dades_rebudes.dart';
class PantPrincipal extends StatefulWidget {
const PantPrincipal({super.key});
@override
State<PantPrincipal> createState() => _PantPrincipalState();
}
class _PantPrincipalState extends State<PantPrincipal> {
Future<DadesRebudes?>? _rebut;
CridaDades cridaDades = CridaDades();
@override
void initState() {
super.initState();
_rebut = cridaDades.llegirDades();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Valors actuals"),),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton(
onPressed: () {
setState(() {
_rebut = cridaDades.llegirDades();
});
},
child: Text("Recarrega"),
),
),
FutureBuilder(future: _rebut, builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
} else if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
} else if(snapshot.hasData){
return mostraBarres(snapshot.data);
} else {
return Text("No s'han trobat resultats");
}
})
],
),
);
}
}
Widget mostraBarres(DadesRebudes? dades){
if(dades != null){
if(dades.nom.length >=5){
ColorsApp colorsApp = ColorsApp();
List<Widget> barres = [];
Widget element;
double llarg;
for(int i = 0; i < dades.nom.length; i++){
llarg = double.parse(dades.darrer[i]) * 250 / double.parse(dades.maxim[i]);
element = Row(
children: [
SizedBox(
width: 30,
child: Text(
dades.titol[i],
style: TextStyle(
fontSize: 20,
color: colorsApp.colorsColumnes[i],
fontWeight: FontWeight.bold
)
),
),
Container(
height: 40,
width: llarg,
color: colorsApp.colorsColumnes[i],
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
"${dades.darrer[i].replaceAll('.', ',')} ${dades.unitats[i]}",
style: TextStyle(
fontSize: 14,
color: colorsApp.colorsColumnes[i],
)
),
),
],
);
barres.add(element);
}
return Padding(
padding: const EdgeInsets.all(12),
child: SizedBox(
width: double.infinity,
height: 300,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: barres,
),
),
);
}
}
return Text("Dades incompletes");
}
En lloc de barres, podem mostrar columnes. En aquest cas el gràfic serà una filera que tindrà una columna per a cada element. A cada columna hi haurà el text amb el valor, el contenidor que dibuixa la columna i el text que fa de títol. A la filera establim que volem que les columnes es situïn a la part inferior i espaiades.
...
Widget mostraColumnes(DadesRebudes? dades){
if(dades != null){
if(dades.nom.length >=5){
ColorsApp colorsApp = ColorsApp();
List<Widget> columnes = [];
Widget element;
double alt;
for(int i = 0; i < dades.nom.length; i++){
alt = double.parse(dades.darrer[i]) * 150 / double.parse(dades.maxim[i]);
element = Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
"${dades.darrer[i].replaceAll('.', ',')} ${dades.unitats[i]}",
style: TextStyle(
fontSize: 14,
color: colorsApp.colorsColumnes[i],
)
),
Container(
height: alt,
width: 40,
color: colorsApp.colorsColumnes[i],
),
Text(
dades.titol[i],
style: TextStyle(
fontSize: 20,
color: colorsApp.colorsColumnes[i],
fontWeight: FontWeight.bold
)
)
],
);
columnes.add(element);
}
return SizedBox(
width: double.infinity,
height: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.end,
children: columnes,
),
);
}
}
return Text("Dades incompletes");
}
Els canvis a la resta de la pantalla principal són mínims:
...
FutureBuilder(future: _rebut, builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
} else if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
} else if(snapshot.hasData){
return mostraColumnes(snapshot.data);
} else {
return Text("No s'han trobat resultats");
}
})
...

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.