In the previous tutorial, we saw how filtering a table can redraw charts. The charts themselves were not interactive. In this tutorial, we close the loop by making the charts trigger changes in the FormHandler table.
We will use a different visualization this time - a colored table which shows total sales for different regions and product categories.
Such a table is called a cross-tabulation or a contingency table - it is a common operation used to aggregate a metric (in this case, sales) across two dimensions (region and product category).
Do play around with the sample application to get an better idea of our goal for this tutorial. Specifically, take a look at how:
By the end of this tutorial, you will have learned how to:
After finishing this tutorial, you will have an application like this:
This tutorial assumes that you have gone through the previous tutorial. Specifically, we will be building on:
To begin with, let’s just reproduce some of what we did in the last tutorial, beginning with laying out a FormHandler table. Add the FormHandler table to your application by adding the following code in the <body> of index.html.
<div class="formhandler" data-src="data"></div>
<script>
$(".formhandler").formhandler({ pageSize: 5 });
</script>
Now we need to add some space in the page to hold the chart. Add a placeholder for the chart in your page as follows:
<div id="chart"></div>
We will be rendering the chart through a javascript
function, similar to the draw_charts
function from the previous tutorial.
FormHandler can be used to transform a dataset
in a variety of ways. In this example, we will use FormHandler’s
modify
function to perform the cross-tabulation.
Add the following to gramex.yaml to create a HTTP resource which cross-tabulates the data.
store-sales-ctab:
pattern: /$YAMLURL/store-sales-ctab
handler: FormHandler
kwargs:
url: $YAMLPATH/store-sales.csv
modify: data.groupby(['Category', 'Region'])['Sales'].sum().reset_index()
In the snippet above, we are creating a new endpoint to serve the cross-tabulated data. After
saving gramex.yaml
, visit
http://localhost:9988/store-sales-ctab
in your
browser. You should see a JSON array containing 12 objects, each of which represents a
combination of a region and a product category, as follows:
[
{ Category: "Furniture", Region: "Central", Sales: 130887.5002000001 },
{ Category: "Office Supplies", Region: "East", Sales: 125007.708 },
{ Category: "Technology", Region: "South", Sales: 97852.945 },
// etc
];
Note: The transforms supported by FormHandler work seamlessly with pandas. Almost evey transformation can be expressed as a pandas expression. See FormHandler documentation for details.
Just like we had a specification for the bar charts in the previous examples, we will use a different specification for the color table chart.
Add the following Vega specification for a heatmap chart to your index.html.
var spec = {
width: 360,
height: 270,
data: { url: "store-sales-ctab" },
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
encoding: {
y: { field: "Category", type: "nominal" },
x: { field: "Region", type: "nominal" },
},
layer: [
{
mark: "rect",
selection: { brush: { type: "interval" } },
encoding: {
color: {
field: "Sales",
type: "quantitative",
legend: { format: "0.1s" },
},
},
},
{
mark: "text",
encoding: {
text: { field: "Sales", type: "quantitative" },
color: {
condition: { test: "datum['Sales'] < 100000", value: "black" },
value: "white",
},
},
},
],
};
Next, let’s write a function to compile this specification into a Vega view and draw the chart. Add the following function to index.html to compile the chart specification into a Vega view and draw the chart.
function draw_chart() {
var view = new vega.View(vega.parse(vl.compile(spec).spec))
.renderer("svg")
.initialize("#chart")
.hover()
.run();
}
draw_chart();
At this point, you should be able to see the chart. Again, as in the previous tutorial, the next step is to redraw the chart on URL changes.
In the previous tutorial, we had managed to obtain the hash changes in the URL and use
these as queries on the original dataset. In this case,
remember that we are using two different endpoints for the table and the chart - i.e.
/data
for the table and
/store-sales-ctab
for the chart. Thus, to
render the chart successfully on URL changes, we must be able to grab filters from the
table and apply them to the cross-tab endpoints. This, too, involves setting the
data.url
attribute of the chart specification on each change in the URL.
Add the following function to index.html to get URL changes and apply them to the chart spec.
var baseDataURL = spec.data.url; // keep the original URL handy
function redrawChartFromURL(e) {
if (e.hash.search) {
// if the URL hash contains filters, add them to the spec's URL
spec.data.url = baseDataURL + "?" + e.hash.search;
} else {
spec.data.url = baseDataURL;
} // otherwise restore to the original URL
draw_chart(); // draw the chart
}
$("body").urlfilter({ target: "pushState" });
$(window).on("#?", redrawChartFromURL).urlchange();
At this point, the chart should redraw itself based on the table filters. As an example,
try setting the Region
column to South
. The chart should contain only one column now.
Similarly, try filtering by some columns except Category
or Region
, and the sales
values in the chart should change.
Finally, we need to close the loop by making the chart itself interactive, i.e., filtering the table automatically as any cell in the chart is clicked. Essentially, this amounts to:
function filterTableOnClick(event, item) {
var query = { Region: item.datum.Region, Category: item.datum.Category };
var url = g1.url.parse(location.hash.replace("#", ""));
}
We need to run this function on every click that is registered on the chart. Therefore,
we will add this function as an event listener to the chart. Since we’re drawing the chart
inside the draw_chart
function, we need to add the event listener within the function as
well.
function draw_chart() {
var view = new vega.View(vega.parse(vl.compile(spec).spec))
.renderer("svg")
.initialize("#chart")
.hover()
.run();
view.addEventListener("click", filterTableOnClick);
}
That’s it!
Save the HTML file and refresh the page. You should be able to see a two-way interaction between the chart and the page. Whenever you click on a cell in the chart, a pair of filters should show up near the top right corner of the table, and conversely whenever you apply a filter to the table, it should reflect in the chart.