Adding Interactivity

In this chapter, we'll add a filter to our dashboard to make it more interactive. The filter will allow users to look at specific sets of orders based on their status: processing, completed, or shipped.

Cube.js makes it easy to add such dynamic features because we don't need to add anything to our data schema. We already have a dimension, Orders.status, and we can just filter by this dimension by adding filters properties to our JSON query.

Say we have the following query, which is used to plot an area chart with the number of orders over time grouped by the product category.

{
  measures: ["Orders.count"],
  timeDimensions: [
    {
      dimension: "Orders.createdAt",
      granularity: "month",
      dateRange: "last year"
    }
  ],
  dimensions: ["ProductCategories.name"]
}

To load only completed orders with this query, we need to add a filters property to it.

{
  measures: ["Orders.count"],
  timeDimensions: [
    {
      dimension: "Orders.createdAt",
      granularity: "month",
      dateRange: "last year"
    }
  ],
  filters: [
    {
      member: "Orders.status",
      operator: "equals",
      values: ["completed"]
    }
  ],
  dimensions: ["ProductCategories.name"]
}

You can learn about all the filters operators in the query format docs.

So all we need to do to make the filter work is to conditionally add this filters property to all our dashboard queries. To do this, let's introduce the dashboardItemsWithFilter method in dashboard-app/src/pages/DashboardPage.js. In this method, we check if the filter value s any other rather than "all" we inject the filters property with the corresponding filter value to all the queries.

const dashboardItemsWithFilter = (dashboardItems, statusFilter) => {
  if (statusFilter === "all") {
    return dashboardItems;
  }

  const statusFilterObj = {
    member: "Orders.status",
    operator: "equals",
    values: [statusFilter]
  };

  return dashboardItems.map(({ vizState, ...dashboardItem }) => (
    {
      ...dashboardItem,
      vizState: {
        ...vizState,
        query: {
          ...vizState.query,
          filters: (vizState.query.filters || []).concat(statusFilterObj),
        },
      }
    }
  ))
};

Now, we need to render the user input for the filter. We can use the <ButtonGroup /> component from the Material UI kit for this and render a button per the possible state of the order plus the "All" button. We'll use the React useState hook to store and update the filter value.

First make sure to import useState and the required components from Material UI.

-import React from "react";
+import React, { useState } from "react";
+import Button from '@material-ui/core/Button';
+import ButtonGroup from '@material-ui/core/ButtonGroup';

Next, we render the buttons group and change the value of the statusFilter on the button's click. Note that we use the newly created dashboardItemsWithFilter method to iterate over dashboard items for rendering.

-  return DashboardItems.length ? (
-    <Dashboard>{DashboardItems.map(dashboardItem)}</Dashboard>
-  ) : (
+  const [statusFilter, setStatusFilter] = useState("all");
+  return DashboardItems.length ? ([
+    <ButtonGroup style={{ padding: "24px 24px 0 24px" }} color="primary">
+      {["all", "processing", "completed", "shipped"].map(value => (
+        <Button
+          variant={value === statusFilter ? "contained" : ""}
+          onClick={() => setStatusFilter(value)}>
+          {value.toUpperCase()}
+        </Button>
+      ))}
+    </ButtonGroup>,
+    <Dashboard>
+      {dashboardItemsWithFilter(DashboardItems, statusFilter).map(dashboardItem)}
+    </Dashboard>
+  ]) : (

That is all we need to create a simple filter and make our D3 dashboard dynamic and interactive.

Congratulations on completing this guide! 🎉

You can check the online demo of this dashboard here and the complete source code of the example app is available on Github.

I’d love to hear from you about your experience following this guide. Please send any comments or feedback you might have in this Slack Community. Thank you and I hope you found this guide helpful!