Data visualization

10-12-2018

After collecting data the next step is to use this information. And one of the best ways to do so is paint beautiful graphs based on the numbers. There are many tools to support you like kibana or grafana which are both open source software and capable to display large amounts of data coming from live streams. If you want to get your hands a bit dirty you can also go for one of the bazillion javascript frameworks. Both ways have benefits and drawbacks, for larger projects with possibly huge datasets, kibana and co. are the way to go. Those tools come with query languages to generate graphs from data stores like elastic search, are battle tested in big data environments and basically industry standard, so support and general knowledgebase is great. The drawbacks are a more complicated setup and hosting than a browser based javascript solution. In principle, with each solution path you have to decide what data you want to display and how you want to display it.

For this project or comparably sized ones I recommend setting up a custom visualization with javascript to save the effort of hosting another data storing solution and a dedicated visualization service. If you already have a complete kibana/grafana or comparable setup this is another story, then you should just add your project to the existing setup. If not the hosting of a static web app is pretty easy, even firebase has a hosting solution included.

So the first step is to install the firebase-tools as a global package to control your firebase projects from the terminal.

npm install -g firebase-tools

Then use create-react-app to bootstrap a new react based web application. Run the following command to create a new folder (APP_NAME) containing the code for the application:

npx create-react-app APP_NAME

When the initialization process is complete you can start a development server to start working on the web app with the command npm start run in the project folder. With the command npm run build you can create an optimized build to serve over the internet.

To publish the web site to firebase start by signing in with the command firebase login and continue with firebase init. During the init process you have to make some choices:

  • Which Firebase CLI features do you want to setup for this folder?: Hosting
  • What do you want to use as your public directory? build
  • Configure as a single-page app (rewrite all urls to /index.html)? yes

With firebase initialized you can now run firebase deploy to publish your application, remember to run npm run build to have a freshly built version in the build folder.

When you got familiar with the basic project setup you need to install two more dependencies: [@firebase/app](https://www.npmjs.com/package/firebase) and [recharts](https://www.npmjs.com/package/recharts). The firebase package let’s you connect to firebase and access your data, the recharts package provides composable react-components to build up charts and graphs.

npm install firebase recharts

To connect to your firestore instance start by initializing the firebase app and then get the reference to the firestore:

import app from 'firebase/app'
import 'firebase/firestore'

const config = {
  apiKey: YOUR_API_KEY,
  databaseURL: YOUR_DATABASE_DOMAIN,
  projectId: YOUR_PROJECT_ID,
}

const firebaseApp = app.initializeApp(config)

const firestore = firebaseApp.firestore()
const settings = { timestampsInSnapshots: true }
firestore.settings(settings)

With the working firestore reference you can now query data by using the firestore API:

function subscribeToSensorData(onUpdate) {
  return firestore
    .collection('sensorData') // choose the collection of your data
    .orderBy('date', 'desc') // order the saved documents by date descending
    .limit(40) // only take the last 40 entries
    .onSnapshot(snapshotData => {
      onUpdate(
        // on an update deliver the data to the callback
        // transform the snapshot data to the actual objects
        snapshotData.docs.map(documentSnapshot => {
          const data = documentSnapshot.data()

          return {
            ...data,
            date: new Date(data.date.seconds * 1000),
          }
        })
      )
    })
}

But when you call this function and try to read data you will get this error:

error message of firestore, notifying that there are permissions missing to read

This means that you have to configure the rules of the database so that reads are allowed. To change the rules go to the database settings in the firebase console and choose the rules tab. The following ruleset will allow all reads and deny writes without admin access.

database rules with public read- and private write-access

Now after allowing reads, the next step is to connect the firebase data with the actual app. A React component extending the Component class can hold an instance state object which will trigger a re-render whenever it gets updated by the setState method of the component. So this state is ideal to hold the graph data. To subscribe to the firestore updates and save the result in the instance state extend the App as follows:

class App extends Component {
  // initial state with an empty data array
  state = { sensorData: [] }

  // method that gets called when this component
  // instance got inserted into the view
  componentDidMount() {
    this.unsubscribe = subscribeToSensorData(this.onSensorData)
  }

  // method that gets called when this component
  // instance will be removed
  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe()
    }
  }

  // method to store the given data in the component state
  onSensorData = sensorData => {
    this.setState({ sensorData })
  }

  render() {...}

}

The App component now has access to the stored sensorData and finally you can start to render a chart with it. The library Recharts provides a lot of functionality and you should definitely have a look into the documentation recharts.org

Here is an example of a line-chart displaying the temperature over the date:

...
  render() {
    return (
      <LineChart width={500} height={300} data={this.state.sensorData}>
        <XAxis dataKey="date" />
        <YAxis
          orientation="left"
          type="number"
          yAxisId="temperature"
          domain={[-50, 50]}
        />
        <Line
          dot={false}
          type="monotone"
          dataKey="temperature"
          yAxisId="temperature"
          stroke="#ee2142"
        />
        <Legend
          content={
            <div>
              <div style={{ color: '#ee2142' }}>Temperature in °C</div>
            </div>
          }
        />
      </LineChart>
    )
  }
...

Tasks

  • choose fitting charts to display the temperature and humidity values
  • deploy your chart(s) to firebase