Design principles for dashboard components

Issue #400 resolved
Brian Lewis repo owner created an issue

Aspects we want to incorporate into a general framework for dashboard components.

@ghachey perhaps - watch this issue?

Comments (14)

  1. Brian Lewis reporter

    Layout requirements: dashboard components should be able to be assembled into a host "dashboard" page. They need to be able to specify their own size requirements, and have the container layout them out accordingly. Should be "responsive"

    Angularjs material offers the flex and "layout-wrap" which seems the best way to accomplist this.

    A template page can be used as a master for every layout page - based on md-card.?

    Identify the standard sections to appear in thi master - title, content, actions.

  2. Brian Lewis reporter

    Actions in Dashboard components:

    -- info? a popup - explaining the indicator / nature of the data. Somehow, built into the component itself.

    -- drill-down - interactively for either the entire component or else specific to some number or chart part ( bar or slice)

  3. Brian Lewis reporter

    Parameterized -

    parameters may be specific to the component or else inherited fro.m the dashboard host page. When inherited, this implies some synchronization can be maintained

  4. Brian Lewis reporter

    Sourcing data -- can components share data? How to co-ordinate data requests?

    Does the dashboard page get a number of relevant data sources that the component can request? Could this be held in a crossfilter?

  5. Ghislain Hachey

    A template page can be used as a master for every layout page - based on md-card.?

    Yes, but each dashboard component should be in a card. All dashboard component cards for the entities dashboard (e.g. Schools -> Dashboard) should be in a panel much like on the demo https://material.angularjs.org/latest/demo/card

    Of course we already make use of tabs in individual entities (e.g. A School) so those could end up looking like https://material.angularjs.org/latest/demo/tabs with the cards inside the first dashboard tab.

  6. Ghislain Hachey

    Actions in Dashboard components: -- info? a popup - explaining the indicator / nature of the data. Somehow, built into the component itself. -- drill-down - interactively for either the entire component or else specific to some number or chart part ( bar or slice)

    Sure, both those will only be architectural placeholders for now with implementation done at a later time.

  7. Ghislain Hachey

    Parameterized - parameters may be specific to the component or else inherited fro.m the dashboard host page. When inherited, this implies some synchronization can be maintained

    Sounds good. But how would it work? What parameter would take precedence? We need something super easy and clear to use for the user. I think maybe just per component parameters to start with.

  8. Ghislain Hachey

    Sourcing data -- can components share data? How to co-ordinate data requests? Does the dashboard page get a number of relevant data sources that the component can request? Could this be held in a crossfilter?

    That sounds like we are complicating things. What's wrong with simply having each component pull its own dataset set? Like a collection of small purpose built RESTful API with the angular component consuming those?

  9. Jeremy Kells

    Regarding the design, sourcing data etc, I'd suggest something similar to a very common pattern in react, where you have a presentational component (that are referentially transparent), and a container component that is responsible for data fetching, etc. So you get both the Parameterized and Sourcing data behaviour you are discussing above - The Presentational component doesn't care where it's data comes from, is not responsible for getting it, just displaying what it receives as parameters (and can be reliably tested, reused, etc). And the Container component can (and is only responsible for) fetch from the Rest API, or the core lookup tables, or anywhere else.

  10. Brian Lewis reporter

    and a container component that is responsible for data fetching, etc. So you get both the Parameterized and Sourcing data behaviour you are discussing above -

    Yes; agreed. This is the direction I am headed - a Dashboard component that provides data, selection parameters and services to its child components. the child component will render that data as it pleases.

    For implementation, I'm using the 'require' option in the AngularJs component definition to give the component access to its parent dashboard:

      export class EnrolComponent implements ng.IComponentOptions {
        public bindings: any;
        public controller: any;
        public controllerAs: string;
        public templateUrl: string;
        public require: { [controller: string]: string } = {};
    
    
        constructor(controller, templateUrl) {
          this.bindings = {
            hostReady: "<"
          };
          this.controller = controller;
          this.controllerAs = "vm";
          this.templateUrl = "dashboard/component/" + templateUrl;
          this.require["host"] = "^tableEnrolHost";
    
        }
      }
    

    this sets up a property 'host' in the component that can be strongly typed, giving the component access to the services provided by the host.

    I've set up a detailed model using crossfilter. This allows a single set of data to be sliced in various ways very efficiently. So a component can:

    -- take the crossfilter objects provided by the dashboard ,

    -- set up some custom grouping on that if required

    -- draw it somehow

    A nice thing is that if filtering is applied directly to the crossfilter objects (known as 'Dimensions;'), then the objects derived from those Dimensions will automatically get that filtering. So - applying filtering to the page can filter all components in synch. I think that entire tables from the warehouse can be dragged into a client-side crossfilter - I've put a bit of effort into optimising these data transmissions ( on top of gzipping them!)

    So - I'm about a day or two away from having a branch up as a first version of all of this. Some practical issues are that I need to get a few prerequisites up first so the relevant branch doesn't have stuff not related to dashboards in there. These are housekeeping chores I will attend to today.

  11. Brian Lewis reporter

    Dashboard Framework

    The dashboard framework is built onthe collaboration betwen objects of two types:

    • a Dashboard;

    and its

    • DashboardChild objects.

    Each DashboardChild provides some rendering (chart, table, map ... ) or some data.

    The Dashboard provides a set of services to these objects.

    The Dashboard:

    1) provides data to the DashboardChild

    2) manages "filters" ie selections to apply to that data

    3) is a "broker" for communication between Child objects

    4) manages the disposition on the page of the Child objects.

    There a couple of models of dashboard, based on the type of data that is made available to the children.

    CrossfilterDashboard - the dashboard builds a crossfilter from data that is generally retrieved from a warehouse table. The Dashboard will create useful crossfilter dimensions and all this is acccessible to the children.

    PAFDashboard - the dashboard loads PAF data, and exposes the API for children to drill into this.

    ModelDashboard -- the dashboard loads an instance of a complex object (school, student, teacher), so children can work with its collections (surveys, enrolments)

    The dashboard functionality is implemented through a combination of

    • code - ie typescript class that can be extended to form new angularjs components;

    • master layouts - implemented via mvc (razor) master pages used by each dashboard and each dashboard child

    • css - to manage dynamic layouts and animation

    Layout

    The dashboard uses masonry.js to layout its children in a grid. The grid is based on units of 150px x 150px.

    Each child decides what size it wants in these units; ie 3x2; 5x4

    This is then specified in css like this :

    class="dashboard-wrapper height3 width4"
    

    Selected Child

    The dashboard allows the user to 'select' a child on the page, and make that child "lift" and expand to fill the full page width.

    Children can test whether they are Selected , or Unselected ( ie some other child is selected ) and can repsond accordingly. Note that most of the time all components are neither Selected nor Unselected.

    Options

    The dashboard manages an options class whose properties specify possible filters on the data. These properties are named selectedYear, selectedDistrict, selectedSchoolType etc.

    When an option is changed, the change is communicated by the dashboard to all children. The child can then adjust its display in response. It may filter its data, or simply highlight a bar or slice of a chart, or highlight a row in a table.

    A child has access to the options object ( via this.dashboard.options) and can set its properties directly. For example, this can be in response to clicking a bar in a chart, or clicking a row in a grid. For example:

    -- the user clicks a bar in a chart

    -- the click handler in the child component sets the option appropriately

    this.dashboard.options.selectedDistrict = <district represented by the bar>

    -- the dashboard detects the option change, and may action it if required

    -- the dashboard communicates the option change to all children.

    -- this fires the onOptionChange event in each child, which executes any reconfiguration

    In a CrossfilterDashboard, the dashboard repsonds to an option change by appropriately applying ( or removing ) a filter from the relevant dimension. In this circumstance, the child built on the crossfilter has no work to do since the change in filter on the dimension is transparently applied to any crossfilter groups built on that dimension.

    Communication

    Each child holds a strongly-typed reference to its dashboard. This is implemented via the require option in the component definition class. (see earlier comment).

    Communication from the dashboard to the children is all handled by bindings, so there is no need for the child to set up any watchers.

    The base classes respond to these bindings by firing particular events; so when developing a child component, you need only to derive from the base class, then override the event to respond to the binding change.

    Bindings and their events are:

    dashboardReady (onDashboardReady)

    Signals that the dashboard has prepared data ready for consumption by the children. The child component should implement any setup in onDashboardReady.

    The property dashboardReady in the Dashboard is passed to the client. This is just a number. The dashboard should increment this number whenever its data changes. This allows for the possibility of more interactive dashboards - ie on change of option, rather than filtering an existing pool of data, the dashboard could respond to the change of option by fetching new data, then incrementing dashboardReady to inform the clients of the change.

    optionChange (onOptionChange)

    Receive an 'optionchange' object that has a property for each option changed, and old and new values ie { selectedYear: { new:2017, old: 2016} }

    This value is passed to the onOptionChange event, however, the child will most probably work directly with dashboard.options in response to this event.

    selectedChild

    The selected Child has changed. The child can check if it has become the selected child and can react accordingly.

    Note that while children get explicit notification of such changes, many responses can be implemented without any action in code. Mechanisms to facilitate this include:

    Crossfilter - as noted, filters applied in the dashboard in response to an optionchange are transprent to the client

    ng-class / css

    -- a table can make use of utility functions in the base ChildDashboard class, and in the options class, css and the ng-class and ng-show directives to render changes in selected options

    e.g. <tr ng-class="{'selected', vm.dashboard.options.isSelectedYear(d.year)}">

    Reporting

    The dashboard child may have an associated report in the Jasper report tree. This can be hard-coded in the defaultReportPath property of the child, or else passed in via the binding reportPath (ie report-path).

    Default handling is -

    -- when a report is set, an icon appears in the child's header.

    -- Clicking this item runs the report

    -- the child can implement setReportContext to set up report parameters; this is intended to be used to do any mappings between the current dashboard options and the report parameters.

    -- As usual , any required parameters that are not supplied will be prompted for at runtime.

  12. Log in to comment