Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

Component

Title

Dashlets

Selector

<sb-dashlet>

Use Case

Dashlets are reporting widgets that can be embedded in any contextual workflow - whether on a consumption, creation or administration screen. Any solution that needs to use dashlets can configure the dashlet with a data source, type of visualisation (bar, line, pi, table, map etc), legends, filters etc.

Description

This generic component/widget will be used to render a chart, table, dataset, map or any other report type. It can make use of multiple libraries & also support building custom components with a common interface.

🧐 Interface Design

Interface Diagrams

IBase<T> if the base interface providing common properties and behaviours.

IChart , ITable or any report Type interface will extend the IBase<T> interface and will add it’s own properties and behaviours with respect to it’s use case.

Any Chart Component will implement IChart interface whereas table Components will implement ITable interface and will provide their own implementation of the specified properties and methods.

Detailed Explaination of the Interfaces

IBase Interface

  • IBase. - This is the base interface that every report Type component should implement.

  • It provides the common attributes and behaviours for each dashlet component like height, width , fetching the data , initialization of the component.

interface IBase <T> {

    reportType: IReportType;

    readonly _defaultConfig: object; // default configurations as per the reportType

    height: string; 
    
    width: string;
    
    id: string;
    
    config: object; // input configuration for the dashlets component. It should be as per the report type. Refer to next sections for more details as per the report Type

    data: <T>[] | IDataLocation; // input data either in JSON format, url or API configuration;

    state: EventEmitter<IReportState>;

    initialize(config: object, data: T[]); // Get the initialisation options used for rendering.

    reset(): void; // resets the component to initial view

    destroy(): void; // performs cleanup post the component is destroyed;

    update(config); // updates and re renders the view

    fetchData<T>(): Promise<T[]> | Observable<T[]>;

    /* genetic methods for addition and removal of data;
        addData();
        removeData
    */
}


/*
##########################################################################
*
*.         depenedent interfaces are as follows:-
*
##########################################################################
*/


type IReportType = "chart" | "table" | "etc"

type methodType = "GET" | "POST";

interface IApiConfig {
    url: string;
    headers: {
        [header: string]: string | string[];
    };
    methodType: methodType;
    params: {
        [param: string]: string | string[];
    };
    response: {
        path: string;  //Gets the value at path of response object;
    }
}

interface IDataSchema {
    type: string,
    enum?: string[],
    default?: string,
    format?: string; //url, date etc
    items?: IDataSchema // if type is array
}

interface IDataLocation {
    location?: {
        apiConfig?: IApiConfig;
        url?: string;
    },
    dataSchema?: {
        [key: string]: IDataSchema;
    }
}

type IReportState = "initialized" | "rendered" | "destroyed" | "etc";  // pending or done state;

interface EventEmitter<T>{
    emit(value? : T);
    subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;
}

IChart Interface - Base Interface for Chart Components. <ReportType = Chart>

  • This is the interface that every chart component should implement and extends the IBase interface

  • config takes the required metadata to render a chart like label datasets/series, tooltips legend, title, axes detailes etc. Please refer to the below. image to know most commonly used chart concepts. Interface also allows additional metadata if required.

  • labelExpr - refers to the column name or key in the JSON which can be used as x axis labels

  • dataExpr - refers to the key in the JSON to be used on y axis as dataset.

interface IChart extends IBase {
    
    readonly reportType: IReportType = "chart"
  
    readonly _defaultConfig: IChartConfig; // default config for tooltips colors legend etc.

    config: IChartConfig; 

    chartClick: EventEmitter<any>;
    chartHover: EventEmitter<any>;

    chartBuilder(config); // (prepares / converts) input chart config as per the underlying chart library used.

    refreshChart(); // refreshes and updates the chart either at specific interval or explicitly triggerd

    addData({ label, data, config }); //appends the label and data at the end;

    removeData(); //pops the last data and label
    removeData(label: string) // removes a specific label

    getTelemetry();

    // mergeData(data1, data2, ...dataN): any[];

    getCurrentSelection(); 
    
    getDatasetAtIndex(index: number);
}

/*
##########################################################################
*
*.         depenedent interfaces are as follows:-
*
##########################################################################
*/


interface IChartConfig {
    type: IChartType;
    options?: IChartOptions;
    [key: string]: any;
}

type IChartType = "bar" | "line" | "pie" | "etc";

type IChartOptions = {
    labels? : string[], // if labels are passed explicitely
    labelExpr ? : string; // column name to use as x-axis labels;
    datasets: IDataset[]; // datasets - y axis data
    tooltip?: object;
    legend?: object
    animation?: object;
    colors?: object;
    title?: string;
    description: string;
    subtitle?: string;
    caption?: object;
    scales?: {
        axes: any;
        [key: string]: any;
    };
    [key: string]: any;
};

type IDataset  = {
    label: string;
    dataExpr?: string;
    data: any[];
}

ITable Interface - Base Interface for Table Components. <ReportType = Table>

  • This interface is implemented by every table report type and extends the IBase Interface.

interface ITable extends IBase {

    readonly reportType: IReportType = "table"

    readonly _defaultConfiguration: ITableConfig;

    config: ITableConfiguration[];

    getRowsCount(); // rows count
    getRowAtIndex(index: number);   //get row at index number
    rowSelector(selectors); //fetch first row matching the config
    rowSelectorAll(selectors); // fetch rows matching a config

    addRow(data: object); // add a new row to the table by passing row configuration
    removeRow(index?: string); //removes row from the end or  at specific index;
    addColumn(config: ITableConfiguration); // adds a new column at the end
    removeColumn(columnName: string); // removes a column

    rowClick: EventEmitter<any>; // row click  event emitter
    rowHover: EventEmitter<any>; // mouse over event emitter

    exportTable(exportType: string); // exports the table into a type
    sortTable(sortByColumnName: string, orderBy: "asc" | "desc");
}


/*
##########################################################################
*
*.         depenedent interfaces are as follows:-
*
##########################################################################
*/

interface ITableConfig {
    title?: string;    // header name for the column
    searchable?: boolean; // if the column is searchable or not - will be used by the search bar at the top
    orderable?: boolean;  // if the column can be ordered or sorted in asecending or descending fashion
    data?: string;  // key in the input JSON to be used as column
    visible?: boolean; // hides or shows a column within a table
    render?: () => any;  // method to override the view and customise it like showing a button or chip instead of normal text
    autoWidth?: boolean;
    widthSize?: string; // customised width either in percentage or fixed width
    [key: string]: any; //any other metadata
}

Filters Config and Component Design <WIP>

This component and interface it to make a generic filter which will serve all for all the reportTypes.

This will also handle nested filters implementation or heirarchical filters implementation.

abstract class Filter<T> {

    data: T[];

    config: IFilterConfig[];

    filteredDataEventEmitter: EventEmitter<T[]>

    abstract init(config: IFilterConfig); //Get the initialisation options to render the filters;

    abstract filterData(data: T, config): object[];
}


interface IFilterConfig {
    reference: string;
    label: string;
    placeholder: string;
    controlType: "single-select" | "multi-select" | "date";
    searchable?: boolean;
    filters: IFilterConfig[];
    default?: string;
}

/* Questions

    Nested Filter Capability..

*/

🧐 Proposed Design

Dashlet Main Component Proposed Structure

(**Please refer to the next sections for more detailed info about each component)

<sb-dashlet [type]="string" [options]="options"
    [dataSourcePath]?="url" , [data]?="data" , [apiInfo]?="apiInfo" [id]="string | uuid" [height]="string"
    [width]="string" [title]="string | html" [subTitle]?="string | html" [description]?="description"
    (...anyOtherEvent)="eventListener($event)">

    <some-title-element>
        <div>{{title}}</div>
        <div>{{subTitle}}</div>
        <div>{{description}}</div>
    </some-title-element>

    <some-action-buttons-component> </some-action-buttons-component>

    <dashlet-filter [data]="data" , [filters]="filters" , (filteredData)=""> </dashlet-filter>

    <container-element [ngSwitch]="reportType" [height]="height" [width]="width" [id]="id">

        <chart-element switchCase="chart" (chartClick)="eventListener($event)" (chartHover)="eventListener($event)"
            (afterChartLoaded)="eventListener($event)">

            <container-element [ngSwitch]="libraryType" default="chartjs">

                <chartJs-element switchCase="chartjs" [options]="options" [chartType]="chartType" (events)="">
                </chartJs-element>

                <highCharts-element switchCase="highCharts" [options]="options" [chartType]="chartType" (events)="">
                </highCharts-element>

                <d3-element switchCase="d3" [options]="options" [chartType]="chartType" (events)=""> </d3-element>

                <custom-chartTypes switchCase="custom">

                    <container-element [ngSwitch]="reportSubType">

                        <map-element switchCase="map" [options]="options" [chartType]="chartType" (events)=""> </map-element>

                        <bigNumber switchCase="bigNumber" [options]="chartOptions"></bigNumber>

                        <other switchCase="others" [options]="chartOptions"></other>

                    </container-element>

                </custom-chartTypes>

            </container-element>

        </chart-element>

        <table-element switchCase="table" (rowClicked)="eventListener($event)">

            <container-element [ngSwitch]="libraryType" default="datatables">

                <dataTable-component switchCase="datatables" [...options]="tableOptions"> </dataTable-component>

                <custom-table switchCase="custom">
                    <container-element [ngSwitch]="reportSubType">
                        <stripped-table switchCase="stripped-table" [...options]="tableOptions"></stripped-table>
                    </container-element>

                </custom-table>
            </container-element>

        </table-element>

        <dataset-element switchCase="dataset" (events)="eventListener($event)"> </dataset-element>

        <new-reportType-element switchCase="newReportType">

            <container-element [ngSwitch]="libraryType" default="datatables">

                <custom-table switchCase="custom">

                    <container-element [ngSwitch]="reportSubType">
                        <custom-element switchCase="type1" [...options]="customOptions"></custom-element>
                    </container-element>

                </custom-table>
            </container-element>


        </new-reportType-element>

    </container-element>

</sb-dashlet>

ChartJs Component

<chartJs-element [datasets]="datasets" [labels]="labels" [chartType]="chartType" [colors]="colors" [options]="options"
    [legends]="legends" [plugins]="plugins" (chartClick)="chartClick" (chartHover)="chartHover">
</chartJs-element>

<script>
    
    // sample high level chartOptions to configure axes, tooltips, labels, datatsets, colors etc for a chart
  
    let chartOptions = {
        datasets: [
            { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },
            { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },
            { data: [180, 480, 770, 90, 1000, 270, 400], label: 'Series C', yAxisID: 'y-axis-1' }
        ],
        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
        options: {
            responsive: true,
            scales: {
                xAxes: [{}],
                yAxes: [
                    {
                        id: 'y-axis-0',
                        position: 'left',
                    }
                ]
            }
        },
        colors: [
            {
                backgroundColor: 'rgba(148,159,177,0.2)',
                borderColor: 'rgba(148,159,177,1)',
                pointBackgroundColor: 'rgba(148,159,177,1)',
                pointBorderColor: '#fff',
                pointHoverBackgroundColor: '#fff',
                pointHoverBorderColor: 'rgba(148,159,177,0.8)'
            }
        ],
        legends: true,
        chartType: 'line',
        plugins: [pluginAnnotations]
    }


</script>

Component specs:-

Properties

Note: For more information about possible options please refer to the original chart.js documentation

  • data (SingleOrMultiDataSet) - set of points of the chart, it should be MultiDataSet only for line, bar, radar and doughnut, otherwise SingleDataSet

  • datasets ({data: SingleDataSet, label: string}[]) - data see about, the label for the dataset which appears in the legend and tooltips

  • labels (Label[]) - x-axis labels. It's necessary for charts: line, bar, and radar. And just labels (on hover) for charts: polar area, pie, and a doughnut. The label is either a single string, or it may be a string[] representing a multi-line label where each array element is on a new line.

  • chartType (ChartType) - indicates the type of charts, it can be: line, bar, radar, pie, polar area, doughnut

  • options (ChartOptions) - chart options (as from Chart.js documentation) with some custom options.

  • colors (Color[]) - data colors, will use the default and|or random colors if not specified (see below)

  • legend: (boolean = false) - if true show legend below the chart, otherwise not be shown

Events

  • chartClick: fires, when click on a chart, has occurred, returns information regarding active points and labels

  • chartHover: fires when mousemove (hover) on a chart has occurred, returns information regarding active points and labels


Map Component

<map [options]="options" [mapData]="mapData" (featureClicked)="eventListener($event)">
</map>

<script>
    let mapData = {
        state: 'Karnataka',
        districts: ['Bengaluru', 'Mysuru'],
        reportData: [{}, {}],
        reportLoc: 'url',
        metrics: [''],
        // country: 'India', // Optional - to show india map with states
        // states: ['Karnataka'], // Optional - list of states to show on the map
    }


  // default config and can be overridden with new configurations 

    let options = {
        initialCoordinate: [20, 78],
        latBounds: [6.4626999, 68.1097],
        lonBounds: [35.513327, 97.39535869999999],
        initialZoomLevel: 5,
        controlTitle: 'India Heat Map',
        tileLayer: {
            urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
            options: {
                attributions: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            }
        },
        rootStyle: {
            fillColor: '#007cbe'
        },
        otherOptions: any
    };
</script>

Table Component

<table-element [data]="[{}]" [columnsConfiguration]="columnsConfiguration" (rowClickHandler)="eventListener($click)"> 
</table-element>

<script>

    let columnsConfiguration = [{
        title: string,
        searchable: boolean,
        orderable: boolean,
        data: 'key',
        visible: boolean,
        render: () => {},
        paging: boolean,
        info: boolean,
        autoWidth: boolean,
        others: any
    }]

</script>

Filter Component

  • This generic filter component should work at the chart level, table level, and also at the report Level as global filters.

  • Moreover, it should handle nested filter capability as well.

  • selector :- <sb-dashlet-filter>

<sb-dashlet-filter [data]="JSON" [filters]="filters" (filteredData)="subscriptionFunction"> </sb-dashlet-filter>


<script>

    let data = [{ key1: any, key2: any }, {key1: any, key2: any}];
    
    filters = [{
        header: "string",
        footer: "string",
        dataExpr: "key1",
        filters: [{
            header: "string",
            footer: "string",
            dataExpr: "string",
            filters: [{
                dataExpr: "key2"
                ...
            }]
        }]
    }];
</script>

Notes:-

  • If there are more charts in a single report, then each graph should be lazy-loaded to improve page performance.

  • client-side caching for the datasets.

  • No labels