How to Build a Responsive Dashboard Application in Angular — Part 3
Tags: webdev, javascript, tutorial, angular
Published: 2022-06-03 21:09:22

---
canonical_url: https://www.grapecity.com/blogs/posts/how-to-build-responsive-dashboard-application-in-angular-part-3
description: In the third part of a new blog series, learn how to build a responsive dashboard application in Angular. See more from Wijmo today.
---

In the previous [article](https://www.grapecity.com/blogs/how-to-build-responsive-dashboard-application-in-angular-part-2), we built out the information cards displayed at the top of the dashboard, the FlexCharts that display session, browser, and platform information, and Linear Gauges that display information regarding the percentage of visitors per country. In this blog, we'll be implementing our FlexMap which will display visual information for users per country, and FlexGrids which show additional information when a user clicks on different countries on the map.

### FlexMap

While many of the other controls that we've implemented in the dashboard serve purely to display data for users to view, FlexMap will act as the primary interactive piece of the dashboard. Users will be able to hover over each country that has data associated with it. If a user clicks on a country, the FlexGrids (which we will implement later in this article) will update to display information for that country.

First, we'll need to generate the component that will contain the FlexMap:

```
    ng g c map-info
```

Next, we'll need to implement the markup within our **map-info.component.html**:

```
    <wj-flex-map #flexMap header="Breakdown by Country" [tooltipContent]="tooltipContent" [selectionMode]="2" style="height: 100%;">
        <wj-geo-map-layer #geoLayer url="../assets/custom.geo.json" (itemsSourceChanged)="initializeMap(geoLayer)">
          <wj-color-scale #colorScale [colors]="colors" [binding]="binding" [scale]="scale"></wj-color-scale>
        </wj-geo-map-layer>
    </wj-flex-map>
```

As you can see, this is a very small amount of markup for a control as complex as an interactive map. While this markup creates our FlexMap control, there's actually three separate components that are used in this map: FlexMap, GeoMapLayer, and ColorScale.

*   FlexMap creates the window that contains the map that is being displayed, as well as the tooltips that display when users hover over different sections of the map
*   GeoMapLayer uses GeoJSON to draw the map, as well as initialize the control
*   ColorScale is used to bind countries that will be colored, as well as the colors that will be bound to the grid

For our application, we've stored the GeoJSON that we're using to build the map within the assets/custom.geo.json file. It is a lot of data which we won't be showing in the article, but if you'd like to see it you can download the application here [link needed].

Now that we've got the markup written, we're going to move over to the **map-info.component.ts **file to write the TypeScript that will allow users to interact with the FlexMap, and we're going to break it down section-by-section.

```
    import { Component, OnInit, Output, ViewChild, EventEmitter } from '@angular/core';

    import { Palettes } from "@grapecity/wijmo.chart";
    import { FlexMap } from '@grapecity/wijmo.chart.map';
    import { DataService } from '../data.service';

    @Component({
      selector: 'app-map-info',
      templateUrl: './map-info.component.html',
      styleUrls: ['./map-info.component.css']
    })
    export class MapInfoComponent implements OnInit {}
```

Here, we're importing all the different packages we'll be using. From Wijmo, we're using **Palettes **to color our map and **FlexMap** so that we can access the control to retrieve data from the map. We're importing our **DataService **to retrieve data that we can use to populate our tooltips. Finally, we're importing **ViewChild**, **EventEmitter**, and **Output to** pull information from the markup and emit the data to other components (in our case, the FlexGrids that we will be implementing down the line).

Next, we're going to look at the properties that we'll be using in our component:

```
    @ViewChild('flexMap') flexMap: FlexMap;
    @Output() countryName = new EventEmitter<string>();
    flexMapData: any;
    dataMap = new Map();
    pageViewMap = new Map();
    issuesReportedMap = new Map();
    colors = Palettes.Diverging.RdYlGn;
    selectedColor: '#188d9b';
    selectedCountry: '';
    selectedID: any;
    hitTestInfo: any;
    binding = (o: any) => this.dataMap.get(o.properties.name);
    scale = (v: number) => 1 - v;
    tooltipContent = (f: any) => this.getCountryToolTip(f);
```

*   flexMap holds the reference to the FlexMap control
*   countryName will be used to emit the selected country name to the FlexGrid controls
*   flexMapData will be used to hold country-specific data from our data service
*   dataMap will be used to build our array of data that will be used to assign each country a color value
*   pageViewMap will hold the total page views for each country; this will be displayed in the tooltip
*   issuesReportedMap will hold the number of issues reported for each country; this will be displayed in the tooltip
*   colors hold our array of color values that get passed to the ColorScale component and be used to color countries
*   selectedColor is used to set the color value of a country that the user clicks on
*   selectedCountry is used to get the string value of the country that the user clicks on
*   selectedID is used to get the ID value of the country that the user clicks on
*   binding holds an array of values that are used to bind the different countries that will be colored in the FlexMap
*   scale is used to select the color that will get assigned to each country
*   tooltipContent builds out the tooltip for each country that will be displayed when a user hovers over the country

The next section of code to go over in this file are the four methods that will be called when the component initializes, when the FlexMap control is created, to create the tooltips, and to emit the country name value when a user clicks on a country. First, we'll cover the ngOnInit() method:

```
    ngOnInit() {
        this.flexMapData = this.dataService.getCountryData();
        Array.prototype.forEach.call(this.flexMapData, el => {
            this.dataMap.set(el.Country, parseFloat(el.AverageResponseTime)); 
            this.pageViewMap.set(el.Country, el.PageViews);
            this.issuesReportedMap.set(el.Country, parseInt(el.IssuesReported));
        });
    }
```

Inside of the **ngOnInit() **method, we're setting our FlexMap's data source, which will be used to assign data to each of the different regions of our map control. This will allow the map control to color each region of the map based on the data that we want to measure (in the case of this sample, we're using response time).

We're also setting the value of our three data maps: **dataMap**, **pageViewMap**, and **issuesReportedMap**. These three maps will be used to color the map and build out the tooltips that get displayed when a user hovers over a country.

Next, we'll set up the method that gets called when the FlexMap is initialized:

    

```
initializeMap(layer: any) {
        this.flexMap.zoomTo(layer.getGeoBBox());
        this.flexMap.hostElement.addEventListener('mousedown', (e) => {
          this.hitTestInfo = this.flexMap.hitTest(e);
          if(hitTestInfo._item !== undefined) {
            this.emitCountryName(hitTestInfo._item.name);
            let el = document.elementFromPoint(e.x, e.y);
            let id = el ? el.getAttribute('wj-map:id') : undefined;
            this.selectedID = id;
            this.flexMap.invalidate(true);
          }
        });
        this.flexMap.rendered.addHandler((s, a) => {
            const layer = this.flexMap.layers[0];
            const g = layer._g;
            if(g && this.selectedID && this.validCountry(this.hitTestInfo._item.name)) {
                let list = [];
                for(let i = 0; i < g.childNodes.length; i++) {
                    const node = g.childNodes[i];
                    let id = node.getAttribute('wj-map:id');
                    if(id === this.selectedID) {
                        node.setAttribute('fill', this.selectedColor);
                        list.push(node);
                    }
                }
                list.forEach((el) => el.parentNode.appendChild(el));
            }
        });
    }

```

The **initializeMap() **method gets used to tie the **mousedown **event to the map, which will then use the **hitTest()** method to track where the user clicks on the map. If the country that the user clicks on has a value associated with it, we'll call the **emitCountryName() **method to emit an event containing the country's name that the user clicked on.

We're also tying an event to the map's **rendered **event; we'll use this to set the selected country based on the country's value that the user clicks on. In this method, we're just checking that the chosen country has a valid ID and checking to see if the country selected is valid (a country that contains values within our data). If it is, we fill the area with the **selectedColor **that we defined earlier in the application.

Finally, we'll create our **emitCountry()**, **getCountryToolTip()**, and **validCountry()** methods:

```
emitCountryName(value: string) {
        this.countryName.emit(value);
    }

    getCountryToolTip(val: any): string {
        if(this.dataService.isValidCountry(val.name)) {
            return `<b>` + val.name + `</b><br>` + 'Average Response Time: ' + this.dataMap.get(val.name) + 's' + `<br>` + 'Page Views: ' + this.pageViewMap.get(val.name) + `<br>` + 'Issues Reported: ' + this.issuesReportedMap.get(val.name);
        }
        return `<b>` + val.name + `</b><br>` + 'No data available';
    }

    validCountry(value: string) {
        for(var i = 0; i < this.flexMapData.length; i++) {
            if(this.flexMapData[i].Country == value) {
                return true;
            }
        }
        return false;
    }
```

The **emitCountry() **method takes the country name value and emits it; we'll use this with the components that use FlexGrid to get data associated with that country. The **getCountryTooltip()** method is used to build out HTML that will get rendered when a user hovers over different countries on the map. The **validCountry()**method takes a value and determines if the selected country is a "valid" country; this checks to see if it is one of the countries that have data associated with it.

Finally, the **getCountryToolTip() **method is used to build out the displayed tooltips. Inside of these tooltips, we're displaying the country name, the average response time for the country, the number of page views for the country, and the number of issues reported for the country.

Here is the completed version of our TypeScript file:

 

```
   import { Component, OnInit, Output, ViewChild, EventEmitter } from '@angular/core';

    import { Palettes } from "@grapecity/wijmo.chart";
    import { FlexMap } from '@grapecity/wijmo.chart.map';
    import { DataService } from '../data.service';

    @Component({
      selector: 'app-map-info',
      templateUrl: './map-info.component.html',
      styleUrls: ['./map-info.component.css']
    })
    export class MapInfoComponent implements OnInit {
      @ViewChild('flexMap') flexMap: FlexMap;
      @Output() countryName = new EventEmitter<string>();
      flexMapData: any;
      dataMap = new Map();
      pageViewMap = new Map();
      issuesReportedMap = new Map();
      colors = Palettes.Diverging.RdYlGn;
      selectedColor: '#188d9b';
      selectedCountry: '';
      selectedID: any;
      hitTestInfo: any;   binding = (o: any) => this.dataMap.get(o.properties.name);
      scale = (v: number) => 1 - v;
      tooltipContent = (f: any) => this.getCountryToolTip(f);

      constructor(private dataService: DataService){}

      ngOnInit() {
        this.flexMapData = this.dataService.getCountryData();
        Array.prototype.forEach.call(this.flexMapData, el => {
          this.dataMap.set(el.Country, parseFloat(el.AverageResponseTime));
          this.pageViewMap.set(el.Country, el.PageViews);
          this.issuesReportedMap.set(el.Country, parseInt(el.IssuesReported));
        });
      }

      initializeMap(layer: any) {
        this.flexMap.zoomTo(layer.getGeoBBox());
        this.flexMap.hostElement.addEventListener('mousedown', (e) => {
          this.hitTestInfo = this.flexMap.hitTest(e);
          if(hitTestInfo._item !== undefined) {
            this.emitCountryName(hitTestInfo._item.name);
          let el = document.elementFromPoint(e.x, e.y);
          let id = el ? el.getAttribute('wj-map:id') : undefined;
          this.selectedID = id;
          this.flexMap.invalidate(true);
        }});
        this.flexMap.rendered.addHandler((s, a) => {
          const layer = this.flexMap.layers[0];
          const g = layer._g;
          if(g && this.selectedID && this.validCountry(this.hitTestInfo._item.name)) {
            let list = [];
            for(let i = 0; i < g.childNodes.length; i++) {
              const node = g.childNodes[i];
              let id = node.getAttribute('wj-map:id');
              if(id === this.selectedID) {
                node.setAttribute('fill', this.selectedColor);
                list.push(node);
              }
            }
            list.forEach((el) => el.parentNode.appendChild(el));
          }
        });
      }

      emitCountryName(value: string) {
        this.countryName.emit(value);
      }

      getCountryToolTip(val: any): string {
        if(this.dataService.isValidCountry(val.name)) {
          return `<b>` + val.name + `</b><br>` + 'Average Response Time: ' + this.dataMap.get(val.name) + 's' + `<br>` + 'Page Views: ' + this.pageViewMap.get(val.name) + `<br>` + 'Issues Reported: ' + this.issuesReportedMap.get(val.name);
        }
        return `<b>` + val.name + `</b><br>` + 'No data available';
      }

      validCountry(value: string) {
        for(var i = 0; i < this.flexMapData.length; i++) {
          if(this.flexMapData[i].Country == value) {
            return true;
          }
        }
        return false;
      }
    }

```

Now all we need to do is drop the component inside of our **app.component.html **file:

```
    <main class="livemap-grid">
      <div class="livemap load-info">
        <app-current-info></app-current-info>
      </div>
      <div class="livemap map">
        <app-map-info (countryName)="countryName($event)"></app-map-info>
      </div>
      <div class="livemap user-info">
      </div>
      <div class="livemap sessions">
        <app-session-info></app-session-info>
      </div>
      <div class="livemap issue-info">
      </div>
      <div class="livemap top-countries">
        <app-top-country-info></app-top-country-info>
      </div>
      <div class="livemap top-platforms">
        <app-top-platform-info></app-top-platform-info>
      </div>
      <div class="livemap browsers">
        <app-top-browser></app-top-browser>
      </div>
    </main>
```

And when we run our application, we'll see the following:

![FlexMap](https://files.grapecity.com/gc-website-media/mctlvbwi/01-flexmap.jpg?width=500&height=227.63578274760386)

And if a user hovers over a country, they'll see a tooltip with the associated data:

![FlexMap Tooltip](https://files.grapecity.com/gc-website-media/glbf5w2e/02-flexmaptooltip.jpg?width=500&height=225.7806244995997)

And that's how we implement a FlexMap control! Next, we'll be going over the process of implementing our dashboard cards that contain our FlexGrids.

### FlexGrid

Now, the final thing that we have to do is implement the two components that we'll use to display FlexGrid and the data associated with the selected country. To generate the components, run the following commands:

```
    ng g c user-info
    ng g c issue-info
```

The first component that we'll look at is the **user-info **component. This component will gather and display data on the last 200 users that have visited our site from the user-selected country. By default, we'll display the data from users visiting from the United States.

First, we'll look at the **user-info.component.html **file:

```
    <div class="users-container">
        <div class="users-header">RECENT USERS <i class="bi bi-question-circle" [wjTooltip]="usersTooltip" [wjTooltipPosition]="6"></i></div>
        <wj-flex-grid [itemsSource]="selectedCountryData" [isReadOnly]="true" [selectionMode]="'Row'" [headersVisibility]="'Column'">
            <wj-flex-grid-column binding="country" header="Country" [width]="'*'"></wj-flex-grid-column>
            <wj-flex-grid-column binding="sessionDuration" header="Session Duration"></wj-flex-grid-column>
            <wj-flex-grid-column binding="ipAddress" header="IP Address"></wj-flex-grid-column>
            <wj-flex-grid-column binding="platform" header="Platform">
                <ng-template wjFlexGridCellTemplate [cellType]="'Cell'" let-cell="cell">
                    <i *ngIf="cell.item.platform == 'Desktop'" class="bi bi-display-fill"></i>
                    <i *ngIf="cell.item.platform == 'Mobile'" class="bi bi-phone-fill"></i>
                    <i *ngIf="cell.item.platform == 'Tablet'" class="bi bi-tablet-landscape-fill"></i>
                    <i *ngIf="cell.item.platform == 'Other'" class="bi bi-terminal-fill"></i>
                    {{cell.item.platform}}
                </ng-template>
            </wj-flex-grid-column>
            <wj-flex-grid-column binding="browser" header="Browser">
                <ng-template wjFlexGridCellTemplate [cellType]="'Cell'" let-cell="cell">
                    <img *ngIf="cell.item.browser == 'Chrome'" src="../../assets/chrome.png" alt="" height="16">
                    <img *ngIf="cell.item.browser == 'Firefox'" src="../../assets/firefox.png" alt="" height="16">
                    <img *ngIf="cell.item.browser == 'Edge'" src="../../assets/edge.png" alt="" height="16">
                    <img *ngIf="cell.item.browser == 'Safari'" src="../../assets/safari.png" alt="" height="16">
                    <i *ngIf="cell.item.browser == 'Other'" class="bi bi-cloud-fill"></i>
                    {{cell.item.browser}}
                </ng-template>
            </wj-flex-grid-column>
        </wj-flex-grid>
        <br>
        <wj-collection-view-navigator headerFormat="Page {currentPage:n0} of {pageCount:n0}" [byPage]="true" [cv]="selectedCountryData"> </wj-collection-view-navigator>
    </div>
```

Here, we're setting up two controls: the **FlexGrid **and the **CollectionViewNavigator**. For FlexGrid, we create the control itself using the **wj-flex-grid **tag, and then we create each column that will be displayed using the **wj-flex-grid-column** tag. Usually, you wouldn't need to set each individual column, but we're going to be using some templates to help us style some cells using the **FlexGridCellTemplate**. Inside our **Platform **and **Browser **columns, we're creating **ng-template **elements; the FlexGridCellTemplate control will use these to style the interior of the cells that reside in that column.

We can include the markup that we want to display inside each cell. For these cells, the markup that we want to display will be dependent on the value that is within the cell, so we'll use ***ngIf **to screen the cell value and then select the appropriate markup that we want to display.

The CollectionViewNavigator allows us to include pagination with FlexGrid; for this to work, we need to supply it with the same CollectionView that our grid is using. FlexGrid and the CollectionViewNavigator will then both have access to the CollectionView. Any changes made to the CollectionView by either of the controls will be mimicked across both controls, including the ability to apply paging.

Next, we'll jump over to the **user-info.component.css **file to apply some styling:

 

```
   .users-container {
        padding: 1rem;
    }

    .users-header {
        font-size: 1.05em;
        color: gray;
    }

    .wj-flexgrid {
        max-height: 440px;
        margin-top: 10px;
    }

    @media (max-width: 799px) {
        .wj-collectionview-navigator {
            width: 300px;
        }
    }
```

All we're doing here is setting some padding and font size for our header, setting a width and margin on the FlexGrid control, and setting a width on our CollectionViewNavigator when the screen size is smaller than 800px.

The last thing that we need to do for this component is to create the TypeScript that will handle collecting data that we'll use to hand to the FlexGrid control to populate it. This will be done inside of the **user-info.component.ts **file:

  

```
  @Component({
      selector: 'app-user-info',
      templateUrl: './user-info.component.html',
      styleUrls: ['./user-info.component.css']
    })
    export class UserInfoComponent implements OnInit, OnChanges {
      @Input('selectedCountryName') selectedCountryName: string;
      selectedCountryData: wjCore.CollectionView;
      usersTooltip = 'Information on the last 200 users from selected country.';

      constructor(private dataService: DataService) {
        this.selectedCountryData = new wjCore.CollectionView(dataService.getCountryInfo('United States'), {
          pageSize: 25
        })
      }

      ngOnInit(): void {}

      ngOnChanges(changes: SimpleChanges): void {
        if(changes.selectedCountryName.currentValue && this.dataService.isValidCountry(changes.selectedCountryName.currentValue)) {
          this.selectedCountryData = new wjCore.CollectionView(this.dataService.getCountryInfo(changes.selectedCountryName.currentValue), {
            pageSize: 25
          });
        }
      }
    }
```

We've got our **@Input **value, which is the country name that we emit from the FlexMap component, our CollectionView, and a tooltip that we use to let users know what the purpose of this card is for. We're also assigning a value to our CollectionView's **pageSize **property; this determines how many rows will be displayed per page, in this case, 25 rows of data. When run, the FlexGrid will look as such:

![FlexGrid User](https://files.grapecity.com/gc-website-media/ehapfvwg/03-flexgriduser.png?width=500&height=200.6726457399103)

We'll now hop over to the **issue-info **component to code that. First, we'll create our markup inside of the **issue-info.component.html **file:

  

```
  <div class="issues-container">
        <div class="issues-header">ISSUES REPORTED <i class="bi bi-question-circle" [wjTooltip]="issuesTooltip" [wjTooltipPosition]="6"></i></div>
        <wj-flex-grid #issueGrid [itemsSource]="selectedCountryData" [isReadOnly]="true" [selectionMode]="'Row'" [headersVisibility]="'Column'" [lazyRender]="false" (initialized)="initializedGrid(issueGrid)">
            <wj-flex-grid-column binding="country" header="Country" [width]="'*'"></wj-flex-grid-column>
            <wj-flex-grid-column binding="issue" header="Issue" [width]="'*'"></wj-flex-grid-column>
            <wj-flex-grid-column binding="status" header="Status" [width]="'*'">
                <ng-template wjFlexGridCellTemplate [cellType]="'Cell'" let-cell="cell">
                    <span *ngIf="cell.item.status == 'High'" class="badge bg-danger">{{ cell.item.status }}</span>
                    <span *ngIf="cell.item.status == 'Moderate'" class="badge bg-warning text-dark">{{ cell.item.status }}</span>
                    <span *ngIf="cell.item.status == 'Low'" class="badge bg-info text-dark">{{ cell.item.status }}</span>
                </ng-template>
            </wj-flex-grid-column>
        </wj-flex-grid>
    </div>
```

This is essentially identical to the markup for our **user-info **component: we create our FlexGrid control, create the columns using the **wj-flex-grid-column **tag, and use **FlexGridCellTemplate **to create a column with custom content.

Hopping over to the **issue-info.component.css **file, we'll add the following CSS:

```
   .issues-container {
        padding: 1rem;
    }

    .issues-header {
        font-size: 1.05em;
        color: gray;
    }

    .wj-flexgrid {
        max-height: 480px;
        margin-top: 10px;
    }
```

Like with our other component, we're just setting font size, some padding, and the size of the FlexGrid.

Finally, we'll go over to the **issue-info.component.ts **file to implement our TypeScript code:

```
    export class IssueInfoComponent implements OnInit, OnChanges {
      @Input('selectedCountryName') selectedCountryName: string;
      selectedCountryData: wjCore.CollectionView;
      issuesTooltip = 'Issues reported by system';

      constructor(private dataService: DataService) {
        this.selectedCountryData = new wjCore.CollectionView(dataService.getIssueData('United States'));
      }

      ngOnInit(): void {
      }

      ngOnChanges(changes: SimpleChanges): void {
        if(changes.selectedCountryName.currentValue && this.dataService.isValidCountry(changes.selectedCountryName.currentValue)) {
          this.selectedCountryData = new wjCore.CollectionView(this.dataService.getIssueData(changes.selectedCountryName.currentValue));
        }
      }

      initializedGrid(grid: wjGrid.FlexGrid) {
        let toolTip = new wjCore.Tooltip();
        grid.formatItem.addHandler((s: wjGrid.FlexGrid, e: wjGrid.FormatItemEventArgs) => {
          if(e.panel == s.cells) {
            let item = s.rows[e.row].dataItem, binding = s.columns[e.col].binding, note = item.message;
            if(e.col == 1) {
              wjCore.toggleClass(e.cell, 'wj-has-notes', true);
              toolTip.setTooltip(e.cell, '<b>Error:</b><br/>' + note);
            }
          }
        });
        grid.updatingView.addHandler(() => {
          toolTip.dispose();
        });
      }

    }
```

You'll notice that this code is identical to our **user-info **TypeScript code with one new piece added: the **initializedGrid() **method. We'll be incorporating tooltips into this grid, and we'll be using the **initializedGrid() **method. We'll assign a method to the grid's **formatItem **event, and inside of this event, we'll check to make sure that we're in the second column of the grid (since indexing an array starts at 0, this will be the column value of 1). Then, we'll assign a class to that cell, the **wj-has-notes **class we created in the first blog in this series, and some HTML to be displayed when a user hovers the cell.

When we run the code, we should see our FlexGrid rendered, including our tooltip notifier and our custom content:

![FlexGrid Issue](https://files.grapecity.com/gc-website-media/quvplw5w/04-flexgridissue.png?width=500&height=200.14992503748124)

And when a user hovers over one of the cells that contain a tooltip, the tooltip will be displayed:

![FlexGrid Tooltip](https://files.grapecity.com/gc-website-media/533f3xwu/05-flexgridtooltip.png?width=500&height=198.57890800299177)

### Conclusion

And with that, our dashboard application is complete! If we run it, we should see the following in our browser:

![Wijmo Live Map](https://files.grapecity.com/gc-website-media/xc0kgpmr/06-wijmolivemap.png?width=395.6340956340956&height=500)

Now, that was a fair amount of coding, so I'll do a once-over of everything that we did throughout this project:

*   Created a set of responsive cards that will hold our different controls
*   Implemented a component to display statistics for active sessions, load time, APDEX score, and bounce rate
*   Used FlexPie and FlexChart to show session info, load time/sessions by platform, and load time/sessions by browser
*   Created a set of gauges to display a list of top countries by session
*   Use FlexMap to create a choropleth map of the world, using a color scale to display countries with users that visit our site based on load time
*   Tied the FlexMap control to two FlexGrid controls, which display data on users as well as issues that users encountered when interacting with our site

With all of that completed, we now have a very clean dashboard that provides users that interact with it a fair amount of information. With the CSS that we wrote, the dashboard is also responsive and will be readable on whatever device the dashboard is accessed from.

Dashboards have become a crucial aspect of data analysis across many industries. They allow their users to quickly make high-level business decisions based on their information. Wijmo makes it easy for developers to implement swiftly different controls for a multitude of ways to view information, hook the controls up to your data source, and have a fully functioning dashboard ready for your company to take advantage of.

If you'd like to either run this application in your browser or download the source code for the application, you can do so [here](https://www.grapecity.com/wijmo/demos/reference-samples/WebsiteSessionsDashboard/angular/dist/).