Skip to content

Commit

Permalink
feat(country-metric-chart): Add country metric chart component encaps…
Browse files Browse the repository at this point in the history
…ulating ApexGraphs
  • Loading branch information
glecko committed Apr 10, 2021
1 parent c5296af commit 5352309
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 9 deletions.
4 changes: 3 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"styles": [
"src/styles/styles.scss"
],
"scripts": []
"scripts": [
"node_modules/apexcharts/dist/apexcharts.min.js"
]
},
"configurations": {
"production": {
Expand Down
93 changes: 93 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"@angular/platform-browser": "~9.1.1",
"@angular/platform-browser-dynamic": "~9.1.1",
"@angular/router": "~9.1.1",
"apexcharts": "^3.18.1",
"highcharts-angular": "^2.4.0",
"lodash": "^4.17.15",
"ng-apexcharts": "^1.2.3",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
Expand Down
18 changes: 17 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,25 @@ <h1>Hydrane Angular Frontend Dev CS</h1>
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Chart results</mat-label>
<mat-select [(value)]="maxChartResults">
<mat-option *ngFor="let maxChartResultOption of maxChartResultOptions" [value]="maxChartResultOption">
{{maxChartResultOption}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="filteredCountriesData">
<mat-table #table [dataSource]="filteredCountriesData" (scroll)="onTableScroll($event)">
<div>
<app-country-metric-chart
*ngFor="let metric of selectedMetrics"
[countriesData]="filteredCountriesData"
[metric]="metric"
[maxChartResults]="maxChartResults"
></app-country-metric-chart>
</div>
<mat-table #table [dataSource]="tableLoadedCountriesData" (scroll)="onTableScroll($event)">
<!-- Continent Name -->
<ng-container matColumnDef="continentName">
<mat-header-cell *matHeaderCellDef>Continent</mat-header-cell>
Expand Down
18 changes: 13 additions & 5 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class AppComponent implements OnInit {

countriesData: CountryDataModel[];
filteredCountriesData: CountryDataModel[];
tableLoadedCountriesData: CountryDataModel[];

// Continents
selectedContinent = ALL_VALUES;
Expand All @@ -30,6 +31,10 @@ export class AppComponent implements OnInit {
selectedMetrics = ALL_SELECTED_METRICS;
metricOptions = METRIC_SELECTION_OTIONS;

// Charts
maxChartResultOptions = MAX_CHART_RESULT_OPTIONS;
maxChartResults = MAX_CHART_DEFAULT_OPTION;

// Scrolling
maxElementsToSelectByScrolling = INFINITE_SCROLL_BATCH_SIZE;

Expand All @@ -51,24 +56,27 @@ export class AppComponent implements OnInit {
const limit = tableScrollHeight - tableViewHeight - INFINITE_SCROLL_BOTTOM_BUFFER;
if (scrollLocation > limit) {
this.maxElementsToSelectByScrolling += INFINITE_SCROLL_BATCH_SIZE;
this.filterCountriesData();
this.filterTableLoadedCountriesData();
}
}

onContinentSelected(e) {
this.filterCountriesData();
}

private filterTableLoadedCountriesData() {
// Filter by max amount allowed by infinite scroll
this.tableLoadedCountriesData = this.filteredCountriesData.slice(0, this.maxElementsToSelectByScrolling);
}

private filterCountriesData() {
let filteredCountriesData = this.countriesData;
if (this.selectedContinent && this.selectedContinent !== ALL_VALUES) {
filteredCountriesData = this.countriesData.filter((country) => country.continentName === this.selectedContinent);
}

// Filter by max amount allowed by infinite scroll
filteredCountriesData = filteredCountriesData.slice(0, this.maxElementsToSelectByScrolling);

// We need to remember to use cloneDeep to update the reference in order for Angular to detect the changes
this.filteredCountriesData = cloneDeep(filteredCountriesData);

this.filterTableLoadedCountriesData();
}
}
7 changes: 7 additions & 0 deletions src/app/app.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ export const METRIC_SELECTION_OTIONS = [
{ label: 'Population', value: ['population'] },
];

export const MAX_CHART_DEFAULT_OPTION = 5;
export const MAX_CHART_RESULT_OPTIONS = [
5,
10,
15,
20
];
16 changes: 14 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CountryDataServiceModule } from './services/country-data/country-data.module';
import { SharedModule } from './shared/shared.module';
import { NgApexchartsModule } from 'ng-apexcharts';
import { CountryMetricChartComponent } from './charts/country-metric-chart.component';
import {
CountryMetricChartSeriesPipe,
CountryMetricChartLabelsPipe,
CountryMetricChartTitlePipe
} from './charts/country-metric-chart.pipe';

@NgModule({
declarations: [
AppComponent
AppComponent,
CountryMetricChartComponent,
CountryMetricChartSeriesPipe,
CountryMetricChartLabelsPipe,
CountryMetricChartTitlePipe
],
imports: [
BrowserModule,
AppRoutingModule,
CountryDataServiceModule,
SharedModule
SharedModule,
NgApexchartsModule
],
providers: [],
bootstrap: [
Expand Down
6 changes: 6 additions & 0 deletions src/app/charts/country-metric-chart.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<apx-chart
[series]="countriesData | toApexChartSeries : metric : maxChartResults"
[labels]="countriesData | toApexChartLabels : metric : maxChartResults"
[chart]="chart"
[title]="metric | toApexChartTitle"
></apx-chart>
18 changes: 18 additions & 0 deletions src/app/charts/country-metric-chart.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
import { CountryDataModel } from '../services/country-data/country-data.model';

@Component({
selector: 'app-country-metric-chart',
templateUrl: './country-metric-chart.component.html',
})
export class CountryMetricChartComponent {

@Input() countriesData: CountryDataModel[];
@Input() metric: string;
@Input() maxChartResults: number;

chart = {
height: 200,
type: 'pie'
};
}
72 changes: 72 additions & 0 deletions src/app/charts/country-metric-chart.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Pipe, PipeTransform } from '@angular/core';
import { ApexNonAxisChartSeries, ApexTitleSubtitle } from 'ng-apexcharts';
import { CountryDataModel } from '../services/country-data/country-data.model';

@Pipe({
name: 'toApexChartTitle',
pure: true
})
export class CountryMetricChartTitlePipe implements PipeTransform {
transform(title: string): ApexTitleSubtitle {
return {
text: title,
};
}
}

@Pipe({
name: 'toApexChartLabels',
pure: true
})
export class CountryMetricChartLabelsPipe implements PipeTransform {
private static getCountryLabels(countriesData: CountryDataModel[], metric: string, maxChartResults: number): string[] {
const sortedCountries = CountryMetricChartSeriesPipe.sortByMetric(countriesData, metric);
const countriesByAggregation = CountryMetricChartSeriesPipe.splitCountriesByAggregation(sortedCountries, maxChartResults);
const displayCountryNames = countriesByAggregation.countriesToDisplay.map((country) => country.countryName);
return ['Others', ...displayCountryNames];
}

transform(countriesData: CountryDataModel[], metric: string, maxChartResults: number): string[] {
return CountryMetricChartLabelsPipe.getCountryLabels(countriesData, metric, maxChartResults);
}
}

@Pipe({
name: 'toApexChartSeries',
pure: true
})
export class CountryMetricChartSeriesPipe implements PipeTransform {
static sortByMetric(countriesData: CountryDataModel[], metric: string): CountryDataModel[] {
return countriesData.sort((a, b) => {
return CountryMetricChartSeriesPipe.getCountryMetricValue(b, metric) - CountryMetricChartSeriesPipe.getCountryMetricValue(a, metric);
})
}

static splitCountriesByAggregation(countriesData: CountryDataModel[], maxChartResults: number): {countriesToDisplay: CountryDataModel[], countriesToAggregate: CountryDataModel[]} {
const countriesToDisplay = countriesData.slice(0, maxChartResults);
const countriesToAggregate = countriesData.slice(maxChartResults, countriesData.length);
return { countriesToDisplay, countriesToAggregate };
}

private static getCountryMetricValue(country: CountryDataModel, metric: string): number {
if (country[metric] && !isNaN(country[metric])) {
return parseInt(country[metric], 10);
}
return 0;
}

private static getCountryMetricSeries(countriesData: CountryDataModel[], metric: string, maxChartResults: number): ApexNonAxisChartSeries {
const sortedCountries = CountryMetricChartSeriesPipe.sortByMetric(countriesData, metric);
const countriesByAggregation = CountryMetricChartSeriesPipe.splitCountriesByAggregation(sortedCountries, maxChartResults);
const otherCountriesAggregate = countriesByAggregation.countriesToAggregate.reduce((acc: number, country: CountryDataModel) => {
return acc + CountryMetricChartSeriesPipe.getCountryMetricValue(country, metric);
}, 0);
const countriesToDisplayMetric = countriesByAggregation.countriesToDisplay.map((country) => CountryMetricChartSeriesPipe.getCountryMetricValue(country, metric));

return [otherCountriesAggregate, ...countriesToDisplayMetric];
}

transform(countriesData: CountryDataModel[], metric: string, maxChartResults: number): ApexNonAxisChartSeries {
return CountryMetricChartSeriesPipe.getCountryMetricSeries(countriesData, metric, maxChartResults);
}
}

0 comments on commit 5352309

Please sign in to comment.