How to Visualise Firebase Data with Chart.js and Ionic Last update: 2018-02-27
We’ve seen many Firebase realtime apps with chats, shopping lists and the classic todo list. But there’s so much else that can benefit from the live updates and sync of your data. What if we could visualise Firebase Data?
In this tutorial we will combine 3 great things: Firebase, Chart.js and Ionic!
We will build an app that can save data to our Firebase backend and at the same time create a nice visualisation of the aggregated Firebase data. It’s going to be tricky, but here’s what you can expect.
This means, we track expenses or incomes with a value and month attached (for simplicity here only 4 months, the rest is left for the reader) and automatically update our charts with the new combined data of each month!
But before we get into the actual app, we need some basics set up on Firebase.
Preparing your Firebase App
First of all you need a Firebase account (which is free) and create a new app there. Inside your app navigate to the Database section and select Rules as we want to disable any authentication checks for this tutorial. Paste in the following dummy rules there:
{
"rules": {
".read": "true",
".write": "true"
}
}
Of course for production you might want to add more advanced security rules and also create user accounts and so on. If you want to learn more on Firebase check out the courses of the Ionic Academy which has got multiple courses on different Firebase topics (like Storage, user management..)!
To finish your setup now go to the overview of your Firebase app and select ”Add Firebase to your web app” and copy the config which we will need soon inside Ionic.
Setting up the App
We start with a blank new Ionic app and install all the packages we need directly, which means AngularFire and Firebase plus the Chart.js npm package as well:
ionic start devdacticCharts blank
cd devdacticCharts
npm i chart.js firebase angularfire2
Next we need to load the plugins and also connect our app to Firebase. Here we need the configuration you copied earlier, so replace the values with yours in the code below. Also, add the needed AngularFire modules to your imports so we can use them later. In this case we only need the basic module and the Database.
Open your app/app.modules.ts and change it to:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { DataProvider } from '../providers/data/data';
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
var firebaseConfig = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
@NgModule({
declarations: [
MyApp
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp),
AngularFireModule.initializeApp(firebaseConfig),
AngularFireDatabaseModule,
],
bootstrap: [IonicApp],
entryComponents: [
MyApp
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler},
]
})
export class AppModule {}
That’s all for the setup, let’s get into some details!
Loading & Adding Data to Firebase
Before we can display any fancy chart inside our app we need some data stored inside our Firebase Database. In this tutorial I’ll simply split up the single functions of the pages/home/home.ts into code listings but it’s all the same controller over the next listings!
First of all make sure you got all the imports plus some variables. We need those to have a reference to our Firebase list, to display clear names for Months and also a transaction object that we can fill and then add to Firebase.
Also, we need a ViewChild so we can access our charts later which are simply a canvas object (more on that later).
For now, change the base of your pages/home/home.ts to:
import { Component, ViewChild } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import { AngularFireList } from 'angularfire2/database/interfaces';
import { ToastController } from 'ionic-angular/components/toast/toast-controller';
import { Chart } from 'chart.js';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
data: Observable<any[]>;
ref: AngularFireList<any>;
months = [
{value: 0, name: 'January'},
{value: 1, name: 'February'},
{value: 2, name: 'March'},
{value: 3, name: 'April'},
];
transaction = {
value: 0,
expense: false,
month: 0
}
@ViewChild('valueBarsCanvas') valueBarsCanvas;
valueBarsChart: any;
chartData = null;
constructor(public navCtrl: NavController, private db: AngularFireDatabase, private toastCtrl: ToastController) {
}
}
First of all we now want to establish a connection to our Firebase data when the view is loaded. Therefore, we query a list and I also added ordering by the child value “month” which is not mandatory but if you want to present the data inside a list as well, ordering it directly makes totally sense! We can now also subscribe to any changes that occur on this node and immediately get the new data. That’s the spot where we want to either create our update our charts. The setup of a chart only needs to happen once, you can also add it somewhere else if you want to, but make sure it’s called before updating the data next time.
Now ad your first function to the pages/home/home.ts:
ionViewDidLoad() {
// Reference to our Firebase List
this.ref = this.db.list('transactions', ref => ref.orderByChild('month'));
// Catch any update to draw the Chart
this.ref.valueChanges().subscribe(result => {
if (this.chartData) {
this.updateCharts(result)
} else {
this.createCharts(result)
}
})
}
Of course we can’t display data if we don’t have any data, therefore we continue with the function to add a new transaction. This function will simply use the existing reference we created before and push a new node with the value of the transaction
object. This object will be filled later through our view, so we only need to send it and then reset it plus showing a little toast to inform the user that something happened!
Go ahead and add the function below the above one:
addTransaction() {
this.ref.push(this.transaction).then(() => {
this.transaction = {
value: 0,
month: 0,
expense: false
};
let toast = this.toastCtrl.create({
message: 'New Transaction added',
duration: 3000
});
toast.present();
})
}
Now we got the basic read/write stuff for Firebase in place. It’s always amazing to see how fast those methods actually can be implemented!
Creating Charts from Data
The previous part was easy (hopefully?) so now it get’s a bit more complicated as we need to create our charts and fill them with the right values.
It’s not super trivial, so if you want to change things and build your own visualisation check out the docs on how your data sets need to look like!
For now, we start with a function that will transform the data list we got from Firebase into an array with cumulated values for each month.
In our example we only got 4 months, so we create an object with the 4 keys for the months and then iterate over our Firebase data. If something is an expense we need to subtract it, else we just add the value. If the value inside reportByMonth
object is not yet set, we simply start with the value of the iteration.
I’ve added the + before every value to make sure it’s converted to a number, so this line looks kinda strange:
0 - +trans.value;
But it simply says to start with zero and subtract the value of the transaction.
Once we got the key/value pairs ready, we convert all of it back to an array as the chart needs this. Yes we could have used an array in the first place, but making the calculation wouldn’t be as easy as it is with the map.
Anyway, now add your next function below your existing ones:
ngetReportValues() {
let reportByMonth = {
0: null,
1: null,
2: null,
3: null
};
for (let trans of this.chartData) {
if (reportByMonth[trans.month]) {
if (trans.expense) {
reportByMonth[trans.month] -= +trans.value;
} else {
reportByMonth[trans.month] += +trans.value;
}
} else {
if (trans.expense) {
reportByMonth[trans.month] = 0 - +trans.value;
} else {
reportByMonth[trans.month] = +trans.value;
}
}
}
return Object.keys(reportByMonth).map(a => reportByMonth[a]);
}
We can now build a useful data object for our chart, so let’s implement the functionality to actually create the chart in the first place.
We can create a new Chart by using our ViewChild and saving it back to a local variable which we need to update the chart later. The initialisation of your Chart can become very long as you can see, and these are just a few settings. You can go really bonkers on all the settings regarding the UI, the axes, the values, the colors..and of course the type of the chart a s well!
In this case we picked the type “bar”, but there are a lot of amazing other types you could give a try.
Besides that special configuration for the bar you only need to make sure you get the data.labels field right and feed in your previously created array of data into data.datasets.data.
Now add the next function like this:
createCharts(data) {
this.chartData = data;
// Calculate Values for the Chart
let chartData = this.getReportValues();
// Create the chart
this.valueBarsChart = new Chart(this.valueBarsCanvas.nativeElement, {
type: 'bar',
data: {
labels: Object.keys(this.months).map(a => this.months[a].name),
datasets: [{
data: chartData,
backgroundColor: '#32db64'
}]
},
options: {
legend: {
display: false
},
tooltips: {
callbacks: {
label: function (tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index] +' $';
}
}
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true
}
}],
yAxes: [{
ticks: {
callback: function (value, index, values) {
return value + '$';
},
suggestedMin: 0
}
}]
},
}
});
}
Finally, the update function makes use of the locally stored reference to our chart and simply updates the datasets. In the setup you’ve already seen that datasets itself can be an array, therefore you should make sure you update all datasets (although we know here that there’ll be only one set in this case).
Create your update function like this:
updateCharts(data) {
this.chartData = data;
let chartData = this.getReportValues();
// Update our dataset
this.valueBarsChart.data.datasets.forEach((dataset) => {
dataset.data = chartData
});
this.valueBarsChart.update();
}
That’s all from the JavaScript side! Hope you are not lost at this point, perhaps take a moment to go back to the report calculation and chart creation before finishing the tutorial with the actual view.
Crafting the View
We have all objects and functions in place so we just need to build a tiny little view that triggers all the great actions. We create a super simple input logic to hook up our fields to the transaction
variable and some logic to the button at the start of the row to switch the expense state and therefore display a plus or minus.
The actual chart is inside a canvas and can be reached as a viewChild by the name ”valueBarsCanvas“.
If you want to hide your charts while loading data, make sure you are not using *ngIf as this will get you into problems as the object will not be created and therefore your viewChild will not be found. Therefore, I simply used the hidden attribute on the card element above to make sure the card will be displayed only when we got some data for the chart!
Now add the las missing piece and change your pages/home/home.html to:
<ion-header>
<ion-navbar color="primary">
<ion-title>Transactions</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-row align-items-end>
<ion-col col-2>
<button ion-button icon-only outline (click)="transaction.expense = !transaction.expense" [color]="transaction.expense ? 'danger' : 'primary'">
<ion-icon name="remove" *ngIf="transaction.expense"></ion-icon>
<ion-icon name="add" *ngIf="!transaction.expense"></ion-icon>
</button>
</ion-col>
<ion-col col-5>
<ion-item>
<ion-label floating>Value</ion-label>
<ion-input type="number" [(ngModel)]="transaction.value"></ion-input>
</ion-item>
</ion-col>
<ion-col col-5>
<ion-item>
<ion-label floating>Month</ion-label>
<ion-select [(ngModel)]="transaction.month">
<ion-option [value]="month.value" *ngFor="let month of months">{{ month.name }}</ion-option>
</ion-select>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<button ion-button full (click)="addTransaction()">Add Transaction</button>
</ion-col>
</ion-row>
<ion-card [hidden]="!chartData">
<ion-card-header>
Analytics
</ion-card-header>
<ion-card-content>
<canvas #valueBarsCanvas></canvas>
</ion-card-content>
</ion-card>
</ion-content>
That’s it!
You’ve just build your own realtime charts app with backend connection - how cool is that?
Think of all the great apps you could build now!
Conclusion
Firebase is really powerful in a lot of ways, and combining it with the great Chart.js package allows us to show a beautiful data visualisation which get’s updated in real time. Perhaps a dashboard with updating charts would be something your boss would really enjoy? I heard they like charts. And colours.
So what could you build based on this template?
Let me know your ideas!
You can also find a video version of this article below.