Vega-Lite chart to show activity grouped by hour of day and day of week


#1

I posted a high-level overview of this example on Losant’s blog, but I wanted to dig in a little deeper in case anyone else was trying to build something similar. First off, here’s a screenshot of the result:

The data behind this graph is a people counter that counts the number of times a beam was crossed. This chart groups that activity by day of week and hour of day, which provides a good visual into the most active times for this area of the building.

The same information graphed on a built-in time series graph looks like this:

The actual device attributes look like this:

The important attribute is tripsPerMinute which is the raw data. peoplePerMinute is being calculated later by dividing the raw trips by two (since each person will trigger the sensor twice as they enter and exit). The sensor connects to Losant every minute and reports the number of trips it counted that minute.

In the Custom Chart Block, the time series query looks like this:

I’m querying 30 days of data with 60 minute resolution. The aggregation is sum. This means I’ll get a data point for each hour with the sum of all trips that occurred in that hour. 30 days of data with a point every 60 minutes will yield 720 data points.

The raw data of this query looks like this:

[
  { "time": <time>, "value": <value> },
  { "time": <time>, "value": <value> },
  { "time": <time>, "value": <value> },
  ...
]

Below is the Vega-Lite configuration that renders the final result. It’s based on Vega-Lite’s Table Bubble Chart example.

{
  "$schema": "https://vega.github.io/schema/vega-lite/v2.json",
  "width": {{block.width}},
  "height": {{block.height}},
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "config": {
    "axis": {
      "labelColor": "#888c95",
      "titleColor": "#888c95",
      "domain": false
    },
    "legend": {
      "labelColor": "#888c95",
      "titleColor": "#888c95"
    },
    "view": {
      "fill": "#414855",
      "stroke": "transparent"
    }
  },
  "data": {
    "name": "time-series-0"
  },
  "mark": "circle",
  "encoding": {
    "y": {
      "field": "time",
      "type": "ordinal",
      "timeUnit": "day",
      "sort": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
    },
    "x": {
      "field": "time",
      "type": "ordinal",
      "timeUnit": "hours"
    },
    "size": {
      "field": "value",
      "type": "quantitative",
      "aggregate": "mean"
    },
    "color": {
      "field": "value",
      "aggregate": "mean",
      "type": "quantitative",
      "scale": {
        "scheme": "plasma"
      }
    }
  }
}

The top config object is setting all the colors so it matches Losant’s dark theme.

The data field is pulling data from a Vega-Lite dataset that is automatically populated by Losant. Losant adds an entry to the datasets object for each query you specify. The name of the dataset matches whatever you entered in the Query Name field when you added the query. In this example, I used the default name provided by Losant, which is “time-series-0”.

"data": {
  "name": "time-series-0"
}

The mark field sets the visual style of the points on the graph, which is this example is circle.

 "mark": "circle"

The encoding field controls what data to display and how to display it.

Since both of my axis are time (day of week and time of day), both the x and y fields are displaying the time field from the underlying data source.

"y": {
  "field": "time",
  "type": "ordinal",
  "timeUnit": "day",
  "sort": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
},
"x": {
  "field": "time",
  "type": "ordinal",
  "timeUnit": "hours"
}

The type is set to ordinal. This could be temporal, however Vega-Lite handles this conversion automatically, and since we’re bucketing our data into larger groups, ordinal data works a little better. The timeUnit fields controls how to bucket our time information. Since the y axis is showing day of week, the timeUnit is set to “day”. Since the x axis is showing hour of day, the timeUnit is set to “hours”.

The actual value of the result is being represented the size of the circle and the color of the circle. The size and color mark properties are what control those.

"size": {
  "field": "value",
  "type": "quantitative",
  "aggregate": "mean"
},
"color": {
  "field": "value",
  "aggregate": "mean",
  "type": "quantitative",
  "scale": {
    "scheme": "plasma"
  }
}

Both the size and the color are based on the value property of the underlying data source. As a reminder, each value will be the sum of all trips that occurred over that hour. Since over 30 days there are many values for the same day of week -> hour of day combination, the aggregate controls how to combine those together. In this example, I averaged them.

Vega-Lite has many built-in color schemes and the plasma option is being used in this example. Vega-Lite will automatically map the data values onto the color scheme to give it that heatmap like appearance.

Understanding the concepts behind Vega-Lite can be a little challenging at first, but once you wrap your head around it, there is a lot of power to display data. Hopefully this blog article helps you figure out some of those concepts with a real-world example.

Additional Resources