Dashboards are the backdrops on which all controls and charts are bundled together. Since they act as the unifying component of the visualization and controls, dashboards can also be intentionally linked to create dependencies between controls. Controls may also be used to set a visualization to a predefined state, or act as a trigger point for ChartWrapper
events.
In general, a visualization with dashboard, controls, and events will always follow a pattern similar to the following outline:
<html> <head> <script> function visualization{ // Listen for an error event to happen google.visualization.events.addListener(chart/dashboard, 'error/ready/select',run_on_error); // Load the Wrapper libraries google.load // Data creation, linking methods don't change var data = google.visualization.DataTable // Create some controls var ctrl = new google.visualization.ControlWrapper // Create a chart var chart = new google.visualization.ChartWrapper // Bundle controls and charts together on a dashboard. Draw the visualization. google.visualization.Dashboard bind( ) draw(data) } // Run this function when addListener "hears" the eventfunction run_on_error{ } </script> </head> . . . </html>
Now that it's possible to create "triggers" to listen for events in the dashboard, it is useful to harness the triggering ability through the user interface. With the Visualization API, the API controls as well as standard JavaScript interactivity, such as onload
and onclick
, are accepted methods to initiate chart events from the user interface.
The purpose of ControlWrapper
controls is to allow the viewer to manipulate the chart data into their own desired visualization using on-screen graphical methods. Controls do not alter the dataset itself, but instead provide a filtered view only. This method is desirable to both developer and user as it presents anyone viewing the chart to customize the visualization to their needs sans concern of damaging the visualization or data by doing so.
There are several kinds of filters available:
StringFilter
NumberRangeFilter
CategoryFilter
ChartRangeFilter
StringFilter
control is essentially a search filter for the data table. The user enters strings of characters as search queries into a field, and the visualization dynamically displays the results of the search. The StringFilter
control is defined using the ControlWrapper
method:
varMyFilter = google.visualization.ControlWrapper({ 'controlType': 'StringFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'your_column_name' } });
The following chart example presents the same 2010 Chicago Census data that has been a continuing resource throughout this book. However, in this case, only the data for the male population is used. In the example, users may enter a sequence of numbers in the search field, and the chart view is reduced to only those entries where the sequence of numbers exists in the Male column.
The preceding data for age groups of males is loaded into the visualization with the arrayToDataTable
function. Of course, an alternative method would be to use a data source method as described in Chapter 6, Data Manipulation and Sources, such as a Spreadsheet URL or JSON feed. If a data source method is chosen, remember that the ChartWrapper
function used for charts with controls provides the data source linkage methods within ChartWrapper
. The following data array is created in the example chart's script.
['Age', 'Male'], ['Under 5 years', 94100], ['5 to 9 years', 84122], ['10 to 14 years', 83274], ['15 to 19 years', 91528], ['20 to 24 years', 108407], ['25 to 29 years', 134931], ['30 to 34 years', 119828], ['35 to 39 years', 100651], ['40 to 44 years', 89957], ['45 to 49 years', 85645], ['50 to 54 years', 80838], ['55 to 59 years', 68441]
As mentioned earlier in the chapter, when building dashboards, the control elements and chart must be defined first. To define the control for the StringFilter
, use the ControlWrapper
class to create the object with the StringFilter
control being the controlType
. Also, define which <div id>
element you wish to use as a container. The containerID
option sets the <div id>
, anchoring the control to the HTML page. Control has a variety of variables that can be set at the time of declaration, most of them being optional. However, for the StringFilter
control in this instance, a column index or label is required in order for the control to know on which column to apply the search. This is accomplished by the addition of the filterColumnLabel
option
which provides the the filter's definition. In the example, the filter is set to search on the Male column.
// Define a StringFilter control for the 'Male Population' column var stringFilter = new google.visualization.ControlWrapper({ 'controlType': 'StringFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'Male' } });
Now that the search filter has been defined, the chart itself must be configured. In this example, a table visualization is the end result. To build the table and make it compatible with the controls just defined, use the ChartWrapper
class. When defining ChartWrapper
, configure chartType
, the <div id>
container HTML element, in addition to other formatting.
// Define a table visualization var table = new google.visualization.ChartWrapper({ 'chartType': 'Table', 'containerId': 'mydivid', 'options': {'height': '13em', 'width': '20em'} });
To complete the dashboard, the ControlWrapper
and ChartWrapper
components must be assigned to the dashboard component. This is accomplished through the bind
function. The semantics for bind
are to first state the control to be bound in parentheses; with the data object it is to be bound to as the second input variable. The bind
function may also be used in series to create dependent controls that manipulate the same visualization. Dependent controls are a topic covered later in this chapter.
// Create the dashboard. var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard')). // Configure the string filter to affect the table contents bind(stringFilter, table). // Draw the dashboard draw(data);
For reference, the following code example for a StringFilter
control is presented in its entirety, including the HTML framework.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title> Google Visualization API Sample </title> <script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript"> google.load('visualization', '1.1', {packages: ['controls']}); </script> <script type="text/javascript"> function drawVisualization() { // Prepare the data. var data = google.visualization.arrayToDataTable([ ['Age', 'Male'], ['Under 5 years', 94100], ['5 to 9 years', 84122], ['10 to 14 years', 83274], ['15 to 19 years', 91528], ['20 to 24 years', 108407], ['25 to 29 years', 134931], ['30 to 34 years', 119828], ['35 to 39 years', 100651], ['40 to 44 years', 89957], ['45 to 49 years', 85645], ['50 to 54 years', 80838], ['55 to 59 years', 68441] ]); // Define a StringFilter control for the 'Male Population' column var stringFilter = new google.visualization.ControlWrapper({ 'controlType': 'StringFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'Male' } }); // Define a table visualization var table = new google.visualization.ChartWrapper({ 'chartType': 'Table', 'containerId': 'chart1', 'options': {'height': '13em', 'width': '20em'} }); // Create the dashboard. var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard')). // Configure the string filter to affect the table contents bind(stringFilter, table). // Draw the dashboard draw(data); } google.setOnLoadCallback(drawVisualization); </script> </head> <body style="font-family: Arial;border: 0 none;"> <div id="dashboard"> <table> <tr style='vertical-align: top'> <td style='width: 300px; font-size: 0.9em;'> <div id="control1"></div> <div id="control2"></div> <div id="control3"></div> </td> <td style='width: 600px'> <div style="float: left;" id="chart1"></div> </td> </tr> </table> </div> </body> </html>
Before publishing a visualization with controls, it is important to understand how the control actually affects the data view. This may be common sense, but can be an easily overlooked step. Often it may be assumed a filter will behave a certain way, when in reality the actual behavior is quite different. In the following example, the number "1" is entered into the search field. The resulting filter of data returns all entries in the Male column that begins with the integer 1
. An incorrect interpretation of this filter functionality may have been to search the column for the number 1
as it appears anywhere, even numerous times, in the entry.
Similarly, searching for 10
returns only the population numbers that start with 10
. Entries that contain other sequences or alternative string locations, such as 180
or 2010
are not among the results.
At this point, it can be assumed searching for the string 108
will only return the exact sequence of the integer one, followed by the integer zero, followed by the integer eight, which is in fact, the result.
At the time of this publication, the Visualization API does not support using the generally accepted notation of language processing searches, such as wildcards and ranges that do not improve results. At this time, it can then be deduced that the StringFilter
is literally just that, a simple string filter. In this case, the filter may sometimes behave as a search-style control, but is actually not designed for typical search functionality.
Live examples can be found at http://gvisapi-packt.appspot.com/ch7-examples/ch7-stringfilter.html.
Overall, adding controls to a chart is fairly simple. Issues primarily arise when expectations of a certain functionality do not coincide with actual functionality.
A NumberRangeFilter
control is a bar-shaped slider with handles at each end of the bar. The individual viewing the chart is able to manipulate the handles in order to dynamically view a specific range of data on the visualization.
A NumberRangeFilter
is built in the same manner as a StringFilter
. First, the control object for the filter is defined. Notice much of the code is exactly the same, with the exception of the controlType
setting. To create a NumberRangeFilter
, create the same object as with StringFilter
but substitute NumberRangeFilter
as the controlType
.
// Define a slider control for the 'Male Population' column var slider = new google.visualization.ControlWrapper({ 'controlType': 'NumberRangeFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'Male', 'ui': {'labelStacking': 'vertical'} } });
Once again, as with StringFilter
, create the chart component for the NumberRangeFilter
.
var piechart = new google.visualization.ChartWrapper({ 'chartType': 'PieChart', 'containerId': 'chart1', 'options': { 'width': 700, 'height': 300, 'legend': 'bottom', 'chartArea': {'left': 15, 'top': 15, 'right': 0, 'bottom': 0}, 'pieSliceText': 'value' } });
Finally, finalize the drawing of the chart by attaching the control to the visualization dashboard component with bind
.
// Create the dashboard. new google.visualization.Dashboard(document.getElementById('dashboard')). // Configure the slider to affect the piechart bind(slider, piechart). // Draw the dashboard draw(data);
Live examples are available at http://gvisapi-packt.appspot.com/ch7-examples/ch7-numrange.html.
A CategoryFilter
control is a drop-down menu where selecting an item from the drop-down filters the view of the visualization based on that selected item. CategoryFilter
has several customization options, such as allowing multiple selections at a time, or user-typed selections. The following example shows a category filter that sets the chart view by selected age group.
To illustrate the power of visualization when using a category filter, note in the preceding figure, the Under 5 years and 20 to 24 years categories make chart data appear relatively close in value. However, when a third, 25 to 29 years category is added, values of data are not as close as first suggested.
Using CategoryFilter
controls allow users to target specific regions of data without losing the perspective of the entire dataset. The CategoryFilter
control is created in a similar manner to other controls. As with any other visualization, create or import data first.
function drawVisualization() { // Prepare the data. var data = google.visualization.arrayToDataTable([ ['Age Group', 'Male', 'Female'], ['Under 5 years', 94100, 91787], ['10 to 14 years', 84122, 81955], ['15 to 19 years', 83274, 81192], ['20 to 24 years', 91528, 91405], ['25 to 29 years', 108407, 114620], ['30 to 24 years', 134931, 141208] ]);
Then, a variable is defined to provide the age selector control. The control creation also defines an initial state for the chart.
// Define a category picker for the Age Group column. var agePicker = new google.visualization.ControlWrapper({ 'controlType': 'CategoryFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'Age Group', 'ui': { 'allowTyping': false, 'allowMultiple': true, 'selectedValuesLayout': 'belowStacked' } }, // Define an initial state, i.e. a set of age groups to be initially selected. 'state': {'selectedValues': ['Under 5 years', '20 to 24 years']} });
Following the control creation, the bar chart visualization must be defined. The label options for the chart are included as part of the options
definitions.
// Define a bar chart with ChartWrapper. var barChart = new google.visualization.ChartWrapper({ 'chartType': 'BarChart', 'containerId': 'chart1', 'options': { 'colors': ['orange', 'purple'], 'title': ['2010 Chicago Census by Gender & Age Group'], 'hAxis':{'title': "Population"}, 'vAxis':{'title': "Age Group"}, 'width': 550, 'height': 350 } });
Finally, the dashboard is created and the bind
function links the selection control and chart together.
// Create the dashboard. var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard')). // Configure the age picker to affect the chart bind(agePicker, barChart). // Draw the dashboard draw(data); }
Live examples are available at http://gvisapi-packt.appspot.com/ch7-examples/ch7-catfilter.html.
Similar to the NumberRangeFilter
control that filters a data view based on a column's range of values, the ChartRangeFilter
control filters a view based on a time period.
ChartRangeFilter
involves time-based data, which requires extra discussion. Given this need, it is better categorized as part of the Time-based charts section in Chapter 8, Advanced Charts. The following graphic is a ChartRangeFilter
control filtering the main chart view data that is linked to a time period in roughly February 2012.
A series of controls may be linked together, such that the subsequent control relies on the previous control's value. Outside of the Visualization API, this is often referred to as a cascading list of values. This technique is particularly useful with charts that require a selection of data to be systematically pared down from larger categories. A control that filters by country may then become a dependency of another control that filters by state, city, or county. The state filter will automatically receive the filtered results of the country control as its selection options. In this way, it is possible to view data at various levels of granularity by using multiple controls in conjunction.
To illustrate, this is a simple bar graph that has been created from the age group population data of Chicago from 2000, 2005, and 2010. One of the controls on the far-left side has a dependency on the other. When the Year is selected, the graph is populated with that particular year's population for the selected year. To further filter the display, one or more Age Groups may be selected for the given year.
To change the selected data to another year, simply select the Year drop-down and pick a different option. The data displayed will automatically adjust to the Age Group filters already selected. The following dependency example is in its entirety. On initial load, the Year option has been preset to 2010. Setting of the initial state takes place as part of the control declaration itself.
function drawVisualization() { // Prepare the data var data = google.visualization.arrayToDataTable([ ['Year', 'Age Group', 'Population'], ['2000', 'Under 5 yrs', 776733], ['2005', 'Under 5 yrs', 8175173], ['2010', 'Under 5 yrs', 21372], ['2000', '5 to 9 yrs', 3694820], ['2005', '5 to 9 yrs', 70708], ['2010', '5 to 9 yrs', 857592], ['2000', '6 to 10 yrs', 2193031], ['2005', '6 to 10 yrs', 852395], ['2010', '6 to 10 yrs', 348556] ]); var yearPicker = new google.visualization.ControlWrapper({ 'controlType': 'CategoryFilter', 'containerId': 'control2', 'options': { 'filterColumnLabel': 'Year', 'ui': { 'labelStacking': 'vertical', 'allowTyping': false, 'allowMultiple': false } }, 'state': {'selectedValues': ['2010']} }); var agePicker = new google.visualization.ControlWrapper({ 'controlType': 'CategoryFilter', 'containerId': 'control3', 'options': { 'filterColumnLabel': 'Age Group', 'ui': { 'labelStacking': 'vertical', 'allowTyping': false, 'allowMultiple': true } } }); // Define a bar chart to show 'Population' data var barChart = new google.visualization.ChartWrapper({ 'chartType': 'BarChart', 'containerId': 'chart1', 'options': { 'title': ['2000 & 2010 Chicago Census by Gender & Age Group (incl. 2005 estimate)'], 'width': 800, 'height': 300, 'chartArea': {top: 50, right: 50, bottom: 50} }, // Configure the barchart to use columns 1 (age group) and 2 (population) 'view': {'columns': [1, 2]} }); // Create the dashboard. new google.visualization.Dashboard(document.getElementById('dashboard')). // Configure the controls so that: // - the 'Year' selection drives the 'Age Group' one, // - and the 'Age Group' output drives the chart bind(yearPicker, agePicker). bind(agePicker, barChart). // Draw the dashboard draw(data); }
Live examples can be found at http://gvisapi-packt.appspot.com/ch7-examples/ch7-dependentctrl.html.
Working with dependent controls may take some trial and error to make them function as desired.
The purpose of providing programmatic control through the Visualization API is to allow the developer to dynamically set chart attributes through various external sources. Programmatic control
is therefore nothing more than the assignment of available chart attributes through the user interface, or other mechanism capable of triggering an event. Just as the Google Visualization API uses events to enable dynamic activity within itself, JavaScript events such as onclick()
or onload()
also allow web page events to trigger Visualization API customization.
The following example illustrates the combination of event triggers designed as buttons, which then set visualization option configurations. The buttons are ultimately just presets for the visualized chart. To filter the pie chart to a view of only the 50 to 54 and 55 to 59 years age group ranges, the user would select the Select 50 to 54 + 55 to 59 years button.
The resulting visualization not only alters the pie chart view, but is also programmed so that the slider configuration changes as well. The button press event is in actuality, linked only to the slider control. In turn, the chart depends on the slider configuration. In this manner, a chain of dependencies is achieved, although the slider can still be manipulated independently of the preset button.
Similarly, when the Select 90K – 12K population button is pressed, the slider values are set, which subsequently alters the chart visualization itself.
To code the preceding example, there is one key difference to recognize as part of creating a programmatic control for a chart. As with previous examples, the function to execute upon triggering the event is organized as a function outside of the main visualization function. The slider and pie chart variables happen to be used in both the functions. However, if these variables are only known to the main visualization function, the execution of the dashboardReady
code will result in an error when the button event is triggered. This occurrence results in the following Uncaught ReferenceError: slider is not defined error message when debugging the code:
The slider
in the error is referring to the use of the slider object in the dashboardReady
function, not the main drawVisualization
function. The slider object is known to the drawVisualization
function, but not to the dashboardReady
function, as they are completely separate functions and slider
is defined in drawVisualization
only. To solve this issue and introduce consistency of variables, a variable that is known to the entire scripted application is required. This type of variable is called a Global variable. To create a global variable, simply create it outside of either function.
<script> var slider; varpiechart; functiondrawVisualization () { … } … </script>
The declaration of the variable must still be inside the <script>
tags, but outside of all JavaScript functions in order to be considered global to the application. The slider example declares the global variables, slider
and piechart
, at the very beginning of the API script. In this way, both slider
and piechart
can be referenced in any function in the API script. The entire HTML code for the programmatic control example is explained by section here. The first section loads the appropriate libraries.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title> Google Visualization API Sample </title> <script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript"> google.load('visualization', '1.1', {packages: ['controls']}); </script> <script type="text/javascript">
Next, the global slider
and piechart
variables are declared. The dataset is created as a DataTable
, and the slider
and piechart
variables are configured.
// Define the slider and pie chart variables as global variables. var slider; var piechart; function drawVisualization() { // Prepare the data var data = google.visualization.arrayToDataTable([ ['Age', 'Male'], ['Under 5 years', 94100], ['5 to 9 years', 84122], ['10 to 14 years', 83274], ['15 to 19 years', 91528], ['20 to 24 years', 108407], ['25 to 29 years', 134931], ['30 to 34 years', 119828], ['35 to 39 years', 100651], ['40 to 44 years', 89957], ['45 to 49 years', 85645], ['50 to 54 years', 80838], ['55 to 59 years', 68441] ]); // Define a slider control for the 'Male Population' column slider = new google.visualization.ControlWrapper({ 'controlType': 'NumberRangeFilter', 'containerId': 'control1', 'options': { 'filterColumnLabel': 'Male', 'ui': {'labelStacking': 'vertical'} } }); // Define a pie chart piechart = new google.visualization.ChartWrapper({ 'chartType': 'PieChart', 'containerId': 'chart1', 'options': { 'width': 700, 'height': 400, 'legend': 'side', 'chartArea': {'left': 15, 'top': 15, 'right': 0, 'bottom': 10}, 'pieSliceText': 'value' } });
At this point, the dashboard
is defined. Also, the event that listens for the chart to finish rendering is created. The final step is to bind the slider
and piechart
controls to the dashboard and draw the chart.
// Create the dashboard var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard')); // Register a listener to be notified once the dashboard is ready to accept presets. google.visualization.events.addListener(dashboard, 'ready', dashboardReady); // Configure the slider to affect the piechart, bind to the // dashboard dashboard.bind(slider, piechart).draw(data); }
Outside of the visualization function, the dashboardReady
function sets the state values for the range slider and preset buttons.
function dashboardReady() { // The dashboard is ready to accept interaction. Configure the buttons to programmatically affect the dashboard when clicked. // This button selects the range that happens to be the 50-54 and 55-59 year age groups document.getElementById('rangeButton').onclick = function() { slider.setState({'lowValue': 68441, 'highValue': 80838}); slider.draw(); }; // This button selects population numbers between 90K to 120K document.getElementById('rangeButton2').onclick = function() { slider.setState({'lowValue': 90000, 'highValue': 120000}); slider.draw(); }; }
The final section of code sets the containers for the slider, buttons, and chart.
google.setOnLoadCallback(drawVisualization); </script> </head> <body style="font-family: Arial;border: 0 none;"> <div id="dashboard"> <table> <tr style='vertical-align: top'> <td style='width: 300px; font-size: 0.9em;'> <div id="control1"></div> <div id="buttons"> <button style="margin: 2em" id="rangeButton">Select 50 to 54 + 55 to 59 years</button><br /> <button style="margin: 2em" id="rangeButton2">Select 90K - 120K population</button><br /> </div> </td> <td style='width: 600px'> <div style="float: left;" id="chart1"></div> </td> </tr> </table> </div> </body> </html>
Live examples are available at http://gvisapi-packt.appspot.com/ch7-examples/ch7-progctrl.html.
Dashboards and controls can at first appear to be complicated given the length of code required. However, breaking down the code into functional components reveals the methodical, but simple approach to dashboard construction.
Dashboard and control documentation is available at https://developers.google.com/chart/interactive/docs/gallery/controls.