Configure a Chart to Grid Toggle

Interface patterns give you an opportunity to explore different interface designs. To learn how to directly use patterns within your interfaces, see How to Adapt a Pattern for Your Application.

Goal

Display a column chart with a toggle to display an alternate grid view of the data.

This recipe uses an employee data structure and objects created through the Use the Write to Data Store Entity Smart Service Function on an Interface recipe. Make sure that recipes has been built first in order to see data in this recipe.

Display a chart with the number of tickets reported each month internally and from customers. Then add a grid to display the same data. Finally, add a link so that a user may toggle between the chart and grid views of the data. This pattern is recommended to provide a visual data display while still making the data accessible to users who employ assistive technology.

This is the data displayed as a chart:

This is the data displayed as a grid:

This design pattern is not recommended for offline interfaces because reflecting immediate changes in an interface based on user interaction requires a connection to the server.

This scenario demonstrates:

  • How to configure alternate displays of the same data.
  • How to modify the expression to display a grid when the user clicks on a link toggle.

Expression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
=load(
  local!pagingInfo: a!pagingInfo(
    startIndex: 1,
    batchSize: -1,
    sort: a!sortInfo(
      field: "department",
      ascending: true
    )
  ),
  local!showAsGrid:false,
  with(
    local!datasubset: a!queryEntity(
      entity: cons!EMPLOYEE_ENTITY,
      query: a!query(
        aggregation: a!queryAggregation(
         aggregationColumns: {
          a!queryAggregationColumn(field: "department", isGrouping: true),
          a!queryAggregationColumn(field: "title", isGrouping: true),
          a!queryAggregationColumn(field: "id", aggregationFunction: "COUNT")
         }
        ),
        pagingInfo: local!pagingInfo
      )
    ), 
    local!categories: index(local!datasubset.data, "department", {}),
    /* Use union() to remove duplicates */
    local!uniqueCategories: union(local!categories, cast(typeof(local!categories), {})),
    local!labels: index(local!datasubset.data, "title", {}),
    /* Use union() to remove duplicates */
    local!uniqueLabels: union(local!labels, cast(typeof(local!labels), {})),
    local!series: a!forEach(
        items: local!uniquelabels,
        expression: with(
          local!label: fv!item,
            a!chartSeries(
              label: local!label,
              /* 
               * Loops over list of categories to find each datapoint that matches  
               * the series label and the category. This will ensure that the 
               * datapoints are in the correct order to display in the chart.                       
               */
              data: a!forEach(
                items: local!uniqueCategories,
                expression: with(
                /* Find all datapoints that match both the category and chart series label   */
                  local!intersection: intersection(
                    where( local!categories = cast( typeof(local!categories), fv!item), 0),
                    where( local!labels = cast( typeof(local!labels), local!label), 0)
                  ),
                  if(
                    length(local!intersection)=0,
                    /* 
                     * If there is no datapoint for this category-label pair, return 0 
                     * so that all subsequent points are in the correct order with the 
                     * categories.      
                     */
                    0,
                    index(index(local!datasubset.data, "id", {}), local!intersection, 0)
                  )
                )
              )
            )
          )
       ),
       a!sectionLayout(
         contents:{
          a!linkField(
          links:{
            a!dynamicLink(
              label: "View this data as a "&if(local!showAsGrid,"chart","grid"),
              value: not(local!showAsGrid),
              saveInto: local!showAsGrid
            )
          }
        ), 
        a!columnChartField(
          categories: local!uniqueCategories,
          series: local!series,
          xAxisTitle: "Departments",
          yAxisTitle: "Number of Employees",
          stacking: "NORMAL",
          showWhen: not ( local!showAsGrid )
        ),
        a!gridField(
         label:"Data",
         labelPosition:"COLLAPSED",
         totalCount:count(local!series),
         columns:{ 
          /* 
           * The first grid text column generates the Department names while the
           * forEach() statement creates an array of grid text columns. This 
           * expression is virtually identical to the forEach statement that
           * generated the chart series array for the stacked column chart.
           */
           a!gridTextColumn(label:"Department",data:local!uniqueCategories),
           a!forEach(
            items:local!uniquelabels,
            expression: with(
              local!gridLabel:fv!item,
               a!gridTextColumn(
                label: local!gridLabel,
                /* Loops over list of categories to find each datapoint that matches  
                 * the series label and the category. This will ensure that the 
                 * datapoints are in the correct order to display in the chart.                       
                 */
                data: a!forEach(
                  items:local!uniqueCategories,
                  expression: with(
                  /* Find all datapoints that match both the category and chart series label   */
                    local!gridIntersection: intersection(
                      where(local!categories=cast(typeof(local!categories), fv!item), 0),
                      where(local!labels=cast(typeof(local!labels), local!gridLabel), 0)
                    ),
                    if(
                    length(local!gridIntersection)=0,
                    /* If there is no datapoint for this category-label pair, return 0 
                     * so that all subsequent points are in the correct order with the 
                     * categories.      
                     */
                    0,
                    index(index(local!datasubset.data, "id", {}), local!gridIntersection, 0)
                    )
                  )
                ),
                alignment:"RIGHT"
              )
            )
           )
         },
         value:local!pagingInfo,
         rowHeader: 1,
         showWhen: local!showAsGrid
      )
       }
     )
  )
)

Test it out

  1. Click the "View this data as a grid" link. The chart will be replaced with a grid that displays all defect tickets.
  2. Click the "View this data as a chart" link. The grid will be replaced with the original chart.

Notable Implementation Details

  • This grid required multiple columns, one for each department. In charts where there is only one grouping, a two-column grid to show the chart label and aggregated data will usually suffice.
FEEDBACK